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