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