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