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