385ef451469933fb545d680301fba6e71a07ea5b
[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(register char *dest, register char *source) {
172         while (*source)
173                 if ((*dest++ = *source++) == '\n')
174                         *dest++ =  '\t';
175 }
176
177 static int
178 count_newlines(register 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                 free(a_name);
1218                 free(a_tag);
1219                 free(a_value);
1220         }
1221
1222         avlist->count = attrs_written;
1223         set_appt_links(avlist);
1224         *appt = avlist;
1225 }
1226
1227
1228 static char *
1229 dow_str(
1230         time_t  tick)
1231 {
1232         switch (dow(tick)) {
1233         case 0:
1234                 return (cm_strdup("SU"));
1235         case 1:
1236                 return (cm_strdup("MO"));
1237         case 2:
1238                 return (cm_strdup("TU"));
1239         case 3:
1240                 return (cm_strdup("WE"));
1241         case 4:
1242                 return (cm_strdup("TH"));
1243         case 5:
1244                 return (cm_strdup("FR"));
1245         case 6:
1246         default:
1247                 return (cm_strdup("SA"));
1248         }
1249 }
1250
1251 /* this routine is designed to take in an appintment, and generate the 
1252    DATAVER4 recurrence rule for that appointment.  It is also responsible 
1253    for crushing out the old style attributes that may not be inserted 
1254    into a newer style data model. */
1255
1256 static void
1257 generate_recurrence_rule(Dtcm_appointment *appt, int version) {
1258
1259         char            *str,
1260                          rule_buf1[32],
1261                          rule_buf2[32];
1262         CSA_sint32      repeat_type;
1263         CSA_uint32      repeat_nth;
1264         CSA_uint32      repeat_for;
1265         Tick            appt_time;
1266         int             wk;
1267
1268         rule_buf1[0] = '\0';
1269         rule_buf2[0] = '\0';
1270
1271         if (appt->repeat_type && appt->repeat_type->value)
1272                 repeat_type = appt->repeat_type->value->item.sint32_value;
1273         else
1274                 repeat_type = CSA_X_DT_REPEAT_ONETIME;
1275
1276         if (appt->repeat_interval && appt->repeat_interval->value)
1277                 repeat_nth = appt->repeat_interval->value->item.uint32_value;
1278
1279         if (appt->repeat_times && appt->repeat_times->value)
1280                 repeat_for = appt->repeat_times->value->item.uint32_value;
1281
1282         _csa_iso8601_to_tick(appt->time->value->item.date_time_value, &appt_time);
1283
1284         if (!appt->recurrence_rule || !appt->recurrence_rule->value) {
1285                 switch (repeat_type) {
1286                 
1287                 case CSA_X_DT_REPEAT_ONETIME : 
1288                         strcpy(rule_buf1, "D1 ");
1289                         break;
1290                 
1291                 case CSA_X_DT_REPEAT_DAILY : 
1292                         strcpy(rule_buf1, "D1 ");
1293                         break;
1294                 
1295                 case CSA_X_DT_REPEAT_WEEKLY : 
1296                         strcpy(rule_buf1, "W1 ");
1297                         break;
1298                 
1299                 case CSA_X_DT_REPEAT_BIWEEKLY : 
1300                         strcpy(rule_buf1, "W2 ");
1301                         break;
1302                 
1303                 case CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY : 
1304
1305                         if (weekofmonth(appt_time, &wk) && wk == 5)
1306                                 sprintf(rule_buf1, "MP1 1- %s ", dow_str(appt_time));
1307                         else
1308                                 strcpy(rule_buf1, "MP1 ");
1309
1310                         break;
1311                 
1312                 case CSA_X_DT_REPEAT_MONTHLY_BY_DATE : 
1313                         strcpy(rule_buf1, "MD1 ");
1314                         break;
1315                 
1316                 case CSA_X_DT_REPEAT_YEARLY : 
1317                         strcpy(rule_buf1, "YM1 ");
1318                         break;
1319                 
1320                 case CSA_X_DT_REPEAT_MON_TO_FRI : 
1321                         strcpy(rule_buf1, "W1 MO TU WE TH FR ");
1322                         break;
1323                 
1324                 case CSA_X_DT_REPEAT_MONWEDFRI : 
1325                         strcpy(rule_buf1, "W1 MO WE FR ");
1326                         break;
1327                 
1328                 case CSA_X_DT_REPEAT_TUETHUR : 
1329                         strcpy(rule_buf1, "W1 TU TH ");
1330                         break;
1331                 
1332                 case CSA_X_DT_REPEAT_EVERY_NDAY : 
1333                         sprintf(rule_buf1, "D%ld ", repeat_nth);
1334                         break;
1335                 
1336                 case CSA_X_DT_REPEAT_EVERY_NWEEK : 
1337                         sprintf(rule_buf1, "W%ld ", repeat_nth);
1338                         break;
1339                 
1340                 case CSA_X_DT_REPEAT_EVERY_NMONTH : 
1341                         sprintf(rule_buf1, "MD%ld ", repeat_nth);
1342                         break;
1343                 }
1344
1345                 if (repeat_for == 0)
1346                         strcat(rule_buf2, "#1");
1347                 else
1348                         sprintf(rule_buf2, "#%ld", repeat_for);
1349         
1350                 strcat (rule_buf1, rule_buf2);
1351
1352                 appt->attrs = realloc(appt->attrs,
1353                                       sizeof(CSA_attribute) * (appt->count + 1));
1354
1355                 build_new_attrval(&appt->attrs[appt->count],
1356                                   CSA_ENTRY_ATTR_RECURRENCE_RULE, 
1357                                   "string", 
1358                                   rule_buf1);
1359
1360                 appt->count++;
1361
1362                 set_appt_links(appt);
1363         }
1364
1365         if (version == DATAVER_ARCHIVE)
1366                 return;
1367
1368         /* crush out the old values, if they exist */
1369
1370         if (appt->repeat_type && appt->repeat_type->name) {
1371                 free(appt->repeat_type->name);
1372                 appt->repeat_type->name = NULL;
1373         }
1374
1375         if (appt->repeat_times && appt->repeat_times->name) {
1376                 free(appt->repeat_times->name);
1377                 appt->repeat_times->name = NULL;
1378         }
1379
1380         if (appt->repeat_interval && appt->repeat_interval->name) {
1381                 free(appt->repeat_interval->name);
1382                 appt->repeat_interval->name = NULL;
1383         }
1384
1385         if (appt->repeat_week_num && appt->repeat_week_num->name) {
1386                 free(appt->repeat_week_num->name);
1387                 appt->repeat_week_num->name = NULL;
1388         }
1389                 
1390 }
1391
1392 /*
1393  * Given a file name, we're going to parse the file and create an appointment
1394  * for the calling routine.
1395  *
1396  * This function will scan for X number of appointments in the file and add
1397  * each to the linked list unless the validation routine returns an error.
1398  * If this happens, the invalid appointment is still added to the list so the
1399  * calling routine can do further processing if necessary, but the remaining
1400  * appointments in the file (if any) are not read.
1401  */
1402 extern Validate_op
1403 parse_appt_from_file(nl_catd catd, char *file, CmDataList *list, Props *p,
1404                      boolean_t(*query)(), void *key_data, int version) {
1405         int                     len, dur;
1406         char                    line[MAXNAMELEN], *tmp, *key_str, *val_str,
1407                                 s_buf[MAXNAMELEN], e_buf[MAXNAMELEN],
1408                                 d_buf[MAXNAMELEN], r_buf[MAXNAMELEN],
1409                                 f_buf[MAXNAMELEN], *w_buf = NULL;
1410         FILE                    *fp;
1411         boolean_t               processing_appt = B_FALSE,
1412                                 processing_what = B_FALSE;
1413         int                     what_lines = 0;
1414         Validate_op             valid_op = VALID_APPT;
1415         Parse_key_op            pko = NOT_A_KEY;
1416         Dtcm_appointment        *appt;
1417         boolean_t               found_appt = B_FALSE;
1418
1419         if (!file || *file == '\0' || (fp = fopen(file, "r")) == NULL)
1420                 return COULD_NOT_OPEN_FILE;
1421
1422         appt = allocate_appt_struct(appt_write, version, NULL);
1423         load_appt_defaults(appt, p);
1424         while (valid_op == VALID_APPT && fgets(line, MAXNAMELEN - 1, fp)) {
1425                 /*
1426                  * Point key_str at the first non-space character
1427                  */
1428                 key_str = line;
1429
1430                 if ((*line != ' ' && *line != '\t') || blank_buf(line)) {
1431                         processing_what = B_FALSE;
1432                         what_lines = 0;
1433                 }
1434
1435                 /* 
1436                  * The check for '*' handles the case when the appt header
1437                  * ``** Calendar Appointment **'' has no white space to its
1438                  * left.
1439                  */
1440                 if (*key_str == '\0' || 
1441                     (!isspace((u_char)*key_str) && (u_char)*key_str != '*'))
1442                         continue;
1443
1444                 while (*key_str != '\0' && isspace((u_char)*key_str))
1445                         ++key_str;
1446
1447                 pko = identify_parse_key(key_str);
1448
1449                 /*
1450                  * Remove any ending white space
1451                  */
1452                 tmp = strrchr(line, '\0'); --tmp;
1453                 while(isspace((u_char)*tmp)) {
1454                         *tmp = '\0';
1455                         --tmp;
1456                 }
1457
1458                 /*
1459                  * If we're not currently processing the what string and we
1460                  * haven't identified a key, continue the loop.  Otherwise,
1461                  * we've identified a key, so we can't be in the middle of
1462                  * processing the what string -- set the flag accordingly.
1463                  *
1464                  */
1465                 if (pko == NOT_A_KEY) {
1466                         if (!processing_what || what_lines >= 5)
1467                                 continue;
1468                         pko = WHAT_KEY;
1469                 } else
1470                 {
1471                         processing_what = B_FALSE;
1472                         what_lines = 0;
1473                 }
1474
1475                 /*
1476                  * Point val_str at the next non-space character after the key
1477                  */
1478                 val_str = key_str;
1479                 while(*val_str != '\0' && !isspace((u_char)*val_str))
1480                         ++val_str;
1481                 while(*val_str != '\0' && isspace((u_char)*val_str))
1482                         ++val_str;
1483
1484                 /*
1485                  * Now, based on the keyword, set the necessary stuff in the
1486                  * new appointment structure.
1487                  */
1488                 switch(pko) {
1489                 case APPOINTMENT_START:
1490                         if (processing_appt) {
1491                                 /*
1492                                  * We've reached another appointment.  Add the
1493                                  * current to the linked list, reset flags, and
1494                                  * check it's validity ...
1495                                  */
1496                                 CmDataListAdd(list, (void *)appt, 0);
1497
1498                                 valid_op = validate_appt(catd, appt, s_buf, 
1499                                         e_buf, d_buf, dur, w_buf, r_buf, f_buf,
1500                                         query, key_data, version);
1501                                 if (valid_op == VALID_APPT) {
1502                                         appt = allocate_appt_struct(appt_write, version, NULL);
1503                                         load_appt_defaults(appt, p);
1504                                 }
1505                                 else
1506                                         processing_appt = B_FALSE;
1507                         } else
1508                                 processing_appt = B_TRUE;
1509
1510                         found_appt = B_TRUE;
1511
1512                         dur = 0;
1513                         memset(s_buf, '\0', MAXNAMELEN);
1514                         memset(e_buf, '\0', MAXNAMELEN);
1515                         memset(d_buf, '\0', MAXNAMELEN);
1516                         memset(r_buf, '\0', MAXNAMELEN);
1517                         memset(f_buf, '\0', MAXNAMELEN);
1518                         if (w_buf)
1519                                 free(w_buf);
1520                         w_buf = NULL;
1521                         break;
1522                 case DATE_KEY:
1523                         cm_strcpy(d_buf, val_str);
1524                         break;
1525                 case START_KEY:
1526                         cm_strcpy(s_buf, val_str);
1527                         len = cm_strlen(s_buf) - 1;
1528                         if (s_buf[len] == 'a' || s_buf[len] == 'p')
1529                                 cm_strcat(s_buf, "m");
1530                         break;
1531                 case STOP_KEY:
1532                         cm_strcpy(e_buf, val_str);
1533                         len = cm_strlen(e_buf) - 1;
1534                         if (e_buf[len] == 'a' || e_buf[len] == 'p')
1535                                 cm_strcat(e_buf, "m");
1536                         break;
1537                 case DURATION_KEY:
1538                         dur = atoi(val_str);
1539
1540                         /*
1541                          * Check for a unit specification
1542                          */
1543                         while(*val_str != '\0' && !isspace((u_char)*val_str))
1544                                 ++val_str;
1545                         while(*val_str != '\0' && isspace((u_char)*val_str))
1546                                 ++val_str;
1547
1548                         switch(*val_str) {
1549                         case 'h':
1550                         case 'H':
1551                                 dur *= 3600;
1552                                 break;
1553                         case 'd':
1554                         case 'D':
1555                                 dur *= 86400;
1556                                 break;
1557                         case 's':
1558                         case 'S':
1559                                 break;
1560                         case 'm':
1561                         case 'M':
1562                         default:
1563                                 dur *= 60;
1564                                 break;
1565                         }
1566                         break;
1567                 case WHAT_KEY:
1568                         /*
1569                          * If we're not currently processing a what string
1570                          * and we've got a what value, there were more than one
1571                          * what strings in the file - delete the first one and
1572                          * take the second.
1573                          *
1574                          * Otherwise, we in process, so alloc more space and
1575                          * concatinate the new string to the end of the last.
1576                          */
1577                         if (!processing_what) {
1578                                 if (w_buf != NULL)
1579                                         free(w_buf);
1580                                 w_buf = cm_strdup(val_str);
1581                                 what_lines = 1;
1582                         } else {
1583                                 tmp = w_buf;
1584                                 w_buf = (char *)ckalloc(cm_strlen(tmp)
1585                                         + cm_strlen(key_str) + 2);
1586                                 cm_strcpy(w_buf, tmp);
1587                                 cm_strcat(w_buf, "\n");
1588                                 cm_strcat(w_buf, key_str);
1589                                 free(tmp);
1590                                 what_lines++;
1591                         }
1592
1593                         processing_what = B_TRUE;
1594                         break;
1595                 case REPEAT_KEY:
1596                         cm_strcpy(r_buf, val_str);
1597                         break;
1598                 case FOR_KEY:
1599                         cm_strcpy(f_buf, val_str);
1600                         break;
1601                 case NEW_APPT_KEY:
1602                         if ((version >= DATAVER4) || 
1603                             (version == DATAVER_ARCHIVE)) {
1604                                 read_new_appt(fp, &appt, p, version);
1605                                 generate_recurrence_rule(appt, version);
1606                                 CmDataListAdd(list, (void *)appt, 0);
1607                                 processing_appt = B_FALSE;
1608                                 found_appt = B_TRUE;
1609                         }
1610                         break;
1611                 case NOT_A_KEY:
1612                 default:
1613                         break;
1614                 }
1615         }
1616
1617         /*
1618          * Because the appointment read is only saved when we start reading the
1619          * next appointment, the last appointment (which doesn't have a next)
1620          * will always be left off ... so, providing we're processing an
1621          * appointment -- meaning we've found at least one header -- save the
1622          * appointment in process.
1623          */
1624
1625         if (found_appt == B_FALSE) {
1626                 fclose(fp);
1627                 return(INVALID_DATE);
1628         }
1629
1630         if (processing_appt) {
1631                 CmDataListAdd(list, (void *)appt, 0);
1632                 valid_op = validate_appt(catd, appt, s_buf, e_buf, d_buf, dur, 
1633                                          w_buf, r_buf, f_buf, query, key_data, 
1634                                          version);
1635                 if (w_buf)
1636                         free(w_buf);
1637         }
1638
1639         scrub_attr_list(appt);
1640
1641         fclose(fp);
1642         return valid_op;
1643 }
1644
1645 void
1646 growcat(char **source, char *new)
1647 {
1648         *source = (char *) realloc(*source, strlen(*source) + strlen(new) + 2);
1649         strcat(*source, new);
1650 }
1651
1652 void
1653 cat_indented_string(char **catbuf, char *string)
1654 {
1655         register char   *p_str = string;
1656         int             nl_count = 0;
1657         char            *buf;
1658         register char   *b_ptr;
1659
1660         nl_count = count_newlines(string);
1661
1662         b_ptr = buf = malloc(strlen(string) + nl_count + 1);
1663
1664         while (*p_str) {
1665                 *b_ptr++ = *p_str;
1666                 if (*p_str == '\n')
1667                         *b_ptr++ = '\t';
1668                 p_str++;
1669         }
1670         *b_ptr = '\0';
1671
1672         growcat(catbuf, buf);
1673
1674         free(buf);
1675 }
1676
1677 char *
1678 attrs_to_string(CSA_attribute * attrs, int num_attrs)
1679 {
1680         int i;
1681         CSA_access_list  a_ptr;
1682         char            *buffer = malloc(1);
1683         char            tmp_buf[MAXNAMELEN];
1684         int             advance_time;
1685
1686         buffer[0] = '\0';
1687         for (i = 0; i < num_attrs; i++) {
1688                 if (!attrs[i].name || (attrs[i].value == NULL))
1689                         continue;
1690
1691                 tmp_buf[0] = '\0';
1692                 sprintf(tmp_buf, "%s:", attrs[i].name);
1693                 switch (attrs[i].value->type) {
1694                 case CSA_VALUE_SINT32: 
1695                                 growcat(&buffer, tmp_buf);
1696                                 sprintf(tmp_buf, "sinteger:%ld\n",
1697                                         attrs[i].value->item.sint32_value);
1698                                 growcat(&buffer, tmp_buf);
1699                                 break;
1700
1701                 case CSA_VALUE_UINT32: 
1702                                 growcat(&buffer, tmp_buf);
1703                                 sprintf(tmp_buf, "uinteger:%ld\n",
1704                                         attrs[i].value->item.uint32_value);
1705                                 growcat(&buffer, tmp_buf);
1706                                 break;
1707
1708                 case CSA_VALUE_DATE_TIME: 
1709                                 if (attrs[i].value->item.string_value == NULL)
1710                                         continue;
1711
1712                                 growcat(&buffer, tmp_buf);
1713                                 growcat(&buffer, "datetime:");
1714                                 if (attrs[i].value->item.date_time_value) 
1715                                         cat_indented_string(&buffer,
1716                                                 attrs[i].value->item.string_value);
1717                                 growcat(&buffer, "\n");
1718                                 break;
1719
1720                 case CSA_VALUE_STRING: 
1721                                 if (attrs[i].value->item.string_value == NULL)
1722                                         continue;
1723
1724                                 growcat(&buffer, tmp_buf);
1725                                 growcat(&buffer, "string:");
1726                                 if (attrs[i].value->item.string_value) 
1727                                         cat_indented_string(&buffer,
1728                                                 attrs[i].value->item.string_value);
1729                                 growcat(&buffer, "\n");
1730                                 break;
1731                 case CSA_VALUE_CALENDAR_USER: 
1732                                 if (attrs[i].value->item.calendar_user_value == NULL)
1733                                         continue;
1734
1735                                 growcat(&buffer, tmp_buf);
1736                                 sprintf(tmp_buf, "caluser:%ld:", attrs[i].value->item.calendar_user_value->user_type);
1737                                 growcat(&buffer, tmp_buf);
1738                                 if (attrs[i].value->item.calendar_user_value->user_name) 
1739                                         cat_indented_string(&buffer,
1740                                                 attrs[i].value->item.calendar_user_value->user_name);
1741
1742                                 growcat(&buffer, "\n");
1743                                 break;
1744
1745                 case CSA_VALUE_REMINDER: 
1746                                 if (attrs[i].value->item.reminder_value->lead_time == NULL)
1747                                         continue;
1748
1749                                 growcat(&buffer, tmp_buf);
1750                                 _csa_iso8601_to_duration(attrs[i].value->item.reminder_value->lead_time, &advance_time);
1751                                 sprintf(tmp_buf, "reminder:%d:", advance_time);
1752                                 growcat(&buffer, tmp_buf);
1753                                 if (attrs[i].value->item.reminder_value->reminder_data.data) 
1754                                         cat_indented_string(&buffer,
1755                                                 (char *) attrs[i].value->item.reminder_value->reminder_data.data);
1756                                 growcat(&buffer, "\n");
1757                                 break;
1758
1759                 case CSA_VALUE_ACCESS_LIST: 
1760                                 if (attrs[i].value->item.access_list_value == NULL)
1761                                         continue;
1762
1763                                 growcat(&buffer, tmp_buf);
1764                                 growcat(&buffer, "accesslist:\n");
1765                                 a_ptr = attrs[i].value->item.access_list_value;
1766                                 while (a_ptr) {
1767                                         sprintf(tmp_buf, "\t%d:%s\n",
1768                                                 (int) a_ptr->rights, a_ptr->user->user_name);
1769                                         growcat(&buffer, tmp_buf);
1770                                         a_ptr = a_ptr->next;
1771                                 }
1772                                 break;
1773                 }
1774         }
1775
1776         return(buffer);
1777 }
1778
1779 char *
1780 entry_to_attrval_string(CSA_session_handle target, CSA_entry_handle entry)
1781 {
1782         int                     i;
1783         char                    *ptr;
1784         CSA_attribute_reference *names;
1785         CSA_attribute           *attrs_ret;
1786         CSA_uint32              num_attrs, num_attrs_ret;
1787
1788         csa_list_entry_attributes(target, entry, &num_attrs, &names, NULL);
1789         csa_read_entry_attributes(target, entry, num_attrs, names, &num_attrs_ret,
1790                 &attrs_ret, NULL);
1791
1792         ptr = attrs_to_string(attrs_ret, num_attrs_ret);
1793
1794 /* PORTING NOTE:
1795 **   Need to check that library's memory management will
1796 **   properly free this.  Old scheme had a special fn to
1797 **   to it, which took a pointer and a count. - dac
1798 **
1799 **      [old version: DtCmFreeAttributeValues(attrs, num_attrs); ]
1800 */
1801         csa_free(attrs_ret);
1802         csa_free(names);
1803
1804         return(ptr);
1805 }
1806
1807 char *
1808 calendar_to_attrval_string(CSA_session_handle cal)
1809 {
1810         int                     i;
1811         CSA_uint32              num_attrs, num_attrs_ret;
1812         CSA_attribute_reference *names;
1813         char                    *ptr;
1814         CSA_attribute           *attrs_ret;
1815
1816         csa_list_calendar_attributes(cal, &num_attrs, &names, NULL);
1817         csa_read_calendar_attributes(cal, num_attrs, names, &num_attrs_ret,
1818                 &attrs_ret, NULL);
1819
1820         ptr = attrs_to_string(attrs_ret, num_attrs_ret);
1821
1822         csa_free(attrs_ret);
1823         csa_free(names);
1824
1825         return(ptr);
1826 }
1827
1828 /*
1829  * Given an appointment structure, return a character string that can be used
1830  * for DnD or written to a file.
1831  */
1832 extern char*
1833 parse_appt_to_string(CSA_session_handle target, CSA_entry_handle entry, Props *p, int version) {
1834         char                    *ret_val, *attr_string;
1835         Dtcm_appointment        *appt;
1836         CSA_uint32              na_ret; /* num of attributes actually read */
1837         CSA_attribute           a_ret;  /* list of attrs actually read */
1838
1839         attr_string = entry_to_attrval_string(target, entry);
1840
1841         /*
1842          * Extract the info we need from the back-end entry
1843          */
1844         appt = allocate_appt_struct(appt_read,
1845                                         version,
1846                                         NULL);
1847
1848         query_appt_struct(target, entry, appt);
1849         ret_val = parse_attrs_to_string(appt, p, attr_string);
1850         free_appt_struct(&appt);
1851         free (attr_string);
1852
1853         return ret_val;
1854 }
1855
1856 extern char*
1857 parse_attrs_to_string(Dtcm_appointment *appt, Props *p, char *attr_string) {
1858         int             nlcount, duration, repeat_nth, repeat_wk, wk;
1859         char            *whatstr, d_buf[MAXNAMELEN],
1860                         s_buf[MAXNAMELEN], e_buf[MAXNAMELEN], w_buf[MAXNAMELEN],
1861                         r_buf[MAXNAMELEN], f_buf[MAXNAMELEN], *appt_what,
1862                         *b_ptr;
1863         time_t          tick, end_tick = 0;
1864         CSA_sint32      repeat_type;
1865         CSA_uint32      repeat_times;
1866         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";
1867
1868         s_buf[0] = '\0';
1869         e_buf[0] = '\0';
1870         w_buf[0] = '\0';
1871         r_buf[0] = '\0';
1872         f_buf[0] = '\0';
1873
1874         _csa_iso8601_to_tick(appt->time->value->item.date_time_value, &tick);
1875         if (appt->end_time) 
1876                 if (appt->end_time->value != NULL)
1877                         _csa_iso8601_to_tick(appt->end_time->value->item.\
1878                                 date_time_value, &end_tick);
1879         
1880         appt_what = appt->what->value->item.string_value;
1881
1882         if (appt->repeat_type && appt->repeat_type->value)
1883                 repeat_type = appt->repeat_type->value->item.sint32_value;
1884
1885         if (appt->repeat_times && appt->repeat_times->value)
1886                 repeat_times = appt->repeat_times->value->item.uint32_value;
1887         else
1888                 repeat_times = 0;
1889
1890         if (appt->repeat_interval && appt->repeat_interval->value)
1891                 repeat_nth = appt->repeat_interval->value->item.uint32_value;
1892         else
1893                 repeat_nth = 0;
1894
1895         if (appt->repeat_week_num && appt->repeat_week_num->value)
1896                 repeat_wk = appt->repeat_week_num->value->item.sint32_value;
1897         else
1898                 repeat_wk = 0;
1899
1900         /*
1901          * Format the date and start/stop strings
1902          */
1903         format_tick(tick, ORDER_MDY, SEPARATOR_SLASH, d_buf);
1904         format_time(tick, get_int_prop(p, CP_DEFAULTDISP), s_buf);
1905         format_time(end_tick, get_int_prop(p, CP_DEFAULTDISP), e_buf);
1906
1907         /*
1908          * Handle the what string
1909          */
1910         whatstr = appt_what;
1911         if ((nlcount = count_newlines(appt_what)) > 0) {
1912                 whatstr = (char *)ckalloc(cm_strlen(appt_what) + nlcount + 1);
1913                 copy_and_pad_newlines(whatstr, appt_what);
1914         }
1915         if (whatstr && !blank_buf(whatstr)) {
1916                 cm_strcat(w_buf, whatstr);
1917                 cm_strcat(w_buf, "\n\t");
1918         }
1919  
1920         /*
1921          * Repeat and For stuff
1922          */
1923         cm_strcpy(r_buf, periodstr_from_period(repeat_type, repeat_nth));
1924         if (repeat_type == CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY) {
1925                 if (weekofmonth(tick, &wk) && wk == 4) {
1926                         if (repeat_wk == -1)
1927                                 strcat(r_buf, ", last");
1928                         else if (repeat_wk == 4)
1929                                 strcat(r_buf, ", 4th");
1930                 }
1931         }
1932
1933         sprintf(f_buf, "%ld", repeat_times);
1934
1935         /*
1936          * Put it all together
1937          */
1938
1939         b_ptr = malloc(cm_strlen(d_buf) + 
1940                         (2 * cm_strlen(CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER)) +
1941                         cm_strlen(s_buf) +
1942                         cm_strlen(e_buf) +
1943                         cm_strlen(r_buf) +
1944                         cm_strlen(f_buf) +
1945                         cm_strlen(w_buf) +
1946                         cm_strlen(format_string) +
1947                         cm_strlen(attr_string) +
1948                         1);
1949
1950         sprintf(b_ptr, format_string,
1951                 CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
1952                 attr_string, 
1953                 CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
1954                 d_buf, s_buf, e_buf, r_buf, f_buf, w_buf);
1955
1956         if (nlcount > 0)
1957                 free(whatstr);
1958         return(b_ptr);
1959 }
1960
1961 /* This routine takes a list of buffers that represent a number 
1962    of message attachments, and glues them into a mime format 
1963    message for eventual submission to the dtmail mailer.  These 
1964    objects do nor *need* to be cm appointment objects. */
1965
1966 char *
1967 create_rfc_message(char *address_list, 
1968                 char *subject, 
1969                 char **appointment_objects,
1970                 int  num_objects) {
1971
1972         char *unique_label;
1973
1974         /* do *not* put these header strings in a message catalog.  
1975            These are invariants specified by MIME */
1976
1977         char *address_header = "To: ";
1978         char *subject_header = "Subject: ";
1979         char *x_content_name = "X-Content-Name: CalendarAppointment\n";
1980         char *content_label = "Mime-Version: 1.0\nContent-Type: multipart/mixed;boundary=";
1981         char divider_string[32];
1982
1983         int  buffer_size;
1984         int  i;
1985         boolean_t  done = B_FALSE;
1986         char *return_buffer;
1987
1988         /* A MIME message is a rather specialized beast.  It consists of
1989            a series of headers describing the mail message, followed by 
1990            the message, and then followed by a set of attachments.   
1991            Each attachments is separated by a magic unique string 
1992            flagging the boundaries between the attached objects.  The
1993            first header is the address list.  The second header is the
1994            subject line for the message.  The third header describes the
1995            MIME version of the message.  The forth describes the type of
1996            information within the message and the magic string used for
1997            part divisions.
1998
1999            Each division is two dashes, the unique string, and a newline
2000            The last dividing string is trailed by two more dashes. */
2001
2002
2003         /* we need to generate a unique dividing string that will 
2004            not be found in any of the parts of the MIME message.  
2005            The following printf will be tried until it generates 
2006            something that isn't in any of the body parts. */
2007
2008         while (done != B_TRUE) {
2009                 sprintf(divider_string, "%x_%x-%x_%x-%x_%x", rand(), rand(), 
2010                         rand(), rand(), rand(), rand());
2011
2012                 done = B_TRUE;
2013
2014                 for (i = 0; i < num_objects; i++) {
2015                         if (strstr(appointment_objects[i], divider_string) != NULL)
2016                                 done = B_FALSE;
2017                 }
2018         }
2019
2020         buffer_size =   strlen(address_header) + 
2021                         strlen(address_list) + 
2022                         1 +                          /* newline */
2023                         strlen(subject_header) +
2024                         strlen(subject) +
2025                         1 +                          /* newline */
2026                         strlen(content_label) +  2 +
2027                         (num_objects + 2) * strlen(divider_string) + 
2028                                                      /* one definition copy,
2029                                                         one terminating copy,
2030                                                         and one per body
2031                                                         part */
2032
2033                         5 + strlen(divider_string) + /* empty body part */
2034
2035                         num_objects * strlen(x_content_name) +
2036                                                      /* one X-Content-Name
2037                                                         line for each
2038                                                         body part */
2039
2040                         (2 * num_objects) + 4 +      /* bracketing on unique 
2041                                                         strings */
2042                         (num_objects * 3) + 2;       /* newlines...3 per
2043                                                         body part boundary
2044                                                         and 2 for the
2045                                                         terminating boundary */
2046
2047         for (i = 0; i < num_objects; i++)
2048                 buffer_size += strlen(appointment_objects[i]);
2049
2050         /* extra byte is added for null char */
2051         return_buffer = (char *)calloc(buffer_size + 1, 1);
2052
2053         sprintf(return_buffer, "%s%s\n%s%s\n%s%s\n\n", 
2054                         address_header, 
2055                         address_list, 
2056                         subject_header, 
2057                         subject, 
2058                         content_label,
2059                         divider_string);
2060
2061         /*
2062          * Add an empty body part.  This is a hack to get dtmail to
2063          * display the object(s) as an attachment.
2064          */
2065         strcat(return_buffer, "\n--");
2066         strcat(return_buffer, divider_string);
2067         strcat(return_buffer, "\n\n");
2068
2069         for (i = 0; i <num_objects; i++) {
2070                 strcat(return_buffer, "\n--");
2071                 strcat(return_buffer, divider_string);
2072                 strcat(return_buffer, "\n");
2073                 strcat(return_buffer, x_content_name);
2074                 strcat(return_buffer, "\n");
2075                 strcat(return_buffer, appointment_objects[i]);
2076         }
2077         strcat(return_buffer, "\n--");
2078         strcat(return_buffer, divider_string);
2079         strcat(return_buffer, "--");
2080         strcat(return_buffer, "\n");
2081
2082         return(return_buffer);
2083 }
2084
2085 boolean_t
2086 appointments_to_file(CSA_session_handle target, CSA_entry_handle *appointment_list, 
2087                      int num_appts, 
2088                      char *file_name) {
2089
2090         int i;
2091         char *entry_string;
2092
2093         FILE *f_ptr = fopen(file_name,  "w");
2094
2095         if (f_ptr == NULL)
2096                 return(B_FALSE);
2097
2098         if (num_appts == 0)
2099                 return(B_FALSE);
2100
2101         fprintf(f_ptr, "DTCM Archive 1.0\n");
2102         for (i = 0; i < num_appts; i++) {
2103                 entry_string = entry_to_attrval_string(target, appointment_list[i]);
2104
2105                 fprintf(f_ptr, "\n-%s:string:begin\n%s%s:string:end\n\n", 
2106                         CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER, 
2107                         entry_string, 
2108                         CSA_X_DT_ENTRY_ATTR_ENTRY_DELIMITER);
2109
2110                 free(entry_string);
2111         }
2112
2113         fclose(f_ptr);
2114
2115         return(B_TRUE);
2116 }
2117
2118 /*
2119  * NOTE!!  These are strings used in versions 1-4 - the repeat strings read
2120  * from a file or passed to the cm_tty_insert routine are checked against
2121  * these strings as well as the V5 API strings.
2122  */
2123 static char *periodstrings[] = {
2124         "One Time",
2125         "Daily",
2126         "Weekly",
2127         "Every Two Weeks",
2128         "Monthly By Date",
2129         "Yearly",
2130         "Monthly By Weekday",
2131         "days",
2132         "weeks",
2133         "months",
2134         "other",
2135         "Monday thru Friday",
2136         "Mon, Wed, Fri",
2137         "Tuesday, Thursday",
2138         "Weekday Combo",
2139         "Every"
2140 };
2141
2142 extern void
2143 str_to_period(char *ps, CSA_sint32 *repeat_type, int *repeat_nth) {
2144         boolean_t       compute_times = B_FALSE;
2145         char            *ps2, *ptr, *ptr2, *unit;
2146  
2147         *repeat_type = '\0';
2148         *repeat_nth = 0;
2149         if (ps == NULL)
2150                 return;
2151
2152         if (strcasecmp(ps, periodstrings[0]) == 0)
2153                 *repeat_type = CSA_X_DT_REPEAT_ONETIME;
2154         else if (strcasecmp(ps, periodstrings[1]) == 0)
2155                 *repeat_type = CSA_X_DT_REPEAT_DAILY;
2156         else if (strcasecmp(ps, periodstrings[2]) == 0)
2157                 *repeat_type = CSA_X_DT_REPEAT_WEEKLY;
2158         else if (strcasecmp(ps, periodstrings[3]) == 0)
2159                 *repeat_type = CSA_X_DT_REPEAT_BIWEEKLY;
2160         else if (strcasecmp(ps, periodstrings[4]) == 0)
2161                 *repeat_type = CSA_X_DT_REPEAT_MONTHLY_BY_DATE;
2162         else if (strcasecmp(ps, periodstrings[5]) == 0)
2163                 *repeat_type = CSA_X_DT_REPEAT_YEARLY;
2164         else if (strncasecmp(ps, periodstrings[6],
2165                              strlen(periodstrings[6])) == 0)
2166                 *repeat_type = CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY;
2167         else if (strcasecmp(ps, periodstrings[10]) == 0)
2168                 *repeat_type = CSA_X_DT_REPEAT_OTHER;
2169         else if (strcasecmp(ps, periodstrings[11]) == 0)
2170                 *repeat_type = CSA_X_DT_REPEAT_MON_TO_FRI;
2171         else if (strcasecmp(ps, periodstrings[12]) == 0)
2172                 *repeat_type = CSA_X_DT_REPEAT_MONWEDFRI;
2173         else if (strcasecmp(ps, periodstrings[13]) == 0)
2174                 *repeat_type = CSA_X_DT_REPEAT_TUETHUR;
2175         else if (strcasecmp(ps, periodstrings[14]) == 0)
2176                 *repeat_type = CSA_X_DT_REPEAT_WEEKDAYCOMBO;
2177         else if (strncasecmp(ps, periodstrings[15], strlen(periodstrings[15])) == 0) {
2178                 compute_times = B_TRUE;
2179         }
2180         else
2181                 *repeat_type = CSA_X_DT_REPEAT_ONETIME;
2182
2183         if ((compute_times) && (unit = strchr(ps, ' '))) {
2184                 while (*unit == ' ')
2185                         unit++;
2186                 ps2 = cm_strdup(unit);
2187                 ptr = strchr(ps2, ' ');
2188                 if (ptr != NULL)
2189                         *ptr = '\0';
2190                 else
2191                         return;
2192
2193                 ptr++;
2194                 while (*ptr == ' ')
2195                         ptr++;
2196
2197                 *repeat_nth = atoi(ps2);
2198                 if (strcasecmp(ptr, periodstrings[7]) == 0) {
2199                         *repeat_type = CSA_X_DT_REPEAT_EVERY_NDAY;
2200                 }
2201                 else if (strcasecmp(ptr, periodstrings[8]) == 0) {
2202                         *repeat_type = CSA_X_DT_REPEAT_EVERY_NWEEK;
2203                 }
2204                 else if (strcasecmp(ptr, periodstrings[9]) == 0) {
2205                         *repeat_type = CSA_X_DT_REPEAT_EVERY_NMONTH;
2206                 }
2207                 free(ps2);
2208         }
2209 }
2210
2211 extern char*
2212 periodstr_from_period(CSA_sint32 repeat_type, int repeat_nth) {
2213         static char pstr[80];
2214  
2215         switch (repeat_type) {
2216         case CSA_X_DT_REPEAT_ONETIME:
2217                 sprintf(pstr, "%s", periodstrings[0]);
2218                 break;
2219         case CSA_X_DT_REPEAT_DAILY:
2220                 sprintf(pstr, "%s", periodstrings[1]);
2221                 break;
2222         case CSA_X_DT_REPEAT_WEEKLY:
2223                 sprintf(pstr, "%s", periodstrings[2]);
2224                 break;
2225         case CSA_X_DT_REPEAT_BIWEEKLY:
2226                 sprintf(pstr, "%s", periodstrings[3]);
2227                 break;
2228         case CSA_X_DT_REPEAT_MONTHLY_BY_DATE:
2229                 sprintf(pstr, "%s", periodstrings[4]);
2230                 break;
2231         case CSA_X_DT_REPEAT_YEARLY:
2232                 sprintf(pstr, "%s", periodstrings[5]);
2233                 break;
2234         case CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY:
2235                 sprintf(pstr, "%s", periodstrings[6]);
2236                 break;
2237         case CSA_X_DT_REPEAT_EVERY_NDAY:
2238                 sprintf(pstr, "Every %d %s", repeat_nth, periodstrings[7]);
2239                 break;
2240         case CSA_X_DT_REPEAT_EVERY_NWEEK:
2241                 sprintf(pstr, "Every %d %s", repeat_nth, periodstrings[8]);
2242                 break;
2243         case CSA_X_DT_REPEAT_EVERY_NMONTH:
2244                 sprintf(pstr, "Every %d %s", repeat_nth, periodstrings[9]);
2245                 break;
2246         case CSA_X_DT_REPEAT_OTHER:
2247                 sprintf(pstr, "%s", periodstrings[10]);
2248                 break;
2249         case CSA_X_DT_REPEAT_MON_TO_FRI:
2250                 sprintf(pstr, "%s", periodstrings[11]);
2251                 break;
2252         case CSA_X_DT_REPEAT_MONWEDFRI:
2253                 sprintf(pstr, "%s", periodstrings[12]);
2254                 break;
2255         case CSA_X_DT_REPEAT_TUETHUR:
2256                 sprintf(pstr, "%s", periodstrings[13]);
2257                 break;
2258         case CSA_X_DT_REPEAT_WEEKDAYCOMBO:
2259                 sprintf(pstr, "%s", periodstrings[14]);
2260                 break;
2261         default:
2262                 sprintf(pstr, "Unknown repeat type");
2263                 break;
2264         }
2265  
2266         return pstr;
2267 }
2268
2269 /*
2270  * NOTE!!  These first set of these strings are used in versions 1-4 - the
2271  * privacy strings read from a file or passed to the cm_tty_insert routine are
2272  * checked against these strings as well as the V5 API strings and the new
2273  * strings.
2274  */
2275 static char *privacy_strs_old[] = {
2276         "Show Time And Text",
2277         "Show Time Only",
2278         "Show Nothing"
2279 };
2280
2281 static char *privacy_strs[] = {
2282         "Others See Time And Text",
2283         "Others See Time Only",
2284         "Others See Nothing"
2285 };
2286
2287 static char *privacy_strs_411[] = {
2288         "none",
2289         "cm_what",
2290         "all"
2291 };
2292
2293 extern char*
2294 privacy_str_old(int op) {
2295         if (op >= 0 && op <= 2)
2296                 return privacy_strs_old[op];
2297         return NULL;
2298 }
2299
2300 extern char*
2301 privacy_str(int op) {
2302         if (op >= 0 && op <= 2)
2303                 return privacy_strs[op];
2304         return NULL;
2305 }
2306
2307 static void
2308 init_repeat_strs(
2309         nl_catd         catd,
2310         char          **repeat_strs)
2311 {
2312         repeat_strs[ONE_TIME] = strdup(catgets(catd, 1, 852, "One Time")); 
2313         repeat_strs[DAILY] = strdup(catgets(catd, 1, 853, "Daily")); 
2314         repeat_strs[WEEKLY] = strdup(catgets(catd, 1, 854, "Weekly")); 
2315         repeat_strs[EVERY_TWO_WEEKS] = 
2316                         strdup(catgets(catd, 1, 855, "Every Two Weeks")); 
2317         repeat_strs[MONTHLY_BY_DATE] = 
2318                         strdup(catgets(catd, 1, 856, "Monthly By Date")); 
2319         repeat_strs[MONTHLY_BY_WEEKDAY] = 
2320                         strdup(catgets(catd, 1, 857, "Monthly By Weekday")); 
2321         repeat_strs[YEARLY] = 
2322                         strdup(catgets(catd, 1, 858, "Yearly")); 
2323         repeat_strs[MONDAY_THRU_FRIDAY] = 
2324                         strdup(catgets(catd, 1, 859, "Monday Thru Friday")); 
2325         repeat_strs[MON_WED_FRI] = 
2326                         strdup(catgets(catd, 1, 860, "Mon, Wed, Fri")); 
2327         repeat_strs[TUESDAY_THURSDAY] = 
2328                         strdup(catgets(catd, 1, 861, "Tuesday, Thursday")); 
2329         repeat_strs[REPEAT_EVERY] = 
2330                         strdup(catgets(catd, 1, 862, "Repeat Every...")); 
2331 }
2332
2333 extern char*
2334 repeat_str(
2335         nl_catd         catd,
2336         Repeat_menu_op  op)
2337 {
2338         if (!repeat_strs[0])
2339                 init_repeat_strs(catd, repeat_strs);
2340
2341         if (op >= ONE_TIME && op <= REPEAT_EVERY)
2342                 return repeat_strs[op];
2343         return NULL;
2344 }
2345
2346 extern char*
2347 privacy_str_411(int op) {
2348         if (op >= 0 && op <= 2)
2349                 return privacy_strs_411[op];
2350         return NULL;
2351 }
2352
2353 extern char*
2354 repeat_scope_str(
2355         nl_catd              catd,
2356         Repeat_scope_menu_op op)
2357 {
2358         if (!repeat_scope_strs[0]) {
2359                 repeat_scope_strs[REPEAT_DAYS] = 
2360                                 strdup(catgets(catd, 1, 994, "days")); 
2361                 repeat_scope_strs[REPEAT_WEEKS] = 
2362                                 strdup(catgets(catd, 1, 995, "weeks")); 
2363                 repeat_scope_strs[REPEAT_MONTHS] = 
2364                                 strdup(catgets(catd, 1, 997, "months")); 
2365         }
2366
2367         if (op >= REPEAT_DAYS && op <= REPEAT_MONTHS)
2368                 return repeat_scope_strs[op];
2369         return NULL;
2370 }
2371
2372 extern char*
2373 separator_str(SeparatorType op) {
2374         if (op >= SEPARATOR_BLANK && op <= SEPARATOR_DASH)
2375                 return separator_strs[op];
2376         return NULL;
2377 }
2378
2379 extern int
2380 timescopestring_to_tick(char *str) {
2381         if (strcasecmp(time_scope_strs[1], str) == 0)
2382                 return hrsec;
2383         else if (strcasecmp(time_scope_strs[2], str) == 0)
2384                 return daysec;
2385         return minsec;
2386 }
2387
2388 extern char*
2389 time_scope_str(Time_scope_menu_op op) {
2390         if (op >= TIME_MINS && op <= TIME_DAYS)
2391                 return time_scope_strs[op];
2392         return NULL;
2393 }
2394
2395 extern char*
2396 time_scope_str_i18n(
2397         nl_catd                 catd,
2398         Time_scope_menu_op      op)
2399 {
2400         if (!time_scope_strs_i18n[0]) {
2401                 time_scope_strs_i18n[TIME_MINS] = 
2402                                         strdup(catgets(catd, 1, 877, "Mins")); 
2403                 time_scope_strs_i18n[TIME_HRS] = 
2404                                         strdup(catgets(catd, 1, 878, "Hrs")); 
2405                 time_scope_strs_i18n[TIME_DAYS] = 
2406                                         strdup(catgets(catd, 1, 879, "Days")); 
2407         }
2408
2409         if (op >= TIME_MINS && op <= TIME_DAYS)
2410                 return time_scope_strs_i18n[op];
2411         return NULL;
2412 }
2413
2414 /*
2415 **  Determine whether or not the time passed is a valid format
2416 */
2417 extern boolean_t
2418 valid_time(Props *p, char *time_str) {
2419         char            *ptr;
2420         register int    num_minutes = 0, num_colons = 0;
2421         boolean_t       after_colon = B_FALSE;
2422         DisplayType     dt = get_int_prop(p, CP_DEFAULTDISP);
2423  
2424         for (ptr = time_str; ptr != NULL && *ptr != '\0'; ptr++) {
2425                 if (dt == HOUR12) {
2426                         if (*ptr == ':') {
2427                                 after_colon = B_TRUE;
2428                                 if ((++num_colons) > 1)
2429                                         return B_FALSE;
2430                                 if (*(ptr+1) == '\0')
2431                                         return B_FALSE;
2432                         }
2433                         else if (*ptr != ' ' && (*ptr < '0' || *ptr > '9') )
2434                                 return B_FALSE;
2435                         if ((after_colon) && (*ptr != ':'))
2436                                 num_minutes++;
2437                         if (num_minutes > 2)
2438                                 return B_FALSE;
2439                         else if (num_minutes == 2) {
2440                                 ++ptr;
2441                                 if (strncasecmp(ptr, "am", 2) == 0 ||
2442                                     strncasecmp(ptr, "pm", 2) == 0)
2443                                         *ptr = '\0';
2444                                 --ptr;
2445                         }
2446                 }
2447                 else if (dt == HOUR24) {
2448                         if (*ptr != ' ' && (*ptr < '0' || *ptr > '9') )
2449                                 return B_FALSE;
2450                         if (++num_minutes > 4)
2451                                 return B_FALSE;
2452                 }
2453         }
2454         if (dt == HOUR12 && ((int)atoi(time_str) > 12))
2455                 return B_FALSE;
2456         else if ((dt == HOUR24)
2457                 && ((int)atoi(time_str) > 2359))
2458                 return B_FALSE;
2459
2460         return B_TRUE;
2461 }
2462
2463 /*
2464  * This method will validate the passed appointment data.
2465  *
2466  * Checks made:
2467  *      Date string incorrect           INVALID_DATE
2468  *      Tick for start is < 0           INVALID_START
2469  *      Tick for stop is < 0            INVALID_STOP
2470  *      Blank date                      MISSING_DATE
2471  *      End time but no start time      MISSING_START
2472  *      Blank what with no times        MISSING_WHAT
2473  *      Period = single & for > 0       REPEAT_FOR_MISMATCH
2474  *      Period != single & for = 0      REPEAT_FOR_MISMATCH
2475  *
2476  * Note the function pointer passed to this function - if the end time is
2477  * before the start time, this function will be executed and should return
2478  * B_TRUE if the appointment should be scheduled to the next day, B_FALSE if
2479  * it should be canceled.
2480  */
2481 extern Validate_op
2482 validate_appt(nl_catd catd, Dtcm_appointment *a, char *s_buf, char *e_buf, 
2483               char *d_buf, int dur, char *w_buf, char *r_buf, char *f_buf,
2484               boolean_t(*query)(void*), void *key_data, int version) {
2485         Validate_op     op;
2486
2487         if ((op = validate_dssw(a, s_buf, e_buf, d_buf, dur, w_buf, query,
2488                                 key_data)) != VALID_APPT)
2489                 return op;
2490         if ((op = validate_rfp(catd, a, r_buf, f_buf, version)) != VALID_APPT)
2491                 return op;
2492         if ((op = validate_reminders(a)) != VALID_APPT)
2493                 return op;
2494
2495         return VALID_APPT;
2496 }
2497
2498 extern Validate_op
2499 validate_dssw(Dtcm_appointment *a, char *s_buf, char *e_buf, char *d_buf,
2500               int dur, char *w_buf, boolean_t(*query)(), void *key_data) {
2501         Tick    end_tick = 0;
2502         char    buf[MAXNAMELEN];
2503         Tick    appt_time = 0;
2504
2505         if (blank_buf(d_buf))
2506                 return MISSING_DATE;
2507
2508         a->time->value->item.date_time_value = malloc(BUFSIZ);
2509         _csa_tick_to_iso8601(0, a->time->value->item.date_time_value);
2510
2511         a->show_time->value->item.sint32_value = B_TRUE;
2512
2513         if (w_buf && w_buf[0] != '\0') {
2514                 a->what->value->item.string_value = cm_strdup(w_buf);
2515                 expand_esc_chars(a->what->value->item.string_value);
2516         } else
2517                 a->what->value->item.string_value = NULL;
2518
2519         if (!blank_buf(s_buf)) {
2520                 /*
2521                  * We have something in the start buffer, is it valid?
2522                  */
2523                 sprintf(buf, "%s %s", d_buf, s_buf);
2524                 if ((appt_time = cm_getdate(buf, NULL)) < 0)
2525                         return INVALID_START;
2526
2527                 _csa_tick_to_iso8601(appt_time, a->time->value->item.date_time_value);
2528                 /*
2529                  * Okay, we have a valid start time - do we have a duration
2530                  * specified?
2531                  */
2532                 if (dur > 0) {
2533                         /*
2534                          * Duration is specified - add duration to start time
2535                          */
2536                         end_tick = appt_time + dur;
2537                         a->end_time->value->item.date_time_value = malloc(BUFSIZ);
2538                         _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2539                 } else if (!blank_buf(e_buf)) {
2540                         /*
2541                          * No duration, but something in the end buffer.  If
2542                          * it's valid set the end tick to it's value.
2543                          */
2544                         sprintf(buf, "%s %s", d_buf, e_buf);
2545                         if ((end_tick = cm_getdate(buf, NULL)) < 0)
2546                                 return INVALID_STOP;
2547                         a->end_time->value->item.date_time_value = malloc(BUFSIZ);
2548                         _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2549                 } else {
2550                         /*
2551                          * No duration or end buffer - set end_tick to starting
2552                          * tick and duration to 0
2553                          */
2554                         a->end_time->value->item.date_time_value = 
2555                                 strdup(a->time->value->item.date_time_value);
2556                 }
2557         } else if (dur > 0 || !blank_buf(e_buf))
2558                 return MISSING_START;
2559         else {
2560                 if (blank_buf(a->what->value->item.string_value))
2561                         return MISSING_WHAT;
2562
2563                 /*
2564                  * If we're here, there was a date with no start or stop time,
2565                  * so set time to magic time (3:41 am - don't ask where that
2566                  * came from, 'cause I certainly don't know) and make sure the
2567                  * date was correct.  If so, set duration to 1 minute and
2568                  * showtime to false.
2569                  */
2570                 sprintf(buf, "%s 3:41am", d_buf);
2571
2572                 if ((appt_time = cm_getdate(buf, NULL)) < 0)
2573                         return INVALID_DATE;
2574
2575                 a->time->value->item.date_time_value = malloc(BUFSIZ);
2576                 _csa_tick_to_iso8601(appt_time, a->time->value->item.date_time_value);
2577
2578                 end_tick = appt_time + minsec;
2579
2580                 a->end_time->value->item.date_time_value = malloc(BUFSIZ);
2581                 _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2582
2583                 a->show_time->value->item.sint32_value = B_FALSE;
2584         }
2585
2586         /*
2587          * Finally, if the ending tick is before the starting tick, execute the
2588          * passed function which should return B_TRUE if we should schedule
2589          * this into the next day and B_FALSE if not.
2590          *
2591          * This allows for methods calling this function to be UI oriented or
2592          * command line oriented as they can "query" the user appropriately.
2593          */
2594         if (end_tick < appt_time) {
2595                 if ((*query)(key_data) == B_TRUE) {
2596                         while (end_tick < appt_time)
2597                                 end_tick += daysec;
2598
2599                         _csa_tick_to_iso8601(end_tick, a->end_time->value->item.date_time_value);
2600
2601                 }
2602                 else
2603                         return CANCEL_APPT;
2604         }
2605
2606         return VALID_APPT;
2607 }
2608
2609 extern Validate_op
2610 validate_reminders(Dtcm_appointment *a) {
2611         return VALID_APPT;
2612 }
2613
2614 extern Validate_op
2615 validate_rfp(
2616         nl_catd                  catd,
2617         Dtcm_appointment        *a, 
2618         char                    *r_buf, 
2619         char                    *f_buf, 
2620         int                      version)
2621 {
2622         int                      repeat_nth, 
2623                                  repeat_wk = -1,
2624                                  repeat_every = 0,
2625                                  repeat_forever = False;
2626         CSA_sint32               repeat_type;
2627         CSA_uint32               repeat_times = 0;
2628         char                     rule_buf[32];
2629
2630         if (r_buf) {
2631                 str_to_period(r_buf, &repeat_type, &repeat_nth);
2632                 if (repeat_type == CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY) {
2633                         r_buf += strlen(periodstrings[6]);
2634                         while(*r_buf != '\0' && !isspace((u_char)*r_buf))
2635                                 ++r_buf;
2636                         while(*r_buf != '\0' && isspace((u_char)*r_buf))
2637                                 ++r_buf;
2638
2639                         if (strncasecmp(r_buf, "last", 4) != 0)
2640                                 repeat_wk = atoi(r_buf);
2641                 }
2642         }
2643
2644         if (f_buf) {
2645                 /* Repeat forever is represented by either:
2646                         f_buf == ``forever''   or
2647                         f_buf == ``0''.
2648                    If it is a CSA_X_DT_REPEAT_ONETIME then 
2649                         f_buf == ``0''.
2650                  */
2651                 if (strcasecmp(f_buf, catgets(catd, 1, 876, "forever")) == 0) {
2652                         repeat_times = CSA_X_DT_DT_REPEAT_FOREVER;
2653                         repeat_forever = True;
2654                 } else {
2655                         repeat_times = atoi(f_buf);
2656                         if (repeat_times == CSA_X_DT_DT_REPEAT_FOREVER &&
2657                             repeat_type != CSA_X_DT_REPEAT_ONETIME)
2658                                 repeat_forever = True;
2659                 }
2660         }
2661
2662         /* If it is a onetime event then it cannot repeat.
2663          * If it is a repeating event then repeat_times must be greater
2664          * than 0 unless it is supposed to repeat forever.
2665          */
2666         if (((repeat_type != CSA_X_DT_REPEAT_ONETIME) &&
2667             (repeat_times == 0) && (repeat_forever != True)) ||
2668             ((repeat_type == CSA_X_DT_REPEAT_ONETIME) &&
2669              ((repeat_times != 0) || (repeat_forever == True))))
2670                 return REPEAT_FOR_MISMATCH;
2671
2672         if (a->repeat_type && a->repeat_type->value)
2673                 a->repeat_type->value->item.sint32_value = repeat_type;
2674         if (a->repeat_times && a->repeat_times->value)
2675                 a->repeat_times->value->item.uint32_value = repeat_times;
2676         if (a->repeat_interval && a->repeat_interval->value)
2677                 a->repeat_interval->value->item.uint32_value = repeat_nth;
2678         if (a->repeat_week_num && a->repeat_week_num->value)
2679                 a->repeat_week_num->value->item.sint32_value = repeat_wk;
2680
2681         /* If we are less than data version 4, we're done. */
2682         if (version < DATAVER4)
2683                 return VALID_APPT;
2684
2685         /* Data version 4 appts use a recurrence rule. */
2686         memset (rule_buf, 0, 32);
2687
2688         switch(repeat_type) {
2689         case CSA_X_DT_REPEAT_ONETIME:
2690         case CSA_X_DT_REPEAT_DAILY:
2691                 strcpy(rule_buf, "D1 ");
2692                 break;
2693         case CSA_X_DT_REPEAT_WEEKLY:
2694                 strcpy(rule_buf, "W1 ");
2695                 break;
2696         case CSA_X_DT_REPEAT_BIWEEKLY:
2697                 strcpy(rule_buf, "W2 ");
2698                 break;
2699         case CSA_X_DT_REPEAT_MONTHLY_BY_DATE:
2700                 strcpy(rule_buf, "MD1 ");
2701                 break;
2702         case CSA_X_DT_REPEAT_MONTHLY_BY_WEEKDAY: {
2703                 int     wk;
2704                 Tick    tick = 0;
2705
2706                 if (a && a->time && a->time->value) {
2707                         _csa_iso8601_to_tick(
2708                                         a->time->value->item.date_time_value, 
2709                                         &tick);
2710                 }
2711
2712                 /*
2713                  * The current behavior of cm/dtcm is that if an appt is
2714                  * scheduled for the 5 wk of the month, it repeats on the
2715                  * last week of the month.
2716                  */
2717                 if (tick && weekofmonth(tick, &wk) && wk == 5)
2718                         sprintf(rule_buf, "MP1 1- %s ", dow_str(tick));
2719                 else
2720                         strcpy(rule_buf, "MP1 ");
2721                 break;
2722         }
2723         case CSA_X_DT_REPEAT_YEARLY:
2724                 strcpy(rule_buf, "YM1 ");
2725                 break;
2726         case CSA_X_DT_REPEAT_MON_TO_FRI:
2727                 strcpy(rule_buf, "W1 MO TU WE TH FR ");
2728                 break;
2729         case CSA_X_DT_REPEAT_MONWEDFRI:
2730                 strcpy(rule_buf, "W1 MO WE FR ");
2731                 break;
2732         case CSA_X_DT_REPEAT_TUETHUR:
2733                 strcpy(rule_buf, "W1 TU TH ");
2734                 break;
2735         case CSA_X_DT_REPEAT_EVERY_NDAY:
2736                 sprintf(rule_buf, "D%d ", repeat_nth);
2737                 repeat_every = True;
2738                 break;
2739         case CSA_X_DT_REPEAT_EVERY_NWEEK:
2740                 sprintf(rule_buf, "W%d ", repeat_nth);
2741                 repeat_every = True;
2742                 break;
2743         case CSA_X_DT_REPEAT_EVERY_NMONTH:
2744                 sprintf(rule_buf, "MD%d ", repeat_nth);
2745                 repeat_every = True;
2746                 break;
2747         default:
2748                 return CANCEL_APPT;
2749         }
2750
2751         /* If the for buffer is NULL then we default to repeating one time.
2752          * If the repeat_type is onetime then we default repeat times to
2753          * one time. 
2754          */
2755         if (!f_buf || repeat_type == CSA_X_DT_REPEAT_ONETIME)
2756                 repeat_times = 1;
2757
2758         if (repeat_times == CSA_X_DT_DT_REPEAT_FOREVER) {
2759                 strcat(rule_buf, "#0");
2760         } else {
2761                 char    buf[16];
2762
2763                 if (repeat_every) {
2764                         int duration;
2765
2766                         if (repeat_times % repeat_nth)
2767                                 duration = 1 + repeat_times/repeat_nth;
2768                         else
2769                                 duration = repeat_times/repeat_nth;
2770                         sprintf(buf, "#%d", duration);
2771                 } else
2772                         sprintf(buf, "#%ld", repeat_times);
2773
2774                 strcat(rule_buf, buf);
2775         }
2776         a->recurrence_rule->value->item.string_value = strdup(rule_buf);
2777
2778         return VALID_APPT;
2779 }