CtdlGetSysConfig() and CtdlPutSysConfig() now have the ability to store large configu...
[citadel.git] / citadel / modules / calendar / serv_calendar.c
1 /* 
2  * This module implements iCalendar object processing and the Calendar>
3  * room on a Citadel server.  It handles iCalendar objects using the
4  * iTIP protocol.  See RFCs 2445 and 2446.
5  *
6  * Copyright (c) 1987-2021 by the citadel.org team
7  *
8  * This program is open source software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16
17 #define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
18
19 #include "ctdl_module.h"
20 #include <libical/ical.h>
21 #include "msgbase.h"
22 #include "internet_addressing.h"
23 #include "serv_calendar.h"
24 #include "room_ops.h"
25 #include "euidindex.h"
26 #include "default_timezone.h"
27 #include "config.h"
28
29 struct ical_respond_data {
30         char desired_partnum[SIZ];
31         icalcomponent *cal;
32 };
33
34
35 /*
36  * Utility function to create a new VCALENDAR component with some of the
37  * required fields already set the way we like them.
38  */
39 icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
40         icalcomponent *encaps;
41
42         encaps = icalcomponent_new_vcalendar();
43         if (encaps == NULL) {
44                 syslog(LOG_ERR, "calendar: could not allocate component");
45                 return NULL;
46         }
47
48         /* Set the Product ID */
49         icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
50
51         /* Set the Version Number */
52         icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
53
54         return(encaps);
55 }
56
57
58 /*
59  * Utility function to encapsulate a subcomponent into a full VCALENDAR
60  */
61 icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
62         icalcomponent *encaps;
63
64         /* If we're already looking at a full VCALENDAR component,
65          * don't bother ... just return itself.
66          */
67         if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
68                 return subcomp;
69         }
70
71         /* Encapsulate the VEVENT component into a complete VCALENDAR */
72         encaps = icalcomponent_new_citadel_vcalendar();
73         if (encaps == NULL) return NULL;
74
75         /* Encapsulate the subcomponent inside */
76         icalcomponent_add_component(encaps, subcomp);
77
78         /* Return the object we just created. */
79         return(encaps);
80 }
81
82
83 /*
84  * Write a calendar object into the specified user's calendar room.
85  * If the supplied user is NULL, this function writes the calendar object
86  * to the currently selected room.
87  */
88 void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
89         char *ser = NULL;
90         long serlen;
91         icalcomponent *encaps = NULL;
92         struct CtdlMessage *msg = NULL;
93         icalcomponent *tmp=NULL;
94
95         if (cal == NULL) return;
96
97         /* If the supplied object is a subcomponent, encapsulate it in
98          * a full VCALENDAR component, and save that instead.
99          */
100         if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
101                 tmp = icalcomponent_new_clone(cal);
102                 encaps = ical_encapsulate_subcomponent(tmp);
103                 ical_write_to_cal(u, encaps);
104                 icalcomponent_free(tmp);
105                 icalcomponent_free(encaps);
106                 return;
107         }
108
109         ser = icalcomponent_as_ical_string_r(cal);
110         if (ser == NULL) return;
111
112         serlen = strlen(ser);
113
114         /* If the caller supplied a user, write to that user's default calendar room */
115         if (u) {
116                 /* This handy API function does all the work for us. */
117                 CtdlWriteObject(USERCALENDARROOM,       /* which room */
118                         "text/calendar",        /* MIME type */
119                         ser,                    /* data */
120                         serlen + 1,             /* length */
121                         u,                      /* which user */
122                         0,                      /* not binary */
123                         0                       /* no flags */
124                 );
125         }
126
127         /* If the caller did not supply a user, write to the currently selected room */
128         if (!u) {
129                 struct CitContext *CCC = CC;
130                 StrBuf *MsgBody;
131
132                 msg = malloc(sizeof(struct CtdlMessage));
133                 memset(msg, 0, sizeof(struct CtdlMessage));
134                 msg->cm_magic = CTDLMESSAGE_MAGIC;
135                 msg->cm_anon_type = MES_NORMAL;
136                 msg->cm_format_type = 4;
137                 CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
138                 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
139
140                 MsgBody = NewStrBufPlain(NULL, serlen + 100);
141                 StrBufAppendBufPlain(MsgBody, HKEY("Content-type: text/calendar\r\n\r\n"), 0);
142                 StrBufAppendBufPlain(MsgBody, ser, serlen, 0);
143
144                 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
145         
146                 /* Now write the data */
147                 CtdlSubmitMsg(msg, NULL, "");
148                 CM_Free(msg);
149         }
150
151         /* In either case, now we can free the serialized calendar object */
152         free(ser);
153 }
154
155
156 /*
157  * Send a reply to a meeting invitation.
158  *
159  * 'request' is the invitation to reply to.
160  * 'action' is the string "accept" or "decline" or "tentative".
161  *
162  */
163 void ical_send_a_reply(icalcomponent *request, char *action) {
164         icalcomponent *the_reply = NULL;
165         icalcomponent *vevent = NULL;
166         icalproperty *attendee = NULL;
167         char attendee_string[SIZ];
168         icalproperty *organizer = NULL;
169         char organizer_string[SIZ];
170         icalproperty *summary = NULL;
171         char summary_string[SIZ];
172         icalproperty *me_attend = NULL;
173         struct recptypes *recp = NULL;
174         icalparameter *partstat = NULL;
175         char *serialized_reply = NULL;
176         char *reply_message_text = NULL;
177         const char *ch;
178         struct CtdlMessage *msg = NULL;
179         struct recptypes *valid = NULL;
180
181         *organizer_string = '\0';
182         strcpy(summary_string, "Calendar item");
183
184         if (request == NULL) {
185                 syslog(LOG_ERR, "calendar: trying to reply to NULL event");
186                 return;
187         }
188
189         the_reply = icalcomponent_new_clone(request);
190         if (the_reply == NULL) {
191                 syslog(LOG_ERR, "calendar: cannot clone request");
192                 return;
193         }
194
195         /* Change the method from REQUEST to REPLY */
196         icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
197
198         vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
199         if (vevent != NULL) {
200                 /* Hunt for attendees, removing ones that aren't us.
201                  * (Actually, remove them all, cloning our own one so we can
202                  * re-insert it later)
203                  */
204                 while (attendee = icalcomponent_get_first_property(vevent,
205                     ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
206                 ) {
207                         ch = icalproperty_get_attendee(attendee);
208                         if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
209                                 safestrncpy(attendee_string, ch + 7, sizeof (attendee_string));
210                                 striplt(attendee_string);
211                                 recp = validate_recipients(attendee_string, NULL, 0);
212                                 if (recp != NULL) {
213                                         if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
214                                                 if (me_attend) icalproperty_free(me_attend);
215                                                 me_attend = icalproperty_new_clone(attendee);
216                                         }
217                                         free_recipients(recp);
218                                 }
219                         }
220
221                         /* Remove it... */
222                         icalcomponent_remove_property(vevent, attendee);
223                         icalproperty_free(attendee);
224                 }
225
226                 /* We found our own address in the attendee list. */
227                 if (me_attend) {
228                         /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
229                         icalproperty_remove_parameter_by_kind(me_attend, ICAL_PARTSTAT_PARAMETER);
230
231                         if (!strcasecmp(action, "accept")) {
232                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
233                         }
234                         else if (!strcasecmp(action, "decline")) {
235                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
236                         }
237                         else if (!strcasecmp(action, "tentative")) {
238                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
239                         }
240
241                         if (partstat) icalproperty_add_parameter(me_attend, partstat);
242
243                         /* Now insert it back into the vevent. */
244                         icalcomponent_add_property(vevent, me_attend);
245                 }
246
247                 /* Figure out who to send this thing to */
248                 organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
249                 if (organizer != NULL) {
250                         if (icalproperty_get_organizer(organizer)) {
251                                 strcpy(organizer_string,
252                                         icalproperty_get_organizer(organizer) );
253                         }
254                 }
255                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
256                         strcpy(organizer_string, &organizer_string[7]);
257                         striplt(organizer_string);
258                 } else {
259                         strcpy(organizer_string, "");
260                 }
261
262                 /* Extract the summary string -- we'll use it as the
263                  * message subject for the reply
264                  */
265                 summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
266                 if (summary != NULL) {
267                         if (icalproperty_get_summary(summary)) {
268                                 strcpy(summary_string,
269                                         icalproperty_get_summary(summary) );
270                         }
271                 }
272         }
273
274         /* Now generate the reply message and send it out. */
275         serialized_reply = icalcomponent_as_ical_string_r(the_reply);
276         icalcomponent_free(the_reply);  /* don't need this anymore */
277         if (serialized_reply == NULL) return;
278
279         reply_message_text = malloc(strlen(serialized_reply) + SIZ);
280         if (reply_message_text != NULL) {
281                 sprintf(reply_message_text,
282                         "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
283                         serialized_reply
284                 );
285
286                 msg = CtdlMakeMessage(&CC->user,
287                         organizer_string,       /* to */
288                         "",                     /* cc */
289                         CC->room.QRname, 0, FMT_RFC822,
290                         "",
291                         "",
292                         summary_string,         /* Use summary for subject */
293                         NULL,
294                         reply_message_text,
295                         NULL);
296         
297                 if (msg != NULL) {
298                         valid = validate_recipients(organizer_string, NULL, 0);
299                         CtdlSubmitMsg(msg, valid, "");
300                         CM_Free(msg);
301                         free_recipients(valid);
302                 }
303         }
304         free(serialized_reply);
305 }
306
307
308 /*
309  * Callback function for mime parser that hunts for calendar content types
310  * and turns them into calendar objects.  If something is found, it is placed
311  * in ird->cal, and the caller now owns that memory and is responsible for freeing it.
312  */
313 void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
314                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
315                 char *cbid, void *cbuserdata) {
316
317         struct ical_respond_data *ird = NULL;
318
319         ird = (struct ical_respond_data *) cbuserdata;
320
321         /* desired_partnum can be set to "_HUNT_" to have it just look for
322          * the first part with a content type of text/calendar.  Otherwise
323          * we have to only process the right one.
324          */
325         if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
326                 if (strcasecmp(partnum, ird->desired_partnum)) {
327                         return;
328                 }
329         }
330
331         if (  (strcasecmp(cbtype, "text/calendar"))
332            && (strcasecmp(cbtype, "application/ics")) ) {
333                 return;
334         }
335
336         if (ird->cal != NULL) {
337                 icalcomponent_free(ird->cal);
338                 ird->cal = NULL;
339         }
340
341         ird->cal = icalcomponent_new_from_string(content);
342 }
343
344
345 /*
346  * Respond to a meeting request.
347  */
348 void ical_respond(long msgnum, char *partnum, char *action) {
349         struct CtdlMessage *msg = NULL;
350         struct ical_respond_data ird;
351
352         if (
353            (strcasecmp(action, "accept"))
354            && (strcasecmp(action, "decline"))
355         ) {
356                 cprintf("%d Action must be 'accept' or 'decline'\n",
357                         ERROR + ILLEGAL_VALUE
358                 );
359                 return;
360         }
361
362         msg = CtdlFetchMessage(msgnum, 1);
363         if (msg == NULL) {
364                 cprintf("%d Message %ld not found.\n",
365                         ERROR + ILLEGAL_VALUE,
366                         (long)msgnum
367                 );
368                 return;
369         }
370
371         memset(&ird, 0, sizeof ird);
372         strcpy(ird.desired_partnum, partnum);
373         mime_parser(CM_RANGE(msg, eMesageText),
374                     *ical_locate_part,          /* callback function */
375                     NULL, NULL,
376                     (void *) &ird,                      /* user data */
377                     0
378         );
379
380         /* We're done with the incoming message, because we now have a
381          * calendar object in memory.
382          */
383         CM_Free(msg);
384
385         /*
386          * Here is the real meat of this function.  Handle the event.
387          */
388         if (ird.cal != NULL) {
389                 /* Save this in the user's calendar if necessary */
390                 if (!strcasecmp(action, "accept")) {
391                         ical_write_to_cal(&CC->user, ird.cal);
392                 }
393
394                 /* Send a reply if necessary */
395                 if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
396                         ical_send_a_reply(ird.cal, action);
397                 }
398
399                 /* We used to delete the invitation after handling it.
400                  * We don't do that anymore, but here is the code that handled it:
401                  * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
402                  */
403
404                 /* Free the memory we allocated and return a response. */
405                 icalcomponent_free(ird.cal);
406                 ird.cal = NULL;
407                 cprintf("%d ok\n", CIT_OK);
408                 return;
409         }
410         else {
411                 cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
412                 return;
413         }
414
415         /* should never get here */
416 }
417
418
419 /*
420  * Figure out the UID of the calendar event being referred to in a
421  * REPLY object.  This function is recursive.
422  */
423 void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
424         icalcomponent *subcomponent;
425         icalproperty *p;
426
427         /* If this object is a REPLY, then extract the UID. */
428         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
429                 p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
430                 if (p != NULL) {
431                         strcpy(uidbuf, icalproperty_get_comment(p));
432                 }
433         }
434
435         /* Otherwise, recurse through any VEVENT subcomponents.  We do NOT want the
436          * UID of the reply; we want the UID of the invitation being replied to.
437          */
438         for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
439             subcomponent != NULL;
440             subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
441                 ical_learn_uid_of_reply(uidbuf, subcomponent);
442         }
443 }
444
445
446 /*
447  * ical_update_my_calendar_with_reply() refers to this callback function; when we
448  * locate the message containing the calendar event we're replying to, this function
449  * gets called.  It basically just sticks the message number in a supplied buffer.
450  */
451 void ical_hunt_for_event_to_update(long msgnum, void *data) {
452         long *msgnumptr;
453
454         msgnumptr = (long *) data;
455         *msgnumptr = msgnum;
456 }
457
458
459 struct original_event_container {
460         icalcomponent *c;
461 };
462
463 /*
464  * Callback function for mime parser that hunts for calendar content types
465  * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
466  * to fetch the object being updated)
467  */
468 void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
469                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
470                 char *cbid, void *cbuserdata) {
471
472         struct original_event_container *oec = NULL;
473
474         if (  (strcasecmp(cbtype, "text/calendar"))
475            && (strcasecmp(cbtype, "application/ics")) ) {
476                 return;
477         }
478         oec = (struct original_event_container *) cbuserdata;
479         if (oec->c != NULL) {
480                 icalcomponent_free(oec->c);
481         }
482         oec->c = icalcomponent_new_from_string(content);
483 }
484
485
486 /*
487  * Merge updated attendee information from a REPLY into an existing event.
488  */
489 void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
490         icalcomponent *c;
491         icalproperty *e_attendee, *r_attendee;
492
493         /* First things first.  If we're not looking at a VEVENT component,
494          * recurse through subcomponents until we find one.
495          */
496         if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
497                 for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
498                     c != NULL;
499                     c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
500                         ical_merge_attendee_reply(c, reply);
501                 }
502                 return;
503         }
504
505         /* Now do the same thing with the reply.
506          */
507         if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
508                 for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
509                     c != NULL;
510                     c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
511                         ical_merge_attendee_reply(event, c);
512                 }
513                 return;
514         }
515
516         /* Clone the reply, because we're going to rip its guts out. */
517         reply = icalcomponent_new_clone(reply);
518
519         /* At this point we're looking at the correct subcomponents.
520          * Iterate through the attendees looking for a match.
521          */
522 STARTOVER:
523         for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
524             e_attendee != NULL;
525             e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
526
527                 for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
528                     r_attendee != NULL;
529                     r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
530
531                         /* Check to see if these two attendees match...
532                          */
533                         const char *e, *r;
534                         e = icalproperty_get_attendee(e_attendee);
535                         r = icalproperty_get_attendee(r_attendee);
536
537                         if ((e != NULL) && 
538                             (r != NULL) && 
539                             !strcasecmp(e, r)) {
540                                 /* ...and if they do, remove the attendee from the event
541                                  * and replace it with the attendee from the reply.  (The
542                                  * reply's copy will have the same address, but an updated
543                                  * status.)
544                                  */
545                                 icalcomponent_remove_property(event, e_attendee);
546                                 icalproperty_free(e_attendee);
547                                 icalcomponent_remove_property(reply, r_attendee);
548                                 icalcomponent_add_property(event, r_attendee);
549
550                                 /* Since we diddled both sets of attendees, we have to start
551                                  * the iteration over again.  This will not create an infinite
552                                  * loop because we removed the attendee from the reply.  (That's
553                                  * why we cloned the reply, and that's what we mean by "ripping
554                                  * its guts out.")
555                                  */
556                                 goto STARTOVER;
557                         }
558         
559                 }
560         }
561
562         /* Free the *clone* of the reply. */
563         icalcomponent_free(reply);
564 }
565
566
567 /*
568  * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
569  * calendar event.  The object has already been deserialized for us; all
570  * we have to do here is hunt for the event in our calendar, merge in the
571  * updated attendee status, and save it again.
572  *
573  * This function returns 0 on success, 1 if the event was not found in the
574  * user's calendar, or 2 if an internal error occurred.
575  */
576 int ical_update_my_calendar_with_reply(icalcomponent *cal) {
577         char uid[SIZ];
578         char hold_rm[ROOMNAMELEN];
579         long msgnum_being_replaced = 0;
580         struct CtdlMessage *msg = NULL;
581         struct original_event_container oec;
582         icalcomponent *original_event;
583         char *serialized_event = NULL;
584         char roomname[ROOMNAMELEN];
585         char *message_text = NULL;
586
587         /* Figure out just what event it is we're dealing with */
588         strcpy(uid, "--==<< InVaLiD uId >>==--");
589         ical_learn_uid_of_reply(uid, cal);
590         syslog(LOG_DEBUG, "calendar: UID of event being replied to is <%s>", uid);
591
592         strcpy(hold_rm, CC->room.QRname);       /* save current room */
593
594         if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
595                 CtdlGetRoom(&CC->room, hold_rm);
596                 syslog(LOG_ERR, "calendar: cannot get user calendar room");
597                 return(2);
598         }
599
600         /*
601          * Look in the EUID index for a message with
602          * the Citadel EUID set to the value we're looking for.  Since
603          * Citadel always sets the message EUID to the iCalendar UID of
604          * the event, this will work.
605          */
606         msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room);
607
608         CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
609
610         syslog(LOG_DEBUG, "calendar: msgnum_being_replaced == %ld", msgnum_being_replaced);
611         if (msgnum_being_replaced == 0) {
612                 return(1);                      /* no calendar event found */
613         }
614
615         /* Now we know the ID of the message containing the event being updated.
616          * We don't actually have to delete it; that'll get taken care of by the
617          * server when we save another event with the same UID.  This just gives
618          * us the ability to load the event into memory so we can diddle the
619          * attendees.
620          */
621         msg = CtdlFetchMessage(msgnum_being_replaced, 1);
622         if (msg == NULL) {
623                 return(2);                      /* internal error */
624         }
625         oec.c = NULL;
626         mime_parser(CM_RANGE(msg, eMesageText),
627                     *ical_locate_original_event,        /* callback function */
628                     NULL, NULL,
629                     &oec,                               /* user data */
630                     0
631         );
632         CM_Free(msg);
633
634         original_event = oec.c;
635         if (original_event == NULL) {
636                 syslog(LOG_ERR, "calendar: original_component is NULL");
637                 return(2);
638         }
639
640         /* Merge the attendee's updated status into the event */
641         ical_merge_attendee_reply(original_event, cal);
642
643         /* Serialize it */
644         serialized_event = icalcomponent_as_ical_string_r(original_event);
645         icalcomponent_free(original_event);     /* Don't need this anymore. */
646         if (serialized_event == NULL) return(2);
647
648         CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
649
650         message_text = malloc(strlen(serialized_event) + SIZ);
651         if (message_text != NULL) {
652                 sprintf(message_text,
653                         "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
654                         serialized_event
655                 );
656
657                 msg = CtdlMakeMessage(&CC->user,
658                         "",                     /* No recipient */
659                         "",                     /* No recipient */
660                         roomname,
661                         0, FMT_RFC822,
662                         "",
663                         "",
664                         "",             /* no subject */
665                         NULL,
666                         message_text,
667                         NULL);
668         
669                 if (msg != NULL) {
670                         CIT_ICAL->avoid_sending_invitations = 1;
671                         CtdlSubmitMsg(msg, NULL, roomname);
672                         CM_Free(msg);
673                         CIT_ICAL->avoid_sending_invitations = 0;
674                 }
675         }
676         free(serialized_event);
677         return(0);
678 }
679
680
681 /*
682  * Handle an incoming RSVP for an event.  (This is the server subcommand part; it
683  * simply extracts the calendar object from the message, deserializes it, and
684  * passes it up to ical_update_my_calendar_with_reply() for processing.
685  */
686 void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
687         struct CtdlMessage *msg = NULL;
688         struct ical_respond_data ird;
689         int ret;
690
691         if (
692            (strcasecmp(action, "update"))
693            && (strcasecmp(action, "ignore"))
694         ) {
695                 cprintf("%d Action must be 'update' or 'ignore'\n",
696                         ERROR + ILLEGAL_VALUE
697                 );
698                 return;
699         }
700
701         msg = CtdlFetchMessage(msgnum, 1);
702         if (msg == NULL) {
703                 cprintf("%d Message %ld not found.\n",
704                         ERROR + ILLEGAL_VALUE,
705                         (long)msgnum
706                 );
707                 return;
708         }
709
710         memset(&ird, 0, sizeof ird);
711         strcpy(ird.desired_partnum, partnum);
712         mime_parser(CM_RANGE(msg, eMesageText),
713                     *ical_locate_part,          /* callback function */
714                     NULL, NULL,
715                     (void *) &ird,                      /* user data */
716                     0
717                 );
718
719         /* We're done with the incoming message, because we now have a
720          * calendar object in memory.
721          */
722         CM_Free(msg);
723
724         /*
725          * Here is the real meat of this function.  Handle the event.
726          */
727         if (ird.cal != NULL) {
728                 /* Update the user's calendar if necessary */
729                 if (!strcasecmp(action, "update")) {
730                         ret = ical_update_my_calendar_with_reply(ird.cal);
731                         if (ret == 0) {
732                                 cprintf("%d Your calendar has been updated with this reply.\n",
733                                         CIT_OK);
734                         }
735                         else if (ret == 1) {
736                                 cprintf("%d This event does not exist in your calendar.\n",
737                                         ERROR + FILE_NOT_FOUND);
738                         }
739                         else {
740                                 cprintf("%d An internal error occurred.\n",
741                                         ERROR + INTERNAL_ERROR);
742                         }
743                 }
744                 else {
745                         cprintf("%d This reply has been ignored.\n", CIT_OK);
746                 }
747
748                 /* Now that we've processed this message, we don't need it
749                  * anymore.  So delete it.  (Don't do this anymore.)
750                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
751                  */
752
753                 /* Free the memory we allocated and return a response. */
754                 icalcomponent_free(ird.cal);
755                 ird.cal = NULL;
756                 return;
757         }
758         else {
759                 cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
760                 return;
761         }
762
763         /* should never get here */
764 }
765
766
767 /*
768  * Search for a property in both the top level and in a VEVENT subcomponent
769  */
770 icalproperty *ical_ctdl_get_subprop(
771                 icalcomponent *cal,
772                 icalproperty_kind which_prop
773 ) {
774         icalproperty *p;
775         icalcomponent *c;
776
777         p = icalcomponent_get_first_property(cal, which_prop);
778         if (p == NULL) {
779                 c = icalcomponent_get_first_component(cal,
780                                                         ICAL_VEVENT_COMPONENT);
781                 if (c != NULL) {
782                         p = icalcomponent_get_first_property(c, which_prop);
783                 }
784         }
785         return p;
786 }
787
788
789 /*
790  * Check to see if two events overlap.  Returns nonzero if they do.
791  * (This function is used in both Citadel and WebCit.  If you change it in
792  * one place, change it in the other.  Better yet, put it in a library.)
793  */
794 int ical_ctdl_is_overlap(
795                         struct icaltimetype t1start,
796                         struct icaltimetype t1end,
797                         struct icaltimetype t2start,
798                         struct icaltimetype t2end
799 ) {
800         if (icaltime_is_null_time(t1start)) return(0);
801         if (icaltime_is_null_time(t2start)) return(0);
802
803         /* if either event lacks end time, assume end = start */
804         if (icaltime_is_null_time(t1end))
805                 memcpy(&t1end, &t1start, sizeof(struct icaltimetype));
806         else {
807                 if (t1end.is_date && icaltime_compare(t1start, t1end)) {
808                         /*
809                          * the end date is non-inclusive so adjust it by one
810                          * day because our test is inclusive, note that a day is
811                          * not too much because we are talking about all day
812                          * events
813                          * if start = end we assume that nevertheless the whole
814                          * day is meant
815                          */
816                         icaltime_adjust(&t1end, -1, 0, 0, 0);   
817                 }
818         }
819
820         if (icaltime_is_null_time(t2end))
821                 memcpy(&t2end, &t2start, sizeof(struct icaltimetype));
822         else {
823                 if (t2end.is_date && icaltime_compare(t2start, t2end)) {
824                         icaltime_adjust(&t2end, -1, 0, 0, 0);   
825                 }
826         }
827
828         /* First, check for all-day events */
829         if (t1start.is_date || t2start.is_date) {
830                 /* If event 1 ends before event 2 starts, we're in the clear. */
831                 if (icaltime_compare_date_only(t1end, t2start) < 0) return(0);
832
833                 /* If event 2 ends before event 1 starts, we're also ok. */
834                 if (icaltime_compare_date_only(t2end, t1start) < 0) return(0);
835
836                 return(1);
837         }
838
839         /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d",
840                 t1start.hour, t1start.minute, t1end.hour, t1end.minute,
841                 t2start.hour, t2start.minute, t2end.hour, t2end.minute);
842         */
843
844         /* Now check for overlaps using date *and* time. */
845
846         /* If event 1 ends before event 2 starts, we're in the clear. */
847         if (icaltime_compare(t1end, t2start) <= 0) return(0);
848         /* syslog(LOG_DEBUG, "calendar: first passed"); */
849
850         /* If event 2 ends before event 1 starts, we're also ok. */
851         if (icaltime_compare(t2end, t1start) <= 0) return(0);
852         /* syslog(LOG_DEBUG, "calendar: second passed"); */
853
854         /* Otherwise, they overlap. */
855         return(1);
856 }
857
858
859 /* 
860  * Phase 6 of "hunt for conflicts"
861  * called by ical_conflicts_phase5()
862  *
863  * Now both the proposed and existing events have been boiled down to start and end times.
864  * Check for overlap and output any conflicts.
865  *
866  * Returns nonzero if a conflict was reported.  This allows the caller to stop iterating.
867  */
868 int ical_conflicts_phase6(struct icaltimetype t1start,
869                         struct icaltimetype t1end,
870                         struct icaltimetype t2start,
871                         struct icaltimetype t2end,
872                         long existing_msgnum,
873                         char *conflict_event_uid,
874                         char *conflict_event_summary,
875                         char *compare_uid)
876 {
877         int conflict_reported = 0;
878
879         /* debugging cruft *
880         time_t tt;
881         tt = icaltime_as_timet_with_zone(t1start, t1start.zone);
882         syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt));
883         tt = icaltime_as_timet_with_zone(t1end, t1end.zone);
884         syslog(LOG_DEBUG, "  PROPOSED END: %s", ctime(&tt));
885         tt = icaltime_as_timet_with_zone(t2start, t2start.zone);
886         syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt));
887         tt = icaltime_as_timet_with_zone(t2end, t2end.zone);
888         syslog(LOG_DEBUG, "  EXISTING END: %s", ctime(&tt));
889         * debugging cruft */
890
891         /* compare and output */
892
893         if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
894                 cprintf("%ld||%s|%s|%d|\n",
895                         existing_msgnum,
896                         conflict_event_uid,
897                         conflict_event_summary,
898                         (       (!IsEmptyStr(compare_uid)
899                                 &&(!strcasecmp(compare_uid,
900                                 conflict_event_uid))) ? 1 : 0
901                                 )
902                         );
903                 conflict_reported = 1;
904         }
905
906         return(conflict_reported);
907 }
908
909
910 /*
911  * Phase 5 of "hunt for conflicts"
912  * Called by ical_conflicts_phase4()
913  *
914  * We have the proposed event boiled down to start and end times.
915  * Now check it against an existing event. 
916  */
917 void ical_conflicts_phase5(struct icaltimetype t1start,
918                         struct icaltimetype t1end,
919                         icalcomponent *existing_event,
920                         long existing_msgnum,
921                         char *compare_uid)
922 {
923         char conflict_event_uid[SIZ];
924         char conflict_event_summary[SIZ];
925         struct icaltimetype t2start, t2end;
926         icalproperty *p;
927
928         /* recur variables */
929         icalproperty *rrule = NULL;
930         struct icalrecurrencetype recur;
931         icalrecur_iterator *ritr = NULL;
932         struct icaldurationtype dur;
933         int num_recur = 0;
934
935         /* initialization */
936         strcpy(conflict_event_uid, "");
937         strcpy(conflict_event_summary, "");
938         t2start = icaltime_null_time();
939         t2end = icaltime_null_time();
940
941         /* existing event stuff */
942         p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY);
943         if (p == NULL) return;
944         if (p != NULL) t2start = icalproperty_get_dtstart(p);
945         if (icaltime_is_utc(t2start)) {
946                 t2start.zone = icaltimezone_get_utc_timezone();
947         }
948         else {
949                 t2start.zone = icalcomponent_get_timezone(existing_event,
950                         icalparameter_get_tzid(
951                                 icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
952                         )
953                 );
954                 if (!t2start.zone) {
955                         t2start.zone = get_default_icaltimezone();
956                 }
957         }
958
959         p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY);
960         if (p != NULL) {
961                 t2end = icalproperty_get_dtend(p);
962
963                 if (icaltime_is_utc(t2end)) {
964                         t2end.zone = icaltimezone_get_utc_timezone();
965                 }
966                 else {
967                         t2end.zone = icalcomponent_get_timezone(existing_event,
968                                 icalparameter_get_tzid(
969                                         icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
970                                 )
971                         );
972                         if (!t2end.zone) {
973                                 t2end.zone = get_default_icaltimezone();
974                         }
975                 }
976                 dur = icaltime_subtract(t2end, t2start);
977         }
978         else {
979                 memset (&dur, 0, sizeof(struct icaldurationtype));
980         }
981
982         rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY);
983         if (rrule) {
984                 recur = icalproperty_get_rrule(rrule);
985                 ritr = icalrecur_iterator_new(recur, t2start);
986         }
987
988         do {
989                 p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY);
990                 if (p != NULL) {
991                         strcpy(conflict_event_uid, icalproperty_get_comment(p));
992                 }
993         
994                 p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY);
995                 if (p != NULL) {
996                         strcpy(conflict_event_summary, icalproperty_get_comment(p));
997                 }
998         
999                 if (ical_conflicts_phase6(t1start, t1end, t2start, t2end,
1000                    existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid))
1001                 {
1002                         num_recur = MAX_RECUR + 1;      /* force it out of scope, no need to continue */
1003                 }
1004
1005                 if (rrule) {
1006                         t2start = icalrecur_iterator_next(ritr);
1007                         if (!icaltime_is_null_time(t2end)) {
1008                                 const icaltimezone *hold_zone = t2end.zone;
1009                                 t2end = icaltime_add(t2start, dur);
1010                                 t2end.zone = hold_zone;
1011                         }
1012                         ++num_recur;
1013                 }
1014
1015                 if (icaltime_compare(t2start, t1end) < 0) {
1016                         num_recur = MAX_RECUR + 1;      /* force it out of scope */
1017                 }
1018
1019         } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) );
1020         icalrecur_iterator_free(ritr);
1021 }
1022
1023
1024 /*
1025  * Phase 4 of "hunt for conflicts"
1026  * Called by ical_hunt_for_conflicts_backend()
1027  *
1028  * At this point we've got it boiled down to two icalcomponent events in memory.
1029  * If they conflict, output something to the client.
1030  */
1031 void ical_conflicts_phase4(icalcomponent *proposed_event,
1032                 icalcomponent *existing_event,
1033                 long existing_msgnum)
1034 {
1035         struct icaltimetype t1start, t1end;
1036         icalproperty *p;
1037         char compare_uid[SIZ];
1038
1039         /* recur variables */
1040         icalproperty *rrule = NULL;
1041         struct icalrecurrencetype recur;
1042         icalrecur_iterator *ritr = NULL;
1043         struct icaldurationtype dur;
1044         int num_recur = 0;
1045
1046         /* initialization */
1047         t1end = icaltime_null_time();
1048         *compare_uid = '\0';
1049
1050         /* proposed event stuff */
1051
1052         p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY);
1053         if (p == NULL)
1054                 return;
1055         else
1056                 t1start = icalproperty_get_dtstart(p);
1057
1058         if (icaltime_is_utc(t1start)) {
1059                 t1start.zone = icaltimezone_get_utc_timezone();
1060         }
1061         else {
1062                 t1start.zone = icalcomponent_get_timezone(proposed_event,
1063                         icalparameter_get_tzid(
1064                                 icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
1065                         )
1066                 );
1067                 if (!t1start.zone) {
1068                         t1start.zone = get_default_icaltimezone();
1069                 }
1070         }
1071         
1072         p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY);
1073         if (p != NULL) {
1074                 t1end = icalproperty_get_dtend(p);
1075
1076                 if (icaltime_is_utc(t1end)) {
1077                         t1end.zone = icaltimezone_get_utc_timezone();
1078                 }
1079                 else {
1080                         t1end.zone = icalcomponent_get_timezone(proposed_event,
1081                                 icalparameter_get_tzid(
1082                                         icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
1083                                 )
1084                         );
1085                         if (!t1end.zone) {
1086                                 t1end.zone = get_default_icaltimezone();
1087                         }
1088                 }
1089
1090                 dur = icaltime_subtract(t1end, t1start);
1091         }
1092         else {
1093                 memset (&dur, 0, sizeof(struct icaldurationtype));
1094         }
1095
1096         rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY);
1097         if (rrule) {
1098                 recur = icalproperty_get_rrule(rrule);
1099                 ritr = icalrecur_iterator_new(recur, t1start);
1100         }
1101
1102         p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY);
1103         if (p != NULL) {
1104                 strcpy(compare_uid, icalproperty_get_comment(p));
1105         }
1106
1107         do {
1108                 ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid);
1109
1110                 if (rrule) {
1111                         t1start = icalrecur_iterator_next(ritr);
1112                         if (!icaltime_is_null_time(t1end)) {
1113                                 const icaltimezone *hold_zone = t1end.zone;
1114                                 t1end = icaltime_add(t1start, dur);
1115                                 t1end.zone = hold_zone;
1116                         }
1117                         ++num_recur;
1118                 }
1119
1120         } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) );
1121         icalrecur_iterator_free(ritr);
1122 }
1123
1124
1125 /*
1126  * Phase 3 of "hunt for conflicts"
1127  * Called by ical_hunt_for_conflicts()
1128  */
1129 void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
1130         icalcomponent *proposed_event;
1131         struct CtdlMessage *msg = NULL;
1132         struct ical_respond_data ird;
1133
1134         proposed_event = (icalcomponent *)data;
1135
1136         msg = CtdlFetchMessage(msgnum, 1);
1137         if (msg == NULL) return;
1138         memset(&ird, 0, sizeof ird);
1139         strcpy(ird.desired_partnum, "_HUNT_");
1140         mime_parser(CM_RANGE(msg, eMesageText),
1141                     *ical_locate_part,          /* callback function */
1142                     NULL, NULL,
1143                     (void *) &ird,                      /* user data */
1144                     0
1145         );
1146         CM_Free(msg);
1147
1148         if (ird.cal == NULL) return;
1149
1150         ical_conflicts_phase4(proposed_event, ird.cal, msgnum);
1151         icalcomponent_free(ird.cal);
1152 }
1153
1154
1155 /* 
1156  * Phase 2 of "hunt for conflicts" operation.
1157  * At this point we have a calendar object which represents the VEVENT that
1158  * is proposed for addition to the calendar.  Now hunt through the user's
1159  * calendar room, and output zero or more existing VEVENTs which conflict
1160  * with this one.
1161  */
1162 void ical_hunt_for_conflicts(icalcomponent *cal) {
1163         char hold_rm[ROOMNAMELEN];
1164
1165         strcpy(hold_rm, CC->room.QRname);       /* save current room */
1166
1167         if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
1168                 CtdlGetRoom(&CC->room, hold_rm);
1169                 cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
1170                 return;
1171         }
1172
1173         cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
1174
1175         CtdlForEachMessage(MSGS_ALL, 0, NULL,
1176                 NULL,
1177                 NULL,
1178                 ical_hunt_for_conflicts_backend,
1179                 (void *) cal
1180         );
1181
1182         cprintf("000\n");
1183         CtdlGetRoom(&CC->room, hold_rm);        /* return to saved room */
1184
1185 }
1186
1187
1188 /*
1189  * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
1190  */
1191 void ical_conflicts(long msgnum, char *partnum) {
1192         struct CtdlMessage *msg = NULL;
1193         struct ical_respond_data ird;
1194
1195         msg = CtdlFetchMessage(msgnum, 1);
1196         if (msg == NULL) {
1197                 cprintf("%d Message %ld not found\n",
1198                         ERROR + ILLEGAL_VALUE,
1199                         (long)msgnum
1200                 );
1201                 return;
1202         }
1203
1204         memset(&ird, 0, sizeof ird);
1205         strcpy(ird.desired_partnum, partnum);
1206         mime_parser(CM_RANGE(msg, eMesageText),
1207                     *ical_locate_part,          /* callback function */
1208                     NULL, NULL,
1209                     (void *) &ird,                      /* user data */
1210                     0
1211                 );
1212
1213         CM_Free(msg);
1214
1215         if (ird.cal != NULL) {
1216                 ical_hunt_for_conflicts(ird.cal);
1217                 icalcomponent_free(ird.cal);
1218                 return;
1219         }
1220
1221         cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
1222 }
1223
1224
1225 /*
1226  * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
1227  *
1228  * fb                   The VFREEBUSY component to which we are appending
1229  * top_level_cal        The top-level VCALENDAR component which contains a VEVENT to be added
1230  */
1231 void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal) {
1232         icalcomponent *cal;
1233         icalproperty *p;
1234         icalvalue *v;
1235         struct icalperiodtype this_event_period = icalperiodtype_null_period();
1236         icaltimetype dtstart;
1237         icaltimetype dtend;
1238
1239         /* recur variables */
1240         icalproperty *rrule = NULL;
1241         struct icalrecurrencetype recur;
1242         icalrecur_iterator *ritr = NULL;
1243         struct icaldurationtype dur;
1244         int num_recur = 0;
1245
1246         if (!top_level_cal) return;
1247
1248         /* Find the VEVENT component containing an event */
1249         cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT);
1250         if (!cal) return;
1251
1252         /* If this event is not opaque, the user isn't publishing it as
1253          * busy time, so don't bother doing anything else.
1254          */
1255         p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
1256         if (p != NULL) {
1257                 v = icalproperty_get_value(p);
1258                 if (v != NULL) {
1259                         if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
1260                                 return;
1261                         }
1262                 }
1263         }
1264
1265         /*
1266          * Now begin calculating the event start and end times.
1267          */
1268         p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
1269         if (!p) return;
1270         dtstart = icalproperty_get_dtstart(p);
1271
1272         if (icaltime_is_utc(dtstart)) {
1273                 dtstart.zone = icaltimezone_get_utc_timezone();
1274         }
1275         else {
1276                 dtstart.zone = icalcomponent_get_timezone(top_level_cal,
1277                         icalparameter_get_tzid(
1278                                 icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
1279                         )
1280                 );
1281                 if (!dtstart.zone) {
1282                         dtstart.zone = get_default_icaltimezone();
1283                 }
1284         }
1285
1286         dtend = icalcomponent_get_dtend(cal);
1287         if (!icaltime_is_null_time(dtend)) {
1288                 dur = icaltime_subtract(dtend, dtstart);
1289         }
1290         else {
1291                 memset (&dur, 0, sizeof(struct icaldurationtype));
1292         }
1293
1294         /* Is a recurrence specified?  If so, get ready to process it... */
1295         rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY);
1296         if (rrule) {
1297                 recur = icalproperty_get_rrule(rrule);
1298                 ritr = icalrecur_iterator_new(recur, dtstart);
1299         }
1300
1301         do {
1302                 /* Convert the DTSTART and DTEND properties to an icalperiod. */
1303                 this_event_period.start = dtstart;
1304         
1305                 if (!icaltime_is_null_time(dtend)) {
1306                         this_event_period.end = dtend;
1307                 }
1308
1309                 /* Convert the timestamps to UTC.  It's ok to do this because we've already expanded
1310                  * recurrences and this data is never going to get used again.
1311                  */
1312                 this_event_period.start = icaltime_convert_to_zone(
1313                         this_event_period.start,
1314                         icaltimezone_get_utc_timezone()
1315                 );
1316                 this_event_period.end = icaltime_convert_to_zone(
1317                         this_event_period.end,
1318                         icaltimezone_get_utc_timezone()
1319                 );
1320         
1321                 /* Now add it. */
1322                 icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period));
1323
1324                 /* Make sure the DTSTART property of the freebusy *list* is set to
1325                  * the DTSTART property of the *earliest event*.
1326                  */
1327                 p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
1328                 if (p == NULL) {
1329                         icalcomponent_set_dtstart(fb, this_event_period.start);
1330                 }
1331                 else {
1332                         if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) {
1333                                 icalcomponent_set_dtstart(fb, this_event_period.start);
1334                         }
1335                 }
1336         
1337                 /* Make sure the DTEND property of the freebusy *list* is set to
1338                  * the DTEND property of the *latest event*.
1339                  */
1340                 p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
1341                 if (p == NULL) {
1342                         icalcomponent_set_dtend(fb, this_event_period.end);
1343                 }
1344                 else {
1345                         if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) {
1346                                 icalcomponent_set_dtend(fb, this_event_period.end);
1347                         }
1348                 }
1349
1350                 if (rrule) {
1351                         dtstart = icalrecur_iterator_next(ritr);
1352                         if (!icaltime_is_null_time(dtend)) {
1353                                 dtend = icaltime_add(dtstart, dur);
1354                                 dtend.zone = dtstart.zone;
1355                         }
1356                         ++num_recur;
1357                 }
1358
1359         } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ;
1360         icalrecur_iterator_free(ritr);
1361 }
1362
1363
1364 /*
1365  * Backend for ical_freebusy()
1366  *
1367  * This function simply loads the messages in the user's calendar room,
1368  * which contain VEVENTs, then strips them of all non-freebusy data, and
1369  * adds them to the supplied VCALENDAR.
1370  *
1371  */
1372 void ical_freebusy_backend(long msgnum, void *data) {
1373         icalcomponent *fb;
1374         struct CtdlMessage *msg = NULL;
1375         struct ical_respond_data ird;
1376
1377         fb = (icalcomponent *)data;             /* User-supplied data will be the VFREEBUSY component */
1378
1379         msg = CtdlFetchMessage(msgnum, 1);
1380         if (msg == NULL) return;
1381         memset(&ird, 0, sizeof ird);
1382         strcpy(ird.desired_partnum, "_HUNT_");
1383         mime_parser(CM_RANGE(msg, eMesageText),
1384                     *ical_locate_part,          /* callback function */
1385                     NULL, NULL,
1386                     (void *) &ird,                      /* user data */
1387                     0
1388                 );
1389         CM_Free(msg);
1390
1391         if (ird.cal) {
1392                 ical_add_to_freebusy(fb, ird.cal);              /* Add VEVENT times to VFREEBUSY */
1393                 icalcomponent_free(ird.cal);
1394         }
1395 }
1396
1397
1398 /*
1399  * Grab another user's free/busy times
1400  */
1401 void ical_freebusy(char *who) {
1402         struct ctdluser usbuf;
1403         char calendar_room_name[ROOMNAMELEN];
1404         char hold_rm[ROOMNAMELEN];
1405         char *serialized_request = NULL;
1406         icalcomponent *encaps = NULL;
1407         icalcomponent *fb = NULL;
1408         int found_user = (-1);
1409         struct recptypes *recp = NULL;
1410         char buf[256];
1411         char host[256];
1412         char type[256];
1413         int i = 0;
1414         int config_lines = 0;
1415
1416         /* First try an exact match. */
1417         found_user = CtdlGetUser(&usbuf, who);
1418
1419         /* If not found, try it as an unqualified email address. */
1420         if (found_user != 0) {
1421                 strcpy(buf, who);
1422                 recp = validate_recipients(buf, NULL, 0);
1423                 syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
1424                 if (recp != NULL) {
1425                         if (recp->num_local == 1) {
1426                                 found_user = CtdlGetUser(&usbuf, recp->recp_local);
1427                         }
1428                         free_recipients(recp);
1429                 }
1430         }
1431
1432         /* If still not found, try it as an address qualified with the
1433          * primary FQDN of this Citadel node.
1434          */
1435         if (found_user != 0) {
1436                 snprintf(buf, sizeof buf, "%s@%s", who, CtdlGetConfigStr("c_fqdn"));
1437                 syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
1438                 recp = validate_recipients(buf, NULL, 0);
1439                 if (recp != NULL) {
1440                         if (recp->num_local == 1) {
1441                                 found_user = CtdlGetUser(&usbuf, recp->recp_local);
1442                         }
1443                         free_recipients(recp);
1444                 }
1445         }
1446
1447         /* Still not found?  Try qualifying it with every domain we
1448          * might have addresses in.
1449          */
1450         if (found_user != 0) {
1451                 config_lines = num_tokens(inetcfg, '\n');
1452                 for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
1453                         extract_token(buf, inetcfg, i, '\n', sizeof buf);
1454                         extract_token(host, buf, 0, '|', sizeof host);
1455                         extract_token(type, buf, 1, '|', sizeof type);
1456
1457                         if ( (!strcasecmp(type, "localhost"))
1458                            || (!strcasecmp(type, "directory")) ) {
1459                                 snprintf(buf, sizeof buf, "%s@%s", who, host);
1460                                 syslog(LOG_DEBUG, "calendar: trying <%s>", buf);
1461                                 recp = validate_recipients(buf, NULL, 0);
1462                                 if (recp != NULL) {
1463                                         if (recp->num_local == 1) {
1464                                                 found_user = CtdlGetUser(&usbuf, recp->recp_local);
1465                                         }
1466                                         free_recipients(recp);
1467                                 }
1468                         }
1469                 }
1470         }
1471
1472         if (found_user != 0) {
1473                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1474                 return;
1475         }
1476
1477         CtdlMailboxName(calendar_room_name, sizeof calendar_room_name,
1478                 &usbuf, USERCALENDARROOM);
1479
1480         strcpy(hold_rm, CC->room.QRname);       /* save current room */
1481
1482         if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) {
1483                 cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
1484                 CtdlGetRoom(&CC->room, hold_rm);
1485                 return;
1486         }
1487
1488         /* Create a VFREEBUSY subcomponent */
1489         syslog(LOG_DEBUG, "calendar: creating VFREEBUSY component");
1490         fb = icalcomponent_new_vfreebusy();
1491         if (fb == NULL) {
1492                 cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR);
1493                 CtdlGetRoom(&CC->room, hold_rm);
1494                 return;
1495         }
1496
1497         /* Set the method to PUBLISH */
1498         icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
1499
1500         /* Set the DTSTAMP to right now. */
1501         icalcomponent_set_dtstamp(fb, icaltime_from_timet_with_zone(time(NULL), 0, icaltimezone_get_utc_timezone()));
1502
1503         /* Add the user's email address as ORGANIZER */
1504         sprintf(buf, "MAILTO:%s", who);
1505         if (strchr(buf, '@') == NULL) {
1506                 strcat(buf, "@");
1507                 strcat(buf, CtdlGetConfigStr("c_fqdn"));
1508         }
1509         for (i=0; buf[i]; ++i) {
1510                 if (buf[i]==' ') buf[i] = '_';
1511         }
1512         icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
1513
1514         /* Add busy time from events */
1515         syslog(LOG_DEBUG, "calendar: adding busy time from events");
1516         CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
1517
1518         /* If values for DTSTART and DTEND are still not present, set them
1519          * to yesterday and tomorrow as default values.
1520          */
1521         if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
1522                 icalcomponent_set_dtstart(fb, icaltime_from_timet_with_zone(time(NULL)-86400L, 0, icaltimezone_get_utc_timezone()));
1523         }
1524         if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
1525                 icalcomponent_set_dtend(fb, icaltime_from_timet_with_zone(time(NULL)+86400L, 0, icaltimezone_get_utc_timezone()));
1526         }
1527
1528         /* Put the freebusy component into the calendar component */
1529         syslog(LOG_DEBUG, "calendar: encapsulating");
1530         encaps = ical_encapsulate_subcomponent(fb);
1531         if (encaps == NULL) {
1532                 icalcomponent_free(fb);
1533                 cprintf("%d Internal error: cannot allocate memory.\n",
1534                         ERROR + INTERNAL_ERROR);
1535                 CtdlGetRoom(&CC->room, hold_rm);
1536                 return;
1537         }
1538
1539         /* Set the method to PUBLISH */
1540         syslog(LOG_DEBUG, "calendar: setting method");
1541         icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
1542
1543         /* Serialize it */
1544         syslog(LOG_DEBUG, "calendar: serializing");
1545         serialized_request = icalcomponent_as_ical_string_r(encaps);
1546         icalcomponent_free(encaps);     /* Don't need this anymore. */
1547
1548         cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname);
1549         if (serialized_request != NULL) {
1550                 client_write(serialized_request, strlen(serialized_request));
1551                 free(serialized_request);
1552         }
1553         cprintf("\n000\n");
1554
1555         /* Go back to the room from which we came... */
1556         CtdlGetRoom(&CC->room, hold_rm);
1557 }
1558
1559
1560 /*
1561  * Backend for ical_getics()
1562  * 
1563  * This is a ForEachMessage() callback function that searches the current room
1564  * for calendar events and adds them each into one big calendar component.
1565  */
1566 void ical_getics_backend(long msgnum, void *data) {
1567         icalcomponent *encaps, *c;
1568         struct CtdlMessage *msg = NULL;
1569         struct ical_respond_data ird;
1570
1571         encaps = (icalcomponent *)data;
1572         if (encaps == NULL) return;
1573
1574         /* Look for the calendar event... */
1575
1576         msg = CtdlFetchMessage(msgnum, 1);
1577         if (msg == NULL) return;
1578         memset(&ird, 0, sizeof ird);
1579         strcpy(ird.desired_partnum, "_HUNT_");
1580         mime_parser(CM_RANGE(msg, eMesageText),
1581                     *ical_locate_part,          /* callback function */
1582                     NULL, NULL,
1583                     (void *) &ird,                      /* user data */
1584                     0
1585         );
1586         CM_Free(msg);
1587
1588         if (ird.cal == NULL) return;
1589
1590         /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
1591          * are responsible for "the_request"'s memory -- it will be freed
1592          * when we free "encaps".
1593          */
1594
1595         /* If the top-level component is *not* a VCALENDAR, we can drop it right
1596          * in.  This will almost never happen.
1597          */
1598         if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
1599                 icalcomponent_add_component(encaps, ird.cal);
1600         }
1601         /*
1602          * In the more likely event that we're looking at a VCALENDAR with the VEVENT
1603          * and other components encapsulated inside, we have to extract them.
1604          */
1605         else {
1606                 for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
1607                     (c != NULL);
1608                     c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
1609
1610                         /* For VTIMEZONE components, suppress duplicates of the same tzid */
1611
1612                         if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) {
1613                                 icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY);
1614                                 if (p) {
1615                                         const char *tzid = icalproperty_get_tzid(p);
1616                                         if (!icalcomponent_get_timezone(encaps, tzid)) {
1617                                                 icalcomponent_add_component(encaps,
1618                                                                         icalcomponent_new_clone(c));
1619                                         }
1620                                 }
1621                         }
1622
1623                         /* All other types of components can go in verbatim */
1624                         else {
1625                                 icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
1626                         }
1627                 }
1628                 icalcomponent_free(ird.cal);
1629         }
1630 }
1631
1632
1633 /*
1634  * Retrieve all of the calendar items in the current room, and output them
1635  * as a single icalendar object.
1636  */
1637 void ical_getics(void)
1638 {
1639         icalcomponent *encaps = NULL;
1640         char *ser = NULL;
1641
1642         if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
1643            &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
1644                 cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
1645                 return;         /* Not an iCalendar-centric room */
1646         }
1647
1648         encaps = icalcomponent_new_vcalendar();
1649         if (encaps == NULL) {
1650                 syslog(LOG_ERR, "calendar: could not allocate component!");
1651                 cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
1652                 return;
1653         }
1654
1655         cprintf("%d one big calendar\n", LISTING_FOLLOWS);
1656
1657         /* Set the Product ID */
1658         icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
1659
1660         /* Set the Version Number */
1661         icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
1662
1663         /* Set the method to PUBLISH */
1664         icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
1665
1666         /* Now go through the room encapsulating all calendar items. */
1667         CtdlForEachMessage(MSGS_ALL, 0, NULL,
1668                 NULL,
1669                 NULL,
1670                 ical_getics_backend,
1671                 (void *) encaps
1672         );
1673
1674         ser = icalcomponent_as_ical_string_r(encaps);
1675         icalcomponent_free(encaps);                     /* Don't need this anymore. */
1676         client_write(ser, strlen(ser));
1677         free(ser);
1678         cprintf("\n000\n");
1679 }
1680
1681
1682 /*
1683  * Helper callback function for ical_putics() to discover which TZID's we need.
1684  * Simply put the tzid name string into a hash table.  After the callbacks are
1685  * done we'll go through them and attach the ones that we have.
1686  */
1687 void ical_putics_grabtzids(icalparameter *param, void *data)
1688 {
1689         const char *tzid = icalparameter_get_tzid(param);
1690         HashList *keys = (HashList *) data;
1691         
1692         if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) {
1693                 Put(keys, tzid, strlen(tzid), strdup(tzid), NULL);
1694         }
1695 }
1696
1697
1698 /*
1699  * Delete all of the calendar items in the current room, and replace them
1700  * with calendar items from a client-supplied data stream.
1701  */
1702 void ical_putics(void)
1703 {
1704         char *calstream = NULL;
1705         icalcomponent *cal;
1706         icalcomponent *c;
1707         icalcomponent *encaps = NULL;
1708         HashList *tzidlist = NULL;
1709         HashPos *HashPos;
1710         void *Value;
1711         const char *Key;
1712         long len;
1713
1714         /* Only allow this operation if we're in a room containing a calendar or tasks view */
1715         if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
1716            &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
1717                 cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
1718                 return;
1719         }
1720
1721         /* Only allow this operation if we have permission to overwrite the existing calendar */
1722         if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
1723                 cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
1724                 return;
1725         }
1726
1727         cprintf("%d Transmit data now\n", SEND_LISTING);
1728         calstream = CtdlReadMessageBody(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0);
1729         if (calstream == NULL) {
1730                 return;
1731         }
1732
1733         cal = icalcomponent_new_from_string(calstream);
1734         free(calstream);
1735
1736         /* We got our data stream -- now do something with it. */
1737
1738         /* Delete the existing messages in the room, because we are overwriting
1739          * the entire calendar with an entire new (or updated) calendar.
1740          * (Careful: this opens an S_ROOMS critical section!)
1741          */
1742         CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
1743
1744         /* If the top-level component is *not* a VCALENDAR, we can drop it right
1745          * in.  This will almost never happen.
1746          */
1747         if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
1748                 ical_write_to_cal(NULL, cal);
1749         }
1750         /*
1751          * In the more likely event that we're looking at a VCALENDAR with the VEVENT
1752          * and other components encapsulated inside, we have to extract them.
1753          */
1754         else {
1755                 for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
1756                     (c != NULL);
1757                     c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
1758
1759                         /* Non-VTIMEZONE components each get written as individual messages.
1760                          * But we also need to attach the relevant VTIMEZONE components to them.
1761                          */
1762                         if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT)
1763                            && (encaps = icalcomponent_new_vcalendar()) ) {
1764                                 icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
1765                                 icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
1766                                 icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
1767
1768                                 /* Attach any needed timezones here */
1769                                 tzidlist = NewHash(1, NULL);
1770                                 if (tzidlist) {
1771                                         icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist);
1772                                 }
1773                                 HashPos = GetNewHashPos(tzidlist, 0);
1774
1775                                 while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) {
1776                                         syslog(LOG_DEBUG, "calendar: attaching timezone '%s'", (char*) Value);
1777                                         icaltimezone *t = NULL;
1778
1779                                         /* First look for a timezone attached to the original calendar */
1780                                         t = icalcomponent_get_timezone(cal, Value);
1781
1782                                         /* Try built-in tzdata if the right one wasn't attached */
1783                                         if (!t) {
1784                                                 t = icaltimezone_get_builtin_timezone(Value);
1785                                         }
1786
1787                                         /* I've got a valid timezone to attach. */
1788                                         if (t) {
1789                                                 icalcomponent_add_component(encaps,
1790                                                         icalcomponent_new_clone(
1791                                                                 icaltimezone_get_component(t)
1792                                                         )
1793                                                 );
1794                                         }
1795
1796                                 }
1797                                 DeleteHashPos(&HashPos);
1798                                 DeleteHash(&tzidlist);
1799
1800                                 /* Now attach the component itself (usually a VEVENT or VTODO) */
1801                                 icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
1802
1803                                 /* Write it to the message store */
1804                                 ical_write_to_cal(NULL, encaps);
1805                                 icalcomponent_free(encaps);
1806                         }
1807                 }
1808         }
1809
1810         icalcomponent_free(cal);
1811 }
1812
1813
1814 /*
1815  * All Citadel calendar commands from the client come through here.
1816  */
1817 void cmd_ical(char *argbuf)
1818 {
1819         char subcmd[64];
1820         long msgnum;
1821         char partnum[256];
1822         char action[256];
1823         char who[256];
1824
1825         extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
1826
1827         /* Allow "test" and "freebusy" subcommands without logging in. */
1828
1829         if (!strcasecmp(subcmd, "test")) {
1830                 cprintf("%d This server supports calendaring\n", CIT_OK);
1831                 return;
1832         }
1833
1834         if (!strcasecmp(subcmd, "freebusy")) {
1835                 extract_token(who, argbuf, 1, '|', sizeof who);
1836                 ical_freebusy(who);
1837                 return;
1838         }
1839
1840         if (!strcasecmp(subcmd, "sgi")) {
1841                 CIT_ICAL->server_generated_invitations = (extract_int(argbuf, 1) ? 1 : 0) ;
1842                 cprintf("%d %d\n", CIT_OK, CIT_ICAL->server_generated_invitations);
1843                 return;
1844         }
1845
1846         if (CtdlAccessCheck(ac_logged_in)) return;
1847
1848         if (!strcasecmp(subcmd, "respond")) {
1849                 msgnum = extract_long(argbuf, 1);
1850                 extract_token(partnum, argbuf, 2, '|', sizeof partnum);
1851                 extract_token(action, argbuf, 3, '|', sizeof action);
1852                 ical_respond(msgnum, partnum, action);
1853                 return;
1854         }
1855
1856         if (!strcasecmp(subcmd, "handle_rsvp")) {
1857                 msgnum = extract_long(argbuf, 1);
1858                 extract_token(partnum, argbuf, 2, '|', sizeof partnum);
1859                 extract_token(action, argbuf, 3, '|', sizeof action);
1860                 ical_handle_rsvp(msgnum, partnum, action);
1861                 return;
1862         }
1863
1864         if (!strcasecmp(subcmd, "conflicts")) {
1865                 msgnum = extract_long(argbuf, 1);
1866                 extract_token(partnum, argbuf, 2, '|', sizeof partnum);
1867                 ical_conflicts(msgnum, partnum);
1868                 return;
1869         }
1870
1871         if (!strcasecmp(subcmd, "getics")) {
1872                 ical_getics();
1873                 return;
1874         }
1875
1876         if (!strcasecmp(subcmd, "putics")) {
1877                 ical_putics();
1878                 return;
1879         }
1880
1881         cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
1882 }
1883
1884
1885 /*
1886  * We don't know if the calendar room exists so we just create it at login
1887  */
1888 void ical_CtdlCreateRoom(void)
1889 {
1890         struct ctdlroom qr;
1891         visit vbuf;
1892
1893         /* Create the calendar room if it doesn't already exist */
1894         CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
1895
1896         /* Set expiration policy to manual; otherwise objects will be lost! */
1897         if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) {
1898                 syslog(LOG_ERR, "calendar: couldn't get the user calendar room");
1899                 return;
1900         }
1901         qr.QRep.expire_mode = EXPIRE_MANUAL;
1902         qr.QRdefaultview = VIEW_CALENDAR;       /* 3 = calendar view */
1903         CtdlPutRoomLock(&qr);
1904
1905         /* Set the view to a calendar view */
1906         CtdlGetRelationship(&vbuf, &CC->user, &qr);
1907         vbuf.v_view = VIEW_CALENDAR;
1908         CtdlSetRelationship(&vbuf, &CC->user, &qr);
1909
1910         /* Create the tasks list room if it doesn't already exist */
1911         CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
1912
1913         /* Set expiration policy to manual; otherwise objects will be lost! */
1914         if (CtdlGetRoomLock(&qr, USERTASKSROOM)) {
1915                 syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
1916                 return;
1917         }
1918         qr.QRep.expire_mode = EXPIRE_MANUAL;
1919         qr.QRdefaultview = VIEW_TASKS;
1920         CtdlPutRoomLock(&qr);
1921
1922         /* Set the view to a task list view */
1923         CtdlGetRelationship(&vbuf, &CC->user, &qr);
1924         vbuf.v_view = VIEW_TASKS;
1925         CtdlSetRelationship(&vbuf, &CC->user, &qr);
1926
1927         /* Create the notes room if it doesn't already exist */
1928         CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
1929
1930         /* Set expiration policy to manual; otherwise objects will be lost! */
1931         if (CtdlGetRoomLock(&qr, USERNOTESROOM)) {
1932                 syslog(LOG_ERR, "calendar: couldn't get the user calendar room!");
1933                 return;
1934         }
1935         qr.QRep.expire_mode = EXPIRE_MANUAL;
1936         qr.QRdefaultview = VIEW_NOTES;
1937         CtdlPutRoomLock(&qr);
1938
1939         /* Set the view to a notes view */
1940         CtdlGetRelationship(&vbuf, &CC->user, &qr);
1941         vbuf.v_view = VIEW_NOTES;
1942         CtdlSetRelationship(&vbuf, &CC->user, &qr);
1943
1944         return;
1945 }
1946
1947
1948 /*
1949  * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT.
1950  *
1951  * top_level_cal is the highest available level calendar object.
1952  * cal is the subcomponent containing the VEVENT.
1953  *
1954  * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent()
1955  */
1956 void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) {
1957         icalcomponent *the_request = NULL;
1958         char *serialized_request = NULL;
1959         icalcomponent *encaps = NULL;
1960         char *request_message_text = NULL;
1961         struct CtdlMessage *msg = NULL;
1962         struct recptypes *valid = NULL;
1963         char attendees_string[SIZ];
1964         int num_attendees = 0;
1965         char this_attendee[256];
1966         icalproperty *attendee = NULL;
1967         char summary_string[SIZ];
1968         icalproperty *summary = NULL;
1969         size_t reqsize;
1970         icalproperty *p;
1971         struct icaltimetype t;
1972         const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL };
1973         int i;
1974         const icaltimezone *z;
1975         int num_zones_attached = 0;
1976         int zone_already_attached;
1977         icalparameter *tzidp = NULL;
1978         const char *tzidc = NULL;
1979
1980         if (cal == NULL) {
1981                 syslog(LOG_ERR, "calendar: trying to reply to NULL event?");
1982                 return;
1983         }
1984
1985         /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
1986         if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
1987                 ical_send_out_invitations(top_level_cal,
1988                         icalcomponent_get_first_component(
1989                                 cal, ICAL_VEVENT_COMPONENT
1990                         )
1991                 );
1992                 return;
1993         }
1994
1995         /* Clone the event */
1996         the_request = icalcomponent_new_clone(cal);
1997         if (the_request == NULL) {
1998                 syslog(LOG_ERR, "calendar: cannot clone calendar object");
1999                 return;
2000         }
2001
2002         /* Extract the summary string -- we'll use it as the
2003          * message subject for the request
2004          */
2005         strcpy(summary_string, "Meeting request");
2006         summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
2007         if (summary != NULL) {
2008                 if (icalproperty_get_summary(summary)) {
2009                         strcpy(summary_string,
2010                                 icalproperty_get_summary(summary) );
2011                 }
2012         }
2013
2014         /* Determine who the recipients of this message are (the attendees) */
2015         strcpy(attendees_string, "");
2016         for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
2017                 const char *ch = icalproperty_get_attendee(attendee);
2018                 if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
2019                         safestrncpy(this_attendee, ch + 7, sizeof(this_attendee));
2020                         
2021                         if (!CtdlIsMe(this_attendee, sizeof this_attendee)) {   /* don't send an invitation to myself! */
2022                                 snprintf(&attendees_string[strlen(attendees_string)],
2023                                          sizeof(attendees_string) - strlen(attendees_string),
2024                                          "%s, ",
2025                                          this_attendee
2026                                         );
2027                                 ++num_attendees;
2028                         }
2029                 }
2030         }
2031
2032         syslog(LOG_DEBUG, "calendar: <%d> attendees: <%s>", num_attendees, attendees_string);
2033
2034         /* If there are no attendees, there are no invitations to send, so...
2035          * don't bother putting one together!  Punch out, Maverick!
2036          */
2037         if (num_attendees == 0) {
2038                 icalcomponent_free(the_request);
2039                 return;
2040         }
2041
2042         /* Encapsulate the VEVENT component into a complete VCALENDAR */
2043         encaps = icalcomponent_new_vcalendar();
2044         if (encaps == NULL) {
2045                 syslog(LOG_ERR, "calendar: could not allocate component!");
2046                 icalcomponent_free(the_request);
2047                 return;
2048         }
2049
2050         /* Set the Product ID */
2051         icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
2052
2053         /* Set the Version Number */
2054         icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
2055
2056         /* Set the method to REQUEST */
2057         icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
2058
2059         /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */
2060         for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY);
2061              p != NULL;
2062              p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY))
2063         {
2064                 if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY)
2065                   || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY)
2066                   || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY)
2067                   || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY)
2068                   || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY)
2069                   || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY)
2070                   || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY)
2071                   || (icalproperty_isa(p) == ICAL_DUE_PROPERTY)
2072                   || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY)
2073                   || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY)
2074                   || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY)
2075                   || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY)
2076                   || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY)
2077                 ) {
2078                         t = icalproperty_get_dtstart(p);        // it's safe to use dtstart for all of them
2079
2080                         /* Determine the tzid in order for some of the conditions below to work */
2081                         tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
2082                         if (tzidp) {
2083                                 tzidc = icalparameter_get_tzid(tzidp);
2084                         }
2085                         else {
2086                                 tzidc = NULL;
2087                         }
2088
2089                         /* First see if there's a timezone attached to the data structure itself */
2090                         if (icaltime_is_utc(t)) {
2091                                 z = icaltimezone_get_utc_timezone();
2092                         }
2093                         else {
2094                                 z = icaltime_get_timezone(t);
2095                         }
2096
2097                         /* If not, try to determine the tzid from the parameter using attached zones */
2098                         if ((!z) && (tzidc)) {
2099                                 z = icalcomponent_get_timezone(top_level_cal, tzidc);
2100                         }
2101
2102                         /* Still no good?  Try our internal database */
2103                         if ((!z) && (tzidc)) {
2104                                 z = icaltimezone_get_builtin_timezone_from_tzid(tzidc);
2105                         }
2106
2107                         if (z) {
2108                                 /* We have a valid timezone.  Good.  Now we need to attach it. */
2109
2110                                 zone_already_attached = 0;
2111                                 for (i=0; i<5; ++i) {
2112                                         if (z == attached_zones[i]) {
2113                                                 /* We've already got this one, no need to attach another. */
2114                                                 ++zone_already_attached;
2115                                         }
2116                                 }
2117                                 if ((!zone_already_attached) && (num_zones_attached < 5)) {
2118                                         /* This is a new one, so attach it. */
2119                                         attached_zones[num_zones_attached++] = z;
2120                                 }
2121
2122                                 icalproperty_set_parameter(p, icalparameter_new_tzid(icaltimezone_get_tzid(z))
2123                                 );
2124                         }
2125                 }
2126         }
2127
2128         /* Encapsulate any timezones we need */
2129         if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
2130                 icalcomponent *zc;
2131                 zc = icalcomponent_new_clone(icaltimezone_get_component(attached_zones[i]));
2132                 icalcomponent_add_component(encaps, zc);
2133         }
2134
2135         /* Here we go: encapsulate the VEVENT into the VCALENDAR.  We now no longer
2136          * are responsible for "the_request"'s memory -- it will be freed
2137          * when we free "encaps".
2138          */
2139         icalcomponent_add_component(encaps, the_request);
2140
2141         /* Serialize it */
2142         serialized_request = icalcomponent_as_ical_string_r(encaps);
2143         icalcomponent_free(encaps);     /* Don't need this anymore. */
2144         if (serialized_request == NULL) return;
2145
2146         reqsize = strlen(serialized_request) + SIZ;
2147         request_message_text = malloc(reqsize);
2148         if (request_message_text != NULL) {
2149                 snprintf(request_message_text, reqsize,
2150                         "Content-type: text/calendar\r\n\r\n%s\r\n",
2151                         serialized_request
2152                 );
2153
2154                 msg = CtdlMakeMessage(
2155                         &CC->user,
2156                         NULL,                   /* No single recipient here */
2157                         NULL,                   /* No single recipient here */
2158                         CC->room.QRname,
2159                         0,
2160                         FMT_RFC822,
2161                         NULL,
2162                         NULL,
2163                         summary_string,         /* Use summary for subject */
2164                         NULL,
2165                         request_message_text,
2166                         NULL
2167                 );
2168         
2169                 if (msg != NULL) {
2170                         valid = validate_recipients(attendees_string, NULL, 0);
2171                         CtdlSubmitMsg(msg, valid, "");
2172                         CM_Free(msg);
2173                         free_recipients(valid);
2174                 }
2175         }
2176         free(serialized_request);
2177 }
2178
2179
2180 /*
2181  * When a calendar object is being saved, determine whether it's a VEVENT
2182  * and the user saving it is the organizer.  If so, send out invitations
2183  * to any listed attendees.
2184  *
2185  * This function is recursive.  The caller can simply supply the same object
2186  * as both arguments.  When it recurses it will alter the second argument
2187  * while holding on to the top level object.  This allows us to go back and
2188  * grab things like time zones which might be attached.
2189  *
2190  */
2191 void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) {
2192         icalcomponent *c;
2193         icalproperty *organizer = NULL;
2194         char organizer_string[SIZ];
2195
2196         syslog(LOG_DEBUG, "calendar: ical_saving_vevent() has been called");
2197
2198         /* Don't send out invitations unless the client wants us to. */
2199         if (CIT_ICAL->server_generated_invitations == 0) {
2200                 return;
2201         }
2202
2203         /* Don't send out invitations if we've been asked not to. */
2204         if (CIT_ICAL->avoid_sending_invitations > 0) {
2205                 return;
2206         }
2207
2208         strcpy(organizer_string, "");
2209         /*
2210          * The VEVENT subcomponent is the one we're interested in.
2211          * Send out invitations if, and only if, this user is the Organizer.
2212          */
2213         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
2214                 organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
2215                 if (organizer != NULL) {
2216                         if (icalproperty_get_organizer(organizer)) {
2217                                 strcpy(organizer_string,
2218                                         icalproperty_get_organizer(organizer));
2219                         }
2220                 }
2221                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
2222                         strcpy(organizer_string, &organizer_string[7]);
2223                         striplt(organizer_string);
2224                         /*
2225                          * If the user saving the event is listed as the
2226                          * organizer, then send out invitations.
2227                          */
2228                         if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
2229                                 ical_send_out_invitations(top_level_cal, cal);
2230                         }
2231                 }
2232         }
2233
2234         /* If the component has subcomponents, recurse through them. */
2235         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
2236             (c != NULL);
2237             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
2238                 /* Recursively process subcomponent */
2239                 ical_saving_vevent(top_level_cal, c);
2240         }
2241
2242 }
2243
2244
2245 /*
2246  * Back end for ical_obj_beforesave()
2247  * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
2248  * the summary of the event (becomes message subject),
2249  * and the start time (becomes message date/time).
2250  */
2251 void ical_obj_beforesave_backend(char *name, char *filename, char *partnum,
2252                 char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
2253                 char *encoding, char *cbid, void *cbuserdata)
2254 {
2255         const char* pch;
2256         icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
2257         icalproperty *p;
2258         char new_uid[256] = "";
2259         struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata;
2260
2261         if (!msg) return;
2262
2263         /* We're only interested in calendar data. */
2264         if (  (strcasecmp(cbtype, "text/calendar"))
2265            && (strcasecmp(cbtype, "application/ics")) ) {
2266                 return;
2267         }
2268
2269         /* Hunt for the UID and drop it in
2270          * the "user data" pointer for the MIME parser.  When
2271          * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
2272          * to that string.
2273          */
2274         whole_cal = icalcomponent_new_from_string(content);
2275         cal = whole_cal;
2276         if (cal != NULL) {
2277                 if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
2278                         nested_event = icalcomponent_get_first_component(
2279                                 cal, ICAL_VEVENT_COMPONENT);
2280                         if (nested_event != NULL) {
2281                                 cal = nested_event;
2282                         }
2283                         else {
2284                                 nested_todo = icalcomponent_get_first_component(
2285                                         cal, ICAL_VTODO_COMPONENT);
2286                                 if (nested_todo != NULL) {
2287                                         cal = nested_todo;
2288                                 }
2289                         }
2290                 }
2291                 
2292                 if (cal != NULL) {
2293
2294                         /* Set the message EUID to the iCalendar UID */
2295
2296                         p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
2297                         if (p == NULL) {
2298                                 /* If there's no uid we must generate one */
2299                                 generate_uuid(new_uid);
2300                                 icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
2301                                 p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
2302                         }
2303                         if (p != NULL) {
2304                                 pch = icalproperty_get_comment(p);
2305                                 if (!IsEmptyStr(pch)) {
2306                                         CM_SetField(msg, eExclusiveID, pch, strlen(pch));
2307                                         syslog(LOG_DEBUG, "calendar: saving calendar UID <%s>", pch);
2308                                 }
2309                         }
2310
2311                         /* Set the message subject to the iCalendar summary */
2312
2313                         p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
2314                         if (p != NULL) {
2315                                 pch = icalproperty_get_comment(p);
2316                                 if (!IsEmptyStr(pch)) {
2317                                         char *subj;
2318
2319                                         subj = rfc2047encode(pch, strlen(pch));
2320                                         CM_SetAsField(msg, eMsgSubject, &subj, strlen(subj));
2321                                 }
2322                         }
2323
2324                         /* Set the message date/time to the iCalendar start time */
2325
2326                         p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
2327                         if (p != NULL) {
2328                                 time_t idtstart;
2329                                 idtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
2330                                 if (idtstart > 0) {
2331                                         CM_SetFieldLONG(msg, eTimestamp, idtstart);
2332                                 }
2333                         }
2334
2335                 }
2336                 icalcomponent_free(cal);
2337                 if (whole_cal != cal) {
2338                         icalcomponent_free(whole_cal);
2339                 }
2340         }
2341 }
2342
2343
2344 /*
2345  * See if we need to prevent the object from being saved (we don't allow
2346  * MIME types other than text/calendar in "calendar" or "tasks" rooms).
2347  *
2348  * If the message is being saved, we also set various message header fields
2349  * using data found in the iCalendar object.
2350  */
2351 int ical_obj_beforesave(struct CtdlMessage *msg, struct recptypes *recp)
2352 {
2353         /* First determine if this is a calendar or tasks room */
2354         if (  (CC->room.QRdefaultview != VIEW_CALENDAR)
2355            && (CC->room.QRdefaultview != VIEW_TASKS)
2356         ) {
2357                 return(0);              /* Not an iCalendar-centric room */
2358         }
2359
2360         /* It must be an RFC822 message! */
2361         if (msg->cm_format_type != 4) {
2362                 syslog(LOG_DEBUG, "calendar: rejecting non-RFC822 message");
2363                 return(1);              /* You tried to save a non-RFC822 message! */
2364         }
2365
2366         if (CM_IsEmpty(msg, eMesageText)) {
2367                 return(1);              /* You tried to save a null message! */
2368         }
2369
2370         /* Do all of our lovely back-end parsing */
2371         mime_parser(CM_RANGE(msg, eMesageText),
2372                     *ical_obj_beforesave_backend,
2373                     NULL, NULL,
2374                     (void *)msg,
2375                     0
2376                 );
2377
2378         return(0);
2379 }
2380
2381
2382 /*
2383  * Things we need to do after saving a calendar event.
2384  */
2385 void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
2386                 char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
2387                 char *encoding, char *cbid, void *cbuserdata)
2388 {
2389         icalcomponent *cal;
2390
2391         /* We're only interested in calendar items here. */
2392         if (  (strcasecmp(cbtype, "text/calendar"))
2393            && (strcasecmp(cbtype, "application/ics")) ) {
2394                 return;
2395         }
2396
2397         /* Hunt for the UID and drop it in
2398          * the "user data" pointer for the MIME parser.  When
2399          * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
2400          * to that string.
2401          */
2402         if (  (!strcasecmp(cbtype, "text/calendar"))
2403            || (!strcasecmp(cbtype, "application/ics")) ) {
2404                 cal = icalcomponent_new_from_string(content);
2405                 if (cal != NULL) {
2406                         ical_saving_vevent(cal, cal);
2407                         icalcomponent_free(cal);
2408                 }
2409         }
2410 }
2411
2412
2413 /* 
2414  * Things we need to do after saving a calendar event.
2415  * (This will start back end tasks such as automatic generation of invitations,
2416  * if such actions are appropriate.)
2417  */
2418 int ical_obj_aftersave(struct CtdlMessage *msg, struct recptypes *recp)
2419 {
2420         char roomname[ROOMNAMELEN];
2421
2422         /*
2423          * If this isn't the Calendar> room, no further action is necessary.
2424          */
2425
2426         /* First determine if this is our room */
2427         CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
2428         if (strcasecmp(roomname, CC->room.QRname)) {
2429                 return(0);      /* Not the Calendar room -- don't do anything. */
2430         }
2431
2432         /* It must be an RFC822 message! */
2433         if (msg->cm_format_type != 4) return(1);
2434
2435         /* Reject null messages */
2436         if (CM_IsEmpty(msg, eMesageText)) return(1);
2437         
2438         /* Now recurse through it looking for our icalendar data */
2439         mime_parser(CM_RANGE(msg, eMesageText),
2440                     *ical_obj_aftersave_backend,
2441                     NULL, NULL,
2442                     NULL,
2443                     0
2444                 );
2445
2446         return(0);
2447 }
2448
2449
2450 void ical_session_startup(void) {
2451         CIT_ICAL = malloc(sizeof(struct cit_ical));
2452         memset(CIT_ICAL, 0, sizeof(struct cit_ical));
2453 }
2454
2455
2456 void ical_session_shutdown(void) {
2457         free(CIT_ICAL);
2458 }
2459
2460
2461 /*
2462  * Back end for ical_fixed_output()
2463  */
2464 void ical_fixed_output_backend(icalcomponent *cal, int recursion_level) {
2465         icalcomponent *c;
2466         icalproperty *p;
2467         char buf[256];
2468         const char *ch;
2469
2470         p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
2471         if (p != NULL) {
2472                 cprintf("%s\n", (const char *)icalproperty_get_comment(p));
2473         }
2474
2475         p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
2476         if (p != NULL) {
2477                 cprintf("%s\n", (const char *)icalproperty_get_comment(p));
2478         }
2479
2480         p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
2481         if (p != NULL) {
2482                 cprintf("%s\n", (const char *)icalproperty_get_comment(p));
2483         }
2484
2485         /* If the component has attendees, iterate through them. */
2486         for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
2487                 ch =  icalproperty_get_attendee(p);
2488                 if ((ch != NULL) && 
2489                     !strncasecmp(ch, "MAILTO:", 7)) {
2490
2491                         /* screen name or email address */
2492                         safestrncpy(buf, ch + 7, sizeof(buf));
2493                         striplt(buf);
2494                         cprintf("%s ", buf);
2495                 }
2496                 cprintf("\n");
2497         }
2498
2499         /* If the component has subcomponents, recurse through them. */
2500         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
2501             (c != 0);
2502             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
2503                 /* Recursively process subcomponent */
2504                 ical_fixed_output_backend(c, recursion_level+1);
2505         }
2506 }
2507
2508
2509 /*
2510  * Function to output iCalendar data as plain text.  Nobody uses MSG0
2511  * anymore, so really this is just so we expose the vCard data to the full
2512  * text indexer.
2513  */
2514 void ical_fixed_output(char *ptr, int len) {
2515         icalcomponent *cal;
2516         char *stringy_cal;
2517
2518         stringy_cal = malloc(len + 1);
2519         safestrncpy(stringy_cal, ptr, len + 1);
2520         cal = icalcomponent_new_from_string(stringy_cal);
2521         free(stringy_cal);
2522
2523         if (cal == NULL) {
2524                 return;
2525         }
2526
2527         ical_fixed_output_backend(cal, 0);
2528
2529         /* Free the memory we obtained from libical's constructor */
2530         icalcomponent_free(cal);
2531 }
2532
2533
2534 /*
2535  * Register this module with the Citadel server.
2536  */
2537 CTDL_MODULE_INIT(calendar)
2538 {
2539         if (!threading)
2540         {
2541
2542                 /* Tell libical to return errors instead of aborting if it gets bad data */
2543
2544 #ifdef LIBICAL_ICAL_EXPORT      // cheap and sleazy way to detect libical >=2.0
2545                 icalerror_set_errors_are_fatal(0);
2546 #else
2547                 icalerror_errors_are_fatal = 0;
2548 #endif
2549
2550                 /* Use our own application prefix in tzid's generated from system tzdata */
2551                 icaltimezone_set_tzid_prefix("/citadel.org/");
2552
2553                 /* Initialize our hook functions */
2554                 CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
2555                 CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
2556                 CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN, PRIO_LOGIN + 1);
2557                 CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCalendar commands");
2558                 CtdlRegisterSessionHook(ical_session_startup, EVT_START, PRIO_START + 1);
2559                 CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP, PRIO_STOP + 80);
2560                 CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
2561                 CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output);
2562         }
2563
2564         /* return our module name for the log */
2565         return "calendar";
2566 }