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