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