Back to index

citadel  8.12
serv_calendar.c
Go to the documentation of this file.
00001 /* 
00002  * This module implements iCalendar object processing and the Calendar>
00003  * room on a Citadel server.  It handles iCalendar objects using the
00004  * iTIP protocol.  See RFCs 2445 and 2446.
00005  *
00006  *
00007  * Copyright (c) 1987-2011 by the citadel.org team
00008  *
00009  *  This program is open source software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 3 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  This program is distributed in the hope that it will be useful,
00015  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  *  GNU General Public License for more details.
00018  *
00019  *  You should have received a copy of the GNU General Public License
00020  *  along with this program; if not, write to the Free Software
00021  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00022  */
00023 
00024 #define PRODID "-//Citadel//NONSGML Citadel Calendar//EN"
00025 
00026 #include "ctdl_module.h"
00027 
00028 #include <libical/ical.h>
00029 
00030 #include "msgbase.h"
00031 #include "internet_addressing.h"
00032 #include "serv_calendar.h"
00033 #include "euidindex.h"
00034 #include "ical_dezonify.h"
00035 
00036 
00037 
00038 struct ical_respond_data {
00039        char desired_partnum[SIZ];
00040        icalcomponent *cal;
00041 };
00042 
00043 
00044 /*
00045  * Utility function to create a new VCALENDAR component with some of the
00046  * required fields already set the way we like them.
00047  */
00048 icalcomponent *icalcomponent_new_citadel_vcalendar(void) {
00049        icalcomponent *encaps;
00050 
00051        encaps = icalcomponent_new_vcalendar();
00052        if (encaps == NULL) {
00053               syslog(LOG_CRIT, "ERROR: could not allocate component!\n");
00054               return NULL;
00055        }
00056 
00057        /* Set the Product ID */
00058        icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
00059 
00060        /* Set the Version Number */
00061        icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
00062 
00063        return(encaps);
00064 }
00065 
00066 
00067 /*
00068  * Utility function to encapsulate a subcomponent into a full VCALENDAR
00069  */
00070 icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
00071        icalcomponent *encaps;
00072 
00073        /* If we're already looking at a full VCALENDAR component,
00074         * don't bother ... just return itself.
00075         */
00076        if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
00077               return subcomp;
00078        }
00079 
00080        /* Encapsulate the VEVENT component into a complete VCALENDAR */
00081        encaps = icalcomponent_new_citadel_vcalendar();
00082        if (encaps == NULL) return NULL;
00083 
00084        /* Encapsulate the subcomponent inside */
00085        icalcomponent_add_component(encaps, subcomp);
00086 
00087        /* Return the object we just created. */
00088        return(encaps);
00089 }
00090 
00091 
00092 
00093 
00094 /*
00095  * Write a calendar object into the specified user's calendar room.
00096  * If the supplied user is NULL, this function writes the calendar object
00097  * to the currently selected room.
00098  */
00099 void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) {
00100        char *ser = NULL;
00101        icalcomponent *encaps = NULL;
00102        struct CtdlMessage *msg = NULL;
00103        icalcomponent *tmp=NULL;
00104 
00105        if (cal == NULL) return;
00106 
00107        /* If the supplied object is a subcomponent, encapsulate it in
00108         * a full VCALENDAR component, and save that instead.
00109         */
00110        if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
00111               tmp = icalcomponent_new_clone(cal);
00112               encaps = ical_encapsulate_subcomponent(tmp);
00113               ical_write_to_cal(u, encaps);
00114               icalcomponent_free(tmp);
00115               icalcomponent_free(encaps);
00116               return;
00117        }
00118 
00119        ser = icalcomponent_as_ical_string_r(cal);
00120        if (ser == NULL) return;
00121 
00122        /* If the caller supplied a user, write to that user's default calendar room */
00123        if (u) {
00124               /* This handy API function does all the work for us. */
00125               CtdlWriteObject(USERCALENDARROOM,  /* which room */
00126                      "text/calendar",     /* MIME type */
00127                      ser,                 /* data */
00128                      strlen(ser)+1,              /* length */
00129                      u,                   /* which user */
00130                      0,                   /* not binary */
00131                      0,                   /* don't delete others of this type */
00132                      0                    /* no flags */
00133               );
00134        }
00135 
00136        /* If the caller did not supply a user, write to the currently selected room */
00137        if (!u) {
00138               msg = malloc(sizeof(struct CtdlMessage));
00139               memset(msg, 0, sizeof(struct CtdlMessage));
00140               msg->cm_magic = CTDLMESSAGE_MAGIC;
00141               msg->cm_anon_type = MES_NORMAL;
00142               msg->cm_format_type = 4;
00143               msg->cm_fields['A'] = strdup(CC->user.fullname);
00144               msg->cm_fields['O'] = strdup(CC->room.QRname);
00145               msg->cm_fields['N'] = strdup(config.c_nodename);
00146               msg->cm_fields['H'] = strdup(config.c_humannode);
00147               msg->cm_fields['M'] = malloc(strlen(ser) + 40);
00148               strcpy(msg->cm_fields['M'], "Content-type: text/calendar\r\n\r\n");
00149               strcat(msg->cm_fields['M'], ser);
00150        
00151               /* Now write the data */
00152               CtdlSubmitMsg(msg, NULL, "", QP_EADDR);
00153               CtdlFreeMessage(msg);
00154        }
00155 
00156        /* In either case, now we can free the serialized calendar object */
00157        free(ser);
00158 }
00159 
00160 
00161 /*
00162  * Send a reply to a meeting invitation.
00163  *
00164  * 'request' is the invitation to reply to.
00165  * 'action' is the string "accept" or "decline" or "tentative".
00166  *
00167  */
00168 void ical_send_a_reply(icalcomponent *request, char *action) {
00169        icalcomponent *the_reply = NULL;
00170        icalcomponent *vevent = NULL;
00171        icalproperty *attendee = NULL;
00172        char attendee_string[SIZ];
00173        icalproperty *organizer = NULL;
00174        char organizer_string[SIZ];
00175        icalproperty *summary = NULL;
00176        char summary_string[SIZ];
00177        icalproperty *me_attend = NULL;
00178        struct recptypes *recp = NULL;
00179        icalparameter *partstat = NULL;
00180        char *serialized_reply = NULL;
00181        char *reply_message_text = NULL;
00182        const char *ch;
00183        struct CtdlMessage *msg = NULL;
00184        struct recptypes *valid = NULL;
00185 
00186        strcpy(organizer_string, "");
00187        strcpy(summary_string, "Calendar item");
00188 
00189        if (request == NULL) {
00190               syslog(LOG_ERR, "ERROR: trying to reply to NULL event?\n");
00191               return;
00192        }
00193 
00194        the_reply = icalcomponent_new_clone(request);
00195        if (the_reply == NULL) {
00196               syslog(LOG_ERR, "ERROR: cannot clone request\n");
00197               return;
00198        }
00199 
00200        /* Change the method from REQUEST to REPLY */
00201        icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY);
00202 
00203        vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT);
00204        if (vevent != NULL) {
00205               /* Hunt for attendees, removing ones that aren't us.
00206                * (Actually, remove them all, cloning our own one so we can
00207                * re-insert it later)
00208                */
00209               while (attendee = icalcomponent_get_first_property(vevent,
00210                   ICAL_ATTENDEE_PROPERTY), (attendee != NULL)
00211               ) {
00212                      ch = icalproperty_get_attendee(attendee);
00213                      if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
00214                             safestrncpy(attendee_string, ch + 7, sizeof (attendee_string));
00215                             striplt(attendee_string);
00216                             recp = validate_recipients(attendee_string, NULL, 0);
00217                             if (recp != NULL) {
00218                                    if (!strcasecmp(recp->recp_local, CC->user.fullname)) {
00219                                           if (me_attend) icalproperty_free(me_attend);
00220                                           me_attend = icalproperty_new_clone(attendee);
00221                                    }
00222                                    free_recipients(recp);
00223                             }
00224                      }
00225 
00226                      /* Remove it... */
00227                      icalcomponent_remove_property(vevent, attendee);
00228                      icalproperty_free(attendee);
00229               }
00230 
00231               /* We found our own address in the attendee list. */
00232               if (me_attend) {
00233                      /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */
00234                      icalproperty_remove_parameter(me_attend, ICAL_PARTSTAT_PARAMETER);
00235 
00236                      if (!strcasecmp(action, "accept")) {
00237                             partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED);
00238                      }
00239                      else if (!strcasecmp(action, "decline")) {
00240                             partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
00241                      }
00242                      else if (!strcasecmp(action, "tentative")) {
00243                             partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE);
00244                      }
00245 
00246                      if (partstat) icalproperty_add_parameter(me_attend, partstat);
00247 
00248                      /* Now insert it back into the vevent. */
00249                      icalcomponent_add_property(vevent, me_attend);
00250               }
00251 
00252               /* Figure out who to send this thing to */
00253               organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY);
00254               if (organizer != NULL) {
00255                      if (icalproperty_get_organizer(organizer)) {
00256                             strcpy(organizer_string,
00257                                    icalproperty_get_organizer(organizer) );
00258                      }
00259               }
00260               if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
00261                      strcpy(organizer_string, &organizer_string[7]);
00262                      striplt(organizer_string);
00263               } else {
00264                      strcpy(organizer_string, "");
00265               }
00266 
00267               /* Extract the summary string -- we'll use it as the
00268                * message subject for the reply
00269                */
00270               summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY);
00271               if (summary != NULL) {
00272                      if (icalproperty_get_summary(summary)) {
00273                             strcpy(summary_string,
00274                                    icalproperty_get_summary(summary) );
00275                      }
00276               }
00277        }
00278 
00279        /* Now generate the reply message and send it out. */
00280        serialized_reply = icalcomponent_as_ical_string_r(the_reply);
00281        icalcomponent_free(the_reply);     /* don't need this anymore */
00282        if (serialized_reply == NULL) return;
00283 
00284        reply_message_text = malloc(strlen(serialized_reply) + SIZ);
00285        if (reply_message_text != NULL) {
00286               sprintf(reply_message_text,
00287                      "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
00288                      serialized_reply
00289               );
00290 
00291               msg = CtdlMakeMessage(&CC->user,
00292                      organizer_string,    /* to */
00293                      "",                  /* cc */
00294                      CC->room.QRname, 0, FMT_RFC822,
00295                      "",
00296                      "",
00297                      summary_string,             /* Use summary for subject */
00298                      NULL,
00299                      reply_message_text,
00300                      NULL);
00301        
00302               if (msg != NULL) {
00303                      valid = validate_recipients(organizer_string, NULL, 0);
00304                      CtdlSubmitMsg(msg, valid, "", QP_EADDR);
00305                      CtdlFreeMessage(msg);
00306                      free_recipients(valid);
00307               }
00308        }
00309        free(serialized_reply);
00310 }
00311 
00312 
00313 
00314 /*
00315  * Callback function for mime parser that hunts for calendar content types
00316  * and turns them into calendar objects.  If something is found, it is placed
00317  * in ird->cal, and the caller now owns that memory and is responsible for freeing it.
00318  */
00319 void ical_locate_part(char *name, char *filename, char *partnum, char *disp,
00320               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
00321               char *cbid, void *cbuserdata) {
00322 
00323        struct ical_respond_data *ird = NULL;
00324 
00325        ird = (struct ical_respond_data *) cbuserdata;
00326 
00327        /* desired_partnum can be set to "_HUNT_" to have it just look for
00328         * the first part with a content type of text/calendar.  Otherwise
00329         * we have to only process the right one.
00330         */
00331        if (strcasecmp(ird->desired_partnum, "_HUNT_")) {
00332               if (strcasecmp(partnum, ird->desired_partnum)) {
00333                      return;
00334               }
00335        }
00336 
00337        if (  (strcasecmp(cbtype, "text/calendar"))
00338           && (strcasecmp(cbtype, "application/ics")) ) {
00339               return;
00340        }
00341 
00342        if (ird->cal != NULL) {
00343               icalcomponent_free(ird->cal);
00344               ird->cal = NULL;
00345        }
00346 
00347        ird->cal = icalcomponent_new_from_string(content);
00348 }
00349 
00350 
00351 /*
00352  * Respond to a meeting request.
00353  */
00354 void ical_respond(long msgnum, char *partnum, char *action) {
00355        struct CtdlMessage *msg = NULL;
00356        struct ical_respond_data ird;
00357 
00358        if (
00359           (strcasecmp(action, "accept"))
00360           && (strcasecmp(action, "decline"))
00361        ) {
00362               cprintf("%d Action must be 'accept' or 'decline'\n",
00363                      ERROR + ILLEGAL_VALUE
00364               );
00365               return;
00366        }
00367 
00368        msg = CtdlFetchMessage(msgnum, 1);
00369        if (msg == NULL) {
00370               cprintf("%d Message %ld not found.\n",
00371                      ERROR + ILLEGAL_VALUE,
00372                      (long)msgnum
00373               );
00374               return;
00375        }
00376 
00377        memset(&ird, 0, sizeof ird);
00378        strcpy(ird.desired_partnum, partnum);
00379        mime_parser(msg->cm_fields['M'],
00380               NULL,
00381               *ical_locate_part,          /* callback function */
00382               NULL, NULL,
00383               (void *) &ird,                     /* user data */
00384               0
00385        );
00386 
00387        /* We're done with the incoming message, because we now have a
00388         * calendar object in memory.
00389         */
00390        CtdlFreeMessage(msg);
00391 
00392        /*
00393         * Here is the real meat of this function.  Handle the event.
00394         */
00395        if (ird.cal != NULL) {
00396               /* Save this in the user's calendar if necessary */
00397               if (!strcasecmp(action, "accept")) {
00398                      ical_write_to_cal(&CC->user, ird.cal);
00399               }
00400 
00401               /* Send a reply if necessary */
00402               if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) {
00403                      ical_send_a_reply(ird.cal, action);
00404               }
00405 
00406               /* We used to delete the invitation after handling it.
00407                * We don't do that anymore, but here is the code that handled it:
00408                * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
00409                */
00410 
00411               /* Free the memory we allocated and return a response. */
00412               icalcomponent_free(ird.cal);
00413               ird.cal = NULL;
00414               cprintf("%d ok\n", CIT_OK);
00415               return;
00416        }
00417        else {
00418               cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
00419               return;
00420        }
00421 
00422        /* should never get here */
00423 }
00424 
00425 
00426 /*
00427  * Figure out the UID of the calendar event being referred to in a
00428  * REPLY object.  This function is recursive.
00429  */
00430 void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) {
00431        icalcomponent *subcomponent;
00432        icalproperty *p;
00433 
00434        /* If this object is a REPLY, then extract the UID. */
00435        if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
00436               p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY);
00437               if (p != NULL) {
00438                      strcpy(uidbuf, icalproperty_get_comment(p));
00439               }
00440        }
00441 
00442        /* Otherwise, recurse through any VEVENT subcomponents.  We do NOT want the
00443         * UID of the reply; we want the UID of the invitation being replied to.
00444         */
00445        for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT);
00446            subcomponent != NULL;
00447            subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) {
00448               ical_learn_uid_of_reply(uidbuf, subcomponent);
00449        }
00450 }
00451 
00452 
00453 /*
00454  * ical_update_my_calendar_with_reply() refers to this callback function; when we
00455  * locate the message containing the calendar event we're replying to, this function
00456  * gets called.  It basically just sticks the message number in a supplied buffer.
00457  */
00458 void ical_hunt_for_event_to_update(long msgnum, void *data) {
00459        long *msgnumptr;
00460 
00461        msgnumptr = (long *) data;
00462        *msgnumptr = msgnum;
00463 }
00464 
00465 
00466 struct original_event_container {
00467        icalcomponent *c;
00468 };
00469 
00470 /*
00471  * Callback function for mime parser that hunts for calendar content types
00472  * and turns them into calendar objects (called by ical_update_my_calendar_with_reply()
00473  * to fetch the object being updated)
00474  */
00475 void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp,
00476               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
00477               char *cbid, void *cbuserdata) {
00478 
00479        struct original_event_container *oec = NULL;
00480 
00481        if (  (strcasecmp(cbtype, "text/calendar"))
00482           && (strcasecmp(cbtype, "application/ics")) ) {
00483               return;
00484        }
00485        oec = (struct original_event_container *) cbuserdata;
00486        if (oec->c != NULL) {
00487               icalcomponent_free(oec->c);
00488        }
00489        oec->c = icalcomponent_new_from_string(content);
00490 }
00491 
00492 
00493 /*
00494  * Merge updated attendee information from a REPLY into an existing event.
00495  */
00496 void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) {
00497        icalcomponent *c;
00498        icalproperty *e_attendee, *r_attendee;
00499 
00500        /* First things first.  If we're not looking at a VEVENT component,
00501         * recurse through subcomponents until we find one.
00502         */
00503        if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) {
00504               for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT);
00505                   c != NULL;
00506                   c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) {
00507                      ical_merge_attendee_reply(c, reply);
00508               }
00509               return;
00510        }
00511 
00512        /* Now do the same thing with the reply.
00513         */
00514        if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) {
00515               for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT);
00516                   c != NULL;
00517                   c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) {
00518                      ical_merge_attendee_reply(event, c);
00519               }
00520               return;
00521        }
00522 
00523        /* Clone the reply, because we're going to rip its guts out. */
00524        reply = icalcomponent_new_clone(reply);
00525 
00526        /* At this point we're looking at the correct subcomponents.
00527         * Iterate through the attendees looking for a match.
00528         */
00529 STARTOVER:
00530        for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY);
00531            e_attendee != NULL;
00532            e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) {
00533 
00534               for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY);
00535                   r_attendee != NULL;
00536                   r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) {
00537 
00538                      /* Check to see if these two attendees match...
00539                       */
00540                      const char *e, *r;
00541                      e = icalproperty_get_attendee(e_attendee);
00542                      r = icalproperty_get_attendee(r_attendee);
00543 
00544                      if ((e != NULL) && 
00545                          (r != NULL) && 
00546                          !strcasecmp(e, r)) {
00547                             /* ...and if they do, remove the attendee from the event
00548                              * and replace it with the attendee from the reply.  (The
00549                              * reply's copy will have the same address, but an updated
00550                              * status.)
00551                              */
00552                             icalcomponent_remove_property(event, e_attendee);
00553                             icalproperty_free(e_attendee);
00554                             icalcomponent_remove_property(reply, r_attendee);
00555                             icalcomponent_add_property(event, r_attendee);
00556 
00557                             /* Since we diddled both sets of attendees, we have to start
00558                              * the iteration over again.  This will not create an infinite
00559                              * loop because we removed the attendee from the reply.  (That's
00560                              * why we cloned the reply, and that's what we mean by "ripping
00561                              * its guts out.")
00562                              */
00563                             goto STARTOVER;
00564                      }
00565        
00566               }
00567        }
00568 
00569        /* Free the *clone* of the reply. */
00570        icalcomponent_free(reply);
00571 }
00572 
00573 
00574 
00575 
00576 /*
00577  * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a
00578  * calendar event.  The object has already been deserialized for us; all
00579  * we have to do here is hunt for the event in our calendar, merge in the
00580  * updated attendee status, and save it again.
00581  *
00582  * This function returns 0 on success, 1 if the event was not found in the
00583  * user's calendar, or 2 if an internal error occurred.
00584  */
00585 int ical_update_my_calendar_with_reply(icalcomponent *cal) {
00586        char uid[SIZ];
00587        char hold_rm[ROOMNAMELEN];
00588        long msgnum_being_replaced = 0;
00589        struct CtdlMessage *msg = NULL;
00590        struct original_event_container oec;
00591        icalcomponent *original_event;
00592        char *serialized_event = NULL;
00593        char roomname[ROOMNAMELEN];
00594        char *message_text = NULL;
00595 
00596        /* Figure out just what event it is we're dealing with */
00597        strcpy(uid, "--==<< InVaLiD uId >>==--");
00598        ical_learn_uid_of_reply(uid, cal);
00599        syslog(LOG_DEBUG, "UID of event being replied to is <%s>\n", uid);
00600 
00601        strcpy(hold_rm, CC->room.QRname);  /* save current room */
00602 
00603        if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
00604               CtdlGetRoom(&CC->room, hold_rm);
00605               syslog(LOG_CRIT, "cannot get user calendar room\n");
00606               return(2);
00607        }
00608 
00609        /*
00610         * Look in the EUID index for a message with
00611         * the Citadel EUID set to the value we're looking for.  Since
00612         * Citadel always sets the message EUID to the iCalendar UID of
00613         * the event, this will work.
00614         */
00615        msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room);
00616 
00617        CtdlGetRoom(&CC->room, hold_rm);   /* return to saved room */
00618 
00619        syslog(LOG_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced);
00620        if (msgnum_being_replaced == 0) {
00621               return(1);                  /* no calendar event found */
00622        }
00623 
00624        /* Now we know the ID of the message containing the event being updated.
00625         * We don't actually have to delete it; that'll get taken care of by the
00626         * server when we save another event with the same UID.  This just gives
00627         * us the ability to load the event into memory so we can diddle the
00628         * attendees.
00629         */
00630        msg = CtdlFetchMessage(msgnum_being_replaced, 1);
00631        if (msg == NULL) {
00632               return(2);                  /* internal error */
00633        }
00634        oec.c = NULL;
00635        mime_parser(msg->cm_fields['M'],
00636               NULL,
00637               *ical_locate_original_event,       /* callback function */
00638               NULL, NULL,
00639               &oec,                       /* user data */
00640               0
00641        );
00642        CtdlFreeMessage(msg);
00643 
00644        original_event = oec.c;
00645        if (original_event == NULL) {
00646               syslog(LOG_ERR, "ERROR: Original_component is NULL.\n");
00647               return(2);
00648        }
00649 
00650        /* Merge the attendee's updated status into the event */
00651        ical_merge_attendee_reply(original_event, cal);
00652 
00653        /* Serialize it */
00654        serialized_event = icalcomponent_as_ical_string_r(original_event);
00655        icalcomponent_free(original_event);       /* Don't need this anymore. */
00656        if (serialized_event == NULL) return(2);
00657 
00658        CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
00659 
00660        message_text = malloc(strlen(serialized_event) + SIZ);
00661        if (message_text != NULL) {
00662               sprintf(message_text,
00663                      "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n",
00664                      serialized_event
00665               );
00666 
00667               msg = CtdlMakeMessage(&CC->user,
00668                      "",                  /* No recipient */
00669                      "",                  /* No recipient */
00670                      roomname,
00671                      0, FMT_RFC822,
00672                      "",
00673                      "",
00674                      "",           /* no subject */
00675                      NULL,
00676                      message_text,
00677                      NULL);
00678        
00679               if (msg != NULL) {
00680                      CIT_ICAL->avoid_sending_invitations = 1;
00681                      CtdlSubmitMsg(msg, NULL, roomname, QP_EADDR);
00682                      CtdlFreeMessage(msg);
00683                      CIT_ICAL->avoid_sending_invitations = 0;
00684               }
00685        }
00686        free(serialized_event);
00687        return(0);
00688 }
00689 
00690 
00691 /*
00692  * Handle an incoming RSVP for an event.  (This is the server subcommand part; it
00693  * simply extracts the calendar object from the message, deserializes it, and
00694  * passes it up to ical_update_my_calendar_with_reply() for processing.
00695  */
00696 void ical_handle_rsvp(long msgnum, char *partnum, char *action) {
00697        struct CtdlMessage *msg = NULL;
00698        struct ical_respond_data ird;
00699        int ret;
00700 
00701        if (
00702           (strcasecmp(action, "update"))
00703           && (strcasecmp(action, "ignore"))
00704        ) {
00705               cprintf("%d Action must be 'update' or 'ignore'\n",
00706                      ERROR + ILLEGAL_VALUE
00707               );
00708               return;
00709        }
00710 
00711        msg = CtdlFetchMessage(msgnum, 1);
00712        if (msg == NULL) {
00713               cprintf("%d Message %ld not found.\n",
00714                      ERROR + ILLEGAL_VALUE,
00715                      (long)msgnum
00716               );
00717               return;
00718        }
00719 
00720        memset(&ird, 0, sizeof ird);
00721        strcpy(ird.desired_partnum, partnum);
00722        mime_parser(msg->cm_fields['M'],
00723               NULL,
00724               *ical_locate_part,          /* callback function */
00725               NULL, NULL,
00726               (void *) &ird,                     /* user data */
00727               0
00728        );
00729 
00730        /* We're done with the incoming message, because we now have a
00731         * calendar object in memory.
00732         */
00733        CtdlFreeMessage(msg);
00734 
00735        /*
00736         * Here is the real meat of this function.  Handle the event.
00737         */
00738        if (ird.cal != NULL) {
00739               /* Update the user's calendar if necessary */
00740               if (!strcasecmp(action, "update")) {
00741                      ret = ical_update_my_calendar_with_reply(ird.cal);
00742                      if (ret == 0) {
00743                             cprintf("%d Your calendar has been updated with this reply.\n",
00744                                    CIT_OK);
00745                      }
00746                      else if (ret == 1) {
00747                             cprintf("%d This event does not exist in your calendar.\n",
00748                                    ERROR + FILE_NOT_FOUND);
00749                      }
00750                      else {
00751                             cprintf("%d An internal error occurred.\n",
00752                                    ERROR + INTERNAL_ERROR);
00753                      }
00754               }
00755               else {
00756                      cprintf("%d This reply has been ignored.\n", CIT_OK);
00757               }
00758 
00759               /* Now that we've processed this message, we don't need it
00760                * anymore.  So delete it.  (Don't do this anymore.)
00761               CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
00762                */
00763 
00764               /* Free the memory we allocated and return a response. */
00765               icalcomponent_free(ird.cal);
00766               ird.cal = NULL;
00767               return;
00768        }
00769        else {
00770               cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
00771               return;
00772        }
00773 
00774        /* should never get here */
00775 }
00776 
00777 
00778 /*
00779  * Search for a property in both the top level and in a VEVENT subcomponent
00780  */
00781 icalproperty *ical_ctdl_get_subprop(
00782               icalcomponent *cal,
00783               icalproperty_kind which_prop
00784 ) {
00785        icalproperty *p;
00786        icalcomponent *c;
00787 
00788        p = icalcomponent_get_first_property(cal, which_prop);
00789        if (p == NULL) {
00790               c = icalcomponent_get_first_component(cal,
00791                                                  ICAL_VEVENT_COMPONENT);
00792               if (c != NULL) {
00793                      p = icalcomponent_get_first_property(c, which_prop);
00794               }
00795        }
00796        return p;
00797 }
00798 
00799 
00800 /*
00801  * Check to see if two events overlap.  Returns nonzero if they do.
00802  * (This function is used in both Citadel and WebCit.  If you change it in
00803  * one place, change it in the other.  Better yet, put it in a library.)
00804  */
00805 int ical_ctdl_is_overlap(
00806                      struct icaltimetype t1start,
00807                      struct icaltimetype t1end,
00808                      struct icaltimetype t2start,
00809                      struct icaltimetype t2end
00810 ) {
00811        if (icaltime_is_null_time(t1start)) return(0);
00812        if (icaltime_is_null_time(t2start)) return(0);
00813 
00814        /* if either event lacks end time, assume end = start */
00815        if (icaltime_is_null_time(t1end))
00816               memcpy(&t1end, &t1start, sizeof(struct icaltimetype));
00817        else {
00818               if (t1end.is_date && icaltime_compare(t1start, t1end)) {
00819                         /*
00820                          * the end date is non-inclusive so adjust it by one
00821                          * day because our test is inclusive, note that a day is
00822                          * not too much because we are talking about all day
00823                          * events
00824                       * if start = end we assume that nevertheless the whole
00825                       * day is meant
00826                          */
00827                      icaltime_adjust(&t1end, -1, 0, 0, 0);     
00828               }
00829        }
00830 
00831        if (icaltime_is_null_time(t2end))
00832               memcpy(&t2end, &t2start, sizeof(struct icaltimetype));
00833        else {
00834               if (t2end.is_date && icaltime_compare(t2start, t2end)) {
00835                      icaltime_adjust(&t2end, -1, 0, 0, 0);     
00836               }
00837        }
00838 
00839        /* First, check for all-day events */
00840        if (t1start.is_date || t2start.is_date) {
00841               /* If event 1 ends before event 2 starts, we're in the clear. */
00842               if (icaltime_compare_date_only(t1end, t2start) < 0) return(0);
00843 
00844               /* If event 2 ends before event 1 starts, we're also ok. */
00845               if (icaltime_compare_date_only(t2end, t1start) < 0) return(0);
00846 
00847               return(1);
00848        }
00849 
00850        /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d \n",
00851               t1start.hour, t1start.minute, t1end.hour, t1end.minute,
00852               t2start.hour, t2start.minute, t2end.hour, t2end.minute);
00853        */
00854 
00855        /* Now check for overlaps using date *and* time. */
00856 
00857        /* If event 1 ends before event 2 starts, we're in the clear. */
00858        if (icaltime_compare(t1end, t2start) <= 0) return(0);
00859        /* syslog(LOG_DEBUG, "first passed\n"); */
00860 
00861        /* If event 2 ends before event 1 starts, we're also ok. */
00862        if (icaltime_compare(t2end, t1start) <= 0) return(0);
00863        /* syslog(LOG_DEBUG, "second passed\n"); */
00864 
00865        /* Otherwise, they overlap. */
00866        return(1);
00867 }
00868 
00869 /* 
00870  * Phase 6 of "hunt for conflicts"
00871  * called by ical_conflicts_phase5()
00872  *
00873  * Now both the proposed and existing events have been boiled down to start and end times.
00874  * Check for overlap and output any conflicts.
00875  *
00876  * Returns nonzero if a conflict was reported.  This allows the caller to stop iterating.
00877  */
00878 int ical_conflicts_phase6(struct icaltimetype t1start,
00879                      struct icaltimetype t1end,
00880                      struct icaltimetype t2start,
00881                      struct icaltimetype t2end,
00882                      long existing_msgnum,
00883                      char *conflict_event_uid,
00884                      char *conflict_event_summary,
00885                      char *compare_uid)
00886 {
00887        int conflict_reported = 0;
00888 
00889        /* debugging cruft *
00890        time_t tt;
00891        tt = icaltime_as_timet_with_zone(t1start, t1start.zone);
00892        syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt));
00893        tt = icaltime_as_timet_with_zone(t1end, t1end.zone);
00894        syslog(LOG_DEBUG, "  PROPOSED END: %s", ctime(&tt));
00895        tt = icaltime_as_timet_with_zone(t2start, t2start.zone);
00896        syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt));
00897        tt = icaltime_as_timet_with_zone(t2end, t2end.zone);
00898        syslog(LOG_DEBUG, "  EXISTING END: %s", ctime(&tt));
00899        * debugging cruft */
00900 
00901        /* compare and output */
00902 
00903        if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) {
00904               cprintf("%ld||%s|%s|%d|\n",
00905                      existing_msgnum,
00906                      conflict_event_uid,
00907                      conflict_event_summary,
00908                      (      ((strlen(compare_uid)>0)
00909                             &&(!strcasecmp(compare_uid,
00910                             conflict_event_uid))) ? 1 : 0
00911                      )
00912               );
00913               conflict_reported = 1;
00914        }
00915 
00916        return(conflict_reported);
00917 }
00918 
00919 
00920 
00921 /*
00922  * Phase 5 of "hunt for conflicts"
00923  * Called by ical_conflicts_phase4()
00924  *
00925  * We have the proposed event boiled down to start and end times.
00926  * Now check it against an existing event. 
00927  */
00928 void ical_conflicts_phase5(struct icaltimetype t1start,
00929                      struct icaltimetype t1end,
00930                      icalcomponent *existing_event,
00931                      long existing_msgnum,
00932                      char *compare_uid)
00933 {
00934        char conflict_event_uid[SIZ];
00935        char conflict_event_summary[SIZ];
00936        struct icaltimetype t2start, t2end;
00937        icalproperty *p;
00938 
00939        /* recur variables */
00940        icalproperty *rrule = NULL;
00941        struct icalrecurrencetype recur;
00942        icalrecur_iterator *ritr = NULL;
00943        struct icaldurationtype dur;
00944        int num_recur = 0;
00945 
00946        /* initialization */
00947        strcpy(conflict_event_uid, "");
00948        strcpy(conflict_event_summary, "");
00949        t2start = icaltime_null_time();
00950        t2end = icaltime_null_time();
00951 
00952        /* existing event stuff */
00953        p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY);
00954        if (p == NULL) return;
00955        if (p != NULL) t2start = icalproperty_get_dtstart(p);
00956        if (icaltime_is_utc(t2start)) {
00957               t2start.zone = icaltimezone_get_utc_timezone();
00958        }
00959        else {
00960               t2start.zone = icalcomponent_get_timezone(existing_event,
00961                      icalparameter_get_tzid(
00962                             icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
00963                      )
00964               );
00965               if (!t2start.zone) {
00966                      t2start.zone = get_default_icaltimezone();
00967               }
00968        }
00969 
00970        p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY);
00971        if (p != NULL) {
00972               t2end = icalproperty_get_dtend(p);
00973 
00974               if (icaltime_is_utc(t2end)) {
00975                      t2end.zone = icaltimezone_get_utc_timezone();
00976               }
00977               else {
00978                      t2end.zone = icalcomponent_get_timezone(existing_event,
00979                             icalparameter_get_tzid(
00980                                    icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
00981                             )
00982                      );
00983                      if (!t2end.zone) {
00984                             t2end.zone = get_default_icaltimezone();
00985                      }
00986               }
00987               dur = icaltime_subtract(t2end, t2start);
00988        }
00989        else {
00990               memset (&dur, 0, sizeof(struct icaldurationtype));
00991        }
00992 
00993        rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY);
00994        if (rrule) {
00995               recur = icalproperty_get_rrule(rrule);
00996               ritr = icalrecur_iterator_new(recur, t2start);
00997        }
00998 
00999        do {
01000               p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY);
01001               if (p != NULL) {
01002                      strcpy(conflict_event_uid, icalproperty_get_comment(p));
01003               }
01004        
01005               p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY);
01006               if (p != NULL) {
01007                      strcpy(conflict_event_summary, icalproperty_get_comment(p));
01008               }
01009        
01010               if (ical_conflicts_phase6(t1start, t1end, t2start, t2end,
01011                  existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid))
01012               {
01013                      num_recur = MAX_RECUR + 1;  /* force it out of scope, no need to continue */
01014               }
01015 
01016               if (rrule) {
01017                      t2start = icalrecur_iterator_next(ritr);
01018                      if (!icaltime_is_null_time(t2end)) {
01019                             const icaltimezone *hold_zone = t2end.zone;
01020                             t2end = icaltime_add(t2start, dur);
01021                             t2end.zone = hold_zone;
01022                      }
01023                      ++num_recur;
01024               }
01025 
01026               if (icaltime_compare(t2start, t1end) < 0) {
01027                      num_recur = MAX_RECUR + 1;  /* force it out of scope */
01028               }
01029 
01030        } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) );
01031        icalrecur_iterator_free(ritr);
01032 }
01033 
01034 
01035 
01036 
01037 /*
01038  * Phase 4 of "hunt for conflicts"
01039  * Called by ical_hunt_for_conflicts_backend()
01040  *
01041  * At this point we've got it boiled down to two icalcomponent events in memory.
01042  * If they conflict, output something to the client.
01043  */
01044 void ical_conflicts_phase4(icalcomponent *proposed_event,
01045               icalcomponent *existing_event,
01046               long existing_msgnum)
01047 {
01048        struct icaltimetype t1start, t1end;
01049        icalproperty *p;
01050        char compare_uid[SIZ];
01051 
01052        /* recur variables */
01053        icalproperty *rrule = NULL;
01054        struct icalrecurrencetype recur;
01055        icalrecur_iterator *ritr = NULL;
01056        struct icaldurationtype dur;
01057        int num_recur = 0;
01058 
01059        /* initialization */
01060        t1end = icaltime_null_time();
01061        *compare_uid = '\0';
01062 
01063        /* proposed event stuff */
01064 
01065        p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY);
01066        if (p == NULL)
01067               return;
01068        else
01069               t1start = icalproperty_get_dtstart(p);
01070 
01071        if (icaltime_is_utc(t1start)) {
01072               t1start.zone = icaltimezone_get_utc_timezone();
01073        }
01074        else {
01075               t1start.zone = icalcomponent_get_timezone(proposed_event,
01076                      icalparameter_get_tzid(
01077                             icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
01078                      )
01079               );
01080               if (!t1start.zone) {
01081                      t1start.zone = get_default_icaltimezone();
01082               }
01083        }
01084        
01085        p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY);
01086        if (p != NULL) {
01087               t1end = icalproperty_get_dtend(p);
01088 
01089               if (icaltime_is_utc(t1end)) {
01090                      t1end.zone = icaltimezone_get_utc_timezone();
01091               }
01092               else {
01093                      t1end.zone = icalcomponent_get_timezone(proposed_event,
01094                             icalparameter_get_tzid(
01095                                    icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
01096                             )
01097                      );
01098                      if (!t1end.zone) {
01099                             t1end.zone = get_default_icaltimezone();
01100                      }
01101               }
01102 
01103               dur = icaltime_subtract(t1end, t1start);
01104        }
01105        else {
01106               memset (&dur, 0, sizeof(struct icaldurationtype));
01107        }
01108 
01109        rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY);
01110        if (rrule) {
01111               recur = icalproperty_get_rrule(rrule);
01112               ritr = icalrecur_iterator_new(recur, t1start);
01113        }
01114 
01115        p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY);
01116        if (p != NULL) {
01117               strcpy(compare_uid, icalproperty_get_comment(p));
01118        }
01119 
01120        do {
01121               ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid);
01122 
01123               if (rrule) {
01124                      t1start = icalrecur_iterator_next(ritr);
01125                      if (!icaltime_is_null_time(t1end)) {
01126                             const icaltimezone *hold_zone = t1end.zone;
01127                             t1end = icaltime_add(t1start, dur);
01128                             t1end.zone = hold_zone;
01129                      }
01130                      ++num_recur;
01131               }
01132 
01133        } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) );
01134        icalrecur_iterator_free(ritr);
01135 }
01136 
01137 
01138 
01139 /*
01140  * Phase 3 of "hunt for conflicts"
01141  * Called by ical_hunt_for_conflicts()
01142  */
01143 void ical_hunt_for_conflicts_backend(long msgnum, void *data) {
01144        icalcomponent *proposed_event;
01145        struct CtdlMessage *msg = NULL;
01146        struct ical_respond_data ird;
01147 
01148        proposed_event = (icalcomponent *)data;
01149 
01150        msg = CtdlFetchMessage(msgnum, 1);
01151        if (msg == NULL) return;
01152        memset(&ird, 0, sizeof ird);
01153        strcpy(ird.desired_partnum, "_HUNT_");
01154        mime_parser(msg->cm_fields['M'],
01155               NULL,
01156               *ical_locate_part,          /* callback function */
01157               NULL, NULL,
01158               (void *) &ird,                     /* user data */
01159               0
01160        );
01161        CtdlFreeMessage(msg);
01162 
01163        if (ird.cal == NULL) return;
01164 
01165        ical_conflicts_phase4(proposed_event, ird.cal, msgnum);
01166        icalcomponent_free(ird.cal);
01167 }
01168 
01169 
01170 
01171 /* 
01172  * Phase 2 of "hunt for conflicts" operation.
01173  * At this point we have a calendar object which represents the VEVENT that
01174  * is proposed for addition to the calendar.  Now hunt through the user's
01175  * calendar room, and output zero or more existing VEVENTs which conflict
01176  * with this one.
01177  */
01178 void ical_hunt_for_conflicts(icalcomponent *cal) {
01179        char hold_rm[ROOMNAMELEN];
01180 
01181        strcpy(hold_rm, CC->room.QRname);  /* save current room */
01182 
01183        if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) {
01184               CtdlGetRoom(&CC->room, hold_rm);
01185               cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND);
01186               return;
01187        }
01188 
01189        cprintf("%d Conflicting events:\n", LISTING_FOLLOWS);
01190 
01191        CtdlForEachMessage(MSGS_ALL, 0, NULL,
01192               NULL,
01193               NULL,
01194               ical_hunt_for_conflicts_backend,
01195               (void *) cal
01196        );
01197 
01198        cprintf("000\n");
01199        CtdlGetRoom(&CC->room, hold_rm);   /* return to saved room */
01200 
01201 }
01202 
01203 
01204 
01205 /*
01206  * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2)
01207  */
01208 void ical_conflicts(long msgnum, char *partnum) {
01209        struct CtdlMessage *msg = NULL;
01210        struct ical_respond_data ird;
01211 
01212        msg = CtdlFetchMessage(msgnum, 1);
01213        if (msg == NULL) {
01214               cprintf("%d Message %ld not found\n",
01215                      ERROR + ILLEGAL_VALUE,
01216                      (long)msgnum
01217               );
01218               return;
01219        }
01220 
01221        memset(&ird, 0, sizeof ird);
01222        strcpy(ird.desired_partnum, partnum);
01223        mime_parser(msg->cm_fields['M'],
01224               NULL,
01225               *ical_locate_part,          /* callback function */
01226               NULL, NULL,
01227               (void *) &ird,                     /* user data */
01228               0
01229        );
01230 
01231        CtdlFreeMessage(msg);
01232 
01233        if (ird.cal != NULL) {
01234               ical_hunt_for_conflicts(ird.cal);
01235               icalcomponent_free(ird.cal);
01236               return;
01237        }
01238 
01239        cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND);
01240 }
01241 
01242 
01243 
01244 /*
01245  * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY.
01246  *
01247  * fb                The VFREEBUSY component to which we are appending
01248  * top_level_cal     The top-level VCALENDAR component which contains a VEVENT to be added
01249  */
01250 void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal) {
01251        icalcomponent *cal;
01252        icalproperty *p;
01253        icalvalue *v;
01254        struct icalperiodtype this_event_period = icalperiodtype_null_period();
01255        icaltimetype dtstart;
01256        icaltimetype dtend;
01257 
01258        /* recur variables */
01259        icalproperty *rrule = NULL;
01260        struct icalrecurrencetype recur;
01261        icalrecur_iterator *ritr = NULL;
01262        struct icaldurationtype dur;
01263        int num_recur = 0;
01264 
01265        if (!top_level_cal) return;
01266 
01267        /* Find the VEVENT component containing an event */
01268        cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT);
01269        if (!cal) return;
01270 
01271        /* If this event is not opaque, the user isn't publishing it as
01272         * busy time, so don't bother doing anything else.
01273         */
01274        p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY);
01275        if (p != NULL) {
01276               v = icalproperty_get_value(p);
01277               if (v != NULL) {
01278                      if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) {
01279                             return;
01280                      }
01281               }
01282        }
01283 
01284        /*
01285         * Now begin calculating the event start and end times.
01286         */
01287        p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY);
01288        if (!p) return;
01289        dtstart = icalproperty_get_dtstart(p);
01290 
01291        if (icaltime_is_utc(dtstart)) {
01292               dtstart.zone = icaltimezone_get_utc_timezone();
01293        }
01294        else {
01295               dtstart.zone = icalcomponent_get_timezone(top_level_cal,
01296                      icalparameter_get_tzid(
01297                             icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER)
01298                      )
01299               );
01300               if (!dtstart.zone) {
01301                      dtstart.zone = get_default_icaltimezone();
01302               }
01303        }
01304 
01305        dtend = icalcomponent_get_dtend(cal);
01306        if (!icaltime_is_null_time(dtend)) {
01307               dur = icaltime_subtract(dtend, dtstart);
01308        }
01309        else {
01310               memset (&dur, 0, sizeof(struct icaldurationtype));
01311        }
01312 
01313        /* Is a recurrence specified?  If so, get ready to process it... */
01314        rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY);
01315        if (rrule) {
01316               recur = icalproperty_get_rrule(rrule);
01317               ritr = icalrecur_iterator_new(recur, dtstart);
01318        }
01319 
01320        do {
01321               /* Convert the DTSTART and DTEND properties to an icalperiod. */
01322               this_event_period.start = dtstart;
01323        
01324               if (!icaltime_is_null_time(dtend)) {
01325                      this_event_period.end = dtend;
01326               }
01327 
01328               /* Convert the timestamps to UTC.  It's ok to do this because we've already expanded
01329                * recurrences and this data is never going to get used again.
01330                */
01331               this_event_period.start = icaltime_convert_to_zone(
01332                      this_event_period.start,
01333                      icaltimezone_get_utc_timezone()
01334               );
01335               this_event_period.end = icaltime_convert_to_zone(
01336                      this_event_period.end,
01337                      icaltimezone_get_utc_timezone()
01338               );
01339        
01340               /* Now add it. */
01341               icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period));
01342 
01343               /* Make sure the DTSTART property of the freebusy *list* is set to
01344                * the DTSTART property of the *earliest event*.
01345                */
01346               p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
01347               if (p == NULL) {
01348                      icalcomponent_set_dtstart(fb, this_event_period.start);
01349               }
01350               else {
01351                      if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) {
01352                             icalcomponent_set_dtstart(fb, this_event_period.start);
01353                      }
01354               }
01355        
01356               /* Make sure the DTEND property of the freebusy *list* is set to
01357                * the DTEND property of the *latest event*.
01358                */
01359               p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
01360               if (p == NULL) {
01361                      icalcomponent_set_dtend(fb, this_event_period.end);
01362               }
01363               else {
01364                      if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) {
01365                             icalcomponent_set_dtend(fb, this_event_period.end);
01366                      }
01367               }
01368 
01369               if (rrule) {
01370                      dtstart = icalrecur_iterator_next(ritr);
01371                      if (!icaltime_is_null_time(dtend)) {
01372                             dtend = icaltime_add(dtstart, dur);
01373                             dtend.zone = dtstart.zone;
01374                             dtend.is_utc = dtstart.is_utc;
01375                      }
01376                      ++num_recur;
01377               }
01378 
01379        } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ;
01380        icalrecur_iterator_free(ritr);
01381 }
01382 
01383 
01384 
01385 /*
01386  * Backend for ical_freebusy()
01387  *
01388  * This function simply loads the messages in the user's calendar room,
01389  * which contain VEVENTs, then strips them of all non-freebusy data, and
01390  * adds them to the supplied VCALENDAR.
01391  *
01392  */
01393 void ical_freebusy_backend(long msgnum, void *data) {
01394        icalcomponent *fb;
01395        struct CtdlMessage *msg = NULL;
01396        struct ical_respond_data ird;
01397 
01398        fb = (icalcomponent *)data;        /* User-supplied data will be the VFREEBUSY component */
01399 
01400        msg = CtdlFetchMessage(msgnum, 1);
01401        if (msg == NULL) return;
01402        memset(&ird, 0, sizeof ird);
01403        strcpy(ird.desired_partnum, "_HUNT_");
01404        mime_parser(msg->cm_fields['M'],
01405               NULL,
01406               *ical_locate_part,          /* callback function */
01407               NULL, NULL,
01408               (void *) &ird,                     /* user data */
01409               0
01410        );
01411        CtdlFreeMessage(msg);
01412 
01413        if (ird.cal) {
01414               ical_add_to_freebusy(fb, ird.cal);        /* Add VEVENT times to VFREEBUSY */
01415               icalcomponent_free(ird.cal);
01416        }
01417 }
01418 
01419 
01420 
01421 /*
01422  * Grab another user's free/busy times
01423  */
01424 void ical_freebusy(char *who) {
01425        struct ctdluser usbuf;
01426        char calendar_room_name[ROOMNAMELEN];
01427        char hold_rm[ROOMNAMELEN];
01428        char *serialized_request = NULL;
01429        icalcomponent *encaps = NULL;
01430        icalcomponent *fb = NULL;
01431        int found_user = (-1);
01432        struct recptypes *recp = NULL;
01433        char buf[256];
01434        char host[256];
01435        char type[256];
01436        int i = 0;
01437        int config_lines = 0;
01438 
01439        /* First try an exact match. */
01440        found_user = CtdlGetUser(&usbuf, who);
01441 
01442        /* If not found, try it as an unqualified email address. */
01443        if (found_user != 0) {
01444               strcpy(buf, who);
01445               recp = validate_recipients(buf, NULL, 0);
01446               syslog(LOG_DEBUG, "Trying <%s>\n", buf);
01447               if (recp != NULL) {
01448                      if (recp->num_local == 1) {
01449                             found_user = CtdlGetUser(&usbuf, recp->recp_local);
01450                      }
01451                      free_recipients(recp);
01452               }
01453        }
01454 
01455        /* If still not found, try it as an address qualified with the
01456         * primary FQDN of this Citadel node.
01457         */
01458        if (found_user != 0) {
01459               snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn);
01460               syslog(LOG_DEBUG, "Trying <%s>\n", buf);
01461               recp = validate_recipients(buf, NULL, 0);
01462               if (recp != NULL) {
01463                      if (recp->num_local == 1) {
01464                             found_user = CtdlGetUser(&usbuf, recp->recp_local);
01465                      }
01466                      free_recipients(recp);
01467               }
01468        }
01469 
01470        /* Still not found?  Try qualifying it with every domain we
01471         * might have addresses in.
01472         */
01473        if (found_user != 0) {
01474               config_lines = num_tokens(inetcfg, '\n');
01475               for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
01476                      extract_token(buf, inetcfg, i, '\n', sizeof buf);
01477                      extract_token(host, buf, 0, '|', sizeof host);
01478                      extract_token(type, buf, 1, '|', sizeof type);
01479 
01480                      if ( (!strcasecmp(type, "localhost"))
01481                         || (!strcasecmp(type, "directory")) ) {
01482                             snprintf(buf, sizeof buf, "%s@%s", who, host);
01483                             syslog(LOG_DEBUG, "Trying <%s>\n", buf);
01484                             recp = validate_recipients(buf, NULL, 0);
01485                             if (recp != NULL) {
01486                                    if (recp->num_local == 1) {
01487                                           found_user = CtdlGetUser(&usbuf, recp->recp_local);
01488                                    }
01489                                    free_recipients(recp);
01490                             }
01491                      }
01492               }
01493        }
01494 
01495        if (found_user != 0) {
01496               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
01497               return;
01498        }
01499 
01500        CtdlMailboxName(calendar_room_name, sizeof calendar_room_name,
01501               &usbuf, USERCALENDARROOM);
01502 
01503        strcpy(hold_rm, CC->room.QRname);  /* save current room */
01504 
01505        if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) {
01506               cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
01507               CtdlGetRoom(&CC->room, hold_rm);
01508               return;
01509        }
01510 
01511        /* Create a VFREEBUSY subcomponent */
01512        syslog(LOG_DEBUG, "Creating VFREEBUSY component\n");
01513        fb = icalcomponent_new_vfreebusy();
01514        if (fb == NULL) {
01515               cprintf("%d Internal error: cannot allocate memory.\n",
01516                      ERROR + INTERNAL_ERROR);
01517               CtdlGetRoom(&CC->room, hold_rm);
01518               return;
01519        }
01520 
01521        /* Set the method to PUBLISH */
01522        icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
01523 
01524        /* Set the DTSTAMP to right now. */
01525        icalcomponent_set_dtstamp(fb, icaltime_from_timet(time(NULL), 0));
01526 
01527        /* Add the user's email address as ORGANIZER */
01528        sprintf(buf, "MAILTO:%s", who);
01529        if (strchr(buf, '@') == NULL) {
01530               strcat(buf, "@");
01531               strcat(buf, config.c_fqdn);
01532        }
01533        for (i=0; buf[i]; ++i) {
01534               if (buf[i]==' ') buf[i] = '_';
01535        }
01536        icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
01537 
01538        /* Add busy time from events */
01539        syslog(LOG_DEBUG, "Adding busy time from events\n");
01540        CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
01541 
01542        /* If values for DTSTART and DTEND are still not present, set them
01543         * to yesterday and tomorrow as default values.
01544         */
01545        if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
01546               icalcomponent_set_dtstart(fb, icaltime_from_timet(time(NULL)-86400L, 0));
01547        }
01548        if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
01549               icalcomponent_set_dtend(fb, icaltime_from_timet(time(NULL)+86400L, 0));
01550        }
01551 
01552        /* Put the freebusy component into the calendar component */
01553        syslog(LOG_DEBUG, "Encapsulating\n");
01554        encaps = ical_encapsulate_subcomponent(fb);
01555        if (encaps == NULL) {
01556               icalcomponent_free(fb);
01557               cprintf("%d Internal error: cannot allocate memory.\n",
01558                      ERROR + INTERNAL_ERROR);
01559               CtdlGetRoom(&CC->room, hold_rm);
01560               return;
01561        }
01562 
01563        /* Set the method to PUBLISH */
01564        syslog(LOG_DEBUG, "Setting method\n");
01565        icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
01566 
01567        /* Serialize it */
01568        syslog(LOG_DEBUG, "Serializing\n");
01569        serialized_request = icalcomponent_as_ical_string_r(encaps);
01570        icalcomponent_free(encaps); /* Don't need this anymore. */
01571 
01572        cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname);
01573        if (serialized_request != NULL) {
01574               client_write(serialized_request, strlen(serialized_request));
01575               free(serialized_request);
01576        }
01577        cprintf("\n000\n");
01578 
01579        /* Go back to the room from which we came... */
01580        CtdlGetRoom(&CC->room, hold_rm);
01581 }
01582 
01583 
01584 
01585 /*
01586  * Backend for ical_getics()
01587  * 
01588  * This is a ForEachMessage() callback function that searches the current room
01589  * for calendar events and adds them each into one big calendar component.
01590  */
01591 void ical_getics_backend(long msgnum, void *data) {
01592        icalcomponent *encaps, *c;
01593        struct CtdlMessage *msg = NULL;
01594        struct ical_respond_data ird;
01595 
01596        encaps = (icalcomponent *)data;
01597        if (encaps == NULL) return;
01598 
01599        /* Look for the calendar event... */
01600 
01601        msg = CtdlFetchMessage(msgnum, 1);
01602        if (msg == NULL) return;
01603        memset(&ird, 0, sizeof ird);
01604        strcpy(ird.desired_partnum, "_HUNT_");
01605        mime_parser(msg->cm_fields['M'],
01606               NULL,
01607               *ical_locate_part,          /* callback function */
01608               NULL, NULL,
01609               (void *) &ird,                     /* user data */
01610               0
01611        );
01612        CtdlFreeMessage(msg);
01613 
01614        if (ird.cal == NULL) return;
01615 
01616        /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
01617         * are responsible for "the_request"'s memory -- it will be freed
01618         * when we free "encaps".
01619         */
01620 
01621        /* If the top-level component is *not* a VCALENDAR, we can drop it right
01622         * in.  This will almost never happen.
01623         */
01624        if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
01625               icalcomponent_add_component(encaps, ird.cal);
01626        }
01627        /*
01628         * In the more likely event that we're looking at a VCALENDAR with the VEVENT
01629         * and other components encapsulated inside, we have to extract them.
01630         */
01631        else {
01632               for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
01633                   (c != NULL);
01634                   c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
01635 
01636                      /* For VTIMEZONE components, suppress duplicates of the same tzid */
01637 
01638                      if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) {
01639                             icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY);
01640                             if (p) {
01641                                    const char *tzid = icalproperty_get_tzid(p);
01642                                    if (!icalcomponent_get_timezone(encaps, tzid)) {
01643                                           icalcomponent_add_component(encaps,
01644                                                                icalcomponent_new_clone(c));
01645                                    }
01646                             }
01647                      }
01648 
01649                      /* All other types of components can go in verbatim */
01650                      else {
01651                             icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
01652                      }
01653               }
01654               icalcomponent_free(ird.cal);
01655        }
01656 }
01657 
01658 
01659 
01660 /*
01661  * Retrieve all of the calendar items in the current room, and output them
01662  * as a single icalendar object.
01663  */
01664 void ical_getics(void)
01665 {
01666        icalcomponent *encaps = NULL;
01667        char *ser = NULL;
01668 
01669        if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
01670           &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
01671               cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
01672               return;              /* Not an iCalendar-centric room */
01673        }
01674 
01675        encaps = icalcomponent_new_vcalendar();
01676        if (encaps == NULL) {
01677               syslog(LOG_ALERT, "ERROR: could not allocate component!\n");
01678               cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
01679               return;
01680        }
01681 
01682        cprintf("%d one big calendar\n", LISTING_FOLLOWS);
01683 
01684        /* Set the Product ID */
01685        icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
01686 
01687        /* Set the Version Number */
01688        icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
01689 
01690        /* Set the method to PUBLISH */
01691        icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
01692 
01693        /* Now go through the room encapsulating all calendar items. */
01694        CtdlForEachMessage(MSGS_ALL, 0, NULL,
01695               NULL,
01696               NULL,
01697               ical_getics_backend,
01698               (void *) encaps
01699        );
01700 
01701        ser = icalcomponent_as_ical_string_r(encaps);
01702        icalcomponent_free(encaps);               /* Don't need this anymore. */
01703        client_write(ser, strlen(ser));
01704        free(ser);
01705        cprintf("\n000\n");
01706 }
01707 
01708 
01709 /*
01710  * Helper callback function for ical_putics() to discover which TZID's we need.
01711  * Simply put the tzid name string into a hash table.  After the callbacks are
01712  * done we'll go through them and attach the ones that we have.
01713  */
01714 void ical_putics_grabtzids(icalparameter *param, void *data)
01715 {
01716        const char *tzid = icalparameter_get_tzid(param);
01717        HashList *keys = (HashList *) data;
01718        
01719        if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) {
01720               Put(keys, tzid, strlen(tzid), strdup(tzid), NULL);
01721        }
01722 }
01723 
01724 
01725 /*
01726  * Delete all of the calendar items in the current room, and replace them
01727  * with calendar items from a client-supplied data stream.
01728  */
01729 void ical_putics(void)
01730 {
01731        char *calstream = NULL;
01732        icalcomponent *cal;
01733        icalcomponent *c;
01734        icalcomponent *encaps = NULL;
01735        HashList *tzidlist = NULL;
01736        HashPos *HashPos;
01737        void *Value;
01738        const char *Key;
01739        long len;
01740 
01741        /* Only allow this operation if we're in a room containing a calendar or tasks view */
01742        if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
01743           &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
01744               cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
01745               return;
01746        }
01747 
01748        /* Only allow this operation if we have permission to overwrite the existing calendar */
01749        if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
01750               cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
01751               return;
01752        }
01753 
01754        cprintf("%d Transmit data now\n", SEND_LISTING);
01755        calstream = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
01756        if (calstream == NULL) {
01757               return;
01758        }
01759 
01760        cal = icalcomponent_new_from_string(calstream);
01761        free(calstream);
01762 
01763        /* We got our data stream -- now do something with it. */
01764 
01765        /* Delete the existing messages in the room, because we are overwriting
01766         * the entire calendar with an entire new (or updated) calendar.
01767         * (Careful: this opens an S_ROOMS critical section!)
01768         */
01769        CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
01770 
01771        /* If the top-level component is *not* a VCALENDAR, we can drop it right
01772         * in.  This will almost never happen.
01773         */
01774        if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
01775               ical_write_to_cal(NULL, cal);
01776        }
01777        /*
01778         * In the more likely event that we're looking at a VCALENDAR with the VEVENT
01779         * and other components encapsulated inside, we have to extract them.
01780         */
01781        else {
01782               for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
01783                   (c != NULL);
01784                   c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
01785 
01786                      /* Non-VTIMEZONE components each get written as individual messages.
01787                       * But we also need to attach the relevant VTIMEZONE components to them.
01788                       */
01789                      if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT)
01790                         && (encaps = icalcomponent_new_vcalendar()) ) {
01791                             icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
01792                             icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
01793                             icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
01794 
01795                             /* Attach any needed timezones here */
01796                             tzidlist = NewHash(1, NULL);
01797                             if (tzidlist) {
01798                                    icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist);
01799                             }
01800                             HashPos = GetNewHashPos(tzidlist, 0);
01801 
01802                             while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) {
01803                                    syslog(LOG_DEBUG, "Attaching timezone '%s'\n", (char*) Value);
01804                                    icaltimezone *t = NULL;
01805 
01806                                    /* First look for a timezone attached to the original calendar */
01807                                    t = icalcomponent_get_timezone(cal, Value);
01808 
01809                                    /* Try built-in tzdata if the right one wasn't attached */
01810                                    if (!t) {
01811                                           t = icaltimezone_get_builtin_timezone(Value);
01812                                    }
01813 
01814                                    /* I've got a valid timezone to attach. */
01815                                    if (t) {
01816                                           icalcomponent_add_component(encaps,
01817                                                  icalcomponent_new_clone(
01818                                                         icaltimezone_get_component(t)
01819                                                  )
01820                                           );
01821                                    }
01822 
01823                             }
01824                             DeleteHashPos(&HashPos);
01825                             DeleteHash(&tzidlist);
01826 
01827                             /* Now attach the component itself (usually a VEVENT or VTODO) */
01828                             icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
01829 
01830                             /* Write it to the message store */
01831                             ical_write_to_cal(NULL, encaps);
01832                             icalcomponent_free(encaps);
01833                      }
01834               }
01835        }
01836 
01837        icalcomponent_free(cal);
01838 }
01839 
01840 
01841 /*
01842  * All Citadel calendar commands from the client come through here.
01843  */
01844 void cmd_ical(char *argbuf)
01845 {
01846        char subcmd[64];
01847        long msgnum;
01848        char partnum[256];
01849        char action[256];
01850        char who[256];
01851 
01852        extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
01853 
01854        /* Allow "test" and "freebusy" subcommands without logging in. */
01855 
01856        if (!strcasecmp(subcmd, "test")) {
01857               cprintf("%d This server supports calendaring\n", CIT_OK);
01858               return;
01859        }
01860 
01861        if (!strcasecmp(subcmd, "freebusy")) {
01862               extract_token(who, argbuf, 1, '|', sizeof who);
01863               ical_freebusy(who);
01864               return;
01865        }
01866 
01867        if (!strcasecmp(subcmd, "sgi")) {
01868               CIT_ICAL->server_generated_invitations =
01869                      (extract_int(argbuf, 1) ? 1 : 0) ;
01870               cprintf("%d %d\n",
01871                      CIT_OK, CIT_ICAL->server_generated_invitations);
01872               return;
01873        }
01874 
01875        if (CtdlAccessCheck(ac_logged_in)) return;
01876 
01877        if (!strcasecmp(subcmd, "respond")) {
01878               msgnum = extract_long(argbuf, 1);
01879               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
01880               extract_token(action, argbuf, 3, '|', sizeof action);
01881               ical_respond(msgnum, partnum, action);
01882               return;
01883        }
01884 
01885        if (!strcasecmp(subcmd, "handle_rsvp")) {
01886               msgnum = extract_long(argbuf, 1);
01887               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
01888               extract_token(action, argbuf, 3, '|', sizeof action);
01889               ical_handle_rsvp(msgnum, partnum, action);
01890               return;
01891        }
01892 
01893        if (!strcasecmp(subcmd, "conflicts")) {
01894               msgnum = extract_long(argbuf, 1);
01895               extract_token(partnum, argbuf, 2, '|', sizeof partnum);
01896               ical_conflicts(msgnum, partnum);
01897               return;
01898        }
01899 
01900        if (!strcasecmp(subcmd, "getics")) {
01901               ical_getics();
01902               return;
01903        }
01904 
01905        if (!strcasecmp(subcmd, "putics")) {
01906               ical_putics();
01907               return;
01908        }
01909 
01910        cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
01911 }
01912 
01913 
01914 
01915 /*
01916  * We don't know if the calendar room exists so we just create it at login
01917  */
01918 void ical_CtdlCreateRoom(void)
01919 {
01920        struct ctdlroom qr;
01921        visit vbuf;
01922 
01923        /* Create the calendar room if it doesn't already exist */
01924        CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR);
01925 
01926        /* Set expiration policy to manual; otherwise objects will be lost! */
01927        if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) {
01928               syslog(LOG_CRIT, "Couldn't get the user calendar room!\n");
01929               return;
01930        }
01931        qr.QRep.expire_mode = EXPIRE_MANUAL;
01932        qr.QRdefaultview = VIEW_CALENDAR;  /* 3 = calendar view */
01933        CtdlPutRoomLock(&qr);
01934 
01935        /* Set the view to a calendar view */
01936        CtdlGetRelationship(&vbuf, &CC->user, &qr);
01937        vbuf.v_view = VIEW_CALENDAR;
01938        CtdlSetRelationship(&vbuf, &CC->user, &qr);
01939 
01940        /* Create the tasks list room if it doesn't already exist */
01941        CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS);
01942 
01943        /* Set expiration policy to manual; otherwise objects will be lost! */
01944        if (CtdlGetRoomLock(&qr, USERTASKSROOM)) {
01945               syslog(LOG_CRIT, "Couldn't get the user calendar room!\n");
01946               return;
01947        }
01948        qr.QRep.expire_mode = EXPIRE_MANUAL;
01949        qr.QRdefaultview = VIEW_TASKS;
01950        CtdlPutRoomLock(&qr);
01951 
01952        /* Set the view to a task list view */
01953        CtdlGetRelationship(&vbuf, &CC->user, &qr);
01954        vbuf.v_view = VIEW_TASKS;
01955        CtdlSetRelationship(&vbuf, &CC->user, &qr);
01956 
01957        /* Create the notes room if it doesn't already exist */
01958        CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES);
01959 
01960        /* Set expiration policy to manual; otherwise objects will be lost! */
01961        if (CtdlGetRoomLock(&qr, USERNOTESROOM)) {
01962               syslog(LOG_CRIT, "Couldn't get the user calendar room!\n");
01963               return;
01964        }
01965        qr.QRep.expire_mode = EXPIRE_MANUAL;
01966        qr.QRdefaultview = VIEW_NOTES;
01967        CtdlPutRoomLock(&qr);
01968 
01969        /* Set the view to a notes view */
01970        CtdlGetRelationship(&vbuf, &CC->user, &qr);
01971        vbuf.v_view = VIEW_NOTES;
01972        CtdlSetRelationship(&vbuf, &CC->user, &qr);
01973 
01974        return;
01975 }
01976 
01977 
01978 /*
01979  * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT.
01980  *
01981  * top_level_cal is the highest available level calendar object.
01982  * cal is the subcomponent containing the VEVENT.
01983  *
01984  * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent()
01985  */
01986 void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) {
01987        icalcomponent *the_request = NULL;
01988        char *serialized_request = NULL;
01989        icalcomponent *encaps = NULL;
01990        char *request_message_text = NULL;
01991        struct CtdlMessage *msg = NULL;
01992        struct recptypes *valid = NULL;
01993        char attendees_string[SIZ];
01994        int num_attendees = 0;
01995        char this_attendee[256];
01996        icalproperty *attendee = NULL;
01997        char summary_string[SIZ];
01998        icalproperty *summary = NULL;
01999        size_t reqsize;
02000        icalproperty *p;
02001        struct icaltimetype t;
02002        const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL };
02003        int i;
02004        const icaltimezone *z;
02005        int num_zones_attached = 0;
02006        int zone_already_attached;
02007        icalparameter *tzidp = NULL;
02008        const char *tzidc = NULL;
02009 
02010        if (cal == NULL) {
02011               syslog(LOG_ERR, "ERROR: trying to reply to NULL event?\n");
02012               return;
02013        }
02014 
02015 
02016        /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */
02017        if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
02018               ical_send_out_invitations(top_level_cal,
02019                      icalcomponent_get_first_component(
02020                             cal, ICAL_VEVENT_COMPONENT
02021                      )
02022               );
02023               return;
02024        }
02025 
02026        /* Clone the event */
02027        the_request = icalcomponent_new_clone(cal);
02028        if (the_request == NULL) {
02029               syslog(LOG_ERR, "ERROR: cannot clone calendar object\n");
02030               return;
02031        }
02032 
02033        /* Extract the summary string -- we'll use it as the
02034         * message subject for the request
02035         */
02036        strcpy(summary_string, "Meeting request");
02037        summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY);
02038        if (summary != NULL) {
02039               if (icalproperty_get_summary(summary)) {
02040                      strcpy(summary_string,
02041                             icalproperty_get_summary(summary) );
02042               }
02043        }
02044 
02045        /* Determine who the recipients of this message are (the attendees) */
02046        strcpy(attendees_string, "");
02047        for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) {
02048               const char *ch = icalproperty_get_attendee(attendee);
02049               if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) {
02050                      safestrncpy(this_attendee, ch + 7, sizeof(this_attendee));
02051                      
02052                      if (!CtdlIsMe(this_attendee, sizeof this_attendee)) {   /* don't send an invitation to myself! */
02053                             snprintf(&attendees_string[strlen(attendees_string)],
02054                                     sizeof(attendees_string) - strlen(attendees_string),
02055                                     "%s, ",
02056                                     this_attendee
02057                                    );
02058                             ++num_attendees;
02059                      }
02060               }
02061        }
02062 
02063        syslog(LOG_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string);
02064 
02065        /* If there are no attendees, there are no invitations to send, so...
02066         * don't bother putting one together!  Punch out, Maverick!
02067         */
02068        if (num_attendees == 0) {
02069               icalcomponent_free(the_request);
02070               return;
02071        }
02072 
02073        /* Encapsulate the VEVENT component into a complete VCALENDAR */
02074        encaps = icalcomponent_new_vcalendar();
02075        if (encaps == NULL) {
02076               syslog(LOG_ALERT, "ERROR: could not allocate component!\n");
02077               icalcomponent_free(the_request);
02078               return;
02079        }
02080 
02081        /* Set the Product ID */
02082        icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
02083 
02084        /* Set the Version Number */
02085        icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
02086 
02087        /* Set the method to REQUEST */
02088        icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
02089 
02090        /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */
02091        for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY);
02092             p != NULL;
02093             p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY))
02094        {
02095               if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY)
02096                 || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY)
02097                 || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY)
02098                 || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY)
02099                 || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY)
02100                 || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY)
02101                 || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY)
02102                 || (icalproperty_isa(p) == ICAL_DUE_PROPERTY)
02103                 || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY)
02104                 || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY)
02105                 || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY)
02106                 || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY)
02107                 || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY)
02108               ) {
02109                      t = icalproperty_get_dtstart(p);   // it's safe to use dtstart for all of them
02110 
02111                      /* Determine the tzid in order for some of the conditions below to work */
02112                      tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER);
02113                      if (tzidp) {
02114                             tzidc = icalparameter_get_tzid(tzidp);
02115                      }
02116                      else {
02117                             tzidc = NULL;
02118                      }
02119 
02120                      /* First see if there's a timezone attached to the data structure itself */
02121                      if (icaltime_is_utc(t)) {
02122                             z = icaltimezone_get_utc_timezone();
02123                      }
02124                      else {
02125                             z = icaltime_get_timezone(t);
02126                      }
02127 
02128                      /* If not, try to determine the tzid from the parameter using attached zones */
02129                      if ((!z) && (tzidc)) {
02130                             z = icalcomponent_get_timezone(top_level_cal, tzidc);
02131                      }
02132 
02133                      /* Still no good?  Try our internal database */
02134                      if ((!z) && (tzidc)) {
02135                             z = icaltimezone_get_builtin_timezone_from_tzid(tzidc);
02136                      }
02137 
02138                      if (z) {
02139                             /* We have a valid timezone.  Good.  Now we need to attach it. */
02140 
02141                             zone_already_attached = 0;
02142                             for (i=0; i<5; ++i) {
02143                                    if (z == attached_zones[i]) {
02144                                           /* We've already got this one, no need to attach another. */
02145                                           ++zone_already_attached;
02146                                    }
02147                             }
02148                             if ((!zone_already_attached) && (num_zones_attached < 5)) {
02149                                    /* This is a new one, so attach it. */
02150                                    attached_zones[num_zones_attached++] = z;
02151                             }
02152 
02153                             icalproperty_set_parameter(p,
02154                                    icalparameter_new_tzid(icaltimezone_get_tzid(z))
02155                             );
02156                      }
02157               }
02158        }
02159 
02160        /* Encapsulate any timezones we need */
02161        if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
02162               icalcomponent *zc;
02163               zc = icalcomponent_new_clone(icaltimezone_get_component(attached_zones[i]));
02164               icalcomponent_add_component(encaps, zc);
02165        }
02166 
02167        /* Here we go: encapsulate the VEVENT into the VCALENDAR.  We now no longer
02168         * are responsible for "the_request"'s memory -- it will be freed
02169         * when we free "encaps".
02170         */
02171        icalcomponent_add_component(encaps, the_request);
02172 
02173        /* Serialize it */
02174        serialized_request = icalcomponent_as_ical_string_r(encaps);
02175        icalcomponent_free(encaps); /* Don't need this anymore. */
02176        if (serialized_request == NULL) return;
02177 
02178        reqsize = strlen(serialized_request) + SIZ;
02179        request_message_text = malloc(reqsize);
02180        if (request_message_text != NULL) {
02181               snprintf(request_message_text, reqsize,
02182                      "Content-type: text/calendar\r\n\r\n%s\r\n",
02183                      serialized_request
02184               );
02185 
02186               msg = CtdlMakeMessage(
02187                      &CC->user,
02188                      NULL,                /* No single recipient here */
02189                      NULL,                /* No single recipient here */
02190                      CC->room.QRname,
02191                      0,
02192                      FMT_RFC822,
02193                      NULL,
02194                      NULL,
02195                      summary_string,             /* Use summary for subject */
02196                      NULL,
02197                      request_message_text,
02198                      NULL
02199               );
02200        
02201               if (msg != NULL) {
02202                      valid = validate_recipients(attendees_string, NULL, 0);
02203                      CtdlSubmitMsg(msg, valid, "", QP_EADDR);
02204                      CtdlFreeMessage(msg);
02205                      free_recipients(valid);
02206               }
02207        }
02208        free(serialized_request);
02209 }
02210 
02211 
02212 /*
02213  * When a calendar object is being saved, determine whether it's a VEVENT
02214  * and the user saving it is the organizer.  If so, send out invitations
02215  * to any listed attendees.
02216  *
02217  * This function is recursive.  The caller can simply supply the same object
02218  * as both arguments.  When it recurses it will alter the second argument
02219  * while holding on to the top level object.  This allows us to go back and
02220  * grab things like time zones which might be attached.
02221  *
02222  */
02223 void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) {
02224        icalcomponent *c;
02225        icalproperty *organizer = NULL;
02226        char organizer_string[SIZ];
02227 
02228        syslog(LOG_DEBUG, "ical_saving_vevent() has been called!\n");
02229 
02230        /* Don't send out invitations unless the client wants us to. */
02231        if (CIT_ICAL->server_generated_invitations == 0) {
02232               return;
02233        }
02234 
02235        /* Don't send out invitations if we've been asked not to. */
02236        if (CIT_ICAL->avoid_sending_invitations > 0) {
02237               return;
02238        }
02239 
02240        strcpy(organizer_string, "");
02241        /*
02242         * The VEVENT subcomponent is the one we're interested in.
02243         * Send out invitations if, and only if, this user is the Organizer.
02244         */
02245        if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
02246               organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY);
02247               if (organizer != NULL) {
02248                      if (icalproperty_get_organizer(organizer)) {
02249                             strcpy(organizer_string,
02250                                    icalproperty_get_organizer(organizer));
02251                      }
02252               }
02253               if (!strncasecmp(organizer_string, "MAILTO:", 7)) {
02254                      strcpy(organizer_string, &organizer_string[7]);
02255                      striplt(organizer_string);
02256                      /*
02257                       * If the user saving the event is listed as the
02258                       * organizer, then send out invitations.
02259                       */
02260                      if (CtdlIsMe(organizer_string, sizeof organizer_string)) {
02261                             ical_send_out_invitations(top_level_cal, cal);
02262                      }
02263               }
02264        }
02265 
02266        /* If the component has subcomponents, recurse through them. */
02267        for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
02268            (c != NULL);
02269            c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
02270               /* Recursively process subcomponent */
02271               ical_saving_vevent(top_level_cal, c);
02272        }
02273 
02274 }
02275 
02276 
02277 
02278 /*
02279  * Back end for ical_obj_beforesave()
02280  * This hunts for the UID of the calendar event (becomes Citadel msg EUID),
02281  * the summary of the event (becomes message subject),
02282  * and the start time (becomes message date/time).
02283  */
02284 void ical_obj_beforesave_backend(char *name, char *filename, char *partnum,
02285               char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
02286               char *encoding, char *cbid, void *cbuserdata)
02287 {
02288        icalcomponent *cal, *nested_event, *nested_todo, *whole_cal;
02289        icalproperty *p;
02290        char new_uid[256] = "";
02291        char buf[1024] = "";
02292        struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata;
02293 
02294        if (!msg) return;
02295 
02296        /* We're only interested in calendar data. */
02297        if (  (strcasecmp(cbtype, "text/calendar"))
02298           && (strcasecmp(cbtype, "application/ics")) ) {
02299               return;
02300        }
02301 
02302        /* Hunt for the UID and drop it in
02303         * the "user data" pointer for the MIME parser.  When
02304         * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
02305         * to that string.
02306         */
02307        whole_cal = icalcomponent_new_from_string(content);
02308        cal = whole_cal;
02309        if (cal != NULL) {
02310               if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) {
02311                      nested_event = icalcomponent_get_first_component(
02312                             cal, ICAL_VEVENT_COMPONENT);
02313                      if (nested_event != NULL) {
02314                             cal = nested_event;
02315                      }
02316                      else {
02317                             nested_todo = icalcomponent_get_first_component(
02318                                    cal, ICAL_VTODO_COMPONENT);
02319                             if (nested_todo != NULL) {
02320                                    cal = nested_todo;
02321                             }
02322                      }
02323               }
02324               
02325               if (cal != NULL) {
02326 
02327                      /* Set the message EUID to the iCalendar UID */
02328 
02329                      p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
02330                      if (p == NULL) {
02331                             /* If there's no uid we must generate one */
02332                             generate_uuid(new_uid);
02333                             icalcomponent_add_property(cal, icalproperty_new_uid(new_uid));
02334                             p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY);
02335                      }
02336                      if (p != NULL) {
02337                             safestrncpy(buf, icalproperty_get_comment(p), sizeof buf);
02338                             if (!IsEmptyStr(buf)) {
02339                                    if (msg->cm_fields['E'] != NULL) {
02340                                           free(msg->cm_fields['E']);
02341                                    }
02342                                    msg->cm_fields['E'] = strdup(buf);
02343                                    syslog(LOG_DEBUG, "Saving calendar UID <%s>\n", buf);
02344                             }
02345                      }
02346 
02347                      /* Set the message subject to the iCalendar summary */
02348 
02349                      p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY);
02350                      if (p != NULL) {
02351                             safestrncpy(buf, icalproperty_get_comment(p), sizeof buf);
02352                             if (!IsEmptyStr(buf)) {
02353                                    if (msg->cm_fields['U'] != NULL) {
02354                                           free(msg->cm_fields['U']);
02355                                    }
02356                                    msg->cm_fields['U'] = rfc2047encode(buf, strlen(buf));
02357                             }
02358                      }
02359 
02360                      /* Set the message date/time to the iCalendar start time */
02361 
02362                      p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY);
02363                      if (p != NULL) {
02364                             time_t idtstart;
02365                             idtstart = icaltime_as_timet(icalproperty_get_dtstart(p));
02366                             if (idtstart > 0) {
02367                                    if (msg->cm_fields['T'] != NULL) {
02368                                           free(msg->cm_fields['T']);
02369                                    }
02370                                    msg->cm_fields['T'] = strdup("000000000000000000");
02371                                    sprintf(msg->cm_fields['T'], "%ld", idtstart);
02372                             }
02373                      }
02374 
02375               }
02376               icalcomponent_free(cal);
02377               if (whole_cal != cal) {
02378                      icalcomponent_free(whole_cal);
02379               }
02380        }
02381 }
02382 
02383 
02384 
02385 
02386 /*
02387  * See if we need to prevent the object from being saved (we don't allow
02388  * MIME types other than text/calendar in "calendar" or "tasks" rooms).
02389  *
02390  * If the message is being saved, we also set various message header fields
02391  * using data found in the iCalendar object.
02392  */
02393 int ical_obj_beforesave(struct CtdlMessage *msg)
02394 {
02395        /* First determine if this is a calendar or tasks room */
02396        if (  (CC->room.QRdefaultview != VIEW_CALENDAR)
02397           && (CC->room.QRdefaultview != VIEW_TASKS)
02398        ) {
02399               return(0);           /* Not an iCalendar-centric room */
02400        }
02401 
02402        /* It must be an RFC822 message! */
02403        if (msg->cm_format_type != 4) {
02404               syslog(LOG_DEBUG, "Rejecting non-RFC822 message\n");
02405               return(1);           /* You tried to save a non-RFC822 message! */
02406        }
02407 
02408        if (msg->cm_fields['M'] == NULL) {
02409               return(1);           /* You tried to save a null message! */
02410        }
02411 
02412        /* Do all of our lovely back-end parsing */
02413        mime_parser(msg->cm_fields['M'],
02414               NULL,
02415               *ical_obj_beforesave_backend,
02416               NULL, NULL,
02417               (void *)msg,
02418               0
02419        );
02420 
02421        return(0);
02422 }
02423 
02424 
02425 /*
02426  * Things we need to do after saving a calendar event.
02427  */
02428 void ical_obj_aftersave_backend(char *name, char *filename, char *partnum,
02429               char *disp, void *content, char *cbtype, char *cbcharset, size_t length,
02430               char *encoding, char *cbid, void *cbuserdata)
02431 {
02432        icalcomponent *cal;
02433 
02434        /* We're only interested in calendar items here. */
02435        if (  (strcasecmp(cbtype, "text/calendar"))
02436           && (strcasecmp(cbtype, "application/ics")) ) {
02437               return;
02438        }
02439 
02440        /* Hunt for the UID and drop it in
02441         * the "user data" pointer for the MIME parser.  When
02442         * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid
02443         * to that string.
02444         */
02445        if (  (!strcasecmp(cbtype, "text/calendar"))
02446           || (!strcasecmp(cbtype, "application/ics")) ) {
02447               cal = icalcomponent_new_from_string(content);
02448               if (cal != NULL) {
02449                      ical_saving_vevent(cal, cal);
02450                      icalcomponent_free(cal);
02451               }
02452        }
02453 }
02454 
02455 
02456 /* 
02457  * Things we need to do after saving a calendar event.
02458  * (This will start back end tasks such as automatic generation of invitations,
02459  * if such actions are appropriate.)
02460  */
02461 int ical_obj_aftersave(struct CtdlMessage *msg)
02462 {
02463        char roomname[ROOMNAMELEN];
02464 
02465        /*
02466         * If this isn't the Calendar> room, no further action is necessary.
02467         */
02468 
02469        /* First determine if this is our room */
02470        CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM);
02471        if (strcasecmp(roomname, CC->room.QRname)) {
02472               return(0);    /* Not the Calendar room -- don't do anything. */
02473        }
02474 
02475        /* It must be an RFC822 message! */
02476        if (msg->cm_format_type != 4) return(1);
02477 
02478        /* Reject null messages */
02479        if (msg->cm_fields['M'] == NULL) return(1);
02480        
02481        /* Now recurse through it looking for our icalendar data */
02482        mime_parser(msg->cm_fields['M'],
02483               NULL,
02484               *ical_obj_aftersave_backend,
02485               NULL, NULL,
02486               NULL,
02487               0
02488        );
02489 
02490        return(0);
02491 }
02492 
02493 
02494 void ical_session_startup(void) {
02495        CIT_ICAL = malloc(sizeof(struct cit_ical));
02496        memset(CIT_ICAL, 0, sizeof(struct cit_ical));
02497 }
02498 
02499 void ical_session_shutdown(void) {
02500        free(CIT_ICAL);
02501 }
02502 
02503 
02504 /*
02505  * Back end for ical_fixed_output()
02506  */
02507 void ical_fixed_output_backend(icalcomponent *cal,
02508                      int recursion_level
02509 ) {
02510        icalcomponent *c;
02511        icalproperty *p;
02512        char buf[256];
02513        const char *ch;
02514 
02515        p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
02516        if (p != NULL) {
02517               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
02518        }
02519 
02520        p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
02521        if (p != NULL) {
02522               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
02523        }
02524 
02525        p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
02526        if (p != NULL) {
02527               cprintf("%s\n", (const char *)icalproperty_get_comment(p));
02528        }
02529 
02530        /* If the component has attendees, iterate through them. */
02531        for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
02532               ch =  icalproperty_get_attendee(p);
02533               if ((ch != NULL) && 
02534                   !strncasecmp(ch, "MAILTO:", 7)) {
02535 
02536                      /* screen name or email address */
02537                      safestrncpy(buf, ch + 7, sizeof(buf));
02538                      striplt(buf);
02539                      cprintf("%s ", buf);
02540               }
02541               cprintf("\n");
02542        }
02543 
02544        /* If the component has subcomponents, recurse through them. */
02545        for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
02546            (c != 0);
02547            c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
02548               /* Recursively process subcomponent */
02549               ical_fixed_output_backend(c, recursion_level+1);
02550        }
02551 }
02552 
02553 
02554 
02555 /*
02556  * Function to output iCalendar data as plain text.  Nobody uses MSG0
02557  * anymore, so really this is just so we expose the vCard data to the full
02558  * text indexer.
02559  */
02560 void ical_fixed_output(char *ptr, int len) {
02561        icalcomponent *cal;
02562        char *stringy_cal;
02563 
02564        stringy_cal = malloc(len + 1);
02565        safestrncpy(stringy_cal, ptr, len + 1);
02566        cal = icalcomponent_new_from_string(stringy_cal);
02567        free(stringy_cal);
02568 
02569        if (cal == NULL) {
02570               return;
02571        }
02572 
02573        ical_fixed_output_backend(cal, 0);
02574 
02575        /* Free the memory we obtained from libical's constructor */
02576        icalcomponent_free(cal);
02577 }
02578 
02579 
02580 
02581 void serv_calendar_destroy(void)
02582 {
02583        icaltimezone_free_builtin_timezones();
02584 }
02585 
02586 /*
02587  * Register this module with the Citadel server.
02588  */
02589 CTDL_MODULE_INIT(calendar)
02590 {
02591        if (!threading)
02592        {
02593 
02594               /* Tell libical to return errors instead of aborting if it gets bad data */
02595               icalerror_errors_are_fatal = 0;
02596 
02597               /* Use our own application prefix in tzid's generated from system tzdata */
02598               icaltimezone_set_tzid_prefix("/citadel.org/");
02599 
02600               /* Initialize our hook functions */
02601               CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE);
02602               CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE);
02603               CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN, PRIO_LOGIN + 1);
02604               CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands");
02605               CtdlRegisterSessionHook(ical_session_startup, EVT_START, PRIO_START + 1);
02606               CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP, PRIO_STOP + 80);
02607               CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output);
02608               CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output);
02609               CtdlRegisterCleanupHook(serv_calendar_destroy);
02610        }
02611        
02612        /* return our module name for the log */
02613        return "calendar";
02614 }