]> code.citadel.org Git - citadel.git/blob - citadel/serv_calendar.c
* Completed the invitation accept/decline code. It now sends back a
[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 #include "sysdep.h"
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <limits.h>
14 #include <stdio.h>
15 #include <string.h>
16 #ifdef HAVE_STRINGS_H
17 #include <strings.h>
18 #endif
19 #include "serv_calendar.h"
20 #include "citadel.h"
21 #include "server.h"
22 #include "citserver.h"
23 #include "sysdep_decls.h"
24 #include "support.h"
25 #include "config.h"
26 #include "dynloader.h"
27 #include "user_ops.h"
28 #include "room_ops.h"
29 #include "tools.h"
30 #include "msgbase.h"
31 #include "mime_parser.h"
32
33
34 #ifdef HAVE_ICAL_H
35
36 #include <ical.h>
37
38 struct ical_respond_data {
39         char desired_partnum[SIZ];
40         icalcomponent *cal;
41 };
42
43
44 /*
45  * Write a calendar object into the specified user's calendar room.
46  */
47 void ical_write_to_cal(struct usersupp *u, icalcomponent *cal) {
48         char temp[PATH_MAX];
49         FILE *fp;
50         char *ser;
51
52         strcpy(temp, tmpnam(NULL));
53         ser = icalcomponent_as_ical_string(cal);
54         if (ser == NULL) return;
55
56         /* Make a temp file out of it */
57         fp = fopen(temp, "w");
58         if (fp == NULL) return;
59         fwrite(ser, strlen(ser), 1, fp);
60         fclose(fp);
61
62         /* This handy API function does all the work for us.
63          */
64         CtdlWriteObject(USERCALENDARROOM,       /* which room */
65                         "text/calendar",        /* MIME type */
66                         temp,                   /* temp file */
67                         u,                      /* which user */
68                         0,                      /* not binary */
69                         0,              /* don't delete others of this type */
70                         0);                     /* no flags */
71
72         unlink(temp);
73 }
74
75
76 /*
77  * Add a calendar object to the user's calendar
78  */
79 void ical_add(icalcomponent *cal, int recursion_level) {
80         icalcomponent *c;
81
82         /*
83          * The VEVENT subcomponent is the one we're interested in saving.
84          */
85         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
86         
87                 ical_write_to_cal(&CC->usersupp, cal);
88
89         }
90
91         /* If the component has subcomponents, recurse through them. */
92         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
93             (c != 0);
94             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
95                 /* Recursively process subcomponent */
96                 ical_add(c, recursion_level+1);
97         }
98
99 }
100
101
102
103 /*
104  * Send a reply to a meeting invitation.
105  *
106  * 'request' is the invitation to reply to.
107  * 'action' is the string "accept" or "decline".
108  *
109  * (Sorry about this being more than 80 columns ... there was just
110  * no easy way to break it down sensibly.)
111  */
112 void ical_send_a_reply(icalcomponent *request, char *action) {
113         icalcomponent *the_reply = NULL;
114         icalcomponent *vevent = NULL;
115         icalproperty *attendee = NULL;
116         char attendee_string[SIZ];
117         icalproperty *organizer = NULL;
118         char organizer_string[SIZ];
119         icalproperty *me_attend = NULL;
120         struct recptypes *recp = NULL;
121         icalparameter *partstat = NULL;
122         char *serialized_reply = NULL;
123         char *reply_message_text = NULL;
124         struct CtdlMessage *msg = NULL;
125         struct recptypes *valid = NULL;
126
127         strcpy(organizer_string, "");
128
129         if (request == NULL) {
130                 lprintf(3, "ERROR: trying to reply to NULL event?\n");
131                 return;
132         }
133
134         the_reply = icalcomponent_new_clone(request);
135         if (the_reply == NULL) {
136                 lprintf(3, "ERROR: cannot clone request\n");
137                 return;
138         }
139
140         /* Change the method from REQUEST to REPLY */
141         icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
142
143         vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
144         if (vevent != NULL) {
145                 /* Hunt for attendees, removing ones that aren't us.
146                  * (Actually, remove them all, cloning our own one so we can
147                  * re-insert it later)
148                  */
149                 while (attendee = icalcomponent_get_first_property(vevent,
150                     ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
151                 ) {
152                         if (icalproperty_get_attendee(attendee)) {
153                                 strcpy(attendee_string,
154                                         icalproperty_get_attendee(attendee) );
155                                 if (!strncasecmp(attendee_string, "MAILTO:", 7)) {
156                                         strcpy(attendee_string, &attendee_string[7]);
157                                         striplt(attendee_string);
158                                         recp = validate_recipients(attendee_string);
159                                         if (recp != NULL) {
160                                                 if (!strcasecmp(recp->recp_local, CC->usersupp.fullname)) {
161                                                         if (me_attend) icalproperty_free(me_attend);
162                                                         me_attend = icalproperty_new_clone(attendee);
163                                                 }
164                                                 phree(recp);
165                                         }
166                                 }
167                         }
168                         /* Remove it... */
169                         icalcomponent_remove_property(vevent, attendee);
170                 }
171
172                 /* We found our own address in the attendee list. */
173                 if (me_attend) {
174                         /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
175                         icalproperty_remove_parameter(me_attend, ICAL_PARTSTAT_PARAMETER);
176
177                         if (!strcasecmp(action, "accept")) {
178                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
179                         }
180                         else if (!strcasecmp(action, "decline")) {
181                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
182                         }
183                         else if (!strcasecmp(action, "tentative")) {
184                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
185                         }
186
187                         if (partstat) icalproperty_add_parameter(me_attend, partstat);
188
189                         /* Now insert it back into the vevent. */
190                         icalcomponent_add_property(vevent, me_attend);
191                 }
192
193                 /* Figure out who to send this thing to */
194                 organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
195                 if (organizer != NULL) {
196                         if (icalproperty_get_organizer(organizer)) {
197                                 strcpy(organizer_string,
198                                         icalproperty_get_organizer(organizer) );
199                         }
200                 }
201                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
202                         strcpy(organizer_string, &organizer_string[7]);
203                         striplt(organizer_string);
204                 } else {
205                         strcpy(organizer_string, "");
206                 }
207         }
208
209         /* Now generate the reply message and send it out. */
210         serialized_reply = strdoop(icalcomponent_as_ical_string(the_reply));
211         icalcomponent_free(the_reply);  /* don't need this anymore */
212         if (serialized_reply == NULL) return;
213
214         reply_message_text = mallok(strlen(serialized_reply) + SIZ);
215         if (reply_message_text != NULL) {
216                 sprintf(reply_message_text,
217                         "Content-type: text/calendar\r\n\r\n%s\r\n",
218                         serialized_reply
219                 );
220
221                 msg = CtdlMakeMessage(&CC->usersupp, organizer_string,
222                         CC->quickroom.QRname, 0, FMT_RFC822,
223                         "", "FIXME subject", reply_message_text);
224         
225                 if (msg != NULL) {
226                         valid = validate_recipients(organizer_string);
227                         CtdlSubmitMsg(msg, valid, "");
228                         CtdlFreeMessage(msg);
229                 }
230         }
231         phree(serialized_reply);
232 }
233
234
235
236 /*
237  * Callback function for mime parser that hunts for calendar content types
238  * and turns them into calendar objects
239  */
240 void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
241                 void *content, char *cbtype, size_t length, char *encoding,
242                 void *cbuserdata) {
243
244         struct ical_respond_data *ird = NULL;
245
246         ird = (struct ical_respond_data *) cbuserdata;
247         if (ird->cal != NULL) {
248                 icalcomponent_free(ird->cal);
249                 ird->cal = NULL;
250         }
251         if (strcasecmp(partnum, ird->desired_partnum)) return;
252         ird->cal = icalcomponent_new_from_string(content);
253 }
254
255
256 /*
257  * Respond to a meeting request.
258  */
259 void ical_respond(long msgnum, char *partnum, char *action) {
260         struct CtdlMessage *msg;
261         struct ical_respond_data ird;
262
263         if (
264            (strcasecmp(action, "accept"))
265            && (strcasecmp(action, "decline"))
266         ) {
267                 cprintf("%d Action must be 'accept' or 'decline'\n",
268                         ERROR + ILLEGAL_VALUE
269                 );
270                 return;
271         }
272
273         msg = CtdlFetchMessage(msgnum);
274         if (msg == NULL) {
275                 cprintf("%d Message %ld not found.\n",
276                         ERROR+ILLEGAL_VALUE,
277                         (long)msgnum
278                 );
279                 return;
280         }
281
282         memset(&ird, 0, sizeof ird);
283         strcpy(ird.desired_partnum, partnum);
284         mime_parser(msg->cm_fields['M'],
285                 NULL,
286                 *ical_locate_part,              /* callback function */
287                 NULL, NULL,
288                 (void *) &ird,                  /* user data */
289                 0
290         );
291
292         /* We're done with the incoming message, because we now have a
293          * calendar object in memory.
294          */
295         CtdlFreeMessage(msg);
296
297         /*
298          * Here is the real meat of this function.  Handle the event.
299          */
300         if (ird.cal != NULL) {
301                 /* Save this in the user's calendar if necessary */
302                 if (!strcasecmp(action, "accept")) {
303                         ical_add(ird.cal, 0);
304                 }
305
306                 /* Send a reply if necessary */
307                 if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
308                         ical_send_a_reply(ird.cal, action);
309                 }
310
311                 /* Now that we've processed this message, we don't need it
312                  * anymore.  So delete it.
313                  */
314                 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
315
316                 /* Free the memory we allocated and return a response. */
317                 icalcomponent_free(ird.cal);
318                 ird.cal = NULL;
319                 cprintf("%d ok\n", CIT_OK);
320                 return;
321         }
322         else {
323                 cprintf("%d No calendar object found\n", ERROR);
324                 return;
325         }
326
327         /* should never get here */
328 }
329
330
331 /*
332  * Search for a property in both the top level and in a VEVENT subcomponent
333  */
334 icalproperty *ical_ctdl_get_subprop(
335                 icalcomponent *cal,
336                 icalproperty_kind which_prop
337 ) {
338         icalproperty *p;
339         icalcomponent *c;
340
341         p = icalcomponent_get_first_property(cal, which_prop);
342         if (p == NULL) {
343                 c = icalcomponent_get_first_component(cal,
344                                                         ICAL_VEVENT_COMPONENT);
345                 if (c != NULL) {
346                         p = icalcomponent_get_first_property(c, which_prop);
347                 }
348         }
349         return p;
350 }
351
352
353 /*
354  * Check to see if two events overlap.  Returns nonzero if they do.
355  */
356 int ical_ctdl_is_overlap(
357                         struct icaltimetype t1start,
358                         struct icaltimetype t1end,
359                         struct icaltimetype t2start,
360                         struct icaltimetype t2end
361 ) {
362
363         if (icaltime_is_null_time(t1start)) return(0);
364         if (icaltime_is_null_time(t2start)) return(0);
365
366         /* First, check for all-day events */
367         if (t1start.is_date) {
368                 if (!icaltime_compare_date_only(t1start, t2start)) {
369                         return(1);
370                 }
371                 if (!icaltime_is_null_time(t2end)) {
372                         if (!icaltime_compare_date_only(t1start, t2end)) {
373                                 return(1);
374                         }
375                 }
376         }
377
378         if (t2start.is_date) {
379                 if (!icaltime_compare_date_only(t2start, t1start)) {
380                         return(1);
381                 }
382                 if (!icaltime_is_null_time(t1end)) {
383                         if (!icaltime_compare_date_only(t2start, t1end)) {
384                                 return(1);
385                         }
386                 }
387         }
388
389         /* Now check for overlaps using date *and* time. */
390
391         /* First, bail out if either event 1 or event 2 is missing end time. */
392         if (icaltime_is_null_time(t1end)) return(0);
393         if (icaltime_is_null_time(t2end)) return(0);
394
395         /* If event 1 ends before event 2 starts, we're in the clear. */
396         if (icaltime_compare(t1end, t2start) <= 0) return(0);
397
398         /* If event 2 ends before event 1 starts, we're also ok. */
399         if (icaltime_compare(t2end, t1start) <= 0) return(0);
400
401         /* Otherwise, they overlap. */
402         return(1);
403 }
404
405
406
407 /*
408  * Backend for ical_hunt_for_conflicts()
409  */
410 void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
411         icalcomponent *cal;
412         struct CtdlMessage *msg;
413         struct ical_respond_data ird;
414         struct icaltimetype t1start, t1end, t2start, t2end;
415         icalproperty *p;
416         char conflict_event_uid[SIZ];
417         char conflict_event_summary[SIZ];
418         char compare_uid[SIZ];
419
420         cal = (icalcomponent *)data;
421         strcpy(compare_uid, "");
422         strcpy(conflict_event_uid, "");
423         strcpy(conflict_event_summary, "");
424
425         msg = CtdlFetchMessage(msgnum);
426         if (msg == NULL) return;
427         memset(&ird, 0, sizeof ird);
428         strcpy(ird.desired_partnum, "1");       /* hopefully it's always 1 */
429         mime_parser(msg->cm_fields['M'],
430                 NULL,
431                 *ical_locate_part,              /* callback function */
432                 NULL, NULL,
433                 (void *) &ird,                  /* user data */
434                 0
435         );
436         CtdlFreeMessage(msg);
437
438         if (ird.cal == NULL) return;
439
440         t1start = icaltime_null_time();
441         t1end = icaltime_null_time();
442         t2start = icaltime_null_time();
443         t1end = icaltime_null_time();
444
445         /* Now compare cal to ird.cal */
446         p = ical_ctdl_get_subprop(ird.cal, ICAL_DTSTART_PROPERTY);
447         if (p == NULL) return;
448         if (p != NULL) t2start = icalproperty_get_dtstart(p);
449         
450         p = ical_ctdl_get_subprop(ird.cal, ICAL_DTEND_PROPERTY);
451         if (p != NULL) t2end = icalproperty_get_dtend(p);
452
453         p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
454         if (p == NULL) return;
455         if (p != NULL) t1start = icalproperty_get_dtstart(p);
456         
457         p = ical_ctdl_get_subprop(cal, ICAL_DTEND_PROPERTY);
458         if (p != NULL) t1end = icalproperty_get_dtend(p);
459         
460         p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
461         if (p != NULL) {
462                 strcpy(compare_uid, icalproperty_get_comment(p));
463         }
464
465         p = ical_ctdl_get_subprop(ird.cal, ICAL_UID_PROPERTY);
466         if (p != NULL) {
467                 strcpy(conflict_event_uid, icalproperty_get_comment(p));
468         }
469
470         p = ical_ctdl_get_subprop(ird.cal, ICAL_SUMMARY_PROPERTY);
471         if (p != NULL) {
472                 strcpy(conflict_event_summary, icalproperty_get_comment(p));
473         }
474
475
476         icalcomponent_free(ird.cal);
477
478         if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
479                 cprintf("%ld||%s|%s|%d|\n",
480                         msgnum,
481                         conflict_event_uid,
482                         conflict_event_summary,
483                         (       ((strlen(compare_uid)>0)
484                                 &&(!strcasecmp(compare_uid,
485                                 conflict_event_uid))) ? 1 : 0
486                         )
487                 );
488         }
489 }
490
491
492
493 /* 
494  * Phase 2 of "hunt for conflicts" operation.
495  * At this point we have a calendar object which represents the VEVENT that
496  * we're considering adding to the calendar.  Now hunt through the user's
497  * calendar room, and output zero or more existing VEVENTs which conflict
498  * with this one.
499  */
500 void ical_hunt_for_conflicts(icalcomponent *cal) {
501         char hold_rm[ROOMNAMELEN];
502
503         strcpy(hold_rm, CC->quickroom.QRname);  /* save current room */
504
505         if (getroom(&CC->quickroom, USERCALENDARROOM) != 0) {
506                 getroom(&CC->quickroom, hold_rm);
507                 cprintf("%d You do not have a calendar.\n", ERROR);
508                 return;
509         }
510
511         cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
512
513         CtdlForEachMessage(MSGS_ALL, 0, "text/calendar",
514                 NULL,
515                 ical_hunt_for_conflicts_backend,
516                 (void *) cal
517         );
518
519         cprintf("000\n");
520         getroom(&CC->quickroom, hold_rm);       /* return to saved room */
521
522 }
523
524
525
526 /*
527  * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
528  */
529 void ical_conflicts(long msgnum, char *partnum) {
530         struct CtdlMessage *msg;
531         struct ical_respond_data ird;
532
533         msg = CtdlFetchMessage(msgnum);
534         if (msg == NULL) {
535                 cprintf("%d Message %ld not found.\n",
536                         ERROR+ILLEGAL_VALUE,
537                         (long)msgnum
538                 );
539                 return;
540         }
541
542         memset(&ird, 0, sizeof ird);
543         strcpy(ird.desired_partnum, partnum);
544         mime_parser(msg->cm_fields['M'],
545                 NULL,
546                 *ical_locate_part,              /* callback function */
547                 NULL, NULL,
548                 (void *) &ird,                  /* user data */
549                 0
550         );
551
552         CtdlFreeMessage(msg);
553
554         if (ird.cal != NULL) {
555                 ical_hunt_for_conflicts(ird.cal);
556                 icalcomponent_free(ird.cal);
557                 return;
558         }
559         else {
560                 cprintf("%d No calendar object found\n", ERROR);
561                 return;
562         }
563
564         /* should never get here */
565 }
566
567
568
569
570 /*
571  * All Citadel calendar commands from the client come through here.
572  */
573 void cmd_ical(char *argbuf)
574 {
575         char subcmd[SIZ];
576         long msgnum;
577         char partnum[SIZ];
578         char action[SIZ];
579
580         if (CtdlAccessCheck(ac_logged_in)) return;
581
582         extract(subcmd, argbuf, 0);
583
584         if (!strcmp(subcmd, "test")) {
585                 cprintf("%d This server supports calendaring\n", CIT_OK);
586                 return;
587         }
588
589         else if (!strcmp(subcmd, "respond")) {
590                 msgnum = extract_long(argbuf, 1);
591                 extract(partnum, argbuf, 2);
592                 extract(action, argbuf, 3);
593                 ical_respond(msgnum, partnum, action);
594         }
595
596         else if (!strcmp(subcmd, "conflicts")) {
597                 msgnum = extract_long(argbuf, 1);
598                 extract(partnum, argbuf, 2);
599                 ical_conflicts(msgnum, partnum);
600         }
601
602         else {
603                 cprintf("%d Invalid subcommand\n", ERROR+CMD_NOT_SUPPORTED);
604                 return;
605         }
606
607         /* should never get here */
608 }
609
610
611
612 /*
613  * We don't know if the calendar room exists so we just create it at login
614  */
615 void ical_create_room(void)
616 {
617         struct quickroom qr;
618         struct visit vbuf;
619
620         /* Create the calendar room if it doesn't already exist */
621         create_room(USERCALENDARROOM, 4, "", 0, 1, 0);
622
623         /* Set expiration policy to manual; otherwise objects will be lost! */
624         if (lgetroom(&qr, USERCALENDARROOM)) {
625                 lprintf(3, "Couldn't get the user calendar room!\n");
626                 return;
627         }
628         qr.QRep.expire_mode = EXPIRE_MANUAL;
629         lputroom(&qr);
630
631         /* Set the view to a calendar view */
632         CtdlGetRelationship(&vbuf, &CC->usersupp, &qr);
633         vbuf.v_view = 3;        /* 3 = calendar */
634         CtdlSetRelationship(&vbuf, &CC->usersupp, &qr);
635
636         /* Create the tasks list room if it doesn't already exist */
637         create_room(USERTASKSROOM, 4, "", 0, 1, 0);
638
639         /* Set expiration policy to manual; otherwise objects will be lost! */
640         if (lgetroom(&qr, USERTASKSROOM)) {
641                 lprintf(3, "Couldn't get the user calendar room!\n");
642                 return;
643         }
644         qr.QRep.expire_mode = EXPIRE_MANUAL;
645         lputroom(&qr);
646
647         /* Set the view to a task list view */
648         CtdlGetRelationship(&vbuf, &CC->usersupp, &qr);
649         vbuf.v_view = 4;        /* 4 = tasks */
650         CtdlSetRelationship(&vbuf, &CC->usersupp, &qr);
651
652         return;
653 }
654
655
656
657 /*
658  * Back end for ical_obj_beforesave()
659  * This hunts for the UID of the calendar event.
660  */
661 void ical_ctdl_set_extended_msgid(char *name, char *filename, char *partnum,
662                 char *disp, void *content, char *cbtype, size_t length,
663                 char *encoding, void *cbuserdata)
664 {
665         icalcomponent *cal;
666         icalproperty *p;
667
668         /* If this is a text/calendar object, hunt for the UID and drop it in
669          * the "user data" pointer for the MIME parser.  When
670          * ical_obj_beforesave() sees it there, it'll set the Extended msgid
671          * to that string.
672          */
673         if (!strcasecmp(cbtype, "text/calendar")) {
674                 cal = icalcomponent_new_from_string(content);
675                 if (cal != NULL) {
676                         p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
677                         if (p != NULL) {
678                                 strcpy((char *)cbuserdata,
679                                         icalproperty_get_comment(p)
680                                 );
681                         }
682                         icalcomponent_free(cal);
683                 }
684         }
685 }
686
687
688
689
690
691 /*
692  * See if we need to prevent the object from being saved (we don't allow
693  * MIME types other than text/calendar in the Calendar> room).  Also, when
694  * saving an event to the calendar, set the message's Citadel extended message
695  * ID to the UID of the object.  This causes our replication checker to
696  * automatically delete any existing instances of the same object.  (Isn't
697  * that cool?)
698  */
699 int ical_obj_beforesave(struct CtdlMessage *msg)
700 {
701         char roomname[ROOMNAMELEN];
702         char *p;
703         int a;
704         char eidbuf[SIZ];
705
706         /*
707          * Only messages with content-type text/calendar
708          * may be saved to Calendar>.  If the message is bound for
709          * Calendar> but doesn't have this content-type, throw an error
710          * so that the message may not be posted.
711          */
712
713         /* First determine if this is our room */
714         MailboxName(roomname, sizeof roomname, &CC->usersupp, USERCALENDARROOM);
715         if (strcasecmp(roomname, CC->quickroom.QRname)) {
716                 return 0;       /* It's not the Calendar room. */
717         }
718
719         /* Then determine content-type of the message */
720         
721         /* It must be an RFC822 message! */
722         /* FIXME: Not handling MIME multipart messages; implement with IMIP */
723         if (msg->cm_format_type != 4)
724                 return 1;       /* You tried to save a non-RFC822 message! */
725         
726         /* Find the Content-Type: header */
727         p = msg->cm_fields['M'];
728         a = strlen(p);
729         while (--a > 0) {
730                 if (!strncasecmp(p, "Content-Type: ", 14)) {    /* Found it */
731                         if (!strncasecmp(p + 14, "text/calendar", 13)) {
732                                 strcpy(eidbuf, "");
733                                 mime_parser(msg->cm_fields['M'],
734                                         NULL,
735                                         *ical_ctdl_set_extended_msgid,
736                                         NULL, NULL,
737                                         (void *)eidbuf,
738                                         0
739                                 );
740                                 if (strlen(eidbuf) > 0) {
741                                         if (msg->cm_fields['E'] != NULL) {
742                                                 phree(msg->cm_fields['E']);
743                                         }
744                                         msg->cm_fields['E'] = strdoop(eidbuf);
745                                 }
746                                 return 0;
747                         }
748                         else {
749                                 return 1;
750                         }
751                 }
752                 p++;
753         }
754         
755         /* Oops!  No Content-Type in this message!  How'd that happen? */
756         lprintf(7, "RFC822 message with no Content-Type header!\n");
757         return 1;
758 }
759
760
761 #endif  /* HAVE_ICAL_H */
762
763 /*
764  * Register this module with the Citadel server.
765  */
766 char *Dynamic_Module_Init(void)
767 {
768 #ifdef HAVE_ICAL_H
769         CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
770         CtdlRegisterSessionHook(ical_create_room, EVT_LOGIN);
771         CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands");
772 #endif
773         return "$Id$";
774 }