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