dtcm: Coverity 89273
[oweals/cde.git] / cde / programs / dtcm / libDtCmP / cm_tty.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these libraries and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /*******************************************************************************
24 **
25 **  cm_tty.c
26 **
27 **  $TOG: cm_tty.c /main/9 1998/04/17 11:22:38 mgreess $
28 **
29 **  RESTRICTED CONFIDENTIAL INFORMATION:
30 **
31 **  The information in this document is subject to special
32 **  restrictions in a confidential disclosure agreement between
33 **  HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
34 **  document outside HP, IBM, Sun, USL, SCO, or Univel without
35 **  Sun's specific written approval.  This document and all copies
36 **  and derivative works thereof must be returned or destroyed at
37 **  Sun's request.
38 **
39 **  Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
40 **
41 *******************************************************************************/
42
43 /*                                                                      *
44  * (c) Copyright 1993, 1994 Hewlett-Packard Company                     *
45  * (c) Copyright 1993, 1994 International Business Machines Corp.       *
46  * (c) Copyright 1993, 1994 Sun Microsystems, Inc.                      *
47  * (c) Copyright 1993, 1994 Novell, Inc.                                *
48  */
49
50 #ifndef lint
51 static  char sccsid[] = "@(#)cm_tty.c 1.91 95/07/27 Copyr 1993 Sun Microsystems, Inc.";
52 #endif
53
54 #include <EUSCompat.h>
55 #include <sys/types.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <ctype.h>
60 #include <nl_types.h>
61 #include <sys/param.h>
62 #include <sys/types.h>
63 #include "cm_tty.h"
64 #include "getdate.h"
65 #include "util.h"
66
67 /*******************************************************************************
68 **
69 **  Globals
70 **
71 *******************************************************************************/
72 static char *separator_strs[] = {
73         " ",
74         "/",
75         ".",
76         "-"
77 };
78
79 static char *repeat_strs[11];
80
81 static char *default_repeat_cnt_strs[] = {
82         "\0",
83         "365",
84         "52",
85         "26",
86         "12",
87         "12",
88         "2",
89         "52",
90         "52",
91         "52",
92         "5"
93 };
94
95 static char *default_repeat_scope_strs[11];
96
97 static char *for_strs[] = {
98         "2",
99         "3",
100         "4",
101         "5",
102         "6",
103         "7",
104         "8",
105         "9",
106         "10",
107         "11",
108         "12",
109         "13",
110         "14",
111         "forever"
112 };
113
114 static char *time_scope_strs_i18n[3];
115 static char *time_scope_strs[] = {
116         "Mins",
117         "Hrs",
118         "Days"
119 };
120
121 static char *repeat_scope_strs[3];
122
123 static char *day_strs[] = {
124         "Sunday",
125         "Monday",
126         "Tuesday",
127         "Wednesday",
128         "Thursday",
129         "Friday",
130         "Saturday",
131         "Sunday"
132 };
133
134 static char *month_strs[] = {
135         "January",
136         "February",
137         "March",
138         "April",
139         "May",
140         "June",
141         "July",
142         "August",
143         "September",
144         "October",
145         "November",
146         "December"
147 };
148
149 typedef enum {
150         TTY_Delete,
151         TTY_Insert,
152         TTY_Lookup
153 } Op_Type;
154
155 nl_catd catd_global;
156
157 static char *new_appt_begin_delimiter = NULL;
158 static char *new_appt_end_delimiter = NULL;
159
160 extern int _csa_iso8601_to_tick(char *, time_t*);
161 extern int _csa_tick_to_iso8601(time_t, char *);
162 extern int _csa_iso8601_to_duration(char *, int*);
163 extern int _csa_duration_to_iso8601(int, char *);
164
165 /*******************************************************************************
166 **
167 **  Static functions
168 **
169 *******************************************************************************/
170 static void
171 copy_and_pad_newlines(char *dest, char *source) {
172         while (*source)
173                 if ((*dest++ = *source++) == '\n')
174                         *dest++ =  '\t';
175 }
176
177 static int
178 count_newlines(char *string) {
179         int count = 0;
180
181         if (string == NULL)
182                 return(0);
183         while (*string)
184                 if (*string++ == '\n')
185                         count++;
186
187         return count;
188 }
189
190 static void
191 mini_err_msg(
192         nl_catd catd,
193         char *appt_what, 
194         Op_Type type) {
195
196         char    *buf, *ptr;
197
198         if (type == TTY_Insert)
199                 fprintf(stderr, "%s", 
200                         catgets(catd, 1, 1042, "Insert Access Denied: "));
201         else if (type == TTY_Delete)
202                 fprintf(stderr, "%s", 
203                         catgets(catd, 1, 1043, "Delete Access Denied: "));
204         else
205                 fprintf(stderr, "%s", 
206                         catgets(catd, 1, 1044, "Lookup Access Denied: "));
207
208         if (appt_what && appt_what[0] != '\0') {
209                 buf = cm_strdup(appt_what);
210                 if (ptr = strrchr(buf, '\n'))
211                         *ptr = '\0';
212                 fprintf(stderr, "%s '%s'\n", 
213                                 catgets(catd, 1, 1045, "Cancelled for"),
214                                 buf);
215                 free(buf);
216         } else
217                 fprintf(stderr, "%s\n", 
218                                 catgets(catd, 1, 1046, 
219                                         "Appointment Cancelled\n"));
220 }
221
222 static boolean_t
223 query_user(void *client_data) {
224         char    ans[MAXNAMELEN], *what_str = (char *)client_data;
225
226         /* NL_COMMENT
227            
228            The following four messages (1047-1050) will be printed to stdout 
229            and can have the following two forms:
230
231                 "This appointment: '<appt text>' has an end
232                  time earlier than its begin time.  Do you
233                  want to schedule it into the next day? [Y/N]  "
234
235            or 
236
237                 "This appointment has an end
238                  time earlier than its begin time.  Do you
239                  want to schedule it into the next day? [Y/N]  "
240
241            The text <appt text> and [Y/N] should not be translated.
242
243         */
244
245         if (what_str && what_str[0] != '\0')
246                 fprintf(stdout, catgets(catd_global, 1, 1047, 
247                         "This appointment: '%s' has an end\n"), what_str);
248         else
249                 fprintf(stdout, "%s", catgets(catd_global, 1, 1048, 
250                         "This appointment has an end\n"));
251         fprintf(stdout, "%s", catgets(catd_global, 1, 1049, 
252                         "time earlier than its begin time.  Do you\n"));
253         fprintf(stdout, "%s", catgets(catd_global, 1, 1050,
254                         "want to schedule it into the next day? [Y/N]  "));
255         fgets(ans, sizeof(ans)-1, stdin);
256         fprintf(stdout, "\n");
257         if (*ans == 'y' || *ans == 'Y')
258                 return B_TRUE;
259         return B_FALSE;
260 }
261
262 /*******************************************************************************
263 **
264 **  External functions
265 **
266 *******************************************************************************/
267 extern char*
268 boolean_str(boolean_t val) {
269         return (val ? "True" : "False");
270 }
271
272 /*
273  * Delete an appointment.  index is the nth appointment in the passed array.
274  */
275 extern int
276 cm_tty_delete(
277         nl_catd         catd,
278         CSA_session_handle session, 
279         int version, 
280         int index, 
281         CSA_entry_handle *list) {
282
283         char                    ans[10];
284         Dtcm_appointment        *appt;
285
286         if (index < 0 || !list[index])
287                 return -1;
288
289         appt = allocate_appt_struct(appt_read,
290                                     version,
291                                     CSA_ENTRY_ATTR_SUMMARY_I,
292                                     CSA_X_DT_ENTRY_ATTR_REPEAT_TYPE_I,
293                                     NULL);
294
295         if (query_appt_struct(session, list[index], appt) != CSA_SUCCESS) {
296                 mini_err_msg(catd, appt->what->value->item.string_value, 
297                              TTY_Delete);
298                 return(0);
299         }
300
301         if (appt->repeat_type->value->item.sint32_value == CSA_X_DT_REPEAT_ONETIME)
302                 *ans = '1';
303         else {
304                 /* NL_COMMENT
305
306                    Message numbers 1051-1057 are printed to stdout and
307                    should appear like:
308
309                    "The appointment '<appt text>' is part of a repeating series. Do you want to:
310                         1.  Delete all of them
311                         2.  Delete this on only
312                         3.  Delete forward
313                         4.  Cancel
314                         Option [1-4]: "
315
316                 */
317                 fprintf(stdout, catgets(catd, 1, 1051, 
318                      "The appointment '%s' is part of a repeating series.  "),
319                       appt->what->value->item.string_value);
320                 fprintf(stdout, "%s", catgets(catd, 1, 1052, "Do you want to:"));
321                 fprintf(stdout, "%s", catgets(catd, 1, 1053, 
322                                         "\n\t1.  Delete all of them"));
323                 fprintf(stdout, "%s", catgets(catd, 1, 1054, 
324                                         "\n\t2.  Delete this one only"));
325                 fprintf(stdout, "%s", catgets(catd, 1, 1055, "\n\t3.  Delete forward"));
326                 fprintf(stdout, "%s", catgets(catd, 1, 1056, "\n\t4.  Cancel"));
327                 fprintf(stdout, "%s", catgets(catd, 1, 1057, "\n\tOption [1-4]: "));
328                 fgets(ans, sizeof(ans)-1, stdin);
329                 fprintf(stdout, "\n");
330         }
331
332         switch(*ans) {
333         case '1':
334                 if (csa_delete_entry(session, list[index], CSA_SCOPE_ALL, NULL)
335                     != CSA_SUCCESS)
336                         mini_err_msg(catd, appt->what->value->item.string_value,
337                                 TTY_Delete);
338                 break;
339         case '2':
340                 if (csa_delete_entry(session, list[index], CSA_SCOPE_ONE, NULL)
341                     != CSA_SUCCESS)
342                         mini_err_msg(catd, appt->what->value->item.string_value,
343                                 TTY_Delete);
344                 break;
345         case '3':
346                 if (csa_delete_entry(session, list[index], CSA_SCOPE_FORWARD,
347                     NULL) != CSA_SUCCESS)
348                         mini_err_msg(catd, appt->what->value->item.string_value,
349                                 TTY_Delete);
350                 break;
351         case '4':
352         default:
353                 break;
354         }
355         free_appt_struct(&appt);
356
357         return 0;
358 }
359
360 /*
361  * Build ascii date/time line from integer (tick)
362  */
363 extern void
364 cm_tty_format_header(Props *p, Tick tick, char *buf) {
365         Days_op         d_op;
366         Months_op       m_op;
367
368         if (!buf)
369                 return;
370
371         d_op = (Days_op)dow(tick);
372         m_op = (Months_op)(month(tick) - 1);
373
374         switch(get_int_prop(p, CP_DATEORDERING)) {
375         case ORDER_MDY:
376                 sprintf(buf, "%s %s %d, %d", day_str(d_op), month_str(m_op),
377                         dom(tick), year(tick));
378                 break;
379         case ORDER_DMY:
380                 sprintf(buf, "%s %d %s, %d", day_str(d_op), dom(tick),
381                         month_str(m_op), year(tick));
382                 break;
383         case ORDER_YMD:
384                 sprintf(buf, "%s, %d %s %d", day_str(d_op), year(tick),
385                         month_str(m_op), dom(tick));
386                 break;
387         default:
388                 buf[0] = '\0';
389                 break;
390         }
391 }
392
393 extern void
394 scrub_attr_list(Dtcm_appointment *appt) {
395
396         int     i;
397
398         for (i = 0; i < appt->count; i++) {
399                 if (appt->attrs[i].value->type == CSA_VALUE_REMINDER) {
400                         if ((appt->attrs[i].value->item.reminder_value->lead_time == NULL) || 
401                              (appt->attrs[i].value->item.reminder_value->lead_time[0] == '\0')) {
402                                 free(appt->attrs[i].name);
403                                 appt->attrs[i].name = NULL;
404                         }
405                 }
406                 else if ((appt->attrs[i].value->type == CSA_VALUE_ACCESS_LIST) && (appt->attrs[i].value->item.access_list_value == NULL)) {
407                         free(appt->attrs[i].name);
408                         appt->attrs[i].name = NULL;
409                 }
410                 else if ((appt->attrs[i].value->type == CSA_VALUE_STRING) && (appt->attrs[i].value->item.string_value == NULL)) {
411                         free(appt->attrs[i].name);
412                         appt->attrs[i].name = NULL;
413                 }
414                 else if ((appt->attrs[i].value->type == CSA_VALUE_DATE_TIME) && (appt->attrs[i].value->item.date_time_value == NULL)) {
415                         free(appt->attrs[i].name);
416                         appt->attrs[i].name = NULL;
417                 }
418         }
419 }
420
421 /*
422  * Insert an appointment!
423  */
424 extern int 
425 cm_tty_insert(nl_catd catd, CSA_session_handle target, int version, 
426               char *date, char *start, char *end,
427               char *repeat, char *repeatfor, char *what, char *filename,
428               Props *p) {
429         int                     ret_stat = 0, cnt;
430         char                    *t1 = NULL;
431         CSA_entry_handle        new_entry;
432         CmDataList              *list = CmDataListCreate();
433         Validate_op             op;
434         CSA_attribute           *attrs;
435         CSA_return_code         status;
436         Dtcm_appointment        *appt;
437
438         /* XXX: This is ugly but the query_user() function needs the catd
439          * and this is the easiest way to get it there.
440          */
441         catd_global = catd;
442
443         if (filename) {
444                 op = parse_appt_from_file(catd, filename, list, p, query_user, 
445                                           NULL, version);
446                 appt = (Dtcm_appointment *)CmDataListGetData(list, 1);
447         } else {
448                 appt = allocate_appt_struct(appt_write, version, NULL);
449                 load_appt_defaults(appt, p);
450
451                 op = validate_appt(catd, appt, start, end, date, 0, what,
452                         repeat, repeatfor, query_user, what, version);
453
454                 CmDataListAdd(list, (void *)appt, 0);
455         }
456         for (cnt = 1; cnt <= list->count; cnt++) {
457                 if ((appt = (Dtcm_appointment *)CmDataListGetData(list, cnt)) == NULL)
458                         continue;
459         
460                 switch(op) {
461                 case INVALID_DATE:
462                         t1 = catgets(catd, 1, 1058, "Invalid Date specified.\n");
463                         break;
464                 case INVALID_START:
465                         t1 = catgets(catd, 1, 1059, 
466                                         "Invalid Start time specified.\n");
467                         break;
468                 case INVALID_TIME:
469                         t1 = "Invalid Due time specified.\n";
470                         break;
471                 case INVALID_STOP:
472                         t1 = catgets(catd, 1, 1060, 
473                                         "Invalid Stop time specified.\n");
474                         break;
475                 case MISSING_DATE:
476                         t1 = catgets(catd, 1, 1061, 
477                                         "Empty or missing Date field.\n");
478                         break;
479                 case MISSING_START:
480                         t1 = catgets(catd, 1, 1062, 
481                                         "Empty or missing Start field.\n");
482                         break;
483                 case MISSING_TIME:
484                         t1 = "Empty or missing Due time field.\n";
485                         break;
486                 case MISSING_WHAT:
487                         t1 = catgets(catd, 1, 1063, 
488                                         "Empty or missing What field.\n");
489                         break;
490                 case REPEAT_FOR_MISMATCH:
491                         t1 = catgets(catd, 1, 1064, 
492                                         "Repeat and For field mismatch.\n");
493                         break;
494                 case VALID_APPT:
495                 case CANCEL_APPT:
496                         t1 = "";
497                         break;
498                 default:
499                         op = CANCEL_APPT;
500                         t1 = catgets(catd, 1, 1065, 
501                                         "Insert appointment was cancelled\n");
502                         break;
503                 }
504         
505                 if (op == VALID_APPT) {
506                         scrub_attr_list(appt);
507         
508                         if ((status = csa_add_entry(target, appt->count, appt->attrs, &new_entry, NULL)) != CSA_SUCCESS) {
509                                 mini_err_msg(catd, 
510                                         appt->what->value->item.string_value, 
511                                         TTY_Insert);
512                                 ret_stat = -1;
513                         } else
514                                 csa_free((CSA_buffer)new_entry);
515                 } else {
516                         char *msg = strdup(t1);
517                         fprintf(stderr, "%s%s\n", msg, catgets(catd, 1, 1066, 
518                                         "Appointment was not inserted."));
519                         free(msg);
520                         ret_stat = -1;
521                 }
522         }
523
524         if ((list->count == 0) && (op != VALID_APPT || op != CANCEL_APPT))
525                 ret_stat = -1;
526
527         for (cnt = 1; cnt <= list->count; cnt++)
528                 if (appt = (Dtcm_appointment *)CmDataListGetData(list, cnt))
529                         free_appt_struct(&appt);
530         CmDataListDestroy(list, B_FALSE);
531
532         return ret_stat;
533 }
534
535 void
536 cm_tty_load_props(Props **p) {
537         int     start, stop;
538
539         if (*p)
540                 free(*p);
541
542         *p = (Props *)ckalloc(sizeof(Props));
543         read_props(*p);
544         cal_convert_cmrc(*p);
545
546         if ((start = get_int_prop(*p, CP_DAYBEGIN)) < 0)
547                 start = 0;
548         else if (start > 22)
549                 start = 22;
550         if ((stop = get_int_prop(*p, CP_DAYEND)) <= start)
551                 stop = start + 1;
552         else if (stop > 23)
553                 stop = 23;
554         set_int_prop(*p, CP_DAYBEGIN, start);
555         set_int_prop(*p, CP_DAYEND, stop);
556 }
557
558 extern int
559 cm_tty_lookup(nl_catd catd, CSA_session_handle target, int version, char *date, char *view, CSA_entry_handle **list,
560               Props *p) {
561         int                     span, day, lineno = 1, last_day = -1, i;
562         CSA_uint32              a_total;
563         char                    start_buf[MAXNAMELEN], end_buf[MAXNAMELEN];
564         char                    buf[MAXNAMELEN], date_str[MAXNAMELEN], *what;
565         time_t                  tick, start, stop;
566         Lines                   *lines = NULL, *next_line;
567         DisplayType             dt;
568         CSA_enum                *ops;
569         CSA_attribute           *range_attrs;
570         Dtcm_appointment        *appt;
571         Tick                    start_tick, end_tick = 0;
572         CSA_return_code         status;
573
574         /*
575          * Preliminary stuff - set defaults
576          */
577         if (!view) {
578                 switch(get_int_prop(p, CP_DEFAULTVIEW)) {
579                 case 1:
580                         view = "month";
581                         break;
582                 case 2:
583                         view = "week";
584                         break;
585                 default:
586                         view = "day";
587                         break;
588                 }
589         }
590         if (!date)
591                 tick = now();
592         else if ((tick = cm_getdate(date, NULL)) < 0) {
593                 fprintf(stdout, "\n%s %s\n\n", 
594                                 catgets(catd, 1, 1067, "Invalid date specified:"),
595                                 date);
596                 return(0);
597         }
598
599         /*
600          * Compute day and span for view specified
601          */
602         if (strncasecmp(view, "week", 4) == 0) {
603                 day = dow(tick);
604                 span = 7;
605         } else if (strncasecmp(view, "month", 5) == 0) {
606                 day = dom(tick) - 1;
607                 span = monthlength(tick);
608         } else {
609                 day = 0;
610                 span = 1;
611         }
612
613         start = lowerbound(tick - (day * daysec));
614         stop = next_ndays(start, span) - 1;
615         setup_range(&range_attrs, &ops, &i, start, stop, CSA_TYPE_EVENT,
616                     0, B_FALSE, version);
617         status = csa_list_entries(target, i, range_attrs, ops, &a_total, list, NULL);
618         free_range(&range_attrs, &ops, i);
619
620         appt = allocate_appt_struct(appt_read,
621                                     version,
622                                     CSA_ENTRY_ATTR_START_DATE_I,
623                                     CSA_ENTRY_ATTR_SUMMARY_I,
624                                     CSA_X_DT_ENTRY_ATTR_SHOWTIME_I,
625                                     CSA_ENTRY_ATTR_END_DATE_I,
626                                     NULL);
627         for (i = 0; i < a_total; i++) {
628
629                 if (query_appt_struct(target, (*list)[i], appt) != CSA_SUCCESS) {
630                         mini_err_msg(catd, appt->what->value->item.string_value,
631                                 TTY_Lookup);
632                         continue;
633                 }
634
635                 _csa_iso8601_to_tick(appt->time->value->item.date_time_value, &start_tick);
636                 if (appt->end_time)
637                         _csa_iso8601_to_tick(appt->end_time->value->item.\
638                                 date_time_value, &end_tick);
639                 day = dom(start_tick);
640                 if (day != last_day) {
641                         cm_tty_format_header(p, start_tick, date_str);
642                         fprintf(stdout, "\n%s %s:\n", 
643                                 catgets(catd, 1, 1068, "Appointments for"),
644                                 date_str);
645                 }
646                 last_day = day;
647
648                 memset(buf, '\0', MAXNAMELEN);
649                 if (appt->show_time->value->item.sint32_value &&
650                     !magic_time(start_tick)) {
651                         dt = get_int_prop(p, CP_DEFAULTDISP);
652                         format_time(start_tick, dt, start_buf);
653                         if (appt->end_time)
654                                 format_time(end_tick, dt, end_buf);
655                         else
656                                 *end_buf = '\0';
657                         sprintf(buf, "%s%c%7s ", start_buf,
658                                 (*end_buf ? '-' : ' '), end_buf);
659                 }
660
661                 fprintf(stdout, "\t%3d) %s", lineno++, buf);
662                 if ((lines = text_to_lines(appt->what->value->item.string_value,                    5)) && lines->s) {
663
664                         fprintf(stdout, "%s\n", lines->s);
665                         next_line = lines->next;
666                         while(next_line) {
667                                 fprintf(stdout,
668                                         "\t%21s%s\n", " ", next_line->s);
669                                 next_line = next_line->next;
670                         }
671                 } else
672                         fprintf(stdout, "\n");
673
674                 destroy_lines(lines);
675                 fprintf(stdout, "\n");
676         }
677         free_appt_struct(&appt);
678
679         if (*list == NULL) {
680                 cm_tty_format_header(p, start + 1, date_str);
681                 fprintf(stdout, "\n%s %s\n\n", 
682                                 catgets(catd, 1, 1069, "No Appointments for"),
683                                 date_str);
684         }
685         return a_total;
686 }
687
688 /*
689  *  These functions will convert a string to the enumerated value
690  */
691 extern boolean_t
692 convert_boolean_str(char *val) {
693         if (strncasecmp(val, "T", 1) == 0 || strcasecmp(val, "B_TRUE") == 0)
694                 return B_TRUE;
695         return B_FALSE;
696 }
697
698 extern int
699 convert_privacy_str_to_op(char *val) {
700         int     i = 2;
701
702
703         /*
704          * i defaults to 1 = CSA_CLASS_PRIVATE, so no need to check for that
705          * string.
706          */
707         if (strcmp(val, privacy_str(0)) == 0 ||
708             strcmp(val, privacy_str_old(0)) == 0 ||
709             strcmp(val, privacy_str_411(0)) == 0)
710                 i = 0;
711         else if (strcmp(val, privacy_str(1)) == 0 ||
712                  strcmp(val, privacy_str_old(1)) == 0 ||
713                  strcmp(val, privacy_str_411(1)) == 0)
714                 i = 1;
715
716         return i;
717 }
718
719 extern CSA_sint32
720 convert_privacy_str(char *val) {
721         CSA_sint32      ret_val = CSA_CLASS_PRIVATE;
722
723         if ((strcasecmp(val, privacy_str(0)) == 0) ||
724             (strcasecmp(val, privacy_str_old(0)) == 0) ||
725             (strcasecmp(val, privacy_str_411(0)) == 0))
726                 ret_val = CSA_CLASS_PUBLIC;
727         else if ((strcasecmp(val, privacy_str(1)) == 0) ||
728                  (strcasecmp(val, privacy_str_old(1)) == 0) ||
729                  (strcasecmp(val, privacy_str_411(1)) == 0))
730                 ret_val = CSA_CLASS_CONFIDENTIAL;
731         else if ((strcasecmp(val, privacy_str(2)) == 0) ||
732                  (strcasecmp(val, privacy_str_old(2)) == 0) ||
733                  (strcasecmp(val, privacy_str_411(2)) == 0))
734                 ret_val = CSA_CLASS_PRIVATE;
735
736         return ret_val;
737 }
738
739 extern SeparatorType
740 convert_separator_str(char *val) {
741         SeparatorType   op = SEPARATOR_BLANK;
742         char            *search_val = separator_str(op);
743
744         while (search_val && strcasecmp(search_val, val) != 0)
745                 search_val = separator_str(++op);
746         return op;
747 }
748
749 extern Time_scope_menu_op
750 convert_time_scope_str(char *val) {
751         Time_scope_menu_op      op = TIME_MINS;
752         char                    *search_val = time_scope_str(op);
753
754         while(search_val && strcasecmp(search_val, val) != 0)
755                 search_val = time_scope_str(++op);
756         return op;
757 }
758
759 extern char*
760 day_str(Days_op op) {
761         if (op >= SUNDAY && op <= SATURDAY)
762                 return day_strs[op];
763         return NULL;
764 }
765
766 extern char*
767 default_repeat_cnt_str(Repeat_menu_op  op) {
768
769         if (op >= ONE_TIME && op <= REPEAT_EVERY)
770                 return default_repeat_cnt_strs[op];
771         return NULL;
772 }
773
774 extern char*
775 default_repeat_scope_str(
776         nl_catd         catd,
777         Repeat_menu_op  op)
778 {
779         if (!default_repeat_scope_strs[DAILY]) {
780                 default_repeat_scope_strs[ONE_TIME] = strdup("\0");
781                 default_repeat_scope_strs[DAILY] = 
782                                 strdup(catgets(catd, 1, 994, "days")); 
783                 default_repeat_scope_strs[WEEKLY] = 
784                                 strdup(catgets(catd, 1, 995, "weeks")); 
785                 default_repeat_scope_strs[EVERY_TWO_WEEKS] = 
786                                 strdup(catgets(catd, 1, 996, "biweeks")); 
787                 default_repeat_scope_strs[MONTHLY_BY_DATE] = 
788                                 strdup(catgets(catd, 1, 997, "months")); 
789                 default_repeat_scope_strs[MONTHLY_BY_WEEKDAY] = 
790                                 default_repeat_scope_strs[MONTHLY_BY_DATE];
791                 default_repeat_scope_strs[YEARLY] = 
792                                 strdup(catgets(catd, 1, 998, "years")); 
793                 default_repeat_scope_strs[MONDAY_THRU_FRIDAY] = 
794                                 default_repeat_scope_strs[WEEKLY];
795                 default_repeat_scope_strs[MON_WED_FRI] = 
796                                 default_repeat_scope_strs[WEEKLY];
797                 default_repeat_scope_strs[TUESDAY_THURSDAY] = 
798                                 default_repeat_scope_strs[WEEKLY];
799                 default_repeat_scope_strs[REPEAT_EVERY] = strdup("\0"); 
800         }
801
802         if (op >= ONE_TIME && op <= REPEAT_EVERY)
803                 return default_repeat_scope_strs[op];
804         return NULL;
805 }
806
807 extern char*
808 for_str(For_menu_op op) {
809         if (op >= TWO && op <= FOR_EVER)
810                 return for_strs[op];
811         return NULL;
812 }
813
814 /*
815 **  Return a date label according to order and sep
816 */
817 extern char*
818 get_datemsg(OrderingType order, SeparatorType sep) {
819         char    *str = separator_str(sep);
820         char    buf[20];
821
822         switch (order) {
823         case ORDER_DMY:
824                 sprintf(buf, "%s %s %s %s %s", "Day", str, "Month", str, "Year");
825                 break;
826         case ORDER_YMD:
827                 sprintf(buf, "%s %s %s %s %s", "Year", str, "Month", str, "Day");
828                 break;
829         case ORDER_MDY:
830         default:
831                 sprintf(buf, "%s %s %s %s %s", "Month", str, "Day", str, "Year");
832                 break;
833         }
834         return(cm_strdup(buf));
835 }
836
837 /*
838  * This function is used by the appointment file parsing routines to return
839  * whether or not the value in the passed buffer is a key word
840  */
841 extern Parse_key_op
842 identify_parse_key(char *str) {
843
844         if (!new_appt_begin_delimiter) {
845                 new_appt_begin_delimiter = malloc(strlen(CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER) + 14);
846                 sprintf(new_appt_begin_delimiter, "%s%s", 
847                         CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
848                         ":string:begin");
849         }
850
851         if (strncasecmp(str, "** Calendar Appointment **",
852                 cm_strlen("** Calendar Appointment **")) == 0)
853                 return APPOINTMENT_START;
854         if (strncasecmp(str, "date:", cm_strlen("date:")) == 0)
855                 return DATE_KEY;
856         if (strncasecmp(str, "time:", cm_strlen("time:")) == 0 ||
857                 strncasecmp(str, "start:", cm_strlen("start:")) == 0 ||
858                 strncasecmp(str, "from:", cm_strlen("from:")) == 0)
859                 return START_KEY;
860         if(strncasecmp(str, "end:", cm_strlen("end:")) == 0 ||
861                 strncasecmp(str, "until:", cm_strlen("until:")) == 0 ||
862                 strncasecmp(str, "stop:", cm_strlen("stop:")) == 0 ||
863                 strncasecmp(str, "to:", cm_strlen("to:")) == 0)
864                 return STOP_KEY;
865         if (strncasecmp(str, "duration:", cm_strlen("duration:")) == 0)
866                 return DURATION_KEY;
867         if (strncasecmp(str, "what:", cm_strlen("what:")) == 0)
868                 return WHAT_KEY;
869         if (strncasecmp(str, "repeat:", cm_strlen("repeat:")) == 0)
870                 return REPEAT_KEY;
871         if (strncasecmp(str, "for:", cm_strlen("for:")) == 0)
872                 return FOR_KEY;
873         if (strncasecmp(str, new_appt_begin_delimiter, cm_strlen(new_appt_begin_delimiter)) == 0)
874                 return NEW_APPT_KEY;
875
876         return NOT_A_KEY;
877 }
878
879 /*
880  * This function will fill in the default values using the properties database
881  * for a given appointment
882  */
883 extern void
884 load_appt_defaults(Dtcm_appointment *a, Props *p) {
885         a->type->value->item.sint32_value = CSA_TYPE_EVENT;
886         a->subtype->value->item.string_value = strdup(CSA_SUBTYPE_APPOINTMENT);
887         a->state->value->item.sint32_value = CSA_X_DT_STATUS_ACTIVE;
888         if (a->repeat_type && a->repeat_type->value)
889                 a->repeat_type->value->item.sint32_value = CSA_X_DT_REPEAT_ONETIME;
890         if (a->repeat_times && a->repeat_times->value)
891                 a->repeat_times->value->item.uint32_value = 0;
892         a->private->value->item.sint32_value =
893                 convert_privacy_str(get_char_prop(p, CP_PRIVACY));
894
895         load_reminder_props(a, p);
896 }
897
898 extern void
899 load_reminder_props(Dtcm_appointment *a, Props *p) {
900         if (convert_boolean_str(get_char_prop(p, CP_BEEPON))) {
901                 a->beep->value->item.reminder_value->lead_time = malloc(BUFSIZ);
902                 _csa_duration_to_iso8601(get_int_prop(p, CP_BEEPADV) *
903                         timescopestring_to_tick(get_char_prop(p, CP_BEEPUNIT)),
904                 a->beep->value->item.reminder_value->lead_time);
905                 a->beep->value->item.reminder_value->reminder_data.data = NULL;
906                 a->beep->value->item.reminder_value->reminder_data.size = 0;
907         }
908         if (convert_boolean_str(get_char_prop(p, CP_FLASHON))) {
909                 a->flash->value->item.reminder_value->lead_time = malloc(BUFSIZ);
910                 _csa_duration_to_iso8601(get_int_prop(p, CP_FLASHADV) *
911                         timescopestring_to_tick(get_char_prop(p, CP_FLASHUNIT)),
912                 a->flash->value->item.reminder_value->lead_time);
913                 a->flash->value->item.reminder_value->reminder_data.data = NULL;
914                 a->flash->value->item.reminder_value->reminder_data.size = 0;
915         }
916         if (convert_boolean_str(get_char_prop(p, CP_OPENON))) {
917                 a->popup->value->item.reminder_value->lead_time = malloc(BUFSIZ);
918                 _csa_duration_to_iso8601(get_int_prop(p, CP_OPENADV) *
919                         timescopestring_to_tick(get_char_prop(p, CP_OPENUNIT)),
920                 a->popup->value->item.reminder_value->lead_time);
921                 a->popup->value->item.reminder_value->reminder_data.data = NULL;
922                 a->popup->value->item.reminder_value->reminder_data.size = 0;
923         }
924         if (convert_boolean_str(get_char_prop(p, CP_MAILON))) {
925                 a->mail->value->item.reminder_value->lead_time = malloc(BUFSIZ);
926                 _csa_duration_to_iso8601(get_int_prop(p, CP_MAILADV) *
927                         timescopestring_to_tick(get_char_prop(p, CP_MAILUNIT)),
928                 a->mail->value->item.reminder_value->lead_time);
929                 a->mail->value->item.reminder_value->reminder_data.data = (CSA_uint8 *) strdup(get_char_prop(p, CP_MAILTO));
930                 a->mail->value->item.reminder_value->reminder_data.size = strlen(get_char_prop(p, CP_MAILTO)) + 1;
931         }
932 }
933
934 extern char*
935 month_str(Months_op op) {
936         if (op >= JANUARY && op <= DECEMBER)
937                 return month_strs[op];
938         return NULL;
939 }
940
941 void
942 build_new_attrval(CSA_attribute *attrval, char *name, char *tag, char *value)
943
944 {
945         CSA_attribute_value *vptr = calloc(sizeof(CSA_attribute_value), 1);
946         char    *s_ptr, *b_ptr;
947         CSA_access_list l_ptr;
948         boolean_t       done = B_FALSE;
949         CSA_access_list *link_location;
950
951         attrval->name = cm_strdup(name);
952         attrval->value = vptr;
953
954         if (!strcmp(tag, "string")) {
955                 vptr->type = CSA_VALUE_STRING;
956                 vptr->item.string_value = cm_strdup(value);
957         }
958
959         if (!strcmp(tag, "datetime")) {
960                 vptr->type = CSA_VALUE_DATE_TIME;
961                 vptr->item.date_time_value = cm_strdup(value);
962         }
963
964         if (!strcmp(tag, "caluser")) {
965                 vptr->type = CSA_VALUE_CALENDAR_USER;
966                 s_ptr = strchr(value, ':');
967                 *s_ptr = '\0';
968                 vptr->item.calendar_user_value = calloc(sizeof(CSA_calendar_user), 1);
969                 vptr->item.calendar_user_value->user_name = cm_strdup(s_ptr + 1);
970                 vptr->item.calendar_user_value->user_type = atoi(value);
971         }
972
973         if (!strcmp(tag, "uinteger")) {
974                 vptr->type = CSA_VALUE_UINT32;
975                 vptr->item.sint32_value = atoi(value);
976         }
977
978         if (!strcmp(tag, "sinteger")) {
979                 vptr->type = CSA_VALUE_SINT32;
980                 vptr->item.sint32_value = atoi(value);
981         }
982
983         if (!strcmp(tag, "reminder")) {
984                 vptr->type = CSA_VALUE_REMINDER;
985                 s_ptr = strchr(value, ':');
986                 *s_ptr = '\0';
987                 
988                 vptr->item.reminder_value = calloc(sizeof(CSA_reminder), 1);
989                 
990                 vptr->item.reminder_value->lead_time = malloc(BUFSIZ);
991                 _csa_duration_to_iso8601(atoi(value), vptr->item.reminder_value->lead_time);
992                 vptr->item.reminder_value->reminder_data.data = (CSA_uint8 *) strdup(s_ptr + 1);
993                 vptr->item.reminder_value->reminder_data.size = strlen(s_ptr + 1) + 1;
994         }
995
996         if (!strcmp(tag, "accesslist")) {
997
998                 /* The access list format is that each member in the 
999                    list is written out on a separate line.  So after 
1000                    the initial newline in the value, the pattern is a 
1001                    TAB followed by the mask value followed by a colon, 
1002                    followed by the string. */
1003
1004                 vptr->type = CSA_VALUE_ACCESS_LIST;
1005                 link_location = &vptr->item.access_list_value;
1006                 b_ptr = value + 1;
1007                 if (value && *b_ptr) {
1008
1009                         /* we have an initial value. */
1010
1011                         while (!done) {
1012                                 l_ptr = calloc(sizeof(CSA_access_rights), 1);
1013
1014                                 s_ptr = strchr(b_ptr, ':');
1015                                 *s_ptr = '\0';
1016
1017                                 l_ptr->rights = atoi(b_ptr);
1018
1019                                 b_ptr = s_ptr + 1;
1020                                 if (s_ptr = strchr(b_ptr, '\n'))
1021                                         *s_ptr = '\0';
1022
1023                                 l_ptr->user->user_name = cm_strdup(b_ptr);
1024
1025                                 if (s_ptr)
1026                                         b_ptr = s_ptr + 1;
1027                                 else
1028                                         done = B_TRUE;
1029
1030                                 *link_location = l_ptr;
1031                                 link_location = &l_ptr->next;
1032
1033                         }
1034                 }
1035         }
1036
1037 }
1038
1039 static void
1040 read_new_appt(FILE *fp, Dtcm_appointment **appt, Props *p, int version)
1041
1042 {
1043         int                     attrs_allocated, attrs_written = 0;
1044         char                    line[MAXNAMELEN], *b_ptr, *c_ptr;
1045         char                    *a_name = NULL, *a_tag = NULL, *a_value = NULL;
1046         Dtcm_appointment        *avlist;
1047
1048         if (!new_appt_end_delimiter) {
1049                 new_appt_end_delimiter = malloc(strlen(CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER) + 14);
1050                 sprintf(new_appt_end_delimiter, "%s%s", 
1051                         CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
1052                         ":string:end");
1053         }
1054         avlist = allocate_appt_struct(appt_write, DATAVER_ARCHIVE, NULL);
1055         load_appt_defaults(avlist, p);
1056
1057         /* At this point, we really want to negate all of the links set 
1058            up by tha appointment allocation routine.  The following code is 
1059            almost certain to invalidate them, and some trailing links would 
1060            be harmful */
1061
1062         avlist->identifier = NULL;
1063         avlist->modified_time = NULL;
1064         avlist->author = NULL;
1065         avlist->time = NULL;
1066         avlist->type = NULL;
1067         avlist->subtype = NULL;
1068         avlist->private = NULL;
1069         avlist->end_time = NULL;
1070         avlist->show_time = NULL;
1071         avlist->what = NULL;
1072         avlist->state = NULL;
1073         avlist->repeat_type = NULL;
1074         avlist->repeat_times = NULL;
1075         avlist->repeat_interval = NULL;
1076         avlist->repeat_week_num = NULL;
1077         avlist->recurrence_rule = NULL;
1078         avlist->beep = NULL;
1079         avlist->flash = NULL;
1080         avlist->mail = NULL;
1081         avlist->popup = NULL;
1082
1083         attrs_allocated = avlist->count;
1084
1085         /* should be starting a new attribute definition */ 
1086
1087         while (fgets(line, MAXNAMELEN - 1, fp))
1088         {
1089
1090                 /* look for new end marker on appointment */
1091
1092                 if (strncmp(line, new_appt_end_delimiter, strlen(new_appt_end_delimiter)) == 0)
1093                         break;
1094
1095                 if (line[0] == '\t') {
1096                 
1097                         /* This is a continuation line from the previous 
1098                            attribute definition.  The value here should 
1099                            be catenated onto the previously yanked value */
1100
1101                         b_ptr = line + 1;
1102
1103                         /* a line with only a tab on it, and no text 
1104                            means that a newline should be inserted 
1105                            into the stream */
1106
1107                         if (!*b_ptr)
1108                                 b_ptr = "\n";
1109
1110                         a_value = realloc(a_value, strlen(a_value) +
1111                                           strlen(b_ptr) + 2);
1112                         strcat(a_value, "\n");
1113                         strcat(a_value, b_ptr);
1114
1115                         a_value[strlen(a_value) - 1] = '\0';
1116
1117                         continue;
1118                 }
1119                 else if (line[0] == '\n') {
1120
1121                         /* An empty line.  This means the end of 
1122                            the appointment definition */
1123
1124                         break;
1125                 }
1126
1127                 /* if the line is neither a termination line, nor 
1128                    a continuation line, then the entry must be a new 
1129                    attribute name.  We should declare the previous 
1130                    triple complete. */
1131
1132                 if (a_name && a_tag && a_value)
1133                 {
1134
1135                 /* see if the allocated buffer is large enough to 
1136                    contain another attibute.  If not, make it a bit 
1137                    larger, and then copy the attribute. */
1138         
1139                         if (ident_name_ro(a_name, DATAVER_ARCHIVE) == B_FALSE) {
1140                                 if ((attrs_written) == attrs_allocated) {
1141                                         attrs_allocated += 10;
1142                                         avlist->attrs = realloc(avlist->attrs,
1143                                                 sizeof(CSA_attribute) *
1144                                                 attrs_allocated);
1145                                 }
1146
1147                                 build_new_attrval(&avlist->attrs[attrs_written],
1148                                                   a_name, a_tag, a_value);
1149                                 ++attrs_written;
1150                         }
1151
1152                         free(a_name);
1153                         free(a_tag);
1154                         free(a_value);
1155                 }
1156
1157                 /* this should pull off the new attribute name */
1158
1159                 a_name = a_tag = a_value = NULL;
1160
1161                 b_ptr = line;
1162                 if (c_ptr = strchr(line, ':'))
1163                 {
1164                         *c_ptr = '\0';
1165                         a_name = cm_strdup(b_ptr);
1166                 }
1167                 else
1168                         /* big problem.  Malformed attribute specification */
1169
1170                         break;
1171
1172                 b_ptr = c_ptr + 1;
1173                 if (!*b_ptr)
1174                         break;
1175
1176                 if (c_ptr = strchr(b_ptr, ':'))
1177                 {       
1178                         *c_ptr = '\0';
1179                         a_tag = cm_strdup(b_ptr);
1180                 }
1181                 else
1182                         /* big problem.  Malformed attribute specification */
1183  
1184                         break;
1185
1186                 b_ptr = c_ptr + 1;
1187                 if (!*b_ptr)
1188                         break;
1189                 else
1190                         b_ptr[strlen(b_ptr) - 1] = '\0';
1191
1192                 a_value =  cm_strdup(b_ptr);
1193         }
1194
1195         /* finished due to end of file.  Early termination, but not too bad */
1196
1197         if (a_name && a_tag && a_value)
1198         {
1199
1200         /* see if the allocated buffer is large enough to 
1201            contain another attibute.  If not, make it a bit 
1202            larger, and then copy the attribute. */
1203
1204                 if (ident_name_ro(a_name, DATAVER_ARCHIVE) == B_FALSE) {
1205                         if ((attrs_written) == attrs_allocated) {
1206                                 attrs_allocated += 10;
1207                                 avlist->attrs = realloc(avlist->attrs,
1208                                         sizeof(CSA_attribute) *
1209                                         attrs_allocated);
1210                         }
1211
1212                         build_new_attrval(&avlist->attrs[attrs_written],
1213                                           a_name, a_tag, a_value);
1214                         ++attrs_written;
1215                 }
1216         }
1217
1218         free(a_name);
1219         free(a_tag);
1220         free(a_value);
1221
1222
1223         avlist->count = attrs_written;
1224         set_appt_links(avlist);
1225         *appt = avlist;
1226 }
1227
1228
1229 static char *
1230 dow_str(
1231         time_t  tick)
1232 {
1233         switch (dow(tick)) {
1234         case 0:
1235                 return (cm_strdup("SU"));
1236         case 1:
1237                 return (cm_strdup("MO"));
1238         case 2:
1239                 return (cm_strdup("TU"));
1240         case 3:
1241                 return (cm_strdup("WE"));
1242         case 4:
1243                 return (cm_strdup("TH"));
1244         case 5:
1245                 return (cm_strdup("FR"));
1246         case 6:
1247         default:
1248                 return (cm_strdup("SA"));
1249         }
1250 }
1251
1252 /* this routine is designed to take in an appintment, and generate the 
1253    DATAVER4 recurrence rule for that appointment.  It is also responsible 
1254    for crushing out the old style attributes that may not be inserted 
1255    into a newer style data model. */
1256
1257 static void
1258 generate_recurrence_rule(Dtcm_appointment *appt, int version) {
1259
1260         char            *str,
1261                          rule_buf1[32],
1262                          rule_buf2[32];
1263         CSA_sint32      repeat_type;
1264         CSA_uint32      repeat_nth;
1265         CSA_uint32      repeat_for;
1266         Tick            appt_time;
1267         int             wk;
1268
1269         rule_buf1[0] = '\0';
1270         rule_buf2[0] = '\0';
1271
1272         if (appt->repeat_type && appt->repeat_type->value)
1273                 repeat_type = appt->repeat_type->value->item.sint32_value;
1274         else
1275                 repeat_type = CSA_X_DT_REPEAT_ONETIME;
1276
1277         if (appt->repeat_interval && appt->repeat_interval->value)
1278                 repeat_nth = appt->repeat_interval->value->item.uint32_value;
1279
1280         if (appt->repeat_times && appt->repeat_times->value)
1281                 repeat_for = appt->repeat_times->value->item.uint32_value;
1282
1283         _csa_iso8601_to_tick(appt->time->value->item.date_time_value, &appt_time);
1284
1285         if (!appt->recurrence_rule || !appt->recurrence_rule->value) {
1286                 switch (repeat_type) {
1287                 
1288                 case CSA_X_DT_REPEAT_ONETIME : 
1289                         strcpy(rule_buf1, "D1 ");
1290                         break;
1291                 
1292                 case CSA_X_DT_REPEAT_DAILY : 
1293                         strcpy(rule_buf1, "D1 ");
1294                         break;
1295                 
1296                 case CSA_X_DT_REPEAT_WEEKLY : 
1297                         strcpy(rule_buf1, "W1 ");
1298                         break;
1299                 
1300                 case CSA_X_DT_REPEAT_BIWEEKLY : 
1301                         strcpy(rule_buf1, "W2 ");
1302                         break;
1303                 
1304                 case CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY : 
1305
1306                         if (weekofmonth(appt_time, &wk) && wk == 5)
1307                                 sprintf(rule_buf1, "MP1 1- %s ", dow_str(appt_time));
1308                         else
1309                                 strcpy(rule_buf1, "MP1 ");
1310
1311                         break;
1312                 
1313                 case CSA_X_DT_REPEAT_MONTHLY_BY_DATE : 
1314                         strcpy(rule_buf1, "MD1 ");
1315                         break;
1316                 
1317                 case CSA_X_DT_REPEAT_YEARLY : 
1318                         strcpy(rule_buf1, "YM1 ");
1319                         break;
1320                 
1321                 case CSA_X_DT_REPEAT_MON_TO_FRI : 
1322                         strcpy(rule_buf1, "W1 MO TU WE TH FR ");
1323                         break;
1324                 
1325                 case CSA_X_DT_REPEAT_MONWEDFRI : 
1326                         strcpy(rule_buf1, "W1 MO WE FR ");
1327                         break;
1328                 
1329                 case CSA_X_DT_REPEAT_TUETHUR : 
1330                         strcpy(rule_buf1, "W1 TU TH ");
1331                         break;
1332                 
1333                 case CSA_X_DT_REPEAT_EVERY_NDAY : 
1334                         sprintf(rule_buf1, "D%ld ", repeat_nth);
1335                         break;
1336                 
1337                 case CSA_X_DT_REPEAT_EVERY_NWEEK : 
1338                         sprintf(rule_buf1, "W%ld ", repeat_nth);
1339                         break;
1340                 
1341                 case CSA_X_DT_REPEAT_EVERY_NMONTH : 
1342                         sprintf(rule_buf1, "MD%ld ", repeat_nth);
1343                         break;
1344                 }
1345
1346                 if (repeat_for == 0)
1347                         strcat(rule_buf2, "#1");
1348                 else
1349                         sprintf(rule_buf2, "#%ld", repeat_for);
1350         
1351                 strcat (rule_buf1, rule_buf2);
1352
1353                 appt->attrs = realloc(appt->attrs,
1354                                       sizeof(CSA_attribute) * (appt->count + 1));
1355
1356                 build_new_attrval(&appt->attrs[appt->count],
1357                                   CSA_ENTRY_ATTR_RECURRENCE_RULE, 
1358                                   "string", 
1359                                   rule_buf1);
1360
1361                 appt->count++;
1362
1363                 set_appt_links(appt);
1364         }
1365
1366         if (version == DATAVER_ARCHIVE)
1367                 return;
1368
1369         /* crush out the old values, if they exist */
1370
1371         if (appt->repeat_type && appt->repeat_type->name) {
1372                 free(appt->repeat_type->name);
1373                 appt->repeat_type->name = NULL;
1374         }
1375
1376         if (appt->repeat_times && appt->repeat_times->name) {
1377                 free(appt->repeat_times->name);
1378                 appt->repeat_times->name = NULL;
1379         }
1380
1381         if (appt->repeat_interval && appt->repeat_interval->name) {
1382                 free(appt->repeat_interval->name);
1383                 appt->repeat_interval->name = NULL;
1384         }
1385
1386         if (appt->repeat_week_num && appt->repeat_week_num->name) {
1387                 free(appt->repeat_week_num->name);
1388                 appt->repeat_week_num->name = NULL;
1389         }
1390                 
1391 }
1392
1393 /*
1394  * Given a file name, we're going to parse the file and create an appointment
1395  * for the calling routine.
1396  *
1397  * This function will scan for X number of appointments in the file and add
1398  * each to the linked list unless the validation routine returns an error.
1399  * If this happens, the invalid appointment is still added to the list so the
1400  * calling routine can do further processing if necessary, but the remaining
1401  * appointments in the file (if any) are not read.
1402  */
1403 extern Validate_op
1404 parse_appt_from_file(nl_catd catd, char *file, CmDataList *list, Props *p,
1405                      boolean_t(*query)(), void *key_data, int version) {
1406         int                     len, dur;
1407         char                    line[MAXNAMELEN], *tmp, *key_str, *val_str,
1408                                 s_buf[MAXNAMELEN], e_buf[MAXNAMELEN],
1409                                 d_buf[MAXNAMELEN], r_buf[MAXNAMELEN],
1410                                 f_buf[MAXNAMELEN], *w_buf = NULL;
1411         FILE                    *fp;
1412         boolean_t               processing_appt = B_FALSE,
1413                                 processing_what = B_FALSE;
1414         int                     what_lines = 0;
1415         Validate_op             valid_op = VALID_APPT;
1416         Parse_key_op            pko = NOT_A_KEY;
1417         Dtcm_appointment        *appt;
1418         boolean_t               found_appt = B_FALSE;
1419
1420         if (!file || *file == '\0' || (fp = fopen(file, "r")) == NULL)
1421                 return COULD_NOT_OPEN_FILE;
1422
1423         appt = allocate_appt_struct(appt_write, version, NULL);
1424         load_appt_defaults(appt, p);
1425         while (valid_op == VALID_APPT && fgets(line, MAXNAMELEN - 1, fp)) {
1426                 /*
1427                  * Point key_str at the first non-space character
1428                  */
1429                 key_str = line;
1430
1431                 if ((*line != ' ' && *line != '\t') || blank_buf(line)) {
1432                         processing_what = B_FALSE;
1433                         what_lines = 0;
1434                 }
1435
1436                 /* 
1437                  * The check for '*' handles the case when the appt header
1438                  * ``** Calendar Appointment **'' has no white space to its
1439                  * left.
1440                  */
1441                 if (*key_str == '\0' || 
1442                     (!isspace((u_char)*key_str) && (u_char)*key_str != '*'))
1443                         continue;
1444
1445                 while (*key_str != '\0' && isspace((u_char)*key_str))
1446                         ++key_str;
1447
1448                 pko = identify_parse_key(key_str);
1449
1450                 /*
1451                  * Remove any ending white space
1452                  */
1453                 tmp = strrchr(line, '\0'); --tmp;
1454                 while(isspace((u_char)*tmp)) {
1455                         *tmp = '\0';
1456                         --tmp;
1457                 }
1458
1459                 /*
1460                  * If we're not currently processing the what string and we
1461                  * haven't identified a key, continue the loop.  Otherwise,
1462                  * we've identified a key, so we can't be in the middle of
1463                  * processing the what string -- set the flag accordingly.
1464                  *
1465                  */
1466                 if (pko == NOT_A_KEY) {
1467                         if (!processing_what || what_lines >= 5)
1468                                 continue;
1469                         pko = WHAT_KEY;
1470                 } else
1471                 {
1472                         processing_what = B_FALSE;
1473                         what_lines = 0;
1474                 }
1475
1476                 /*
1477                  * Point val_str at the next non-space character after the key
1478                  */
1479                 val_str = key_str;
1480                 while(*val_str != '\0' && !isspace((u_char)*val_str))
1481                         ++val_str;
1482                 while(*val_str != '\0' && isspace((u_char)*val_str))
1483                         ++val_str;
1484
1485                 /*
1486                  * Now, based on the keyword, set the necessary stuff in the
1487                  * new appointment structure.
1488                  */
1489                 switch(pko) {
1490                 case APPOINTMENT_START:
1491                         if (processing_appt) {
1492                                 /*
1493                                  * We've reached another appointment.  Add the
1494                                  * current to the linked list, reset flags, and
1495                                  * check it's validity ...
1496                                  */
1497                                 CmDataListAdd(list, (void *)appt, 0);
1498
1499                                 valid_op = validate_appt(catd, appt, s_buf, 
1500                                         e_buf, d_buf, dur, w_buf, r_buf, f_buf,
1501                                         query, key_data, version);
1502                                 if (valid_op == VALID_APPT) {
1503                                         appt = allocate_appt_struct(appt_write, version, NULL);
1504                                         load_appt_defaults(appt, p);
1505                                 }
1506                                 else
1507                                         processing_appt = B_FALSE;
1508                         } else
1509                                 processing_appt = B_TRUE;
1510
1511                         found_appt = B_TRUE;
1512
1513                         dur = 0;
1514                         memset(s_buf, '\0', MAXNAMELEN);
1515                         memset(e_buf, '\0', MAXNAMELEN);
1516                         memset(d_buf, '\0', MAXNAMELEN);
1517                         memset(r_buf, '\0', MAXNAMELEN);
1518                         memset(f_buf, '\0', MAXNAMELEN);
1519                         if (w_buf)
1520                                 free(w_buf);
1521                         w_buf = NULL;
1522                         break;
1523                 case DATE_KEY:
1524                         cm_strcpy(d_buf, val_str);
1525                         break;
1526                 case START_KEY:
1527                         cm_strcpy(s_buf, val_str);
1528                         len = cm_strlen(s_buf) - 1;
1529                         if (s_buf[len] == 'a' || s_buf[len] == 'p')
1530                                 cm_strcat(s_buf, "m");
1531                         break;
1532                 case STOP_KEY:
1533                         cm_strcpy(e_buf, val_str);
1534                         len = cm_strlen(e_buf) - 1;
1535                         if (e_buf[len] == 'a' || e_buf[len] == 'p')
1536                                 cm_strcat(e_buf, "m");
1537                         break;
1538                 case DURATION_KEY:
1539                         dur = atoi(val_str);
1540
1541                         /*
1542                          * Check for a unit specification
1543                          */
1544                         while(*val_str != '\0' && !isspace((u_char)*val_str))
1545                                 ++val_str;
1546                         while(*val_str != '\0' && isspace((u_char)*val_str))
1547                                 ++val_str;
1548
1549                         switch(*val_str) {
1550                         case 'h':
1551                         case 'H':
1552                                 dur *= 3600;
1553                                 break;
1554                         case 'd':
1555                         case 'D':
1556                                 dur *= 86400;
1557                                 break;
1558                         case 's':
1559                         case 'S':
1560                                 break;
1561                         case 'm':
1562                         case 'M':
1563                         default:
1564                                 dur *= 60;
1565                                 break;
1566                         }
1567                         break;
1568                 case WHAT_KEY:
1569                         /*
1570                          * If we're not currently processing a what string
1571                          * and we've got a what value, there were more than one
1572                          * what strings in the file - delete the first one and
1573                          * take the second.
1574                          *
1575                          * Otherwise, we in process, so alloc more space and
1576                          * concatinate the new string to the end of the last.
1577                          */
1578                         if (!processing_what) {
1579                                 if (w_buf != NULL)
1580                                         free(w_buf);
1581                                 w_buf = cm_strdup(val_str);
1582                                 what_lines = 1;
1583                         } else {
1584                                 tmp = w_buf;
1585                                 w_buf = (char *)ckalloc(cm_strlen(tmp)
1586                                         + cm_strlen(key_str) + 2);
1587                                 cm_strcpy(w_buf, tmp);
1588                                 cm_strcat(w_buf, "\n");
1589                                 cm_strcat(w_buf, key_str);
1590                                 free(tmp);
1591                                 what_lines++;
1592                         }
1593
1594                         processing_what = B_TRUE;
1595                         break;
1596                 case REPEAT_KEY:
1597                         cm_strcpy(r_buf, val_str);
1598                         break;
1599                 case FOR_KEY:
1600                         cm_strcpy(f_buf, val_str);
1601                         break;
1602                 case NEW_APPT_KEY:
1603                         if ((version >= DATAVER4) || 
1604                             (version == DATAVER_ARCHIVE)) {
1605                                 read_new_appt(fp, &appt, p, version);
1606                                 generate_recurrence_rule(appt, version);
1607                                 CmDataListAdd(list, (void *)appt, 0);
1608                                 processing_appt = B_FALSE;
1609                                 found_appt = B_TRUE;
1610                         }
1611                         break;
1612                 case NOT_A_KEY:
1613                 default:
1614                         break;
1615                 }
1616         }
1617
1618         /*
1619          * Because the appointment read is only saved when we start reading the
1620          * next appointment, the last appointment (which doesn't have a next)
1621          * will always be left off ... so, providing we're processing an
1622          * appointment -- meaning we've found at least one header -- save the
1623          * appointment in process.
1624          */
1625
1626         if (found_appt == B_FALSE) {
1627                 free(w_buf);
1628                 fclose(fp);
1629                 return(INVALID_DATE);
1630         }
1631
1632         if (processing_appt) {
1633                 CmDataListAdd(list, (void *)appt, 0);
1634                 valid_op = validate_appt(catd, appt, s_buf, e_buf, d_buf, dur, 
1635                                          w_buf, r_buf, f_buf, query, key_data, 
1636                                          version);
1637         }
1638
1639         free(w_buf);
1640
1641
1642         scrub_attr_list(appt);
1643
1644         fclose(fp);
1645         return valid_op;
1646 }
1647
1648 void
1649 growcat(char **source, char *new)
1650 {
1651         *source = (char *) realloc(*source, strlen(*source) + strlen(new) + 2);
1652         strcat(*source, new);
1653 }
1654
1655 void
1656 cat_indented_string(char **catbuf, char *string)
1657 {
1658         char    *p_str = string;
1659         int             nl_count = 0;
1660         char            *buf;
1661         char    *b_ptr;
1662
1663         nl_count = count_newlines(string);
1664
1665         b_ptr = buf = malloc(strlen(string) + nl_count + 1);
1666
1667         while (*p_str) {
1668                 *b_ptr++ = *p_str;
1669                 if (*p_str == '\n')
1670                         *b_ptr++ = '\t';
1671                 p_str++;
1672         }
1673         *b_ptr = '\0';
1674
1675         growcat(catbuf, buf);
1676
1677         free(buf);
1678 }
1679
1680 char *
1681 attrs_to_string(CSA_attribute * attrs, int num_attrs)
1682 {
1683         int i;
1684         CSA_access_list  a_ptr;
1685         char            *buffer = malloc(1);
1686         char            tmp_buf[MAXNAMELEN];
1687         int             advance_time;
1688
1689         buffer[0] = '\0';
1690         for (i = 0; i < num_attrs; i++) {
1691                 if (!attrs[i].name || (attrs[i].value == NULL))
1692                         continue;
1693
1694                 tmp_buf[0] = '\0';
1695                 sprintf(tmp_buf, "%s:", attrs[i].name);
1696                 switch (attrs[i].value->type) {
1697                 case CSA_VALUE_SINT32: 
1698                                 growcat(&buffer, tmp_buf);
1699                                 sprintf(tmp_buf, "sinteger:%ld\n",
1700                                         attrs[i].value->item.sint32_value);
1701                                 growcat(&buffer, tmp_buf);
1702                                 break;
1703
1704                 case CSA_VALUE_UINT32: 
1705                                 growcat(&buffer, tmp_buf);
1706                                 sprintf(tmp_buf, "uinteger:%ld\n",
1707                                         attrs[i].value->item.uint32_value);
1708                                 growcat(&buffer, tmp_buf);
1709                                 break;
1710
1711                 case CSA_VALUE_DATE_TIME: 
1712                                 if (attrs[i].value->item.string_value == NULL)
1713                                         continue;
1714
1715                                 growcat(&buffer, tmp_buf);
1716                                 growcat(&buffer, "datetime:");
1717                                 if (attrs[i].value->item.date_time_value) 
1718                                         cat_indented_string(&buffer,
1719                                                 attrs[i].value->item.string_value);
1720                                 growcat(&buffer, "\n");
1721                                 break;
1722
1723                 case CSA_VALUE_STRING: 
1724                                 if (attrs[i].value->item.string_value == NULL)
1725                                         continue;
1726
1727                                 growcat(&buffer, tmp_buf);
1728                                 growcat(&buffer, "string:");
1729                                 if (attrs[i].value->item.string_value) 
1730                                         cat_indented_string(&buffer,
1731                                                 attrs[i].value->item.string_value);
1732                                 growcat(&buffer, "\n");
1733                                 break;
1734                 case CSA_VALUE_CALENDAR_USER: 
1735                                 if (attrs[i].value->item.calendar_user_value == NULL)
1736                                         continue;
1737
1738                                 growcat(&buffer, tmp_buf);
1739                                 sprintf(tmp_buf, "caluser:%ld:", attrs[i].value->item.calendar_user_value->user_type);
1740                                 growcat(&buffer, tmp_buf);
1741                                 if (attrs[i].value->item.calendar_user_value->user_name) 
1742                                         cat_indented_string(&buffer,
1743                                                 attrs[i].value->item.calendar_user_value->user_name);
1744
1745                                 growcat(&buffer, "\n");
1746                                 break;
1747
1748                 case CSA_VALUE_REMINDER: 
1749                                 if (attrs[i].value->item.reminder_value->lead_time == NULL)
1750                                         continue;
1751
1752                                 growcat(&buffer, tmp_buf);
1753                                 _csa_iso8601_to_duration(attrs[i].value->item.reminder_value->lead_time, &advance_time);
1754                                 sprintf(tmp_buf, "reminder:%d:", advance_time);
1755                                 growcat(&buffer, tmp_buf);
1756                                 if (attrs[i].value->item.reminder_value->reminder_data.data) 
1757                                         cat_indented_string(&buffer,
1758                                                 (char *) attrs[i].value->item.reminder_value->reminder_data.data);
1759                                 growcat(&buffer, "\n");
1760                                 break;
1761
1762                 case CSA_VALUE_ACCESS_LIST: 
1763                                 if (attrs[i].value->item.access_list_value == NULL)
1764                                         continue;
1765
1766                                 growcat(&buffer, tmp_buf);
1767                                 growcat(&buffer, "accesslist:\n");
1768                                 a_ptr = attrs[i].value->item.access_list_value;
1769                                 while (a_ptr) {
1770                                         sprintf(tmp_buf, "\t%d:%s\n",
1771                                                 (int) a_ptr->rights, a_ptr->user->user_name);
1772                                         growcat(&buffer, tmp_buf);
1773                                         a_ptr = a_ptr->next;
1774                                 }
1775                                 break;
1776                 }
1777         }
1778
1779         return(buffer);
1780 }
1781
1782 char *
1783 entry_to_attrval_string(CSA_session_handle target, CSA_entry_handle entry)
1784 {
1785         int                     i;
1786         char                    *ptr;
1787         CSA_attribute_reference *names;
1788         CSA_attribute           *attrs_ret;
1789         CSA_uint32              num_attrs, num_attrs_ret;
1790
1791         csa_list_entry_attributes(target, entry, &num_attrs, &names, NULL);
1792         csa_read_entry_attributes(target, entry, num_attrs, names, &num_attrs_ret,
1793                 &attrs_ret, NULL);
1794
1795         ptr = attrs_to_string(attrs_ret, num_attrs_ret);
1796
1797 /* PORTING NOTE:
1798 **   Need to check that library's memory management will
1799 **   properly free this.  Old scheme had a special fn to
1800 **   to it, which took a pointer and a count. - dac
1801 **
1802 **      [old version: DtCmFreeAttributeValues(attrs, num_attrs); ]
1803 */
1804         csa_free(attrs_ret);
1805         csa_free(names);
1806
1807         return(ptr);
1808 }
1809
1810 char *
1811 calendar_to_attrval_string(CSA_session_handle cal)
1812 {
1813         int                     i;
1814         CSA_uint32              num_attrs, num_attrs_ret;
1815         CSA_attribute_reference *names;
1816         char                    *ptr;
1817         CSA_attribute           *attrs_ret;
1818
1819         csa_list_calendar_attributes(cal, &num_attrs, &names, NULL);
1820         csa_read_calendar_attributes(cal, num_attrs, names, &num_attrs_ret,
1821                 &attrs_ret, NULL);
1822
1823         ptr = attrs_to_string(attrs_ret, num_attrs_ret);
1824
1825         csa_free(attrs_ret);
1826         csa_free(names);
1827
1828         return(ptr);
1829 }
1830
1831 /*
1832  * Given an appointment structure, return a character string that can be used
1833  * for DnD or written to a file.
1834  */
1835 extern char*
1836 parse_appt_to_string(CSA_session_handle target, CSA_entry_handle entry, Props *p, int version) {
1837         char                    *ret_val, *attr_string;
1838         Dtcm_appointment        *appt;
1839         CSA_uint32              na_ret; /* num of attributes actually read */
1840         CSA_attribute           a_ret;  /* list of attrs actually read */
1841
1842         attr_string = entry_to_attrval_string(target, entry);
1843
1844         /*
1845          * Extract the info we need from the back-end entry
1846          */
1847         appt = allocate_appt_struct(appt_read,
1848                                         version,
1849                                         NULL);
1850
1851         query_appt_struct(target, entry, appt);
1852         ret_val = parse_attrs_to_string(appt, p, attr_string);
1853         free_appt_struct(&appt);
1854         free (attr_string);
1855
1856         return ret_val;
1857 }
1858
1859 extern char*
1860 parse_attrs_to_string(Dtcm_appointment *appt, Props *p, char *attr_string) {
1861         int             nlcount, duration, repeat_nth, repeat_wk, wk;
1862         char            *whatstr, d_buf[MAXNAMELEN],
1863                         s_buf[MAXNAMELEN], e_buf[MAXNAMELEN], w_buf[MAXNAMELEN],
1864                         r_buf[MAXNAMELEN], f_buf[MAXNAMELEN], *appt_what,
1865                         *b_ptr;
1866         time_t          tick, end_tick = 0;
1867         CSA_sint32      repeat_type;
1868         CSA_uint32      repeat_times;
1869         static char *format_string = "\n\n\t** Calendar Appointment **\n%s:string:begin\n%s%s:string:end\n\tDate: %s\n\tStart: %s\n\tEnd: %s\n\tRepeat: %s\n\tFor: %s\n\tWhat: %s";
1870
1871         s_buf[0] = '\0';
1872         e_buf[0] = '\0';
1873         w_buf[0] = '\0';
1874         r_buf[0] = '\0';
1875         f_buf[0] = '\0';
1876
1877         _csa_iso8601_to_tick(appt->time->value->item.date_time_value, &tick);
1878         if (appt->end_time) 
1879                 if (appt->end_time->value != NULL)
1880                         _csa_iso8601_to_tick(appt->end_time->value->item.\
1881                                 date_time_value, &end_tick);
1882         
1883         appt_what = appt->what->value->item.string_value;
1884
1885         if (appt->repeat_type && appt->repeat_type->value)
1886                 repeat_type = appt->repeat_type->value->item.sint32_value;
1887
1888         if (appt->repeat_times && appt->repeat_times->value)
1889                 repeat_times = appt->repeat_times->value->item.uint32_value;
1890         else
1891                 repeat_times = 0;
1892
1893         if (appt->repeat_interval && appt->repeat_interval->value)
1894                 repeat_nth = appt->repeat_interval->value->item.uint32_value;
1895         else
1896                 repeat_nth = 0;
1897
1898         if (appt->repeat_week_num && appt->repeat_week_num->value)
1899                 repeat_wk = appt->repeat_week_num->value->item.sint32_value;
1900         else
1901                 repeat_wk = 0;
1902
1903         /*
1904          * Format the date and start/stop strings
1905          */
1906         format_tick(tick, ORDER_MDY, SEPARATOR_SLASH, d_buf);
1907         format_time(tick, get_int_prop(p, CP_DEFAULTDISP), s_buf);
1908         format_time(end_tick, get_int_prop(p, CP_DEFAULTDISP), e_buf);
1909
1910         /*
1911          * Handle the what string
1912          */
1913         whatstr = appt_what;
1914         if ((nlcount = count_newlines(appt_what)) > 0) {
1915                 whatstr = (char *)ckalloc(cm_strlen(appt_what) + nlcount + 1);
1916                 copy_and_pad_newlines(whatstr, appt_what);
1917         }
1918         if (whatstr && !blank_buf(whatstr)) {
1919                 cm_strcat(w_buf, whatstr);
1920                 cm_strcat(w_buf, "\n\t");
1921         }
1922  
1923         /*
1924          * Repeat and For stuff
1925          */
1926         cm_strcpy(r_buf, periodstr_from_period(repeat_type, repeat_nth));
1927         if (repeat_type == CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY) {
1928                 if (weekofmonth(tick, &wk) && wk == 4) {
1929                         if (repeat_wk == -1)
1930                                 strcat(r_buf, ", last");
1931                         else if (repeat_wk == 4)
1932                                 strcat(r_buf, ", 4th");
1933                 }
1934         }
1935
1936         sprintf(f_buf, "%ld", repeat_times);
1937
1938         /*
1939          * Put it all together
1940          */
1941
1942         b_ptr = malloc(cm_strlen(d_buf) + 
1943                         (2 * cm_strlen(CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER)) +
1944                         cm_strlen(s_buf) +
1945                         cm_strlen(e_buf) +
1946                         cm_strlen(r_buf) +
1947                         cm_strlen(f_buf) +
1948                         cm_strlen(w_buf) +
1949                         cm_strlen(format_string) +
1950                         cm_strlen(attr_string) +
1951                         1);
1952
1953         sprintf(b_ptr, format_string,
1954                 CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
1955                 attr_string, 
1956                 CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
1957                 d_buf, s_buf, e_buf, r_buf, f_buf, w_buf);
1958
1959         if (nlcount > 0)
1960                 free(whatstr);
1961         return(b_ptr);
1962 }
1963
1964 /* This routine takes a list of buffers that represent a number 
1965    of message attachments, and glues them into a mime format 
1966    message for eventual submission to the dtmail mailer.  These 
1967    objects do nor *need* to be cm appointment objects. */
1968
1969 char *
1970 create_rfc_message(char *address_list, 
1971                 char *subject, 
1972                 char **appointment_objects,
1973                 int  num_objects) {
1974
1975         char *unique_label;
1976
1977         /* do *not* put these header strings in a message catalog.  
1978            These are invariants specified by MIME */
1979
1980         char *address_header = "To: ";
1981         char *subject_header = "Subject: ";
1982         char *x_content_name = "X-Content-Name: CalendarAppointment\n";
1983         char *content_label = "Mime-Version: 1.0\nContent-Type: multipart/mixed;boundary=";
1984         char divider_string[32];
1985
1986         int  buffer_size;
1987         int  i;
1988         boolean_t  done = B_FALSE;
1989         char *return_buffer;
1990
1991         /* A MIME message is a rather specialized beast.  It consists of
1992            a series of headers describing the mail message, followed by 
1993            the message, and then followed by a set of attachments.   
1994            Each attachments is separated by a magic unique string 
1995            flagging the boundaries between the attached objects.  The
1996            first header is the address list.  The second header is the
1997            subject line for the message.  The third header describes the
1998            MIME version of the message.  The forth describes the type of
1999            information within the message and the magic string used for
2000            part divisions.
2001
2002            Each division is two dashes, the unique string, and a newline
2003            The last dividing string is trailed by two more dashes. */
2004
2005
2006         /* we need to generate a unique dividing string that will 
2007            not be found in any of the parts of the MIME message.  
2008            The following printf will be tried until it generates 
2009            something that isn't in any of the body parts. */
2010
2011         while (done != B_TRUE) {
2012                 sprintf(divider_string, "%x_%x-%x_%x-%x_%x", rand(), rand(), 
2013                         rand(), rand(), rand(), rand());
2014
2015                 done = B_TRUE;
2016
2017                 for (i = 0; i < num_objects; i++) {
2018                         if (strstr(appointment_objects[i], divider_string) != NULL)
2019                                 done = B_FALSE;
2020                 }
2021         }
2022
2023         buffer_size =   strlen(address_header) + 
2024                         strlen(address_list) + 
2025                         1 +                          /* newline */
2026                         strlen(subject_header) +
2027                         strlen(subject) +
2028                         1 +                          /* newline */
2029                         strlen(content_label) +  2 +
2030                         (num_objects + 2) * strlen(divider_string) + 
2031                                                      /* one definition copy,
2032                                                         one terminating copy,
2033                                                         and one per body
2034                                                         part */
2035
2036                         5 + strlen(divider_string) + /* empty body part */
2037
2038                         num_objects * strlen(x_content_name) +
2039                                                      /* one X-Content-Name
2040                                                         line for each
2041                                                         body part */
2042
2043                         (2 * num_objects) + 4 +      /* bracketing on unique 
2044                                                         strings */
2045                         (num_objects * 3) + 2;       /* newlines...3 per
2046                                                         body part boundary
2047                                                         and 2 for the
2048                                                         terminating boundary */
2049
2050         for (i = 0; i < num_objects; i++)
2051                 buffer_size += strlen(appointment_objects[i]);
2052
2053         /* extra byte is added for null char */
2054         return_buffer = (char *)calloc(buffer_size + 1, 1);
2055
2056         sprintf(return_buffer, "%s%s\n%s%s\n%s%s\n\n", 
2057                         address_header, 
2058                         address_list, 
2059                         subject_header, 
2060                         subject, 
2061                         content_label,
2062                         divider_string);
2063
2064         /*
2065          * Add an empty body part.  This is a hack to get dtmail to
2066          * display the object(s) as an attachment.
2067          */
2068         strcat(return_buffer, "\n--");
2069         strcat(return_buffer, divider_string);
2070         strcat(return_buffer, "\n\n");
2071
2072         for (i = 0; i <num_objects; i++) {
2073                 strcat(return_buffer, "\n--");
2074                 strcat(return_buffer, divider_string);
2075                 strcat(return_buffer, "\n");
2076                 strcat(return_buffer, x_content_name);
2077                 strcat(return_buffer, "\n");
2078                 strcat(return_buffer, appointment_objects[i]);
2079         }
2080         strcat(return_buffer, "\n--");
2081         strcat(return_buffer, divider_string);
2082         strcat(return_buffer, "--");
2083         strcat(return_buffer, "\n");
2084
2085         return(return_buffer);
2086 }
2087
2088 boolean_t
2089 appointments_to_file(CSA_session_handle target, CSA_entry_handle *appointment_list, 
2090                      int num_appts, 
2091                      char *file_name) {
2092
2093         int i;
2094         char *entry_string;
2095
2096         FILE *f_ptr = fopen(file_name,  "w");
2097
2098         if (f_ptr == NULL)
2099                 return(B_FALSE);
2100
2101         if (num_appts == 0) {
2102                 fclose(f_ptr);
2103                 return(B_FALSE);
2104         }
2105
2106         fprintf(f_ptr, "DTCM Archive 1.0\n");
2107         for (i = 0; i < num_appts; i++) {
2108                 entry_string = entry_to_attrval_string(target, appointment_list[i]);
2109
2110                 fprintf(f_ptr, "\n-%s:string:begin\n%s%s:string:end\n\n", 
2111                         CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
2112                         entry_string, 
2113                         CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER);
2114
2115                 free(entry_string);
2116         }
2117
2118         fclose(f_ptr);
2119
2120         return(B_TRUE);
2121 }
2122
2123 /*
2124  * NOTE!!  These are strings used in versions 1-4 - the repeat strings read
2125  * from a file or passed to the cm_tty_insert routine are checked against
2126  * these strings as well as the V5 API strings.
2127  */
2128 static char *periodstrings[] = {
2129         "One Time",
2130         "Daily",
2131         "Weekly",
2132         "Every Two Weeks",
2133         "Monthly By Date",
2134         "Yearly",
2135         "Monthly By Weekday",
2136         "days",
2137         "weeks",
2138         "months",
2139         "other",
2140         "Monday thru Friday",
2141         "Mon, Wed, Fri",
2142         "Tuesday, Thursday",
2143         "Weekday Combo",
2144         "Every"
2145 };
2146
2147 extern void
2148 str_to_period(char *ps, CSA_sint32 *repeat_type, int *repeat_nth) {
2149         boolean_t       compute_times = B_FALSE;
2150         char            *ps2, *ptr, *ptr2, *unit;
2151  
2152         *repeat_type = '\0';
2153         *repeat_nth = 0;
2154         if (ps == NULL)
2155                 return;
2156
2157         if (strcasecmp(ps, periodstrings[0]) == 0)
2158                 *repeat_type = CSA_X_DT_REPEAT_ONETIME;
2159         else if (strcasecmp(ps, periodstrings[1]) == 0)
2160                 *repeat_type = CSA_X_DT_REPEAT_DAILY;
2161         else if (strcasecmp(ps, periodstrings[2]) == 0)
2162                 *repeat_type = CSA_X_DT_REPEAT_WEEKLY;
2163         else if (strcasecmp(ps, periodstrings[3]) == 0)
2164                 *repeat_type = CSA_X_DT_REPEAT_BIWEEKLY;
2165         else if (strcasecmp(ps, periodstrings[4]) == 0)
2166                 *repeat_type = CSA_X_DT_REPEAT_MONTHLY_BY_DATE;
2167         else if (strcasecmp(ps, periodstrings[5]) == 0)
2168                 *repeat_type = CSA_X_DT_REPEAT_YEARLY;
2169         else if (strncasecmp(ps, periodstrings[6],
2170                              strlen(periodstrings[6])) == 0)
2171                 *repeat_type = CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY;
2172         else if (strcasecmp(ps, periodstrings[10]) == 0)
2173                 *repeat_type = CSA_X_DT_REPEAT_OTHER;
2174         else if (strcasecmp(ps, periodstrings[11]) == 0)
2175                 *repeat_type = CSA_X_DT_REPEAT_MON_TO_FRI;
2176         else if (strcasecmp(ps, periodstrings[12]) == 0)
2177                 *repeat_type = CSA_X_DT_REPEAT_MONWEDFRI;
2178         else if (strcasecmp(ps, periodstrings[13]) == 0)
2179                 *repeat_type = CSA_X_DT_REPEAT_TUETHUR;
2180         else if (strcasecmp(ps, periodstrings[14]) == 0)
2181                 *repeat_type = CSA_X_DT_REPEAT_WEEKDAYCOMBO;
2182         else if (strncasecmp(ps, periodstrings[15], strlen(periodstrings[15])) == 0) {
2183                 compute_times = B_TRUE;
2184         }
2185         else
2186                 *repeat_type = CSA_X_DT_REPEAT_ONETIME;
2187
2188         if ((compute_times) && (unit = strchr(ps, ' '))) {
2189                 while (*unit == ' ')
2190                         unit++;
2191                 ps2 = cm_strdup(unit);
2192                 ptr = strchr(ps2, ' ');
2193                 if (ptr != NULL) {
2194                         *ptr = '\0';
2195                 } else {
2196                         free(ps2);
2197                         return;
2198                 }
2199
2200                 ptr++;
2201                 while (*ptr == ' ')
2202                         ptr++;
2203
2204                 *repeat_nth = atoi(ps2);
2205                 if (strcasecmp(ptr, periodstrings[7]) == 0) {
2206                         *repeat_type = CSA_X_DT_REPEAT_EVERY_NDAY;
2207                 }
2208                 else if (strcasecmp(ptr, periodstrings[8]) == 0) {
2209                         *repeat_type = CSA_X_DT_REPEAT_EVERY_NWEEK;
2210                 }
2211                 else if (strcasecmp(ptr, periodstrings[9]) == 0) {
2212                         *repeat_type = CSA_X_DT_REPEAT_EVERY_NMONTH;
2213                 }
2214                 free(ps2);
2215         }
2216 }
2217
2218 extern char*
2219 periodstr_from_period(CSA_sint32 repeat_type, int repeat_nth) {
2220         static char pstr[80];
2221  
2222         switch (repeat_type) {
2223         case CSA_X_DT_REPEAT_ONETIME:
2224                 sprintf(pstr, "%s", periodstrings[0]);
2225                 break;
2226         case CSA_X_DT_REPEAT_DAILY:
2227                 sprintf(pstr, "%s", periodstrings[1]);
2228                 break;
2229         case CSA_X_DT_REPEAT_WEEKLY:
2230                 sprintf(pstr, "%s", periodstrings[2]);
2231                 break;
2232         case CSA_X_DT_REPEAT_BIWEEKLY:
2233                 sprintf(pstr, "%s", periodstrings[3]);
2234                 break;
2235         case CSA_X_DT_REPEAT_MONTHLY_BY_DATE:
2236                 sprintf(pstr, "%s", periodstrings[4]);
2237                 break;
2238         case CSA_X_DT_REPEAT_YEARLY:
2239                 sprintf(pstr, "%s", periodstrings[5]);
2240                 break;
2241         case CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY:
2242                 sprintf(pstr, "%s", periodstrings[6]);
2243                 break;
2244         case CSA_X_DT_REPEAT_EVERY_NDAY:
2245                 sprintf(pstr, "Every %d %s", repeat_nth, periodstrings[7]);
2246                 break;
2247         case CSA_X_DT_REPEAT_EVERY_NWEEK:
2248                 sprintf(pstr, "Every %d %s", repeat_nth, periodstrings[8]);
2249                 break;
2250         case CSA_X_DT_REPEAT_EVERY_NMONTH:
2251                 sprintf(pstr, "Every %d %s", repeat_nth, periodstrings[9]);
2252                 break;
2253         case CSA_X_DT_REPEAT_OTHER:
2254                 sprintf(pstr, "%s", periodstrings[10]);
2255                 break;
2256         case CSA_X_DT_REPEAT_MON_TO_FRI:
2257                 sprintf(pstr, "%s", periodstrings[11]);
2258                 break;
2259         case CSA_X_DT_REPEAT_MONWEDFRI:
2260                 sprintf(pstr, "%s", periodstrings[12]);
2261                 break;
2262         case CSA_X_DT_REPEAT_TUETHUR:
2263                 sprintf(pstr, "%s", periodstrings[13]);
2264                 break;
2265         case CSA_X_DT_REPEAT_WEEKDAYCOMBO:
2266                 sprintf(pstr, "%s", periodstrings[14]);
2267                 break;
2268         default:
2269                 sprintf(pstr, "Unknown repeat type");
2270                 break;
2271         }
2272  
2273         return pstr;
2274 }
2275
2276 /*
2277  * NOTE!!  These first set of these strings are used in versions 1-4 - the
2278  * privacy strings read from a file or passed to the cm_tty_insert routine are
2279  * checked against these strings as well as the V5 API strings and the new
2280  * strings.
2281  */
2282 static char *privacy_strs_old[] = {
2283         "Show Time And Text",
2284         "Show Time Only",
2285         "Show Nothing"
2286 };
2287
2288 static char *privacy_strs[] = {
2289         "Others See Time And Text",
2290         "Others See Time Only",
2291         "Others See Nothing"
2292 };
2293
2294 static char *privacy_strs_411[] = {
2295         "none",
2296         "cm_what",
2297         "all"
2298 };
2299
2300 extern char*
2301 privacy_str_old(int op) {
2302         if (op >= 0 && op <= 2)
2303                 return privacy_strs_old[op];
2304         return NULL;
2305 }
2306
2307 extern char*
2308 privacy_str(int op) {
2309         if (op >= 0 && op <= 2)
2310                 return privacy_strs[op];
2311         return NULL;
2312 }
2313
2314 static void
2315 init_repeat_strs(
2316         nl_catd         catd,
2317         char          **repeat_strs)
2318 {
2319         repeat_strs[ONE_TIME] = strdup(catgets(catd, 1, 852, "One Time")); 
2320         repeat_strs[DAILY] = strdup(catgets(catd, 1, 853, "Daily")); 
2321         repeat_strs[WEEKLY] = strdup(catgets(catd, 1, 854, "Weekly")); 
2322         repeat_strs[EVERY_TWO_WEEKS] = 
2323                         strdup(catgets(catd, 1, 855, "Every Two Weeks")); 
2324         repeat_strs[MONTHLY_BY_DATE] = 
2325                         strdup(catgets(catd, 1, 856, "Monthly By Date")); 
2326         repeat_strs[MONTHLY_BY_WEEKDAY] = 
2327                         strdup(catgets(catd, 1, 857, "Monthly By Weekday")); 
2328         repeat_strs[YEARLY] = 
2329                         strdup(catgets(catd, 1, 858, "Yearly")); 
2330         repeat_strs[MONDAY_THRU_FRIDAY] = 
2331                         strdup(catgets(catd, 1, 859, "Monday Thru Friday")); 
2332         repeat_strs[MON_WED_FRI] = 
2333                         strdup(catgets(catd, 1, 860, "Mon, Wed, Fri")); 
2334         repeat_strs[TUESDAY_THURSDAY] = 
2335                         strdup(catgets(catd, 1, 861, "Tuesday, Thursday")); 
2336         repeat_strs[REPEAT_EVERY] = 
2337                         strdup(catgets(catd, 1, 862, "Repeat Every...")); 
2338 }
2339
2340 extern char*
2341 repeat_str(
2342         nl_catd         catd,
2343         Repeat_menu_op  op)
2344 {
2345         if (!repeat_strs[0])
2346                 init_repeat_strs(catd, repeat_strs);
2347
2348         if (op >= ONE_TIME && op <= REPEAT_EVERY)
2349                 return repeat_strs[op];
2350         return NULL;
2351 }
2352
2353 extern char*
2354 privacy_str_411(int op) {
2355         if (op >= 0 && op <= 2)
2356                 return privacy_strs_411[op];
2357         return NULL;
2358 }
2359
2360 extern char*
2361 repeat_scope_str(
2362         nl_catd              catd,
2363         Repeat_scope_menu_op op)
2364 {
2365         if (!repeat_scope_strs[0]) {
2366                 repeat_scope_strs[REPEAT_DAYS] = 
2367                                 strdup(catgets(catd, 1, 994, "days")); 
2368                 repeat_scope_strs[REPEAT_WEEKS] = 
2369                                 strdup(catgets(catd, 1, 995, "weeks")); 
2370                 repeat_scope_strs[REPEAT_MONTHS] = 
2371                                 strdup(catgets(catd, 1, 997, "months")); 
2372         }
2373
2374         if (op >= REPEAT_DAYS && op <= REPEAT_MONTHS)
2375                 return repeat_scope_strs[op];
2376         return NULL;
2377 }
2378
2379 extern char*
2380 separator_str(SeparatorType op) {
2381         if (op >= SEPARATOR_BLANK && op <= SEPARATOR_DASH)
2382                 return separator_strs[op];
2383         return NULL;
2384 }
2385
2386 extern int
2387 timescopestring_to_tick(char *str) {
2388         if (strcasecmp(time_scope_strs[1], str) == 0)
2389                 return hrsec;
2390         else if (strcasecmp(time_scope_strs[2], str) == 0)
2391                 return daysec;
2392         return minsec;
2393 }
2394
2395 extern char*
2396 time_scope_str(Time_scope_menu_op op) {
2397         if (op >= TIME_MINS && op <= TIME_DAYS)
2398                 return time_scope_strs[op];
2399         return NULL;
2400 }
2401
2402 extern char*
2403 time_scope_str_i18n(
2404         nl_catd                 catd,
2405         Time_scope_menu_op      op)
2406 {
2407         if (!time_scope_strs_i18n[0]) {
2408                 time_scope_strs_i18n[TIME_MINS] = 
2409                                         strdup(catgets(catd, 1, 877, "Mins")); 
2410                 time_scope_strs_i18n[TIME_HRS] = 
2411                                         strdup(catgets(catd, 1, 878, "Hrs")); 
2412                 time_scope_strs_i18n[TIME_DAYS] = 
2413                                         strdup(catgets(catd, 1, 879, "Days")); 
2414         }
2415
2416         if (op >= TIME_MINS && op <= TIME_DAYS)
2417                 return time_scope_strs_i18n[op];
2418         return NULL;
2419 }
2420
2421 /*
2422 **  Determine whether or not the time passed is a valid format
2423 */
2424 extern boolean_t
2425 valid_time(Props *p, char *time_str) {
2426         char            *ptr;
2427         int     num_minutes = 0, num_colons = 0;
2428         boolean_t       after_colon = B_FALSE;
2429         DisplayType     dt = get_int_prop(p, CP_DEFAULTDISP);
2430  
2431         for (ptr = time_str; ptr != NULL && *ptr != '\0'; ptr++) {
2432                 if (dt == HOUR12) {
2433                         if (*ptr == ':') {
2434                                 after_colon = B_TRUE;
2435                                 if ((++num_colons) > 1)
2436                                         return B_FALSE;
2437                                 if (*(ptr+1) == '\0')
2438                                         return B_FALSE;
2439                         }
2440                         else if (*ptr != ' ' && (*ptr < '0' || *ptr > '9') )
2441                                 return B_FALSE;
2442                         if ((after_colon) && (*ptr != ':'))
2443                                 num_minutes++;
2444                         if (num_minutes > 2)
2445                                 return B_FALSE;
2446                         else if (num_minutes == 2) {
2447                                 ++ptr;
2448                                 if (strncasecmp(ptr, "am", 2) == 0 ||
2449                                     strncasecmp(ptr, "pm", 2) == 0)
2450                                         *ptr = '\0';
2451                                 --ptr;
2452                         }
2453                 }
2454                 else if (dt == HOUR24) {
2455                         if (*ptr != ' ' && (*ptr < '0' || *ptr > '9') )
2456                                 return B_FALSE;
2457                         if (++num_minutes > 4)
2458                                 return B_FALSE;
2459                 }
2460         }
2461         if (dt == HOUR12 && ((int)atoi(time_str) > 12))
2462                 return B_FALSE;
2463         else if ((dt == HOUR24)
2464                 && ((int)atoi(time_str) > 2359))
2465                 return B_FALSE;
2466
2467         return B_TRUE;
2468 }
2469
2470 /*
2471  * This method will validate the passed appointment data.
2472  *
2473  * Checks made:
2474  *      Date string incorrect           INVALID_DATE
2475  *      Tick for start is < 0           INVALID_START
2476  *      Tick for stop is < 0            INVALID_STOP
2477  *      Blank date                      MISSING_DATE
2478  *      End time but no start time      MISSING_START
2479  *      Blank what with no times        MISSING_WHAT
2480  *      Period = single & for > 0       REPEAT_FOR_MISMATCH
2481  *      Period != single & for = 0      REPEAT_FOR_MISMATCH
2482  *
2483  * Note the function pointer passed to this function - if the end time is
2484  * before the start time, this function will be executed and should return
2485  * B_TRUE if the appointment should be scheduled to the next day, B_FALSE if
2486  * it should be canceled.
2487  */
2488 extern Validate_op
2489 validate_appt(nl_catd catd, Dtcm_appointment *a, char *s_buf, char *e_buf, 
2490               char *d_buf, int dur, char *w_buf, char *r_buf, char *f_buf,
2491               boolean_t(*query)(void*), void *key_data, int version) {
2492         Validate_op     op;
2493
2494         if ((op = validate_dssw(a, s_buf, e_buf, d_buf, dur, w_buf, query,
2495                                 key_data)) != VALID_APPT)
2496                 return op;
2497         if ((op = validate_rfp(catd, a, r_buf, f_buf, version)) != VALID_APPT)
2498                 return op;
2499         if ((op = validate_reminders(a)) != VALID_APPT)
2500                 return op;
2501
2502         return VALID_APPT;
2503 }
2504
2505 extern Validate_op
2506 validate_dssw(Dtcm_appointment *a, char *s_buf, char *e_buf, char *d_buf,
2507               int dur, char *w_buf, boolean_t(*query)(), void *key_data) {
2508         Tick    end_tick = 0;
2509         char    buf[MAXNAMELEN];
2510         Tick    appt_time = 0;
2511
2512         if (blank_buf(d_buf))
2513                 return MISSING_DATE;
2514
2515         a->time->value->item.date_time_value = malloc(BUFSIZ);
2516         _csa_tick_to_iso8601(0, a->time->value->item.date_time_value);
2517
2518         a->show_time->value->item.sint32_value = B_TRUE;
2519
2520         if (w_buf && w_buf[0] != '\0') {
2521                 a->what->value->item.string_value = cm_strdup(w_buf);
2522                 expand_esc_chars(a->what->value->item.string_value);
2523         } else
2524                 a->what->value->item.string_value = NULL;
2525
2526         if (!blank_buf(s_buf)) {
2527                 /*
2528                  * We have something in the start buffer, is it valid?
2529                  */
2530                 sprintf(buf, "%s %s", d_buf, s_buf);
2531                 if ((appt_time = cm_getdate(buf, NULL)) < 0)
2532                         return INVALID_START;
2533
2534                 _csa_tick_to_iso8601(appt_time, a->time->value->item.date_time_value);
2535                 /*
2536                  * Okay, we have a valid start time - do we have a duration
2537                  * specified?
2538                  */
2539                 if (dur > 0) {
2540                         /*
2541                          * Duration is specified - add duration to start time
2542                          */
2543                         end_tick = appt_time + dur;
2544                         a->end_time->value->item.date_time_value = malloc(BUFSIZ);
2545                         _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2546                 } else if (!blank_buf(e_buf)) {
2547                         /*
2548                          * No duration, but something in the end buffer.  If
2549                          * it's valid set the end tick to it's value.
2550                          */
2551                         sprintf(buf, "%s %s", d_buf, e_buf);
2552                         if ((end_tick = cm_getdate(buf, NULL)) < 0)
2553                                 return INVALID_STOP;
2554                         a->end_time->value->item.date_time_value = malloc(BUFSIZ);
2555                         _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2556                 } else {
2557                         /*
2558                          * No duration or end buffer - set end_tick to starting
2559                          * tick and duration to 0
2560                          */
2561                         a->end_time->value->item.date_time_value = 
2562                                 strdup(a->time->value->item.date_time_value);
2563                 }
2564         } else if (dur > 0 || !blank_buf(e_buf))
2565                 return MISSING_START;
2566         else {
2567                 if (blank_buf(a->what->value->item.string_value))
2568                         return MISSING_WHAT;
2569
2570                 /*
2571                  * If we're here, there was a date with no start or stop time,
2572                  * so set time to magic time (3:41 am - don't ask where that
2573                  * came from, 'cause I certainly don't know) and make sure the
2574                  * date was correct.  If so, set duration to 1 minute and
2575                  * showtime to false.
2576                  */
2577                 sprintf(buf, "%s 3:41am", d_buf);
2578
2579                 if ((appt_time = cm_getdate(buf, NULL)) < 0)
2580                         return INVALID_DATE;
2581
2582                 a->time->value->item.date_time_value = malloc(BUFSIZ);
2583                 _csa_tick_to_iso8601(appt_time, a->time->value->item.date_time_value);
2584
2585                 end_tick = appt_time + minsec;
2586
2587                 a->end_time->value->item.date_time_value = malloc(BUFSIZ);
2588                 _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2589
2590                 a->show_time->value->item.sint32_value = B_FALSE;
2591         }
2592
2593         /*
2594          * Finally, if the ending tick is before the starting tick, execute the
2595          * passed function which should return B_TRUE if we should schedule
2596          * this into the next day and B_FALSE if not.
2597          *
2598          * This allows for methods calling this function to be UI oriented or
2599          * command line oriented as they can "query" the user appropriately.
2600          */
2601         if (end_tick < appt_time) {
2602                 if ((*query)(key_data) == B_TRUE) {
2603                         while (end_tick < appt_time)
2604                                 end_tick += daysec;
2605
2606                         _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2607
2608                 }
2609                 else
2610                         return CANCEL_APPT;
2611         }
2612
2613         return VALID_APPT;
2614 }
2615
2616 extern Validate_op
2617 validate_reminders(Dtcm_appointment *a) {
2618         return VALID_APPT;
2619 }
2620
2621 extern Validate_op
2622 validate_rfp(
2623         nl_catd                  catd,
2624         Dtcm_appointment        *a, 
2625         char                    *r_buf, 
2626         char                    *f_buf, 
2627         int                      version)
2628 {
2629         int                      repeat_nth, 
2630                                  repeat_wk = -1,
2631                                  repeat_every = 0,
2632                                  repeat_forever = False;
2633         CSA_sint32               repeat_type;
2634         CSA_uint32               repeat_times = 0;
2635         char                     rule_buf[32];
2636
2637         if (r_buf) {
2638                 str_to_period(r_buf, &repeat_type, &repeat_nth);
2639                 if (repeat_type == CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY) {
2640                         r_buf += strlen(periodstrings[6]);
2641                         while(*r_buf != '\0' && !isspace((u_char)*r_buf))
2642                                 ++r_buf;
2643                         while(*r_buf != '\0' && isspace((u_char)*r_buf))
2644                                 ++r_buf;
2645
2646                         if (strncasecmp(r_buf, "last", 4) != 0)
2647                                 repeat_wk = atoi(r_buf);
2648                 }
2649         }
2650
2651         if (f_buf) {
2652                 /* Repeat forever is represented by either:
2653                         f_buf == ``forever''   or
2654                         f_buf == ``0''.
2655                    If it is a CSA_X_DT_REPEAT_ONETIME then 
2656                         f_buf == ``0''.
2657                  */
2658                 if (strcasecmp(f_buf, catgets(catd, 1, 876, "forever")) == 0) {
2659                         repeat_times = CSA_X_DT_DT_REPEAT_FOREVER;
2660                         repeat_forever = True;
2661                 } else {
2662                         repeat_times = atoi(f_buf);
2663                         if (repeat_times == CSA_X_DT_DT_REPEAT_FOREVER &&
2664                             repeat_type != CSA_X_DT_REPEAT_ONETIME)
2665                                 repeat_forever = True;
2666                 }
2667         }
2668
2669         /* If it is a onetime event then it cannot repeat.
2670          * If it is a repeating event then repeat_times must be greater
2671          * than 0 unless it is supposed to repeat forever.
2672          */
2673         if (((repeat_type != CSA_X_DT_REPEAT_ONETIME) &&
2674             (repeat_times == 0) && (repeat_forever != True)) ||
2675             ((repeat_type == CSA_X_DT_REPEAT_ONETIME) &&
2676              ((repeat_times != 0) || (repeat_forever == True))))
2677                 return REPEAT_FOR_MISMATCH;
2678
2679         if (a->repeat_type && a->repeat_type->value)
2680                 a->repeat_type->value->item.sint32_value = repeat_type;
2681         if (a->repeat_times && a->repeat_times->value)
2682                 a->repeat_times->value->item.uint32_value = repeat_times;
2683         if (a->repeat_interval && a->repeat_interval->value)
2684                 a->repeat_interval->value->item.uint32_value = repeat_nth;
2685         if (a->repeat_week_num && a->repeat_week_num->value)
2686                 a->repeat_week_num->value->item.sint32_value = repeat_wk;
2687
2688         /* If we are less than data version 4, we're done. */
2689         if (version < DATAVER4)
2690                 return VALID_APPT;
2691
2692         /* Data version 4 appts use a recurrence rule. */
2693         memset (rule_buf, 0, 32);
2694
2695         switch(repeat_type) {
2696         case CSA_X_DT_REPEAT_ONETIME:
2697         case CSA_X_DT_REPEAT_DAILY:
2698                 strcpy(rule_buf, "D1 ");
2699                 break;
2700         case CSA_X_DT_REPEAT_WEEKLY:
2701                 strcpy(rule_buf, "W1 ");
2702                 break;
2703         case CSA_X_DT_REPEAT_BIWEEKLY:
2704                 strcpy(rule_buf, "W2 ");
2705                 break;
2706         case CSA_X_DT_REPEAT_MONTHLY_BY_DATE:
2707                 strcpy(rule_buf, "MD1 ");
2708                 break;
2709         case CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY: {
2710                 int     wk;
2711                 Tick    tick = 0;
2712
2713                 if (a && a->time && a->time->value) {
2714                         _csa_iso8601_to_tick(
2715                                         a->time->value->item.date_time_value, 
2716                                         &tick);
2717                 }
2718
2719                 /*
2720                  * The current behavior of cm/dtcm is that if an appt is
2721                  * scheduled for the 5 wk of the month, it repeats on the
2722                  * last week of the month.
2723                  */
2724                 if (tick && weekofmonth(tick, &wk) && wk == 5)
2725                         sprintf(rule_buf, "MP1 1- %s ", dow_str(tick));
2726                 else
2727                         strcpy(rule_buf, "MP1 ");
2728                 break;
2729         }
2730         case CSA_X_DT_REPEAT_YEARLY:
2731                 strcpy(rule_buf, "YM1 ");
2732                 break;
2733         case CSA_X_DT_REPEAT_MON_TO_FRI:
2734                 strcpy(rule_buf, "W1 MO TU WE TH FR ");
2735                 break;
2736         case CSA_X_DT_REPEAT_MONWEDFRI:
2737                 strcpy(rule_buf, "W1 MO WE FR ");
2738                 break;
2739         case CSA_X_DT_REPEAT_TUETHUR:
2740                 strcpy(rule_buf, "W1 TU TH ");
2741                 break;
2742         case CSA_X_DT_REPEAT_EVERY_NDAY:
2743                 sprintf(rule_buf, "D%d ", repeat_nth);
2744                 repeat_every = True;
2745                 break;
2746         case CSA_X_DT_REPEAT_EVERY_NWEEK:
2747                 sprintf(rule_buf, "W%d ", repeat_nth);
2748                 repeat_every = True;
2749                 break;
2750         case CSA_X_DT_REPEAT_EVERY_NMONTH:
2751                 sprintf(rule_buf, "MD%d ", repeat_nth);
2752                 repeat_every = True;
2753                 break;
2754         default:
2755                 return CANCEL_APPT;
2756         }
2757
2758         /* If the for buffer is NULL then we default to repeating one time.
2759          * If the repeat_type is onetime then we default repeat times to
2760          * one time. 
2761          */
2762         if (!f_buf || repeat_type == CSA_X_DT_REPEAT_ONETIME)
2763                 repeat_times = 1;
2764
2765         if (repeat_times == CSA_X_DT_DT_REPEAT_FOREVER) {
2766                 strcat(rule_buf, "#0");
2767         } else {
2768                 char    buf[16];
2769
2770                 if (repeat_every) {
2771                         int duration;
2772
2773                         if (repeat_times % repeat_nth)
2774                                 duration = 1 + repeat_times/repeat_nth;
2775                         else
2776                                 duration = repeat_times/repeat_nth;
2777                         sprintf(buf, "#%d", duration);
2778                 } else
2779                         sprintf(buf, "#%ld", repeat_times);
2780
2781                 strcat(rule_buf, buf);
2782         }
2783         a->recurrence_rule->value->item.string_value = strdup(rule_buf);
2784
2785         return VALID_APPT;
2786 }