]> code.citadel.org Git - citadel.git/blob - citadel/serv_calendar.c
* Fixed a bug in the allocation of per-session dynamic data for calendar module
[citadel.git] / citadel / serv_calendar.c
1 /* 
2  * $Id$ 
3  *
4  * This module implements iCalendar object processing and the Calendar>
5  * room on a Citadel/UX server.  It handles iCalendar objects using the
6  * iTIP protocol.  See RFCs 2445 and 2446.
7  *
8  */
9
10 #define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
11
12 #include "sysdep.h"
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <limits.h>
16 #include <stdio.h>
17 #include <string.h>
18 #ifdef HAVE_STRINGS_H
19 #include <strings.h>
20 #endif
21 #include "serv_calendar.h"
22 #include "citadel.h"
23 #include "server.h"
24 #include "citserver.h"
25 #include "sysdep_decls.h"
26 #include "support.h"
27 #include "config.h"
28 #include "dynloader.h"
29 #include "user_ops.h"
30 #include "room_ops.h"
31 #include "tools.h"
32 #include "msgbase.h"
33 #include "mime_parser.h"
34
35 #ifdef HAVE_ICAL_H
36
37 #include <ical.h>
38
39 struct ical_respond_data {
40         char desired_partnum[SIZ];
41         icalcomponent *cal;
42 };
43
44 /* Session-local data for calendaring. */
45 long SYM_CIT_ICAL;
46
47 /*
48  * Write a calendar object into the specified user's calendar room.
49  */
50 void ical_write_to_cal(struct usersupp *u, icalcomponent *cal) {
51         char temp[PATH_MAX];
52         FILE *fp;
53         char *ser;
54
55         strcpy(temp, tmpnam(NULL));
56         ser = icalcomponent_as_ical_string(cal);
57         if (ser == NULL) return;
58
59         /* Make a temp file out of it */
60         fp = fopen(temp, "w");
61         if (fp == NULL) return;
62         fwrite(ser, strlen(ser), 1, fp);
63         fclose(fp);
64
65         /* This handy API function does all the work for us.
66          */
67         CtdlWriteObject(USERCALENDARROOM,       /* which room */
68                         "text/calendar",        /* MIME type */
69                         temp,                   /* temp file */
70                         u,                      /* which user */
71                         0,                      /* not binary */
72                         0,              /* don't delete others of this type */
73                         0);                     /* no flags */
74
75         unlink(temp);
76 }
77
78
79 /*
80  * Add a calendar object to the user's calendar
81  */
82 void ical_add(icalcomponent *cal, int recursion_level) {
83         icalcomponent *c;
84
85         /*
86          * The VEVENT subcomponent is the one we're interested in saving.
87          */
88         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
89         
90                 ical_write_to_cal(&CC->usersupp, cal);
91
92         }
93
94         /* If the component has subcomponents, recurse through them. */
95         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
96             (c != 0);
97             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
98                 /* Recursively process subcomponent */
99                 ical_add(c, recursion_level+1);
100         }
101
102 }
103
104
105
106 /*
107  * Send a reply to a meeting invitation.
108  *
109  * 'request' is the invitation to reply to.
110  * 'action' is the string "accept" or "decline".
111  *
112  * (Sorry about this being more than 80 columns ... there was just
113  * no easy way to break it down sensibly.)
114  */
115 void ical_send_a_reply(icalcomponent *request, char *action) {
116         icalcomponent *the_reply = NULL;
117         icalcomponent *vevent = NULL;
118         icalproperty *attendee = NULL;
119         char attendee_string[SIZ];
120         icalproperty *organizer = NULL;
121         char organizer_string[SIZ];
122         icalproperty *summary = NULL;
123         char summary_string[SIZ];
124         icalproperty *me_attend = NULL;
125         struct recptypes *recp = NULL;
126         icalparameter *partstat = NULL;
127         char *serialized_reply = NULL;
128         char *reply_message_text = NULL;
129         struct CtdlMessage *msg = NULL;
130         struct recptypes *valid = NULL;
131
132         strcpy(organizer_string, "");
133         strcpy(summary_string, "Calendar item");
134
135         if (request == NULL) {
136                 lprintf(3, "ERROR: trying to reply to NULL event?\n");
137                 return;
138         }
139
140         the_reply = icalcomponent_new_clone(request);
141         if (the_reply == NULL) {
142                 lprintf(3, "ERROR: cannot clone request\n");
143                 return;
144         }
145
146         /* Change the method from REQUEST to REPLY */
147         icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
148
149         vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
150         if (vevent != NULL) {
151                 /* Hunt for attendees, removing ones that aren't us.
152                  * (Actually, remove them all, cloning our own one so we can
153                  * re-insert it later)
154                  */
155                 while (attendee = icalcomponent_get_first_property(vevent,
156                     ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
157                 ) {
158                         if (icalproperty_get_attendee(attendee)) {
159                                 strcpy(attendee_string,
160                                         icalproperty_get_attendee(attendee) );
161                                 if (!strncasecmp(attendee_string, "MAILTO:", 7)) {
162                                         strcpy(attendee_string, &attendee_string[7]);
163                                         striplt(attendee_string);
164                                         recp = validate_recipients(attendee_string);
165                                         if (recp != NULL) {
166                                                 if (!strcasecmp(recp->recp_local, CC->usersupp.fullname)) {
167                                                         if (me_attend) icalproperty_free(me_attend);
168                                                         me_attend = icalproperty_new_clone(attendee);
169                                                 }
170                                                 phree(recp);
171                                         }
172                                 }
173                         }
174                         /* Remove it... */
175                         icalcomponent_remove_property(vevent, attendee);
176                         icalproperty_free(attendee);
177                 }
178
179                 /* We found our own address in the attendee list. */
180                 if (me_attend) {
181                         /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
182                         icalproperty_remove_parameter(me_attend, ICAL_PARTSTAT_PARAMETER);
183
184                         if (!strcasecmp(action, "accept")) {
185                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
186                         }
187                         else if (!strcasecmp(action, "decline")) {
188                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
189                         }
190                         else if (!strcasecmp(action, "tentative")) {
191                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
192                         }
193
194                         if (partstat) icalproperty_add_parameter(me_attend, partstat);
195
196                         /* Now insert it back into the vevent. */
197                         icalcomponent_add_property(vevent, me_attend);
198                 }
199
200                 /* Figure out who to send this thing to */
201                 organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
202                 if (organizer != NULL) {
203                         if (icalproperty_get_organizer(organizer)) {
204                                 strcpy(organizer_string,
205                                         icalproperty_get_organizer(organizer) );
206                         }
207                 }
208                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
209                         strcpy(organizer_string, &organizer_string[7]);
210                         striplt(organizer_string);
211                 } else {
212                         strcpy(organizer_string, "");
213                 }
214
215                 /* Extract the summary string -- we'll use it as the
216                  * message subject for the reply
217                  */
218                 summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
219                 if (summary != NULL) {
220                         if (icalproperty_get_summary(summary)) {
221                                 strcpy(summary_string,
222                                         icalproperty_get_summary(summary) );
223                         }
224                 }
225
226         }
227
228         /* Now generate the reply message and send it out. */
229         serialized_reply = strdoop(icalcomponent_as_ical_string(the_reply));
230         icalcomponent_free(the_reply);  /* don't need this anymore */
231         if (serialized_reply == NULL) return;
232
233         reply_message_text = mallok(strlen(serialized_reply) + SIZ);
234         if (reply_message_text != NULL) {
235                 sprintf(reply_message_text,
236                         "Content-type: text/calendar\r\n\r\n%s\r\n",
237                         serialized_reply
238                 );
239
240                 msg = CtdlMakeMessage(&CC->usersupp, organizer_string,
241                         CC->quickroom.QRname, 0, FMT_RFC822,
242                         "",
243                         summary_string,         /* Use summary for subject */
244                         reply_message_text);
245         
246                 if (msg != NULL) {
247                         valid = validate_recipients(organizer_string);
248                         CtdlSubmitMsg(msg, valid, "");
249                         CtdlFreeMessage(msg);
250                 }
251         }
252         phree(serialized_reply);
253 }
254
255
256
257 /*
258  * Callback function for mime parser that hunts for calendar content types
259  * and turns them into calendar objects
260  */
261 void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
262                 void *content, char *cbtype, size_t length, char *encoding,
263                 void *cbuserdata) {
264
265         struct ical_respond_data *ird = NULL;
266
267         ird = (struct ical_respond_data *) cbuserdata;
268         if (ird->cal != NULL) {
269                 icalcomponent_free(ird->cal);
270                 ird->cal = NULL;
271         }
272         if (strcasecmp(partnum, ird->desired_partnum)) return;
273         ird->cal = icalcomponent_new_from_string(content);
274 }
275
276
277 /*
278  * Respond to a meeting request.
279  */
280 void ical_respond(long msgnum, char *partnum, char *action) {
281         struct CtdlMessage *msg;
282         struct ical_respond_data ird;
283
284         if (
285            (strcasecmp(action, "accept"))
286            && (strcasecmp(action, "decline"))
287         ) {
288                 cprintf("%d Action must be 'accept' or 'decline'\n",
289                         ERROR + ILLEGAL_VALUE
290                 );
291                 return;
292         }
293
294         msg = CtdlFetchMessage(msgnum);
295         if (msg == NULL) {
296                 cprintf("%d Message %ld not found.\n",
297                         ERROR+ILLEGAL_VALUE,
298                         (long)msgnum
299                 );
300                 return;
301         }
302
303         memset(&ird, 0, sizeof ird);
304         strcpy(ird.desired_partnum, partnum);
305         mime_parser(msg->cm_fields['M'],
306                 NULL,
307                 *ical_locate_part,              /* callback function */
308                 NULL, NULL,
309                 (void *) &ird,                  /* user data */
310                 0
311         );
312
313         /* We're done with the incoming message, because we now have a
314          * calendar object in memory.
315          */
316         CtdlFreeMessage(msg);
317
318         /*
319          * Here is the real meat of this function.  Handle the event.
320          */
321         if (ird.cal != NULL) {
322                 /* Save this in the user's calendar if necessary */
323                 if (!strcasecmp(action, "accept")) {
324                         ical_add(ird.cal, 0);
325                 }
326
327                 /* Send a reply if necessary */
328                 if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
329                         ical_send_a_reply(ird.cal, action);
330                 }
331
332                 /* Now that we've processed this message, we don't need it
333                  * anymore.  So delete it.
334                  */
335                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
336
337                 /* Free the memory we allocated and return a response. */
338                 icalcomponent_free(ird.cal);
339                 ird.cal = NULL;
340                 cprintf("%d ok\n", CIT_OK);
341                 return;
342         }
343         else {
344                 cprintf("%d No calendar object found\n", ERROR);
345                 return;
346         }
347
348         /* should never get here */
349 }
350
351
352 /*
353  * Figure out the UID of the calendar event being referred to in a
354  * REPLY object.  This function is recursive.
355  */
356 void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
357         icalcomponent *subcomponent;
358         icalproperty *p;
359
360         /* If this object is a REPLY, then extract the UID. */
361         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
362                 p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
363                 if (p != NULL) {
364                         strcpy(uidbuf, icalproperty_get_comment(p));
365                 }
366         }
367
368         /* Otherwise, recurse through any VEVENT subcomponents.  We do NOT want the
369          * UID of the reply; we want the UID of the invitation being replied to.
370          */
371         for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
372             subcomponent != NULL;
373             subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
374                 ical_learn_uid_of_reply(uidbuf, subcomponent);
375         }
376 }
377
378
379 /*
380  * ical_update_my_calendar_with_reply() refers to this callback function; when we
381  * locate the message containing the calendar event we're replying to, this function
382  * gets called.  It basically just sticks the message number in a supplied buffer.
383  */
384 void ical_hunt_for_event_to_update(long msgnum, void *data) {
385         long *msgnumptr;
386
387         msgnumptr = (long *) data;
388         *msgnumptr = msgnum;
389 }
390
391
392 struct original_event_container {
393         icalcomponent *c;
394 };
395
396 /*
397  * Callback function for mime parser that hunts for calendar content types
398  * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
399  * to fetch the object being updated)
400  */
401 void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
402                 void *content, char *cbtype, size_t length, char *encoding,
403                 void *cbuserdata) {
404
405         struct original_event_container *oec = NULL;
406
407         if (strcasecmp(cbtype, "text/calendar")) {
408                 return;
409         }
410         oec = (struct original_event_container *) cbuserdata;
411         if (oec->c != NULL) {
412                 icalcomponent_free(oec->c);
413         }
414         oec->c = icalcomponent_new_from_string(content);
415 }
416
417
418 /*
419  * Merge updated attendee information from a REPLY into an existing event.
420  */
421 void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
422         icalcomponent *c;
423         icalproperty *e_attendee, *r_attendee;
424
425         /* First things first.  If we're not looking at a VEVENT component,
426          * recurse through subcomponents until we find one.
427          */
428         if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
429                 for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
430                     c != NULL;
431                     c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
432                         ical_merge_attendee_reply(c, reply);
433                 }
434                 return;
435         }
436
437         /* Now do the same thing with the reply.
438          */
439         if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
440                 for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
441                     c != NULL;
442                     c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
443                         ical_merge_attendee_reply(event, c);
444                 }
445                 return;
446         }
447
448         /* Clone the reply, because we're going to rip its guts out. */
449         reply = icalcomponent_new_clone(reply);
450
451         /* At this point we're looking at the correct subcomponents.
452          * Iterate through the attendees looking for a match.
453          */
454 STARTOVER:
455         for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
456             e_attendee != NULL;
457             e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
458
459                 for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
460                     r_attendee != NULL;
461                     r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
462
463                         /* Check to see if these two attendees match...
464                          */
465                         if (!strcasecmp(
466                            icalproperty_get_attendee(e_attendee),
467                            icalproperty_get_attendee(r_attendee)
468                         )) {
469                                 /* ...and if they do, remove the attendee from the event
470                                  * and replace it with the attendee from the reply.  (The
471                                  * reply's copy will have the same address, but an updated
472                                  * status.)
473                                  */
474                                 TRACE;
475                                 icalcomponent_remove_property(event, e_attendee);
476                                 TRACE;
477                                 icalproperty_free(e_attendee);
478                                 TRACE;
479                                 icalcomponent_remove_property(reply, r_attendee);
480                                 TRACE;
481                                 icalcomponent_add_property(event, r_attendee);
482                                 TRACE;
483
484                                 /* Since we diddled both sets of attendees, we have to start
485                                  * the iteration over again.  This will not create an infinite
486                                  * loop because we removed the attendee from the reply.  (That's
487                                  * why we cloned the reply, and that's what we mean by "ripping
488                                  * its guts out.")
489                                  */
490                                 goto STARTOVER;
491                         }
492         
493                 }
494         }
495
496         /* Free the *clone* of the reply. */
497         icalcomponent_free(reply);
498 }
499
500
501
502
503 /*
504  * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
505  * calendar event.  The object has already been deserialized for us; all
506  * we have to do here is hunt for the event in our calendar, merge in the
507  * updated attendee status, and save it again.
508  *
509  * This function returns 0 on success, 1 if the event was not found in the
510  * user's calendar, or 2 if an internal error occurred.
511  */
512 int ical_update_my_calendar_with_reply(icalcomponent *cal) {
513         char uid[SIZ];
514         char hold_rm[ROOMNAMELEN];
515         long msgnum_being_replaced = 0;
516         struct CtdlMessage *template = NULL;
517         struct CtdlMessage *msg;
518         struct original_event_container oec;
519         icalcomponent *original_event;
520         char *serialized_event = NULL;
521         char roomname[ROOMNAMELEN];
522         char *message_text = NULL;
523
524         /* Figure out just what event it is we're dealing with */
525         strcpy(uid, "--==<< InVaLiD uId >>==--");
526         ical_learn_uid_of_reply(uid, cal);
527         lprintf(9, "UID of event being replied to is <%s>\n", uid);
528
529         strcpy(hold_rm, CC->quickroom.QRname);  /* save current room */
530
531         if (getroom(&CC->quickroom, USERCALENDARROOM) != 0) {
532                 getroom(&CC->quickroom, hold_rm);
533                 lprintf(3, "cannot get user calendar room\n");
534                 return(2);
535         }
536
537         /*
538          * Pound through the user's calendar looking for a message with
539          * the Citadel EUID set to the value we're looking for.  Since
540          * Citadel always sets the message EUID to the vCalendar UID of
541          * the event, this will work.
542          */
543         template = (struct CtdlMessage *)
544                 mallok(sizeof(struct CtdlMessage));
545         memset(template, 0, sizeof(struct CtdlMessage));
546         template->cm_fields['E'] = strdoop(uid);
547         CtdlForEachMessage(MSGS_ALL, 0, "text/calendar",
548                 template, ical_hunt_for_event_to_update, &msgnum_being_replaced);
549         CtdlFreeMessage(template);
550         getroom(&CC->quickroom, hold_rm);       /* return to saved room */
551
552         lprintf(9, "msgnum_being_replaced == %ld\n", msgnum_being_replaced);
553         if (msgnum_being_replaced == 0) {
554                 return(1);                      /* no calendar event found */
555         }
556
557         /* Now we know the ID of the message containing the event being updated.
558          * We don't actually have to delete it; that'll get taken care of by the
559          * server when we save another event with the same UID.  This just gives
560          * us the ability to load the event into memory so we can diddle the
561          * attendees.
562          */
563         msg = CtdlFetchMessage(msgnum_being_replaced);
564         if (msg == NULL) {
565                 return(2);                      /* internal error */
566         }
567         oec.c = NULL;
568         mime_parser(msg->cm_fields['M'],
569                 NULL,
570                 *ical_locate_original_event,    /* callback function */
571                 NULL, NULL,
572                 &oec,                           /* user data */
573                 0
574         );
575         CtdlFreeMessage(msg);
576
577         original_event = oec.c;
578         if (original_event == NULL) {
579                 lprintf(3, "ERROR: Original_component is NULL.\n");
580                 return(2);
581         }
582
583         /* Merge the attendee's updated status into the event */
584         ical_merge_attendee_reply(original_event, cal);
585
586         /* Serialize it */
587         serialized_event = strdoop(icalcomponent_as_ical_string(original_event));
588         icalcomponent_free(original_event);     /* Don't need this anymore. */
589         if (serialized_event == NULL) return(2);
590
591         MailboxName(roomname, sizeof roomname, &CC->usersupp, USERCALENDARROOM);
592
593         message_text = mallok(strlen(serialized_event) + SIZ);
594         if (message_text != NULL) {
595                 sprintf(message_text,
596                         "Content-type: text/calendar\r\n\r\n%s\r\n",
597                         serialized_event
598                 );
599
600                 msg = CtdlMakeMessage(&CC->usersupp,
601                         "",                     /* No recipient */
602                         roomname,
603                         0, FMT_RFC822,
604                         "",
605                         "",             /* no subject */
606                         message_text);
607         
608                 if (msg != NULL) {
609                         CIT_ICAL->avoid_sending_invitations = 1;
610                         CtdlSubmitMsg(msg, NULL, roomname);
611                         CtdlFreeMessage(msg);
612                         CIT_ICAL->avoid_sending_invitations = 0;
613                 }
614         }
615         phree(serialized_event);
616         return(0);
617 }
618
619
620 /*
621  * Handle an incoming RSVP for an event.  (This is the server subcommand part; it
622  * simply extracts the calendar object from the message, deserializes it, and
623  * passes it up to ical_update_my_calendar_with_reply() for processing.
624  */
625 void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
626         struct CtdlMessage *msg;
627         struct ical_respond_data ird;
628         int ret;
629
630         if (
631            (strcasecmp(action, "update"))
632            && (strcasecmp(action, "ignore"))
633         ) {
634                 cprintf("%d Action must be 'update' or 'ignore'\n",
635                         ERROR + ILLEGAL_VALUE
636                 );
637                 return;
638         }
639
640         msg = CtdlFetchMessage(msgnum);
641         if (msg == NULL) {
642                 cprintf("%d Message %ld not found.\n",
643                         ERROR+ILLEGAL_VALUE,
644                         (long)msgnum
645                 );
646                 return;
647         }
648
649         memset(&ird, 0, sizeof ird);
650         strcpy(ird.desired_partnum, partnum);
651         mime_parser(msg->cm_fields['M'],
652                 NULL,
653                 *ical_locate_part,              /* callback function */
654                 NULL, NULL,
655                 (void *) &ird,                  /* user data */
656                 0
657         );
658
659         /* We're done with the incoming message, because we now have a
660          * calendar object in memory.
661          */
662         CtdlFreeMessage(msg);
663
664         /*
665          * Here is the real meat of this function.  Handle the event.
666          */
667         if (ird.cal != NULL) {
668                 /* Update the user's calendar if necessary */
669                 if (!strcasecmp(action, "update")) {
670                         ret = ical_update_my_calendar_with_reply(ird.cal);
671                         if (ret == 0) {
672                                 cprintf("%d Your calendar has been updated with this reply.\n",
673                                         CIT_OK);
674                         }
675                         else if (ret == 1) {
676                                 cprintf("%d This event does not exist in your calendar.\n",
677                                         ERROR + FILE_NOT_FOUND);
678                         }
679                         else {
680                                 cprintf("%d An internal error occurred.\n",
681                                         ERROR + INTERNAL_ERROR);
682                         }
683                 }
684                 else {
685                         cprintf("%d This reply has been ignored.\n", CIT_OK);
686                 }
687
688                 /* Now that we've processed this message, we don't need it
689                  * anymore.  So delete it.  FIXME uncomment this when ready!
690                  */
691                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
692
693                 /* Free the memory we allocated and return a response. */
694                 icalcomponent_free(ird.cal);
695                 ird.cal = NULL;
696                 return;
697         }
698         else {
699                 cprintf("%d No calendar object found\n", ERROR);
700                 return;
701         }
702
703         /* should never get here */
704 }
705
706
707 /*
708  * Search for a property in both the top level and in a VEVENT subcomponent
709  */
710 icalproperty *ical_ctdl_get_subprop(
711                 icalcomponent *cal,
712                 icalproperty_kind which_prop
713 ) {
714         icalproperty *p;
715         icalcomponent *c;
716
717         p = icalcomponent_get_first_property(cal, which_prop);
718         if (p == NULL) {
719                 c = icalcomponent_get_first_component(cal,
720                                                         ICAL_VEVENT_COMPONENT);
721                 if (c != NULL) {
722                         p = icalcomponent_get_first_property(c, which_prop);
723                 }
724         }
725         return p;
726 }
727
728
729 /*
730  * Check to see if two events overlap.  Returns nonzero if they do.
731  */
732 int ical_ctdl_is_overlap(
733                         struct icaltimetype t1start,
734                         struct icaltimetype t1end,
735                         struct icaltimetype t2start,
736                         struct icaltimetype t2end
737 ) {
738
739         if (icaltime_is_null_time(t1start)) return(0);
740         if (icaltime_is_null_time(t2start)) return(0);
741
742         /* First, check for all-day events */
743         if (t1start.is_date) {
744                 if (!icaltime_compare_date_only(t1start, t2start)) {
745                         return(1);
746                 }
747                 if (!icaltime_is_null_time(t2end)) {
748                         if (!icaltime_compare_date_only(t1start, t2end)) {
749                                 return(1);
750                         }
751                 }
752         }
753
754         if (t2start.is_date) {
755                 if (!icaltime_compare_date_only(t2start, t1start)) {
756                         return(1);
757                 }
758                 if (!icaltime_is_null_time(t1end)) {
759                         if (!icaltime_compare_date_only(t2start, t1end)) {
760                                 return(1);
761                         }
762                 }
763         }
764
765         /* Now check for overlaps using date *and* time. */
766
767         /* First, bail out if either event 1 or event 2 is missing end time. */
768         if (icaltime_is_null_time(t1end)) return(0);
769         if (icaltime_is_null_time(t2end)) return(0);
770
771         /* If event 1 ends before event 2 starts, we're in the clear. */
772         if (icaltime_compare(t1end, t2start) <= 0) return(0);
773
774         /* If event 2 ends before event 1 starts, we're also ok. */
775         if (icaltime_compare(t2end, t1start) <= 0) return(0);
776
777         /* Otherwise, they overlap. */
778         return(1);
779 }
780
781
782
783 /*
784  * Backend for ical_hunt_for_conflicts()
785  */
786 void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
787         icalcomponent *cal;
788         struct CtdlMessage *msg;
789         struct ical_respond_data ird;
790         struct icaltimetype t1start, t1end, t2start, t2end;
791         icalproperty *p;
792         char conflict_event_uid[SIZ];
793         char conflict_event_summary[SIZ];
794         char compare_uid[SIZ];
795
796         cal = (icalcomponent *)data;
797         strcpy(compare_uid, "");
798         strcpy(conflict_event_uid, "");
799         strcpy(conflict_event_summary, "");
800
801         msg = CtdlFetchMessage(msgnum);
802         if (msg == NULL) return;
803         memset(&ird, 0, sizeof ird);
804         strcpy(ird.desired_partnum, "1");       /* hopefully it's always 1 */
805         mime_parser(msg->cm_fields['M'],
806                 NULL,
807                 *ical_locate_part,              /* callback function */
808                 NULL, NULL,
809                 (void *) &ird,                  /* user data */
810                 0
811         );
812         CtdlFreeMessage(msg);
813
814         if (ird.cal == NULL) return;
815
816         t1start = icaltime_null_time();
817         t1end = icaltime_null_time();
818         t2start = icaltime_null_time();
819         t1end = icaltime_null_time();
820
821         /* Now compare cal to ird.cal */
822         p = ical_ctdl_get_subprop(ird.cal, ICAL_DTSTART_PROPERTY);
823         if (p == NULL) return;
824         if (p != NULL) t2start = icalproperty_get_dtstart(p);
825         
826         p = ical_ctdl_get_subprop(ird.cal, ICAL_DTEND_PROPERTY);
827         if (p != NULL) t2end = icalproperty_get_dtend(p);
828
829         p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
830         if (p == NULL) return;
831         if (p != NULL) t1start = icalproperty_get_dtstart(p);
832         
833         p = ical_ctdl_get_subprop(cal, ICAL_DTEND_PROPERTY);
834         if (p != NULL) t1end = icalproperty_get_dtend(p);
835         
836         p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
837         if (p != NULL) {
838                 strcpy(compare_uid, icalproperty_get_comment(p));
839         }
840
841         p = ical_ctdl_get_subprop(ird.cal, ICAL_UID_PROPERTY);
842         if (p != NULL) {
843                 strcpy(conflict_event_uid, icalproperty_get_comment(p));
844         }
845
846         p = ical_ctdl_get_subprop(ird.cal, ICAL_SUMMARY_PROPERTY);
847         if (p != NULL) {
848                 strcpy(conflict_event_summary, icalproperty_get_comment(p));
849         }
850
851
852         icalcomponent_free(ird.cal);
853
854         if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
855                 cprintf("%ld||%s|%s|%d|\n",
856                         msgnum,
857                         conflict_event_uid,
858                         conflict_event_summary,
859                         (       ((strlen(compare_uid)>0)
860                                 &&(!strcasecmp(compare_uid,
861                                 conflict_event_uid))) ? 1 : 0
862                         )
863                 );
864         }
865 }
866
867
868
869 /* 
870  * Phase 2 of "hunt for conflicts" operation.
871  * At this point we have a calendar object which represents the VEVENT that
872  * we're considering adding to the calendar.  Now hunt through the user's
873  * calendar room, and output zero or more existing VEVENTs which conflict
874  * with this one.
875  */
876 void ical_hunt_for_conflicts(icalcomponent *cal) {
877         char hold_rm[ROOMNAMELEN];
878
879         strcpy(hold_rm, CC->quickroom.QRname);  /* save current room */
880
881         if (getroom(&CC->quickroom, USERCALENDARROOM) != 0) {
882                 getroom(&CC->quickroom, hold_rm);
883                 cprintf("%d You do not have a calendar.\n", ERROR);
884                 return;
885         }
886
887         cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
888
889         CtdlForEachMessage(MSGS_ALL, 0, "text/calendar",
890                 NULL,
891                 ical_hunt_for_conflicts_backend,
892                 (void *) cal
893         );
894
895         cprintf("000\n");
896         getroom(&CC->quickroom, hold_rm);       /* return to saved room */
897
898 }
899
900
901
902 /*
903  * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
904  */
905 void ical_conflicts(long msgnum, char *partnum) {
906         struct CtdlMessage *msg;
907         struct ical_respond_data ird;
908
909         msg = CtdlFetchMessage(msgnum);
910         if (msg == NULL) {
911                 cprintf("%d Message %ld not found.\n",
912                         ERROR+ILLEGAL_VALUE,
913                         (long)msgnum
914                 );
915                 return;
916         }
917
918         memset(&ird, 0, sizeof ird);
919         strcpy(ird.desired_partnum, partnum);
920         mime_parser(msg->cm_fields['M'],
921                 NULL,
922                 *ical_locate_part,              /* callback function */
923                 NULL, NULL,
924                 (void *) &ird,                  /* user data */
925                 0
926         );
927
928         CtdlFreeMessage(msg);
929
930         if (ird.cal != NULL) {
931                 ical_hunt_for_conflicts(ird.cal);
932                 icalcomponent_free(ird.cal);
933                 return;
934         }
935         else {
936                 cprintf("%d No calendar object found\n", ERROR);
937                 return;
938         }
939
940         /* should never get here */
941 }
942
943
944
945
946 /*
947  * All Citadel calendar commands from the client come through here.
948  */
949 void cmd_ical(char *argbuf)
950 {
951         char subcmd[SIZ];
952         long msgnum;
953         char partnum[SIZ];
954         char action[SIZ];
955
956         if (CtdlAccessCheck(ac_logged_in)) return;
957
958         extract(subcmd, argbuf, 0);
959
960         if (!strcmp(subcmd, "test")) {
961                 cprintf("%d This server supports calendaring\n", CIT_OK);
962                 return;
963         }
964
965         else if (!strcmp(subcmd, "respond")) {
966                 msgnum = extract_long(argbuf, 1);
967                 extract(partnum, argbuf, 2);
968                 extract(action, argbuf, 3);
969                 ical_respond(msgnum, partnum, action);
970         }
971
972         else if (!strcmp(subcmd, "handle_rsvp")) {
973                 msgnum = extract_long(argbuf, 1);
974                 extract(partnum, argbuf, 2);
975                 extract(action, argbuf, 3);
976                 ical_handle_rsvp(msgnum, partnum, action);
977         }
978
979         else if (!strcmp(subcmd, "conflicts")) {
980                 msgnum = extract_long(argbuf, 1);
981                 extract(partnum, argbuf, 2);
982                 ical_conflicts(msgnum, partnum);
983         }
984
985         else {
986                 cprintf("%d Invalid subcommand\n", ERROR+CMD_NOT_SUPPORTED);
987                 return;
988         }
989
990         /* should never get here */
991 }
992
993
994
995 /*
996  * We don't know if the calendar room exists so we just create it at login
997  */
998 void ical_create_room(void)
999 {
1000         struct quickroom qr;
1001         struct visit vbuf;
1002
1003         /* Create the calendar room if it doesn't already exist */
1004         create_room(USERCALENDARROOM, 4, "", 0, 1, 0);
1005
1006         /* Set expiration policy to manual; otherwise objects will be lost! */
1007         if (lgetroom(&qr, USERCALENDARROOM)) {
1008                 lprintf(3, "Couldn't get the user calendar room!\n");
1009                 return;
1010         }
1011         qr.QRep.expire_mode = EXPIRE_MANUAL;
1012         lputroom(&qr);
1013
1014         /* Set the view to a calendar view */
1015         CtdlGetRelationship(&vbuf, &CC->usersupp, &qr);
1016         vbuf.v_view = 3;        /* 3 = calendar */
1017         CtdlSetRelationship(&vbuf, &CC->usersupp, &qr);
1018
1019         /* Create the tasks list room if it doesn't already exist */
1020         create_room(USERTASKSROOM, 4, "", 0, 1, 0);
1021
1022         /* Set expiration policy to manual; otherwise objects will be lost! */
1023         if (lgetroom(&qr, USERTASKSROOM)) {
1024                 lprintf(3, "Couldn't get the user calendar room!\n");
1025                 return;
1026         }
1027         qr.QRep.expire_mode = EXPIRE_MANUAL;
1028         lputroom(&qr);
1029
1030         /* Set the view to a task list view */
1031         CtdlGetRelationship(&vbuf, &CC->usersupp, &qr);
1032         vbuf.v_view = 4;        /* 4 = tasks */
1033         CtdlSetRelationship(&vbuf, &CC->usersupp, &qr);
1034
1035         return;
1036 }
1037
1038
1039 /*
1040  * ical_send_out_invitations() is called by ical_saving_vevent() when it
1041  * finds a VEVENT.   FIXME ... finish implementing.
1042  */
1043 void ical_send_out_invitations(icalcomponent *cal) {
1044         icalcomponent *the_request = NULL;
1045         char *serialized_request = NULL;
1046         char *request_message_text = NULL;
1047         struct CtdlMessage *msg = NULL;
1048         struct recptypes *valid = NULL;
1049         char attendees_string[SIZ];
1050         char this_attendee[SIZ];
1051         icalproperty *attendee = NULL;
1052         char summary_string[SIZ];
1053         icalproperty *summary = NULL;
1054         icalcomponent *encaps = NULL;
1055
1056         if (cal == NULL) {
1057                 lprintf(3, "ERROR: trying to reply to NULL event?\n");
1058                 return;
1059         }
1060
1061         /* Clone the event */
1062         the_request = icalcomponent_new_clone(cal);
1063         if (the_request == NULL) {
1064                 lprintf(3, "ERROR: cannot clone calendar object\n");
1065                 return;
1066         }
1067
1068         /* Extract the summary string -- we'll use it as the
1069          * message subject for the request
1070          */
1071         strcpy(summary_string, "Meeting request");
1072         summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
1073         if (summary != NULL) {
1074                 if (icalproperty_get_summary(summary)) {
1075                         strcpy(summary_string,
1076                                 icalproperty_get_summary(summary) );
1077                 }
1078         }
1079
1080         /* Determine who the recipients of this message are (the attendees) */
1081         strcpy(attendees_string, "");
1082         for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
1083                 if (icalproperty_get_attendee(attendee)) {
1084                         strcpy(this_attendee, icalproperty_get_attendee(attendee) );
1085                         if (!strncasecmp(this_attendee, "MAILTO:", 7)) {
1086                                 strcpy(this_attendee, &this_attendee[7]);
1087                                 snprintf(&attendees_string[strlen(attendees_string)],
1088                                         sizeof(attendees_string) - strlen(attendees_string),
1089                                         "%s, ",
1090                                         this_attendee
1091                                 );
1092                         }
1093                 }
1094         }
1095
1096         lprintf(9, "attendees_string: <%s>\n", attendees_string);
1097
1098         /* Encapsulate the VEVENT component into a complete VCALENDAR */
1099         encaps = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
1100         if (encaps == NULL) {
1101                 lprintf(3, "Error at %s:%d - could not allocate component!\n",
1102                         __FILE__, __LINE__);
1103                 icalcomponent_free(the_request);
1104                 return;
1105         }
1106
1107         /* Set the Product ID */
1108         icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
1109
1110         /* Set the Version Number */
1111         icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
1112
1113         /* Set the method to REQUEST */
1114         icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
1115
1116         /* FIXME: here we need to insert a VTIMEZONE object. */
1117
1118         /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
1119          * are responsible for "the_request"'s memory -- it will be freed
1120          * when we free "encaps".
1121          */
1122         icalcomponent_add_component(encaps, the_request);
1123
1124         /* Serialize it */
1125         serialized_request = strdoop(icalcomponent_as_ical_string(encaps));
1126         icalcomponent_free(encaps);     /* Don't need this anymore. */
1127         if (serialized_request == NULL) return;
1128
1129         request_message_text = mallok(strlen(serialized_request) + SIZ);
1130         if (request_message_text != NULL) {
1131                 sprintf(request_message_text,
1132                         "Content-type: text/calendar\r\n\r\n%s\r\n",
1133                         serialized_request
1134                 );
1135
1136                 msg = CtdlMakeMessage(&CC->usersupp,
1137                         "",                     /* No single recipient here */
1138                         CC->quickroom.QRname, 0, FMT_RFC822,
1139                         "",
1140                         summary_string,         /* Use summary for subject */
1141                         request_message_text);
1142         
1143                 if (msg != NULL) {
1144                         valid = validate_recipients(attendees_string);
1145                         CtdlSubmitMsg(msg, valid, "");
1146                         CtdlFreeMessage(msg);
1147                 }
1148         }
1149         phree(serialized_request);
1150 }
1151
1152
1153 /*
1154  * When a calendar object is being saved, determine whether it's a VEVENT
1155  * and the user saving it is the organizer.  If so, send out invitations
1156  * to any listed attendees.
1157  *
1158  */
1159 void ical_saving_vevent(icalcomponent *cal) {
1160         icalcomponent *c;
1161         icalproperty *organizer = NULL;
1162         char organizer_string[SIZ];
1163
1164         /* Don't send out invitations if we've been asked not to. */
1165         lprintf(9, "CIT_ICAL->avoid_sending_invitations = %d\n",
1166                 CIT_ICAL->avoid_sending_invitations);
1167         if (CIT_ICAL->avoid_sending_invitations > 0) {
1168                 return;
1169         }
1170
1171         strcpy(organizer_string, "");
1172         /*
1173          * The VEVENT subcomponent is the one we're interested in.
1174          * Send out invitations if, and only if, this user is the Organizer.
1175          */
1176         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
1177                 organizer = icalcomponent_get_first_property(cal,
1178                                                 ICAL_ORGANIZER_PROPERTY);
1179                 if (organizer != NULL) {
1180                         if (icalproperty_get_organizer(organizer)) {
1181                                 strcpy(organizer_string,
1182                                         icalproperty_get_organizer(organizer));
1183                         }
1184                 }
1185                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
1186                         strcpy(organizer_string, &organizer_string[7]);
1187                         striplt(organizer_string);
1188                         /*
1189                          * If the user saving the event is listed as the
1190                          * organizer, then send out invitations.
1191                          */
1192                         if (CtdlIsMe(organizer_string)) {
1193                                 ical_send_out_invitations(cal);
1194                         }
1195                 }
1196         }
1197
1198         /* If the component has subcomponents, recurse through them. */
1199         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
1200             (c != NULL);
1201             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
1202                 /* Recursively process subcomponent */
1203                 ical_saving_vevent(c);
1204         }
1205
1206 }
1207
1208
1209
1210 /*
1211  * Back end for ical_obj_beforesave()
1212  * This hunts for the UID of the calendar event.
1213  */
1214 void ical_ctdl_set_extended_msgid(char *name, char *filename, char *partnum,
1215                 char *disp, void *content, char *cbtype, size_t length,
1216                 char *encoding, void *cbuserdata)
1217 {
1218         icalcomponent *cal;
1219         icalproperty *p;
1220
1221         /* If this is a text/calendar object, hunt for the UID and drop it in
1222          * the "user data" pointer for the MIME parser.  When
1223          * ical_obj_beforesave() sees it there, it'll set the Extended msgid
1224          * to that string.
1225          */
1226         if (!strcasecmp(cbtype, "text/calendar")) {
1227                 cal = icalcomponent_new_from_string(content);
1228                 if (cal != NULL) {
1229                         p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
1230                         if (p != NULL) {
1231                                 strcpy((char *)cbuserdata,
1232                                         icalproperty_get_comment(p)
1233                                 );
1234                         }
1235                         icalcomponent_free(cal);
1236                 }
1237         }
1238 }
1239
1240
1241
1242
1243
1244 /*
1245  * See if we need to prevent the object from being saved (we don't allow
1246  * MIME types other than text/calendar in the Calendar> room).  Also, when
1247  * saving an event to the calendar, set the message's Citadel extended message
1248  * ID to the UID of the object.  This causes our replication checker to
1249  * automatically delete any existing instances of the same object.  (Isn't
1250  * that cool?)
1251  */
1252 int ical_obj_beforesave(struct CtdlMessage *msg)
1253 {
1254         char roomname[ROOMNAMELEN];
1255         char *p;
1256         int a;
1257         char eidbuf[SIZ];
1258
1259         /*
1260          * Only messages with content-type text/calendar
1261          * may be saved to Calendar>.  If the message is bound for
1262          * Calendar> but doesn't have this content-type, throw an error
1263          * so that the message may not be posted.
1264          */
1265
1266         /* First determine if this is our room */
1267         MailboxName(roomname, sizeof roomname, &CC->usersupp, USERCALENDARROOM);
1268         if (strcasecmp(roomname, CC->quickroom.QRname)) {
1269                 return 0;       /* It's not the Calendar room. */
1270         }
1271
1272         /* Then determine content-type of the message */
1273         
1274         /* It must be an RFC822 message! */
1275         /* FIXME: Not handling MIME multipart messages; implement with IMIP */
1276         if (msg->cm_format_type != 4)
1277                 return 1;       /* You tried to save a non-RFC822 message! */
1278         
1279         /* Find the Content-Type: header */
1280         p = msg->cm_fields['M'];
1281         a = strlen(p);
1282         while (--a > 0) {
1283                 if (!strncasecmp(p, "Content-Type: ", 14)) {    /* Found it */
1284                         if (!strncasecmp(p + 14, "text/calendar", 13)) {
1285                                 strcpy(eidbuf, "");
1286                                 mime_parser(msg->cm_fields['M'],
1287                                         NULL,
1288                                         *ical_ctdl_set_extended_msgid,
1289                                         NULL, NULL,
1290                                         (void *)eidbuf,
1291                                         0
1292                                 );
1293                                 if (strlen(eidbuf) > 0) {
1294                                         if (msg->cm_fields['E'] != NULL) {
1295                                                 phree(msg->cm_fields['E']);
1296                                         }
1297                                         msg->cm_fields['E'] = strdoop(eidbuf);
1298                                 }
1299                                 return 0;
1300                         }
1301                         else {
1302                                 return 1;
1303                         }
1304                 }
1305                 p++;
1306         }
1307         
1308         /* Oops!  No Content-Type in this message!  How'd that happen? */
1309         lprintf(7, "RFC822 message with no Content-Type header!\n");
1310         return 1;
1311 }
1312
1313
1314 /*
1315  * Things we need to do after saving a calendar event.
1316  */
1317 void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
1318                 char *disp, void *content, char *cbtype, size_t length,
1319                 char *encoding, void *cbuserdata)
1320 {
1321         icalcomponent *cal;
1322
1323         /* If this is a text/calendar object, hunt for the UID and drop it in
1324          * the "user data" pointer for the MIME parser.  When
1325          * ical_obj_beforesave() sees it there, it'll set the Extended msgid
1326          * to that string.
1327          */
1328         if (!strcasecmp(cbtype, "text/calendar")) {
1329                 cal = icalcomponent_new_from_string(content);
1330                 if (cal != NULL) {
1331                         ical_saving_vevent(cal);
1332                         icalcomponent_free(cal);
1333                 }
1334         }
1335 }
1336
1337
1338 /* 
1339  * Things we need to do after saving a calendar event.
1340  */
1341 int ical_obj_aftersave(struct CtdlMessage *msg)
1342 {
1343         char roomname[ROOMNAMELEN];
1344         char *p;
1345         int a;
1346
1347         /*
1348          * If this isn't the Calendar> room, no further action is necessary.
1349          */
1350
1351         /* First determine if this is our room */
1352         MailboxName(roomname, sizeof roomname, &CC->usersupp, USERCALENDARROOM);
1353         if (strcasecmp(roomname, CC->quickroom.QRname)) {
1354                 return 0;       /* It's not the Calendar room. */
1355         }
1356
1357         /* Then determine content-type of the message */
1358         
1359         /* It must be an RFC822 message! */
1360         /* FIXME: Not handling MIME multipart messages; implement with IMIP */
1361         if (msg->cm_format_type != 4) return(1);
1362         
1363         /* Find the Content-Type: header */
1364         p = msg->cm_fields['M'];
1365         a = strlen(p);
1366         while (--a > 0) {
1367                 if (!strncasecmp(p, "Content-Type: ", 14)) {    /* Found it */
1368                         if (!strncasecmp(p + 14, "text/calendar", 13)) {
1369                                 mime_parser(msg->cm_fields['M'],
1370                                         NULL,
1371                                         *ical_obj_aftersave_backend,
1372                                         NULL, NULL,
1373                                         NULL,
1374                                         0
1375                                 );
1376                                 return 0;
1377                         }
1378                         else {
1379                                 return 1;
1380                         }
1381                 }
1382                 p++;
1383         }
1384         
1385         /* Oops!  No Content-Type in this message!  How'd that happen? */
1386         lprintf(7, "RFC822 message with no Content-Type header!\n");
1387         return 1;
1388 }
1389
1390
1391 void ical_session_startup(void) {
1392         SYM_CIT_ICAL = CtdlGetDynamicSymbol();
1393         CtdlAllocUserData(SYM_CIT_ICAL, sizeof(struct cit_ical));
1394         memset(CIT_ICAL, 0, sizeof(struct cit_ical));
1395         
1396 }
1397
1398
1399 #endif  /* HAVE_ICAL_H */
1400
1401 /*
1402  * Register this module with the Citadel server.
1403  */
1404 char *Dynamic_Module_Init(void)
1405 {
1406 #ifdef HAVE_ICAL_H
1407         CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
1408         CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
1409         CtdlRegisterSessionHook(ical_create_room, EVT_LOGIN);
1410         CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands");
1411         CtdlRegisterSessionHook(ical_session_startup, EVT_START);
1412 #endif
1413         return "$Id$";
1414 }