7106a367273cfc00eae5dd34ea4991fc7c6a8351
[citadel.git] / citadel / modules / calendar / serv_calendar.c
1 /* 
2  * $Id$ 
3  *
4  * This module implements iCalendar object processing and the Calendar>
5  * room on a Citadel 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 <stdlib.h>
14 #include <unistd.h>
15 #include <sys/types.h>
16 #include <limits.h>
17 #include <stdio.h>
18 #include <string.h>
19 #ifdef HAVE_STRINGS_H
20 #include <strings.h>
21 #endif
22 #include <ical.h>
23 #include <libcitadel.h>
24 #include "citadel.h"
25 #include "server.h"
26 #include "citserver.h"
27 #include "support.h"
28 #include "config.h"
29 #include "user_ops.h"
30 #include "room_ops.h"
31 #include "msgbase.h"
32 #include "internet_addressing.h"
33 #include "serv_calendar.h"
34 #include "euidindex.h"
35 #include "ctdl_module.h"
36 #include "ical_dezonify.h"
37
38
39
40 struct ical_respond_data {
41         char desired_partnum[SIZ];
42         icalcomponent *cal;
43 };
44
45
46 /*
47  * Utility function to create a new VCALENDAR component with some of the
48  * required fields already set the way we like them.
49  */
50 icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
51         icalcomponent *encaps;
52
53         encaps = icalcomponent_new_vcalendar();
54         if (encaps == NULL) {
55                 CtdlLogPrintf(CTDL_CRIT, "Error at %s:%d - could not allocate component!\n",
56                         __FILE__, __LINE__);
57                 return NULL;
58         }
59
60         /* Set the Product ID */
61         icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
62
63         /* Set the Version Number */
64         icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
65
66         return(encaps);
67 }
68
69
70 /*
71  * Utility function to encapsulate a subcomponent into a full VCALENDAR
72  */
73 icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
74         icalcomponent *encaps;
75
76         /* If we're already looking at a full VCALENDAR component,
77          * don't bother ... just return itself.
78          */
79         if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
80                 return subcomp;
81         }
82
83         /* Encapsulate the VEVENT component into a complete VCALENDAR */
84         encaps = icalcomponent_new_citadel_vcalendar();
85         if (encaps == NULL) return NULL;
86
87         /* Encapsulate the subcomponent inside */
88         icalcomponent_add_component(encaps, subcomp);
89
90         /* Convert all timestamps to UTC so we don't have to deal with
91          * stupid VTIMEZONE crap.
92          */
93         ical_dezonify(encaps);
94
95         /* Return the object we just created. */
96         return(encaps);
97 }
98
99
100
101
102 /*
103  * Write a calendar object into the specified user's calendar room.
104  * If the supplied user is NULL, this function writes the calendar object
105  * to the currently selected room.
106  */
107 void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
108         char temp[PATH_MAX];
109         FILE *fp = NULL;
110         char *ser = NULL;
111         icalcomponent *encaps = NULL;
112         struct CtdlMessage *msg = NULL;
113         icalcomponent *tmp=NULL;
114
115         if (cal == NULL) return;
116
117         /* If the supplied object is a subcomponent, encapsulate it in
118          * a full VCALENDAR component, and save that instead.
119          */
120         if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
121                 tmp = icalcomponent_new_clone(cal);
122                 encaps = ical_encapsulate_subcomponent(tmp);
123                 ical_write_to_cal(u, encaps);
124                 icalcomponent_free(tmp);
125                 icalcomponent_free(encaps);
126                 return;
127         }
128
129         ser = icalcomponent_as_ical_string(cal);
130         if (ser == NULL) return;
131
132         /* If the caller supplied a user, write to that user's default calendar room */
133         if (u) {
134                 /* Make a temp file out of it */
135                 CtdlMakeTempFileName(temp, sizeof temp);
136                 fp = fopen(temp, "w");
137                 if (fp != NULL) {
138                         fwrite(ser, strlen(ser), 1, fp);
139                         fclose(fp);
140                 
141                         /* This handy API function does all the work for us. */
142                         CtdlWriteObject(USERCALENDARROOM,       /* which room */
143                                 "text/calendar",        /* MIME type */
144                                 temp,                   /* temp file */
145                                 u,                      /* which user */
146                                 0,                      /* not binary */
147                                 0,                      /* don't delete others of this type */
148                                 0                       /* no flags */
149                         );
150                         unlink(temp);
151                 }
152         }
153
154         /* If the caller did not supply a user, write to the currently selected room */
155         if (!u) {
156                 msg = malloc(sizeof(struct CtdlMessage));
157                 memset(msg, 0, sizeof(struct CtdlMessage));
158                 msg->cm_magic = CTDLMESSAGE_MAGIC;
159                 msg->cm_anon_type = MES_NORMAL;
160                 msg->cm_format_type = 4;
161                 msg->cm_fields['A'] = strdup(CC->user.fullname);
162                 msg->cm_fields['O'] = strdup(CC->room.QRname);
163                 msg->cm_fields['N'] = strdup(config.c_nodename);
164                 msg->cm_fields['H'] = strdup(config.c_humannode);
165                 msg->cm_fields['M'] = malloc(strlen(ser) + 40);
166                 strcpy(msg->cm_fields['M'], "Content-type: text/calendar\r\n\r\n");
167                 strcat(msg->cm_fields['M'], ser);
168         
169                 /* Now write the data */
170                 CtdlSubmitMsg(msg, NULL, "");
171                 CtdlFreeMessage(msg);
172         }
173
174         /* In either case, now we can free the serialized calendar object */
175 //      free(ser);
176 }
177
178
179 /*
180  * Add a calendar object to the user's calendar
181  * 
182  * ok because it uses ical_write_to_cal()
183  */
184 void ical_add(icalcomponent *cal, int recursion_level) {
185         icalcomponent *c;
186
187         /*
188          * The VEVENT subcomponent is the one we're interested in saving.
189          */
190         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
191         
192                 ical_write_to_cal(&CC->user, cal);
193
194         }
195
196         /* If the component has subcomponents, recurse through them. */
197         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
198             (c != 0);
199             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
200                 /* Recursively process subcomponent */
201                 ical_add(c, recursion_level+1);
202         }
203
204 }
205
206
207
208 /*
209  * Send a reply to a meeting invitation.
210  *
211  * 'request' is the invitation to reply to.
212  * 'action' is the string "accept" or "decline" or "tentative".
213  *
214  */
215 void ical_send_a_reply(icalcomponent *request, char *action) {
216         icalcomponent *the_reply = NULL;
217         icalcomponent *vevent = NULL;
218         icalproperty *attendee = NULL;
219         char attendee_string[SIZ];
220         icalproperty *organizer = NULL;
221         char organizer_string[SIZ];
222         icalproperty *summary = NULL;
223         char summary_string[SIZ];
224         icalproperty *me_attend = NULL;
225         struct recptypes *recp = NULL;
226         icalparameter *partstat = NULL;
227         char *serialized_reply = NULL;
228         char *reply_message_text = NULL;
229         struct CtdlMessage *msg = NULL;
230         struct recptypes *valid = NULL;
231
232         strcpy(organizer_string, "");
233         strcpy(summary_string, "Calendar item");
234
235         if (request == NULL) {
236                 CtdlLogPrintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n");
237                 return;
238         }
239
240         the_reply = icalcomponent_new_clone(request);
241         if (the_reply == NULL) {
242                 CtdlLogPrintf(CTDL_ERR, "ERROR: cannot clone request\n");
243                 return;
244         }
245
246         /* Change the method from REQUEST to REPLY */
247         icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
248
249         vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
250         if (vevent != NULL) {
251                 /* Hunt for attendees, removing ones that aren't us.
252                  * (Actually, remove them all, cloning our own one so we can
253                  * re-insert it later)
254                  */
255                 while (attendee = icalcomponent_get_first_property(vevent,
256                     ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
257                 ) {
258                         if (icalproperty_get_attendee(attendee)) {
259                                 strcpy(attendee_string,
260                                         icalproperty_get_attendee(attendee) );
261                                 if (!strncasecmp(attendee_string, "MAILTO:", 7)) {
262                                         strcpy(attendee_string, &attendee_string[7]);
263                                         striplt(attendee_string);
264                                         recp = validate_recipients(attendee_string, NULL, 0);
265                                         if (recp != NULL) {
266                                                 if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
267                                                         if (me_attend) icalproperty_free(me_attend);
268                                                         me_attend = icalproperty_new_clone(attendee);
269                                                 }
270                                                 free_recipients(recp);
271                                         }
272                                 }
273                         }
274                         /* Remove it... */
275                         icalcomponent_remove_property(vevent, attendee);
276                         icalproperty_free(attendee);
277                 }
278
279                 /* We found our own address in the attendee list. */
280                 if (me_attend) {
281                         /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
282                         icalproperty_remove_parameter(me_attend, ICAL_PARTSTAT_PARAMETER);
283
284                         if (!strcasecmp(action, "accept")) {
285                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
286                         }
287                         else if (!strcasecmp(action, "decline")) {
288                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
289                         }
290                         else if (!strcasecmp(action, "tentative")) {
291                                 partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
292                         }
293
294                         if (partstat) icalproperty_add_parameter(me_attend, partstat);
295
296                         /* Now insert it back into the vevent. */
297                         icalcomponent_add_property(vevent, me_attend);
298                 }
299
300                 /* Figure out who to send this thing to */
301                 organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
302                 if (organizer != NULL) {
303                         if (icalproperty_get_organizer(organizer)) {
304                                 strcpy(organizer_string,
305                                         icalproperty_get_organizer(organizer) );
306                         }
307                 }
308                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
309                         strcpy(organizer_string, &organizer_string[7]);
310                         striplt(organizer_string);
311                 } else {
312                         strcpy(organizer_string, "");
313                 }
314
315                 /* Extract the summary string -- we'll use it as the
316                  * message subject for the reply
317                  */
318                 summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
319                 if (summary != NULL) {
320                         if (icalproperty_get_summary(summary)) {
321                                 strcpy(summary_string,
322                                         icalproperty_get_summary(summary) );
323                         }
324                 }
325         }
326
327         /* Now generate the reply message and send it out. */
328         serialized_reply = strdup(icalcomponent_as_ical_string(the_reply));
329         icalcomponent_free(the_reply);  /* don't need this anymore */
330         if (serialized_reply == NULL) return;
331
332         reply_message_text = malloc(strlen(serialized_reply) + SIZ);
333         if (reply_message_text != NULL) {
334                 sprintf(reply_message_text,
335                         "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n",
336                         serialized_reply
337                 );
338
339                 msg = CtdlMakeMessage(&CC->user,
340                         organizer_string,       /* to */
341                         "",                     /* cc */
342                         CC->room.QRname, 0, FMT_RFC822,
343                         "",
344                         "",
345                         summary_string,         /* Use summary for subject */
346                         NULL,
347                         reply_message_text,
348                         NULL);
349         
350                 if (msg != NULL) {
351                         valid = validate_recipients(organizer_string, NULL, 0);
352                         CtdlSubmitMsg(msg, valid, "");
353                         CtdlFreeMessage(msg);
354                         free_recipients(valid);
355                 }
356         }
357         free(serialized_reply);
358 }
359
360
361
362 /*
363  * Callback function for mime parser that hunts for calendar content types
364  * and turns them into calendar objects
365  */
366 void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
367                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
368                 void *cbuserdata) {
369
370         struct ical_respond_data *ird = NULL;
371
372         ird = (struct ical_respond_data *) cbuserdata;
373
374         /* desired_partnum can be set to "_HUNT_" to have it just look for
375          * the first part with a content type of text/calendar.  Otherwise
376          * we have to only process the right one.
377          */
378         if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
379                 if (strcasecmp(partnum, ird->desired_partnum)) {
380                         return;
381                 }
382         }
383
384         if (  (strcasecmp(cbtype, "text/calendar"))
385            && (strcasecmp(cbtype, "application/ics")) ) {
386                 return;
387         }
388
389         if (ird->cal != NULL) {
390                 icalcomponent_free(ird->cal);
391                 ird->cal = NULL;
392         }
393
394         ird->cal = icalcomponent_new_from_string(content);
395         if (ird->cal != NULL) {
396                 ical_dezonify(ird->cal);
397         }
398 }
399
400
401 /*
402  * Respond to a meeting request.
403  */
404 void ical_respond(long msgnum, char *partnum, char *action) {
405         struct CtdlMessage *msg = NULL;
406         struct ical_respond_data ird;
407
408         if (
409            (strcasecmp(action, "accept"))
410            && (strcasecmp(action, "decline"))
411         ) {
412                 cprintf("%d Action must be 'accept' or 'decline'\n",
413                         ERROR + ILLEGAL_VALUE
414                 );
415                 return;
416         }
417
418         msg = CtdlFetchMessage(msgnum, 1);
419         if (msg == NULL) {
420                 cprintf("%d Message %ld not found.\n",
421                         ERROR + ILLEGAL_VALUE,
422                         (long)msgnum
423                 );
424                 return;
425         }
426
427         memset(&ird, 0, sizeof ird);
428         strcpy(ird.desired_partnum, partnum);
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
437         /* We're done with the incoming message, because we now have a
438          * calendar object in memory.
439          */
440         CtdlFreeMessage(msg);
441
442         /*
443          * Here is the real meat of this function.  Handle the event.
444          */
445         if (ird.cal != NULL) {
446                 /* Save this in the user's calendar if necessary */
447                 if (!strcasecmp(action, "accept")) {
448                         ical_add(ird.cal, 0);
449                 }
450
451                 /* Send a reply if necessary */
452                 if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
453                         ical_send_a_reply(ird.cal, action);
454                 }
455
456                 /* Now that we've processed this message, we don't need it
457                  * anymore.  So delete it.  (NOTE we don't do this anymore.)
458                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
459                  */
460
461                 /* Free the memory we allocated and return a response. */
462                 icalcomponent_free(ird.cal);
463                 ird.cal = NULL;
464                 cprintf("%d ok\n", CIT_OK);
465                 return;
466         }
467         else {
468                 cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
469                 return;
470         }
471
472         /* should never get here */
473 }
474
475
476 /*
477  * Figure out the UID of the calendar event being referred to in a
478  * REPLY object.  This function is recursive.
479  */
480 void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
481         icalcomponent *subcomponent;
482         icalproperty *p;
483
484         /* If this object is a REPLY, then extract the UID. */
485         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
486                 p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
487                 if (p != NULL) {
488                         strcpy(uidbuf, icalproperty_get_comment(p));
489                 }
490         }
491
492         /* Otherwise, recurse through any VEVENT subcomponents.  We do NOT want the
493          * UID of the reply; we want the UID of the invitation being replied to.
494          */
495         for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
496             subcomponent != NULL;
497             subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
498                 ical_learn_uid_of_reply(uidbuf, subcomponent);
499         }
500 }
501
502
503 /*
504  * ical_update_my_calendar_with_reply() refers to this callback function; when we
505  * locate the message containing the calendar event we're replying to, this function
506  * gets called.  It basically just sticks the message number in a supplied buffer.
507  */
508 void ical_hunt_for_event_to_update(long msgnum, void *data) {
509         long *msgnumptr;
510
511         msgnumptr = (long *) data;
512         *msgnumptr = msgnum;
513 }
514
515
516 struct original_event_container {
517         icalcomponent *c;
518 };
519
520 /*
521  * Callback function for mime parser that hunts for calendar content types
522  * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
523  * to fetch the object being updated)
524  */
525 void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
526                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
527                 void *cbuserdata) {
528
529         struct original_event_container *oec = NULL;
530
531         if (  (strcasecmp(cbtype, "text/calendar"))
532            && (strcasecmp(cbtype, "application/ics")) ) {
533                 return;
534         }
535         oec = (struct original_event_container *) cbuserdata;
536         if (oec->c != NULL) {
537                 icalcomponent_free(oec->c);
538         }
539         oec->c = icalcomponent_new_from_string(content);
540 }
541
542
543 /*
544  * Merge updated attendee information from a REPLY into an existing event.
545  */
546 void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
547         icalcomponent *c;
548         icalproperty *e_attendee, *r_attendee;
549
550         /* First things first.  If we're not looking at a VEVENT component,
551          * recurse through subcomponents until we find one.
552          */
553         if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
554                 for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
555                     c != NULL;
556                     c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
557                         ical_merge_attendee_reply(c, reply);
558                 }
559                 return;
560         }
561
562         /* Now do the same thing with the reply.
563          */
564         if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
565                 for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
566                     c != NULL;
567                     c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
568                         ical_merge_attendee_reply(event, c);
569                 }
570                 return;
571         }
572
573         /* Clone the reply, because we're going to rip its guts out. */
574         reply = icalcomponent_new_clone(reply);
575
576         /* At this point we're looking at the correct subcomponents.
577          * Iterate through the attendees looking for a match.
578          */
579 STARTOVER:
580         for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
581             e_attendee != NULL;
582             e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
583
584                 for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
585                     r_attendee != NULL;
586                     r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
587
588                         /* Check to see if these two attendees match...
589                          */
590                         if (!strcasecmp(
591                            icalproperty_get_attendee(e_attendee),
592                            icalproperty_get_attendee(r_attendee)
593                         )) {
594                                 /* ...and if they do, remove the attendee from the event
595                                  * and replace it with the attendee from the reply.  (The
596                                  * reply's copy will have the same address, but an updated
597                                  * status.)
598                                  */
599                                 icalcomponent_remove_property(event, e_attendee);
600                                 icalproperty_free(e_attendee);
601                                 icalcomponent_remove_property(reply, r_attendee);
602                                 icalcomponent_add_property(event, r_attendee);
603
604                                 /* Since we diddled both sets of attendees, we have to start
605                                  * the iteration over again.  This will not create an infinite
606                                  * loop because we removed the attendee from the reply.  (That's
607                                  * why we cloned the reply, and that's what we mean by "ripping
608                                  * its guts out.")
609                                  */
610                                 goto STARTOVER;
611                         }
612         
613                 }
614         }
615
616         /* Free the *clone* of the reply. */
617         icalcomponent_free(reply);
618 }
619
620
621
622
623 /*
624  * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
625  * calendar event.  The object has already been deserialized for us; all
626  * we have to do here is hunt for the event in our calendar, merge in the
627  * updated attendee status, and save it again.
628  *
629  * This function returns 0 on success, 1 if the event was not found in the
630  * user's calendar, or 2 if an internal error occurred.
631  */
632 int ical_update_my_calendar_with_reply(icalcomponent *cal) {
633         char uid[SIZ];
634         char hold_rm[ROOMNAMELEN];
635         long msgnum_being_replaced = 0;
636         struct CtdlMessage *msg = NULL;
637         struct original_event_container oec;
638         icalcomponent *original_event;
639         char *serialized_event = NULL;
640         char roomname[ROOMNAMELEN];
641         char *message_text = NULL;
642
643         /* Figure out just what event it is we're dealing with */
644         strcpy(uid, "--==<< InVaLiD uId >>==--");
645         ical_learn_uid_of_reply(uid, cal);
646         CtdlLogPrintf(CTDL_DEBUG, "UID of event being replied to is <%s>\n", uid);
647
648         strcpy(hold_rm, CC->room.QRname);       /* save current room */
649
650         if (getroom(&CC->room, USERCALENDARROOM) != 0) {
651                 getroom(&CC->room, hold_rm);
652                 CtdlLogPrintf(CTDL_CRIT, "cannot get user calendar room\n");
653                 return(2);
654         }
655
656         /*
657          * Look in the EUID index for a message with
658          * the Citadel EUID set to the value we're looking for.  Since
659          * Citadel always sets the message EUID to the vCalendar UID of
660          * the event, this will work.
661          */
662         msgnum_being_replaced = locate_message_by_euid(uid, &CC->room);
663
664         getroom(&CC->room, hold_rm);    /* return to saved room */
665
666         CtdlLogPrintf(CTDL_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced);
667         if (msgnum_being_replaced == 0) {
668                 return(1);                      /* no calendar event found */
669         }
670
671         /* Now we know the ID of the message containing the event being updated.
672          * We don't actually have to delete it; that'll get taken care of by the
673          * server when we save another event with the same UID.  This just gives
674          * us the ability to load the event into memory so we can diddle the
675          * attendees.
676          */
677         msg = CtdlFetchMessage(msgnum_being_replaced, 1);
678         if (msg == NULL) {
679                 return(2);                      /* internal error */
680         }
681         oec.c = NULL;
682         mime_parser(msg->cm_fields['M'],
683                 NULL,
684                 *ical_locate_original_event,    /* callback function */
685                 NULL, NULL,
686                 &oec,                           /* user data */
687                 0
688         );
689         CtdlFreeMessage(msg);
690
691         original_event = oec.c;
692         if (original_event == NULL) {
693                 CtdlLogPrintf(CTDL_ERR, "ERROR: Original_component is NULL.\n");
694                 return(2);
695         }
696
697         /* Merge the attendee's updated status into the event */
698         ical_merge_attendee_reply(original_event, cal);
699
700         /* Serialize it */
701         serialized_event = strdup(icalcomponent_as_ical_string(original_event));
702         icalcomponent_free(original_event);     /* Don't need this anymore. */
703         if (serialized_event == NULL) return(2);
704
705         MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
706
707         message_text = malloc(strlen(serialized_event) + SIZ);
708         if (message_text != NULL) {
709                 sprintf(message_text,
710                         "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n",
711                         serialized_event
712                 );
713
714                 msg = CtdlMakeMessage(&CC->user,
715                         "",                     /* No recipient */
716                         "",                     /* No recipient */
717                         roomname,
718                         0, FMT_RFC822,
719                         "",
720                         "",
721                         "",             /* no subject */
722                         NULL,
723                         message_text,
724                         NULL);
725         
726                 if (msg != NULL) {
727                         CIT_ICAL->avoid_sending_invitations = 1;
728                         CtdlSubmitMsg(msg, NULL, roomname);
729                         CtdlFreeMessage(msg);
730                         CIT_ICAL->avoid_sending_invitations = 0;
731                 }
732         }
733         free(serialized_event);
734         return(0);
735 }
736
737
738 /*
739  * Handle an incoming RSVP for an event.  (This is the server subcommand part; it
740  * simply extracts the calendar object from the message, deserializes it, and
741  * passes it up to ical_update_my_calendar_with_reply() for processing.
742  */
743 void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
744         struct CtdlMessage *msg = NULL;
745         struct ical_respond_data ird;
746         int ret;
747
748         if (
749            (strcasecmp(action, "update"))
750            && (strcasecmp(action, "ignore"))
751         ) {
752                 cprintf("%d Action must be 'update' or 'ignore'\n",
753                         ERROR + ILLEGAL_VALUE
754                 );
755                 return;
756         }
757
758         msg = CtdlFetchMessage(msgnum, 1);
759         if (msg == NULL) {
760                 cprintf("%d Message %ld not found.\n",
761                         ERROR + ILLEGAL_VALUE,
762                         (long)msgnum
763                 );
764                 return;
765         }
766
767         memset(&ird, 0, sizeof ird);
768         strcpy(ird.desired_partnum, partnum);
769         mime_parser(msg->cm_fields['M'],
770                 NULL,
771                 *ical_locate_part,              /* callback function */
772                 NULL, NULL,
773                 (void *) &ird,                  /* user data */
774                 0
775         );
776
777         /* We're done with the incoming message, because we now have a
778          * calendar object in memory.
779          */
780         CtdlFreeMessage(msg);
781
782         /*
783          * Here is the real meat of this function.  Handle the event.
784          */
785         if (ird.cal != NULL) {
786                 /* Update the user's calendar if necessary */
787                 if (!strcasecmp(action, "update")) {
788                         ret = ical_update_my_calendar_with_reply(ird.cal);
789                         if (ret == 0) {
790                                 cprintf("%d Your calendar has been updated with this reply.\n",
791                                         CIT_OK);
792                         }
793                         else if (ret == 1) {
794                                 cprintf("%d This event does not exist in your calendar.\n",
795                                         ERROR + FILE_NOT_FOUND);
796                         }
797                         else {
798                                 cprintf("%d An internal error occurred.\n",
799                                         ERROR + INTERNAL_ERROR);
800                         }
801                 }
802                 else {
803                         cprintf("%d This reply has been ignored.\n", CIT_OK);
804                 }
805
806                 /* Now that we've processed this message, we don't need it
807                  * anymore.  So delete it.  (Don't do this anymore.)
808                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
809                  */
810
811                 /* Free the memory we allocated and return a response. */
812                 icalcomponent_free(ird.cal);
813                 ird.cal = NULL;
814                 return;
815         }
816         else {
817                 cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
818                 return;
819         }
820
821         /* should never get here */
822 }
823
824
825 /*
826  * Search for a property in both the top level and in a VEVENT subcomponent
827  */
828 icalproperty *ical_ctdl_get_subprop(
829                 icalcomponent *cal,
830                 icalproperty_kind which_prop
831 ) {
832         icalproperty *p;
833         icalcomponent *c;
834
835         p = icalcomponent_get_first_property(cal, which_prop);
836         if (p == NULL) {
837                 c = icalcomponent_get_first_component(cal,
838                                                         ICAL_VEVENT_COMPONENT);
839                 if (c != NULL) {
840                         p = icalcomponent_get_first_property(c, which_prop);
841                 }
842         }
843         return p;
844 }
845
846
847 /*
848  * Check to see if two events overlap.  Returns nonzero if they do.
849  * (This function is used in both Citadel and WebCit.  If you change it in
850  * one place, change it in the other.  Better yet, put it in a library.)
851  */
852 int ical_ctdl_is_overlap(
853                         struct icaltimetype t1start,
854                         struct icaltimetype t1end,
855                         struct icaltimetype t2start,
856                         struct icaltimetype t2end
857 ) {
858
859         if (icaltime_is_null_time(t1start)) return(0);
860         if (icaltime_is_null_time(t2start)) return(0);
861
862         /* First, check for all-day events */
863         if (t1start.is_date) {
864                 if (!icaltime_compare_date_only(t1start, t2start, 
865                                                 icaltimezone_get_utc_timezone())) {
866                         return(1);
867                 }
868                 if (!icaltime_is_null_time(t2end)) {
869                         if (!icaltime_compare_date_only(t1start, t2end, 
870                                                         icaltimezone_get_utc_timezone())) {
871                                 return(1);
872                         }
873                 }
874         }
875
876         if (t2start.is_date) {
877                 if (!icaltime_compare_date_only(t2start, t1start,
878                                                 icaltimezone_get_utc_timezone())) {
879                         return(1);
880                 }
881                 if (!icaltime_is_null_time(t1end)) {
882                         if (!icaltime_compare_date_only(t2start, t1end, 
883                                                         icaltimezone_get_utc_timezone())) {
884                                 return(1);
885                         }
886                 }
887         }
888
889         /* Now check for overlaps using date *and* time. */
890
891         /* First, bail out if either event 1 or event 2 is missing end time. */
892         if (icaltime_is_null_time(t1end)) return(0);
893         if (icaltime_is_null_time(t2end)) return(0);
894
895         /* If event 1 ends before event 2 starts, we're in the clear. */
896         if (icaltime_compare(t1end, t2start) <= 0) return(0);
897
898         /* If event 2 ends before event 1 starts, we're also ok. */
899         if (icaltime_compare(t2end, t1start) <= 0) return(0);
900
901         /* Otherwise, they overlap. */
902         return(1);
903 }
904
905
906
907 /*
908  * Backend for ical_hunt_for_conflicts()
909  */
910 void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
911         icalcomponent *cal;
912         struct CtdlMessage *msg = NULL;
913         struct ical_respond_data ird;
914         struct icaltimetype t1start, t1end, t2start, t2end;
915         icalproperty *p;
916         char conflict_event_uid[SIZ];
917         char conflict_event_summary[SIZ];
918         char compare_uid[SIZ];
919
920         cal = (icalcomponent *)data;
921         strcpy(compare_uid, "");
922         strcpy(conflict_event_uid, "");
923         strcpy(conflict_event_summary, "");
924
925         msg = CtdlFetchMessage(msgnum, 1);
926         if (msg == NULL) return;
927         memset(&ird, 0, sizeof ird);
928         strcpy(ird.desired_partnum, "_HUNT_");
929         mime_parser(msg->cm_fields['M'],
930                 NULL,
931                 *ical_locate_part,              /* callback function */
932                 NULL, NULL,
933                 (void *) &ird,                  /* user data */
934                 0
935         );
936         CtdlFreeMessage(msg);
937
938         if (ird.cal == NULL) return;
939
940         t1start = icaltime_null_time();
941         t1end = icaltime_null_time();
942         t2start = icaltime_null_time();
943         t1end = icaltime_null_time();
944
945         /* Now compare cal to ird.cal */
946         p = ical_ctdl_get_subprop(ird.cal, ICAL_DTSTART_PROPERTY);
947         if (p == NULL) return;
948         if (p != NULL) t2start = icalproperty_get_dtstart(p);
949         
950         p = ical_ctdl_get_subprop(ird.cal, ICAL_DTEND_PROPERTY);
951         if (p != NULL) t2end = icalproperty_get_dtend(p);
952
953         p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
954         if (p == NULL) return;
955         if (p != NULL) t1start = icalproperty_get_dtstart(p);
956         
957         p = ical_ctdl_get_subprop(cal, ICAL_DTEND_PROPERTY);
958         if (p != NULL) t1end = icalproperty_get_dtend(p);
959         
960         p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
961         if (p != NULL) {
962                 strcpy(compare_uid, icalproperty_get_comment(p));
963         }
964
965         p = ical_ctdl_get_subprop(ird.cal, ICAL_UID_PROPERTY);
966         if (p != NULL) {
967                 strcpy(conflict_event_uid, icalproperty_get_comment(p));
968         }
969
970         p = ical_ctdl_get_subprop(ird.cal, ICAL_SUMMARY_PROPERTY);
971         if (p != NULL) {
972                 strcpy(conflict_event_summary, icalproperty_get_comment(p));
973         }
974
975         icalcomponent_free(ird.cal);
976
977         if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
978                 cprintf("%ld||%s|%s|%d|\n",
979                         msgnum,
980                         conflict_event_uid,
981                         conflict_event_summary,
982                         (       ((strlen(compare_uid)>0)
983                                 &&(!strcasecmp(compare_uid,
984                                 conflict_event_uid))) ? 1 : 0
985                         )
986                 );
987         }
988 }
989
990
991
992 /* 
993  * Phase 2 of "hunt for conflicts" operation.
994  * At this point we have a calendar object which represents the VEVENT that
995  * we're considering adding to the calendar.  Now hunt through the user's
996  * calendar room, and output zero or more existing VEVENTs which conflict
997  * with this one.
998  */
999 void ical_hunt_for_conflicts(icalcomponent *cal) {
1000         char hold_rm[ROOMNAMELEN];
1001
1002         strcpy(hold_rm, CC->room.QRname);       /* save current room */
1003
1004         if (getroom(&CC->room, USERCALENDARROOM) != 0) {
1005                 getroom(&CC->room, hold_rm);
1006                 cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
1007                 return;
1008         }
1009
1010         cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
1011
1012         CtdlForEachMessage(MSGS_ALL, 0, NULL,
1013                 NULL,
1014                 NULL,
1015                 ical_hunt_for_conflicts_backend,
1016                 (void *) cal
1017         );
1018
1019         cprintf("000\n");
1020         getroom(&CC->room, hold_rm);    /* return to saved room */
1021
1022 }
1023
1024
1025
1026 /*
1027  * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
1028  */
1029 void ical_conflicts(long msgnum, char *partnum) {
1030         struct CtdlMessage *msg = NULL;
1031         struct ical_respond_data ird;
1032
1033         msg = CtdlFetchMessage(msgnum, 1);
1034         if (msg == NULL) {
1035                 cprintf("%d Message %ld not found.\n",
1036                         ERROR + ILLEGAL_VALUE,
1037                         (long)msgnum
1038                 );
1039                 return;
1040         }
1041
1042         memset(&ird, 0, sizeof ird);
1043         strcpy(ird.desired_partnum, partnum);
1044         mime_parser(msg->cm_fields['M'],
1045                 NULL,
1046                 *ical_locate_part,              /* callback function */
1047                 NULL, NULL,
1048                 (void *) &ird,                  /* user data */
1049                 0
1050         );
1051
1052         CtdlFreeMessage(msg);
1053
1054         if (ird.cal != NULL) {
1055                 ical_hunt_for_conflicts(ird.cal);
1056                 icalcomponent_free(ird.cal);
1057                 return;
1058         }
1059         else {
1060                 cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
1061                 return;
1062         }
1063
1064         /* should never get here */
1065 }
1066
1067
1068
1069 /*
1070  * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
1071  */
1072 void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) {
1073         icalproperty *p;
1074         icalvalue *v;
1075         struct icalperiodtype my_period;
1076
1077         if (cal == NULL) return;
1078         my_period = icalperiodtype_null_period();
1079
1080         if (icalcomponent_isa(cal) != ICAL_VEVENT_COMPONENT) {
1081                 ical_add_to_freebusy(fb,
1082                         icalcomponent_get_first_component(
1083                                 cal, ICAL_VEVENT_COMPONENT
1084                         )
1085                 );
1086                 return;
1087         }
1088
1089         ical_dezonify(cal);
1090
1091         /* If this event is not opaque, the user isn't publishing it as
1092          * busy time, so don't bother doing anything else.
1093          */
1094         p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
1095         if (p != NULL) {
1096                 v = icalproperty_get_value(p);
1097                 if (v != NULL) {
1098                         if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
1099                                 return;
1100                         }
1101                 }
1102         }
1103
1104         /* Convert the DTSTART and DTEND properties to an icalperiod. */
1105         p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
1106         if (p != NULL) {
1107                 my_period.start = icalproperty_get_dtstart(p);
1108         }
1109
1110         p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
1111         if (p != NULL) {
1112                 my_period.end = icalproperty_get_dtstart(p);
1113         }
1114
1115         /* Now add it. */
1116         icalcomponent_add_property(fb,
1117                 icalproperty_new_freebusy(my_period)
1118         );
1119
1120         /* Make sure the DTSTART property of the freebusy *list* is set to
1121          * the DTSTART property of the *earliest event*.
1122          */
1123         p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
1124         if (p == NULL) {
1125                 icalcomponent_set_dtstart(fb,
1126                         icalcomponent_get_dtstart(cal) );
1127         }
1128         else {
1129                 if (icaltime_compare(
1130                         icalcomponent_get_dtstart(cal),
1131                         icalcomponent_get_dtstart(fb)
1132                    ) < 0) {
1133                         icalcomponent_set_dtstart(fb,
1134                                 icalcomponent_get_dtstart(cal) );
1135                 }
1136         }
1137
1138         /* Make sure the DTEND property of the freebusy *list* is set to
1139          * the DTEND property of the *latest event*.
1140          */
1141         p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
1142         if (p == NULL) {
1143                 icalcomponent_set_dtend(fb,
1144                         icalcomponent_get_dtend(cal) );
1145         }
1146         else {
1147                 if (icaltime_compare(
1148                         icalcomponent_get_dtend(cal),
1149                         icalcomponent_get_dtend(fb)
1150                    ) > 0) {
1151                         icalcomponent_set_dtend(fb,
1152                                 icalcomponent_get_dtend(cal) );
1153                 }
1154         }
1155
1156 }
1157
1158
1159
1160 /*
1161  * Backend for ical_freebusy()
1162  *
1163  * This function simply loads the messages in the user's calendar room,
1164  * which contain VEVENTs, then strips them of all non-freebusy data, and
1165  * adds them to the supplied VCALENDAR.
1166  *
1167  */
1168 void ical_freebusy_backend(long msgnum, void *data) {
1169         icalcomponent *cal;
1170         struct CtdlMessage *msg = NULL;
1171         struct ical_respond_data ird;
1172
1173         cal = (icalcomponent *)data;
1174
1175         msg = CtdlFetchMessage(msgnum, 1);
1176         if (msg == NULL) return;
1177         memset(&ird, 0, sizeof ird);
1178         strcpy(ird.desired_partnum, "_HUNT_");
1179         mime_parser(msg->cm_fields['M'],
1180                 NULL,
1181                 *ical_locate_part,              /* callback function */
1182                 NULL, NULL,
1183                 (void *) &ird,                  /* user data */
1184                 0
1185         );
1186         CtdlFreeMessage(msg);
1187
1188         if (ird.cal == NULL) return;
1189
1190         ical_add_to_freebusy(cal, ird.cal);
1191
1192         /* Now free the memory. */
1193         icalcomponent_free(ird.cal);
1194 }
1195
1196
1197
1198 /*
1199  * Grab another user's free/busy times
1200  */
1201 void ical_freebusy(char *who) {
1202         struct ctdluser usbuf;
1203         char calendar_room_name[ROOMNAMELEN];
1204         char hold_rm[ROOMNAMELEN];
1205         char *serialized_request = NULL;
1206         icalcomponent *encaps = NULL;
1207         icalcomponent *fb = NULL;
1208         int found_user = (-1);
1209         struct recptypes *recp = NULL;
1210         char buf[256];
1211         char host[256];
1212         char type[256];
1213         int i = 0;
1214         int config_lines = 0;
1215
1216         /* First try an exact match. */
1217         found_user = getuser(&usbuf, who);
1218
1219         /* If not found, try it as an unqualified email address. */
1220         if (found_user != 0) {
1221                 strcpy(buf, who);
1222                 recp = validate_recipients(buf, NULL, 0);
1223                 CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf);
1224                 if (recp != NULL) {
1225                         if (recp->num_local == 1) {
1226                                 found_user = getuser(&usbuf, recp->recp_local);
1227                         }
1228                         free_recipients(recp);
1229                 }
1230         }
1231
1232         /* If still not found, try it as an address qualified with the
1233          * primary FQDN of this Citadel node.
1234          */
1235         if (found_user != 0) {
1236                 snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn);
1237                 CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf);
1238                 recp = validate_recipients(buf, NULL, 0);
1239                 if (recp != NULL) {
1240                         if (recp->num_local == 1) {
1241                                 found_user = getuser(&usbuf, recp->recp_local);
1242                         }
1243                         free_recipients(recp);
1244                 }
1245         }
1246
1247         /* Still not found?  Try qualifying it with every domain we
1248          * might have addresses in.
1249          */
1250         if (found_user != 0) {
1251                 config_lines = num_tokens(inetcfg, '\n');
1252                 for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
1253                         extract_token(buf, inetcfg, i, '\n', sizeof buf);
1254                         extract_token(host, buf, 0, '|', sizeof host);
1255                         extract_token(type, buf, 1, '|', sizeof type);
1256
1257                         if ( (!strcasecmp(type, "localhost"))
1258                            || (!strcasecmp(type, "directory")) ) {
1259                                 snprintf(buf, sizeof buf, "%s@%s", who, host);
1260                                 CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf);
1261                                 recp = validate_recipients(buf, NULL, 0);
1262                                 if (recp != NULL) {
1263                                         if (recp->num_local == 1) {
1264                                                 found_user = getuser(&usbuf, recp->recp_local);
1265                                         }
1266                                         free_recipients(recp);
1267                                 }
1268                         }
1269                 }
1270         }
1271
1272         if (found_user != 0) {
1273                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1274                 return;
1275         }
1276
1277         MailboxName(calendar_room_name, sizeof calendar_room_name,
1278                 &usbuf, USERCALENDARROOM);
1279
1280         strcpy(hold_rm, CC->room.QRname);       /* save current room */
1281
1282         if (getroom(&CC->room, calendar_room_name) != 0) {
1283                 cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
1284                 getroom(&CC->room, hold_rm);
1285                 return;
1286         }
1287
1288         /* Create a VFREEBUSY subcomponent */
1289         CtdlLogPrintf(CTDL_DEBUG, "Creating VFREEBUSY component\n");
1290         fb = icalcomponent_new_vfreebusy();
1291         if (fb == NULL) {
1292                 cprintf("%d Internal error: cannot allocate memory.\n",
1293                         ERROR + INTERNAL_ERROR);
1294                 getroom(&CC->room, hold_rm);
1295                 return;
1296         }
1297
1298         /* Set the method to PUBLISH */
1299         icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
1300
1301         /* Set the DTSTAMP to right now. */
1302         icalcomponent_set_dtstamp(fb, icaltime_from_timet(time(NULL), 0));
1303
1304         /* Add the user's email address as ORGANIZER */
1305         sprintf(buf, "MAILTO:%s", who);
1306         if (strchr(buf, '@') == NULL) {
1307                 strcat(buf, "@");
1308                 strcat(buf, config.c_fqdn);
1309         }
1310         for (i=0; buf[i]; ++i) {
1311                 if (buf[i]==' ') buf[i] = '_';
1312         }
1313         icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
1314
1315         /* Add busy time from events */
1316         CtdlLogPrintf(CTDL_DEBUG, "Adding busy time from events\n");
1317         CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
1318
1319         /* If values for DTSTART and DTEND are still not present, set them
1320          * to yesterday and tomorrow as default values.
1321          */
1322         if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
1323                 icalcomponent_set_dtstart(fb, icaltime_from_timet(time(NULL)-86400L, 0));
1324         }
1325         if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
1326                 icalcomponent_set_dtend(fb, icaltime_from_timet(time(NULL)+86400L, 0));
1327         }
1328
1329         /* Put the freebusy component into the calendar component */
1330         CtdlLogPrintf(CTDL_DEBUG, "Encapsulating\n");
1331         encaps = ical_encapsulate_subcomponent(fb);
1332         if (encaps == NULL) {
1333                 icalcomponent_free(fb);
1334                 cprintf("%d Internal error: cannot allocate memory.\n",
1335                         ERROR + INTERNAL_ERROR);
1336                 getroom(&CC->room, hold_rm);
1337                 return;
1338         }
1339
1340         /* Set the method to PUBLISH */
1341         CtdlLogPrintf(CTDL_DEBUG, "Setting method\n");
1342         icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
1343
1344         /* Serialize it */
1345         CtdlLogPrintf(CTDL_DEBUG, "Serializing\n");
1346         serialized_request = strdup(icalcomponent_as_ical_string(encaps));
1347         icalcomponent_free(encaps);     /* Don't need this anymore. */
1348
1349         cprintf("%d Here is the free/busy data:\n", LISTING_FOLLOWS);
1350         if (serialized_request != NULL) {
1351                 client_write(serialized_request, strlen(serialized_request));
1352                 free(serialized_request);
1353         }
1354         cprintf("\n000\n");
1355
1356         /* Go back to the room from which we came... */
1357         getroom(&CC->room, hold_rm);
1358 }
1359
1360
1361
1362 /*
1363  * Backend for ical_getics()
1364  * 
1365  * This is a ForEachMessage() callback function that searches the current room
1366  * for calendar events and adds them each into one big calendar component.
1367  */
1368 void ical_getics_backend(long msgnum, void *data) {
1369         icalcomponent *encaps, *c;
1370         struct CtdlMessage *msg = NULL;
1371         struct ical_respond_data ird;
1372
1373         encaps = (icalcomponent *)data;
1374         if (encaps == NULL) return;
1375
1376         /* Look for the calendar event... */
1377
1378         msg = CtdlFetchMessage(msgnum, 1);
1379         if (msg == NULL) return;
1380         memset(&ird, 0, sizeof ird);
1381         strcpy(ird.desired_partnum, "_HUNT_");
1382         mime_parser(msg->cm_fields['M'],
1383                 NULL,
1384                 *ical_locate_part,              /* callback function */
1385                 NULL, NULL,
1386                 (void *) &ird,                  /* user data */
1387                 0
1388         );
1389         CtdlFreeMessage(msg);
1390
1391         if (ird.cal == NULL) return;
1392
1393         /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
1394          * are responsible for "the_request"'s memory -- it will be freed
1395          * when we free "encaps".
1396          */
1397
1398         /* If the top-level component is *not* a VCALENDAR, we can drop it right
1399          * in.  This will almost never happen.
1400          */
1401         if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
1402                 icalcomponent_add_component(encaps, ird.cal);
1403         }
1404         /*
1405          * In the more likely event that we're looking at a VCALENDAR with the VEVENT
1406          * and other components encapsulated inside, we have to extract them.
1407          */
1408         else {
1409                 for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
1410                     (c != NULL);
1411                     c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
1412                         icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
1413                 }
1414                 icalcomponent_free(ird.cal);
1415         }
1416 }
1417
1418
1419
1420 /*
1421  * Retrieve all of the calendar items in the current room, and output them
1422  * as a single icalendar object.
1423  */
1424 void ical_getics(void)
1425 {
1426         icalcomponent *encaps = NULL;
1427         char *ser = NULL;
1428
1429         if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
1430            &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
1431                 cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
1432                 return;         /* Not a vCalendar-centric room */
1433         }
1434
1435         encaps = icalcomponent_new_vcalendar();
1436         if (encaps == NULL) {
1437                 CtdlLogPrintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n",
1438                         __FILE__, __LINE__);
1439                 cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
1440                 return;
1441         }
1442
1443         cprintf("%d one big calendar\n", LISTING_FOLLOWS);
1444
1445         /* Set the Product ID */
1446         icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
1447
1448         /* Set the Version Number */
1449         icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
1450
1451         /* Set the method to PUBLISH */
1452         icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
1453
1454         /* Now go through the room encapsulating all calendar items. */
1455         CtdlForEachMessage(MSGS_ALL, 0, NULL,
1456                 NULL,
1457                 NULL,
1458                 ical_getics_backend,
1459                 (void *) encaps
1460         );
1461
1462         ser = strdup(icalcomponent_as_ical_string(encaps));
1463         client_write(ser, strlen(ser));
1464         free(ser);
1465         cprintf("\n000\n");
1466         icalcomponent_free(encaps);     /* Don't need this anymore. */
1467
1468 }
1469
1470
1471 /*
1472  * Delete all of the calendar items in the current room, and replace them
1473  * with calendar items from a client-supplied data stream.
1474  */
1475 void ical_putics(void)
1476 {
1477         char *calstream = NULL;
1478         icalcomponent *cal;
1479         icalcomponent *c;
1480
1481         if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
1482            &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
1483                 cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
1484                 return;         /* Not a vCalendar-centric room */
1485         }
1486
1487         if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
1488                 cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
1489                 return;
1490         }
1491
1492         cprintf("%d Transmit data now\n", SEND_LISTING);
1493         calstream = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
1494         if (calstream == NULL) {
1495                 return;
1496         }
1497
1498         cal = icalcomponent_new_from_string(calstream);
1499         free(calstream);
1500         ical_dezonify(cal);
1501
1502         /* We got our data stream -- now do something with it. */
1503
1504         /* Delete the existing messages in the room, because we are replacing
1505          * the entire calendar with an entire new (or updated) calendar.
1506          * (Careful: this opens an S_ROOMS critical section!)
1507          */
1508         CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
1509
1510         /* If the top-level component is *not* a VCALENDAR, we can drop it right
1511          * in.  This will almost never happen.
1512          */
1513         if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
1514                 ical_write_to_cal(NULL, cal);
1515         }
1516         /*
1517          * In the more likely event that we're looking at a VCALENDAR with the VEVENT
1518          * and other components encapsulated inside, we have to extract them.
1519          */
1520         else {
1521                 for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
1522                     (c != NULL);
1523                     c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
1524                         ical_write_to_cal(NULL, c);
1525                 }
1526         }
1527
1528         icalcomponent_free(cal);
1529 }
1530
1531
1532 /*
1533  * All Citadel calendar commands from the client come through here.
1534  */
1535 void cmd_ical(char *argbuf)
1536 {
1537         char subcmd[64];
1538         long msgnum;
1539         char partnum[256];
1540         char action[256];
1541         char who[256];
1542
1543         extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
1544
1545         /* Allow "test" and "freebusy" subcommands without logging in. */
1546
1547         if (!strcasecmp(subcmd, "test")) {
1548                 cprintf("%d This server supports calendaring\n", CIT_OK);
1549                 return;
1550         }
1551
1552         if (!strcasecmp(subcmd, "freebusy")) {
1553                 extract_token(who, argbuf, 1, '|', sizeof who);
1554                 ical_freebusy(who);
1555                 return;
1556         }
1557
1558         if (!strcasecmp(subcmd, "sgi")) {
1559                 CIT_ICAL->server_generated_invitations =
1560                         (extract_int(argbuf, 1) ? 1 : 0) ;
1561                 cprintf("%d %d\n",
1562                         CIT_OK, CIT_ICAL->server_generated_invitations);
1563                 return;
1564         }
1565
1566         if (CtdlAccessCheck(ac_logged_in)) return;
1567
1568         if (!strcasecmp(subcmd, "respond")) {
1569                 msgnum = extract_long(argbuf, 1);
1570                 extract_token(partnum, argbuf, 2, '|', sizeof partnum);
1571                 extract_token(action, argbuf, 3, '|', sizeof action);
1572                 ical_respond(msgnum, partnum, action);
1573                 return;
1574         }
1575
1576         if (!strcasecmp(subcmd, "handle_rsvp")) {
1577                 msgnum = extract_long(argbuf, 1);
1578                 extract_token(partnum, argbuf, 2, '|', sizeof partnum);
1579                 extract_token(action, argbuf, 3, '|', sizeof action);
1580                 ical_handle_rsvp(msgnum, partnum, action);
1581                 return;
1582         }
1583
1584         if (!strcasecmp(subcmd, "conflicts")) {
1585                 msgnum = extract_long(argbuf, 1);
1586                 extract_token(partnum, argbuf, 2, '|', sizeof partnum);
1587                 ical_conflicts(msgnum, partnum);
1588                 return;
1589         }
1590
1591         if (!strcasecmp(subcmd, "getics")) {
1592                 ical_getics();
1593                 return;
1594         }
1595
1596         if (!strcasecmp(subcmd, "putics")) {
1597                 ical_putics();
1598                 return;
1599         }
1600
1601         cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
1602 }
1603
1604
1605
1606 /*
1607  * We don't know if the calendar room exists so we just create it at login
1608  */
1609 void ical_create_room(void)
1610 {
1611         struct ctdlroom qr;
1612         struct visit vbuf;
1613
1614         /* Create the calendar room if it doesn't already exist */
1615         create_room(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
1616
1617         /* Set expiration policy to manual; otherwise objects will be lost! */
1618         if (lgetroom(&qr, USERCALENDARROOM)) {
1619                 CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
1620                 return;
1621         }
1622         qr.QRep.expire_mode = EXPIRE_MANUAL;
1623         qr.QRdefaultview = VIEW_CALENDAR;       /* 3 = calendar view */
1624         lputroom(&qr);
1625
1626         /* Set the view to a calendar view */
1627         CtdlGetRelationship(&vbuf, &CC->user, &qr);
1628         vbuf.v_view = VIEW_CALENDAR;
1629         CtdlSetRelationship(&vbuf, &CC->user, &qr);
1630
1631         /* Create the tasks list room if it doesn't already exist */
1632         create_room(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
1633
1634         /* Set expiration policy to manual; otherwise objects will be lost! */
1635         if (lgetroom(&qr, USERTASKSROOM)) {
1636                 CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
1637                 return;
1638         }
1639         qr.QRep.expire_mode = EXPIRE_MANUAL;
1640         qr.QRdefaultview = VIEW_TASKS;
1641         lputroom(&qr);
1642
1643         /* Set the view to a task list view */
1644         CtdlGetRelationship(&vbuf, &CC->user, &qr);
1645         vbuf.v_view = VIEW_TASKS;
1646         CtdlSetRelationship(&vbuf, &CC->user, &qr);
1647
1648         /* Create the notes room if it doesn't already exist */
1649         create_room(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
1650
1651         /* Set expiration policy to manual; otherwise objects will be lost! */
1652         if (lgetroom(&qr, USERNOTESROOM)) {
1653                 CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n");
1654                 return;
1655         }
1656         qr.QRep.expire_mode = EXPIRE_MANUAL;
1657         qr.QRdefaultview = VIEW_NOTES;
1658         lputroom(&qr);
1659
1660         /* Set the view to a notes view */
1661         CtdlGetRelationship(&vbuf, &CC->user, &qr);
1662         vbuf.v_view = VIEW_NOTES;
1663         CtdlSetRelationship(&vbuf, &CC->user, &qr);
1664
1665         return;
1666 }
1667
1668
1669 /*
1670  * ical_send_out_invitations() is called by ical_saving_vevent() when it
1671  * finds a VEVENT.
1672  */
1673 void ical_send_out_invitations(icalcomponent *cal) {
1674         icalcomponent *the_request = NULL;
1675         char *serialized_request = NULL;
1676         icalcomponent *encaps = NULL;
1677         char *request_message_text = NULL;
1678         struct CtdlMessage *msg = NULL;
1679         struct recptypes *valid = NULL;
1680         char attendees_string[SIZ];
1681         int num_attendees = 0;
1682         char this_attendee[256];
1683         icalproperty *attendee = NULL;
1684         char summary_string[SIZ];
1685         icalproperty *summary = NULL;
1686         size_t reqsize;
1687
1688         if (cal == NULL) {
1689                 CtdlLogPrintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n");
1690                 return;
1691         }
1692
1693
1694         /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
1695         if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
1696                 ical_send_out_invitations(
1697                         icalcomponent_get_first_component(
1698                                 cal, ICAL_VEVENT_COMPONENT
1699                         )
1700                 );
1701                 return;
1702         }
1703
1704         /* Clone the event */
1705         the_request = icalcomponent_new_clone(cal);
1706         if (the_request == NULL) {
1707                 CtdlLogPrintf(CTDL_ERR, "ERROR: cannot clone calendar object\n");
1708                 return;
1709         }
1710
1711         /* Extract the summary string -- we'll use it as the
1712          * message subject for the request
1713          */
1714         strcpy(summary_string, "Meeting request");
1715         summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
1716         if (summary != NULL) {
1717                 if (icalproperty_get_summary(summary)) {
1718                         strcpy(summary_string,
1719                                 icalproperty_get_summary(summary) );
1720                 }
1721         }
1722
1723         /* Determine who the recipients of this message are (the attendees) */
1724         strcpy(attendees_string, "");
1725         for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
1726                 if (icalproperty_get_attendee(attendee)) {
1727                         safestrncpy(this_attendee, icalproperty_get_attendee(attendee), sizeof this_attendee);
1728                         if (!strncasecmp(this_attendee, "MAILTO:", 7)) {
1729                                 strcpy(this_attendee, &this_attendee[7]);
1730
1731                                 if (!CtdlIsMe(this_attendee, sizeof this_attendee)) {   /* don't send an invitation to myself! */
1732                                         snprintf(&attendees_string[strlen(attendees_string)],
1733                                                 sizeof(attendees_string) - strlen(attendees_string),
1734                                                 "%s, ",
1735                                                 this_attendee
1736                                         );
1737                                         ++num_attendees;
1738                                 }
1739                         }
1740                 }
1741         }
1742
1743         CtdlLogPrintf(CTDL_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string);
1744
1745         /* If there are no attendees, there are no invitations to send, so...
1746          * don't bother putting one together!  Punch out, Maverick!
1747          */
1748         if (num_attendees == 0) {
1749                 icalcomponent_free(the_request);
1750                 return;
1751         }
1752
1753         /* Encapsulate the VEVENT component into a complete VCALENDAR */
1754         encaps = icalcomponent_new_vcalendar();
1755         if (encaps == NULL) {
1756                 CtdlLogPrintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n",
1757                         __FILE__, __LINE__);
1758                 icalcomponent_free(the_request);
1759                 return;
1760         }
1761
1762         /* Set the Product ID */
1763         icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
1764
1765         /* Set the Version Number */
1766         icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
1767
1768         /* Set the method to REQUEST */
1769         icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
1770
1771         /* Now make sure all of the DTSTART and DTEND properties are UTC. */
1772         ical_dezonify(the_request);
1773
1774         /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
1775          * are responsible for "the_request"'s memory -- it will be freed
1776          * when we free "encaps".
1777          */
1778         icalcomponent_add_component(encaps, the_request);
1779
1780         /* Serialize it */
1781         serialized_request = strdup(icalcomponent_as_ical_string(encaps));
1782         icalcomponent_free(encaps);     /* Don't need this anymore. */
1783         if (serialized_request == NULL) return;
1784
1785         reqsize = strlen(serialized_request) + SIZ;
1786         request_message_text = malloc(reqsize);
1787         if (request_message_text != NULL) {
1788                 snprintf(request_message_text, reqsize,
1789                         "Content-type: text/calendar\r\n\r\n%s\r\n",
1790                         serialized_request
1791                 );
1792
1793                 msg = CtdlMakeMessage(&CC->user,
1794                         "",                     /* No single recipient here */
1795                         "",                     /* No single recipient here */
1796                         CC->room.QRname, 0, FMT_RFC822,
1797                         "",
1798                         "",
1799                         summary_string,         /* Use summary for subject */
1800                         NULL,
1801                         request_message_text,
1802                         NULL);
1803         
1804                 if (msg != NULL) {
1805                         valid = validate_recipients(attendees_string, NULL, 0);
1806                         CtdlSubmitMsg(msg, valid, "");
1807                         CtdlFreeMessage(msg);
1808                         free_recipients(valid);
1809                 }
1810         }
1811         free(serialized_request);
1812 }
1813
1814
1815 /*
1816  * When a calendar object is being saved, determine whether it's a VEVENT
1817  * and the user saving it is the organizer.  If so, send out invitations
1818  * to any listed attendees.
1819  *
1820  */
1821 void ical_saving_vevent(icalcomponent *cal) {
1822         icalcomponent *c;
1823         icalproperty *organizer = NULL;
1824         char organizer_string[SIZ];
1825
1826         CtdlLogPrintf(CTDL_DEBUG, "ical_saving_vevent() has been called!\n");
1827
1828         /* Don't send out invitations unless the client wants us to. */
1829         if (CIT_ICAL->server_generated_invitations == 0) {
1830                 return;
1831         }
1832
1833         /* Don't send out invitations if we've been asked not to. */
1834         if (CIT_ICAL->avoid_sending_invitations > 0) {
1835                 return;
1836         }
1837
1838         strcpy(organizer_string, "");
1839         /*
1840          * The VEVENT subcomponent is the one we're interested in.
1841          * Send out invitations if, and only if, this user is the Organizer.
1842          */
1843         if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
1844                 organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
1845                 if (organizer != NULL) {
1846                         if (icalproperty_get_organizer(organizer)) {
1847                                 strcpy(organizer_string,
1848                                         icalproperty_get_organizer(organizer));
1849                         }
1850                 }
1851                 if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
1852                         strcpy(organizer_string, &organizer_string[7]);
1853                         striplt(organizer_string);
1854                         /*
1855                          * If the user saving the event is listed as the
1856                          * organizer, then send out invitations.
1857                          */
1858                         if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
1859                                 ical_send_out_invitations(cal);
1860                         }
1861                 }
1862         }
1863
1864         /* If the component has subcomponents, recurse through them. */
1865         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
1866             (c != NULL);
1867             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
1868                 /* Recursively process subcomponent */
1869                 ical_saving_vevent(c);
1870         }
1871
1872 }
1873
1874
1875
1876 /*
1877  * Back end for ical_obj_beforesave()
1878  * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
1879  * the summary of the event (becomes message subject),
1880  * and the start time (becomes message date/time).
1881  */
1882 void ical_ctdl_set_exclusive_msgid(char *name, char *filename, char *partnum,
1883                 char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
1884                 char *encoding, void *cbuserdata)
1885 {
1886         icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
1887         icalproperty *p;
1888         struct icalmessagemod *imm;
1889         char new_uid[SIZ];
1890
1891         imm = (struct icalmessagemod *)cbuserdata;
1892
1893         /* We're only interested in calendar data. */
1894         if (  (strcasecmp(cbtype, "text/calendar"))
1895            && (strcasecmp(cbtype, "application/ics")) ) {
1896                 return;
1897         }
1898
1899         /* Hunt for the UID and drop it in
1900          * the "user data" pointer for the MIME parser.  When
1901          * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
1902          * to that string.
1903          */
1904         whole_cal = icalcomponent_new_from_string(content);
1905         cal = whole_cal;
1906         if (cal != NULL) {
1907                 if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
1908                         nested_event = icalcomponent_get_first_component(
1909                                 cal, ICAL_VEVENT_COMPONENT);
1910                         if (nested_event != NULL) {
1911                                 cal = nested_event;
1912                         }
1913                         else {
1914                                 nested_todo = icalcomponent_get_first_component(
1915                                         cal, ICAL_VTODO_COMPONENT);
1916                                 if (nested_todo != NULL) {
1917                                         cal = nested_todo;
1918                                 }
1919                         }
1920                 }
1921                 
1922                 if (cal != NULL) {
1923                         p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
1924                         if (p == NULL) {
1925                                 /* If there's no uid we must generate one */
1926                                 generate_uuid(new_uid);
1927                                 icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
1928                                 p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
1929                         }
1930                         if (p != NULL) {
1931                                 strcpy(imm->uid, icalproperty_get_comment(p));
1932                         }
1933                         p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
1934                         if (p != NULL) {
1935                                 strcpy(imm->subject, icalproperty_get_comment(p));
1936                         }
1937                         p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
1938                         if (p != NULL) {
1939                                 imm->dtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
1940                         }
1941                 }
1942                 icalcomponent_free(cal);
1943                 if (whole_cal != cal) {
1944                         icalcomponent_free(whole_cal);
1945                 }
1946         }
1947 }
1948
1949
1950
1951
1952 /*
1953  * See if we need to prevent the object from being saved (we don't allow
1954  * MIME types other than text/calendar in "calendar" or "tasks"  rooms).  Also,
1955  * when saving an event to the calendar, set the message's Citadel exclusive
1956  * message ID to the UID of the object.  This causes our replication checker to
1957  * automatically delete any existing instances of the same object.  (Isn't
1958  * that cool?)
1959  *
1960  * We also set the message's Subject to the event summary, and the Date/time to
1961  * the event start time.
1962  */
1963 int ical_obj_beforesave(struct CtdlMessage *msg)
1964 {
1965         struct icalmessagemod imm;
1966
1967         /* First determine if this is a calendar or tasks room */
1968         if (  (CC->room.QRdefaultview != VIEW_CALENDAR)
1969            && (CC->room.QRdefaultview != VIEW_TASKS)
1970         ) {
1971                 return(0);              /* Not a vCalendar-centric room */
1972         }
1973
1974         /* It must be an RFC822 message! */
1975         if (msg->cm_format_type != 4) {
1976                 CtdlLogPrintf(CTDL_DEBUG, "Rejecting non-RFC822 message\n");
1977                 return(1);              /* You tried to save a non-RFC822 message! */
1978         }
1979
1980         if (msg->cm_fields['M'] == NULL) {
1981                 return(1);              /* You tried to save a null message! */
1982         }
1983
1984         memset(&imm, 0, sizeof(struct icalmessagemod));
1985         
1986         /* Do all of our lovely back-end parsing */
1987         mime_parser(msg->cm_fields['M'],
1988                 NULL,
1989                 *ical_ctdl_set_exclusive_msgid,
1990                 NULL, NULL,
1991                 (void *)&imm,
1992                 0
1993         );
1994
1995         if (!IsEmptyStr(imm.uid)) {
1996                 if (msg->cm_fields['E'] != NULL) {
1997                         free(msg->cm_fields['E']);
1998                 }
1999                 msg->cm_fields['E'] = strdup(imm.uid);
2000                 CtdlLogPrintf(CTDL_DEBUG, "Saving calendar UID <%s>\n", msg->cm_fields['E']);
2001         }
2002         if (!IsEmptyStr(imm.subject)) {
2003                 if (msg->cm_fields['U'] != NULL) {
2004                         free(msg->cm_fields['U']);
2005                 }
2006                 msg->cm_fields['U'] = strdup(imm.subject);
2007         }
2008         if (imm.dtstart > 0) {
2009                 if (msg->cm_fields['T'] != NULL) {
2010                         free(msg->cm_fields['T']);
2011                 }
2012                 msg->cm_fields['T'] = strdup("000000000000000000");
2013                 sprintf(msg->cm_fields['T'], "%ld", imm.dtstart);
2014         }
2015
2016         return(0);
2017 }
2018
2019
2020 /*
2021  * Things we need to do after saving a calendar event.
2022  */
2023 void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
2024                 char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
2025                 char *encoding, void *cbuserdata)
2026 {
2027         icalcomponent *cal;
2028
2029         /* We're only interested in calendar items here. */
2030         if (  (strcasecmp(cbtype, "text/calendar"))
2031            && (strcasecmp(cbtype, "application/ics")) ) {
2032                 return;
2033         }
2034
2035         /* Hunt for the UID and drop it in
2036          * the "user data" pointer for the MIME parser.  When
2037          * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
2038          * to that string.
2039          */
2040         if (  (!strcasecmp(cbtype, "text/calendar"))
2041            || (!strcasecmp(cbtype, "application/ics")) ) {
2042                 cal = icalcomponent_new_from_string(content);
2043                 if (cal != NULL) {
2044                         ical_saving_vevent(cal);
2045                         icalcomponent_free(cal);
2046                 }
2047         }
2048 }
2049
2050
2051 /* 
2052  * Things we need to do after saving a calendar event.
2053  * (This will start back end tasks such as automatic generation of invitations,
2054  * if such actions are appropriate.)
2055  */
2056 int ical_obj_aftersave(struct CtdlMessage *msg)
2057 {
2058         char roomname[ROOMNAMELEN];
2059
2060         /*
2061          * If this isn't the Calendar> room, no further action is necessary.
2062          */
2063
2064         /* First determine if this is our room */
2065         MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
2066         if (strcasecmp(roomname, CC->room.QRname)) {
2067                 return(0);      /* Not the Calendar room -- don't do anything. */
2068         }
2069
2070         /* It must be an RFC822 message! */
2071         if (msg->cm_format_type != 4) return(1);
2072
2073         /* Reject null messages */
2074         if (msg->cm_fields['M'] == NULL) return(1);
2075         
2076         /* Now recurse through it looking for our icalendar data */
2077         mime_parser(msg->cm_fields['M'],
2078                 NULL,
2079                 *ical_obj_aftersave_backend,
2080                 NULL, NULL,
2081                 NULL,
2082                 0
2083         );
2084
2085         return(0);
2086 }
2087
2088
2089 void ical_session_startup(void) {
2090         CIT_ICAL = malloc(sizeof(struct cit_ical));
2091         memset(CIT_ICAL, 0, sizeof(struct cit_ical));
2092 }
2093
2094 void ical_session_shutdown(void) {
2095         free(CIT_ICAL);
2096 }
2097
2098
2099 /*
2100  * Back end for ical_fixed_output()
2101  */
2102 void ical_fixed_output_backend(icalcomponent *cal,
2103                         int recursion_level
2104 ) {
2105         icalcomponent *c;
2106         icalproperty *p;
2107         char buf[256];
2108
2109         p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
2110         if (p != NULL) {
2111                 cprintf("%s\n", (const char *)icalproperty_get_comment(p));
2112         }
2113
2114         p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
2115         if (p != NULL) {
2116                 cprintf("%s\n", (const char *)icalproperty_get_comment(p));
2117         }
2118
2119         p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
2120         if (p != NULL) {
2121                 cprintf("%s\n", (const char *)icalproperty_get_comment(p));
2122         }
2123
2124         /* If the component has attendees, iterate through them. */
2125         for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
2126                 safestrncpy(buf, icalproperty_get_attendee(p), sizeof buf);
2127                 if (!strncasecmp(buf, "MAILTO:", 7)) {
2128
2129                         /* screen name or email address */
2130                         strcpy(buf, &buf[7]);
2131                         striplt(buf);
2132                         cprintf("%s ", buf);
2133                 }
2134                 cprintf("\n");
2135         }
2136
2137         /* If the component has subcomponents, recurse through them. */
2138         for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
2139             (c != 0);
2140             c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
2141                 /* Recursively process subcomponent */
2142                 ical_fixed_output_backend(c, recursion_level+1);
2143         }
2144 }
2145
2146
2147
2148 /*
2149  * Function to output vcalendar data as plain text.  Nobody uses MSG0
2150  * anymore, so really this is just so we expose the vCard data to the full
2151  * text indexer.
2152  */
2153 void ical_fixed_output(char *ptr, int len) {
2154         icalcomponent *cal;
2155         char *stringy_cal;
2156
2157         stringy_cal = malloc(len + 1);
2158         safestrncpy(stringy_cal, ptr, len + 1);
2159         cal = icalcomponent_new_from_string(stringy_cal);
2160         free(stringy_cal);
2161
2162         if (cal == NULL) {
2163                 return;
2164         }
2165
2166         ical_dezonify(cal);
2167         ical_fixed_output_backend(cal, 0);
2168
2169         /* Free the memory we obtained from libical's constructor */
2170         icalcomponent_free(cal);
2171 }
2172
2173
2174
2175 void serv_calendar_destroy(void)
2176 {
2177         icaltimezone_free_builtin_timezones();
2178 }
2179
2180 /*
2181  * Register this module with the Citadel server.
2182  */
2183 CTDL_MODULE_INIT(calendar)
2184 {
2185         if (!threading)
2186         {
2187                 CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
2188                 CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
2189                 CtdlRegisterSessionHook(ical_create_room, EVT_LOGIN);
2190                 CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands");
2191                 CtdlRegisterSessionHook(ical_session_startup, EVT_START);
2192                 CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP);
2193                 CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
2194                 CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output);
2195                 CtdlRegisterCleanupHook(serv_calendar_destroy);
2196         }
2197         
2198         /* return our Subversion id for the Log */
2199         return "$Id$";
2200 }
2201