+ p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
+ if (p != NULL) {
+ my_period.end = icalproperty_get_dtstart(p);
+ }
+
+ /* Now add it. */
+ icalcomponent_add_property(fb,
+ icalproperty_new_freebusy(my_period)
+ );
+
+ /* Make sure the DTSTART property of the freebusy *list* is set to
+ * the DTSTART property of the *earliest event*.
+ */
+ p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY);
+ if (p == NULL) {
+ icalcomponent_set_dtstart(fb,
+ icalcomponent_get_dtstart(cal) );
+ }
+ else {
+ if (icaltime_compare(
+ icalcomponent_get_dtstart(cal),
+ icalcomponent_get_dtstart(fb)
+ ) < 0) {
+ icalcomponent_set_dtstart(fb,
+ icalcomponent_get_dtstart(cal) );
+ }
+ }
+
+ /* Make sure the DTEND property of the freebusy *list* is set to
+ * the DTEND property of the *latest event*.
+ */
+ p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY);
+ if (p == NULL) {
+ icalcomponent_set_dtend(fb,
+ icalcomponent_get_dtend(cal) );
+ }
+ else {
+ if (icaltime_compare(
+ icalcomponent_get_dtend(cal),
+ icalcomponent_get_dtend(fb)
+ ) > 0) {
+ icalcomponent_set_dtend(fb,
+ icalcomponent_get_dtend(cal) );
+ }
+ }
+
+}
+
+
+
+/*
+ * Backend for ical_freebusy()
+ *
+ * This function simply loads the messages in the user's calendar room,
+ * which contain VEVENTs, then strips them of all non-freebusy data, and
+ * adds them to the supplied VCALENDAR.
+ *
+ */
+void ical_freebusy_backend(long msgnum, void *data) {
+ icalcomponent *cal;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ cal = (icalcomponent *)data;
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CtdlFreeMessage(msg);
+
+ if (ird.cal == NULL) return;
+
+ ical_add_to_freebusy(cal, ird.cal);
+
+ /* Now free the memory. */
+ icalcomponent_free(ird.cal);
+}
+
+
+
+/*
+ * Grab another user's free/busy times
+ */
+void ical_freebusy(char *who) {
+ struct ctdluser usbuf;
+ char calendar_room_name[ROOMNAMELEN];
+ char hold_rm[ROOMNAMELEN];
+ char *serialized_request = NULL;
+ icalcomponent *encaps = NULL;
+ icalcomponent *fb = NULL;
+ int found_user = (-1);
+ struct recptypes *recp = NULL;
+ char buf[256];
+ char host[256];
+ char type[256];
+ int i = 0;
+ int config_lines = 0;
+
+ /* First try an exact match. */
+ found_user = getuser(&usbuf, who);
+
+ /* If not found, try it as an unqualified email address. */
+ if (found_user != 0) {
+ strcpy(buf, who);
+ recp = validate_recipients(buf);
+ lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = getuser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+
+ /* If still not found, try it as an address qualified with the
+ * primary FQDN of this Citadel node.
+ */
+ if (found_user != 0) {
+ snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn);
+ lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
+ recp = validate_recipients(buf);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = getuser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+
+ /* Still not found? Try qualifying it with every domain we
+ * might have addresses in.
+ */
+ if (found_user != 0) {
+ config_lines = num_tokens(inetcfg, '\n');
+ for (i=0; ((i < config_lines) && (found_user != 0)); ++i) {
+ extract_token(buf, inetcfg, i, '\n', sizeof buf);
+ extract_token(host, buf, 0, '|', sizeof host);
+ extract_token(type, buf, 1, '|', sizeof type);
+
+ if ( (!strcasecmp(type, "localhost"))
+ || (!strcasecmp(type, "directory")) ) {
+ snprintf(buf, sizeof buf, "%s@%s", who, host);
+ lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
+ recp = validate_recipients(buf);
+ if (recp != NULL) {
+ if (recp->num_local == 1) {
+ found_user = getuser(&usbuf, recp->recp_local);
+ }
+ free_recipients(recp);
+ }
+ }
+ }
+ }
+
+ if (found_user != 0) {
+ cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
+ return;
+ }
+
+ MailboxName(calendar_room_name, sizeof calendar_room_name,
+ &usbuf, USERCALENDARROOM);
+
+ strcpy(hold_rm, CC->room.QRname); /* save current room */
+
+ if (getroom(&CC->room, calendar_room_name) != 0) {
+ cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND);
+ getroom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Create a VFREEBUSY subcomponent */
+ lprintf(CTDL_DEBUG, "Creating VFREEBUSY component\n");
+ fb = icalcomponent_new_vfreebusy();
+ if (fb == NULL) {
+ cprintf("%d Internal error: cannot allocate memory.\n",
+ ERROR + INTERNAL_ERROR);
+ icalcomponent_free(encaps);
+ getroom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Set the method to PUBLISH */
+ icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH);
+
+ /* Set the DTSTAMP to right now. */
+ icalcomponent_set_dtstamp(fb, icaltime_from_timet(time(NULL), 0));
+
+ /* Add the user's email address as ORGANIZER */
+ sprintf(buf, "MAILTO:%s", who);
+ if (strchr(buf, '@') == NULL) {
+ strcat(buf, "@");
+ strcat(buf, config.c_fqdn);
+ }
+ for (i=0; i<strlen(buf); ++i) {
+ if (buf[i]==' ') buf[i] = '_';
+ }
+ icalcomponent_add_property(fb, icalproperty_new_organizer(buf));
+
+ /* Add busy time from events */
+ lprintf(CTDL_DEBUG, "Adding busy time from events\n");
+ CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb );
+
+ /* If values for DTSTART and DTEND are still not present, set them
+ * to yesterday and tomorrow as default values.
+ */
+ if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) {
+ icalcomponent_set_dtstart(fb, icaltime_from_timet(time(NULL)-86400L, 0));
+ }
+ if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) {
+ icalcomponent_set_dtend(fb, icaltime_from_timet(time(NULL)+86400L, 0));
+ }
+
+ /* Put the freebusy component into the calendar component */
+ lprintf(CTDL_DEBUG, "Encapsulating\n");
+ encaps = ical_encapsulate_subcomponent(fb);
+ if (encaps == NULL) {
+ icalcomponent_free(fb);
+ cprintf("%d Internal error: cannot allocate memory.\n",
+ ERROR + INTERNAL_ERROR);
+ getroom(&CC->room, hold_rm);
+ return;
+ }
+
+ /* Set the method to PUBLISH */
+ lprintf(CTDL_DEBUG, "Setting method\n");
+ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+ /* Serialize it */
+ lprintf(CTDL_DEBUG, "Serializing\n");
+ serialized_request = strdup(icalcomponent_as_ical_string(encaps));
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+
+ cprintf("%d Here is the free/busy data:\n", LISTING_FOLLOWS);
+ if (serialized_request != NULL) {
+ client_write(serialized_request, strlen(serialized_request));
+ free(serialized_request);
+ }
+ cprintf("\n000\n");
+
+ /* Go back to the room from which we came... */
+ getroom(&CC->room, hold_rm);
+}
+
+
+
+/*
+ * Backend for ical_getics()
+ *
+ * This is a ForEachMessage() callback function that searches the current room
+ * for calendar events and adds them each into one big calendar component.
+ */
+void ical_getics_backend(long msgnum, void *data) {
+ icalcomponent *encaps, *c;
+ struct CtdlMessage *msg = NULL;
+ struct ical_respond_data ird;
+
+ encaps = (icalcomponent *)data;
+ if (encaps == NULL) return;
+
+ /* Look for the calendar event... */
+
+ msg = CtdlFetchMessage(msgnum, 1);
+ if (msg == NULL) return;
+ memset(&ird, 0, sizeof ird);
+ strcpy(ird.desired_partnum, "_HUNT_");
+ mime_parser(msg->cm_fields['M'],
+ NULL,
+ *ical_locate_part, /* callback function */
+ NULL, NULL,
+ (void *) &ird, /* user data */
+ 0
+ );
+ CtdlFreeMessage(msg);
+
+ if (ird.cal == NULL) return;
+
+ /* Here we go: put the VEVENT into the VCALENDAR. We now no longer
+ * are responsible for "the_request"'s memory -- it will be freed
+ * when we free "encaps".
+ */
+
+ /* If the top-level component is *not* a VCALENDAR, we can drop it right
+ * in. This will almost never happen.
+ */
+ if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) {
+ icalcomponent_add_component(encaps, ird.cal);
+ }
+ /*
+ * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+ * and other components encapsulated inside, we have to extract them.
+ */
+ else {
+ for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) {
+ icalcomponent_add_component(encaps, icalcomponent_new_clone(c));
+ }
+ icalcomponent_free(ird.cal);
+ }
+}
+
+
+
+/*
+ * Retrieve all of the calendar items in the current room, and output them
+ * as a single icalendar object.
+ */
+void ical_getics(void)
+{
+ icalcomponent *encaps = NULL;
+ char *ser = NULL;
+
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+ cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+ return; /* Not a vCalendar-centric room */
+ }
+
+ encaps = icalcomponent_new_vcalendar();
+ if (encaps == NULL) {
+ lprintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n",
+ __FILE__, __LINE__);
+ cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
+ return;
+ }
+
+ cprintf("%d one big calendar\n", LISTING_FOLLOWS);
+
+ /* Set the Product ID */
+ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
+
+ /* Set the Version Number */
+ icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
+
+ /* Set the method to PUBLISH */
+ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH);
+
+ /* Now go through the room encapsulating all calendar items. */
+ CtdlForEachMessage(MSGS_ALL, 0, NULL,
+ NULL,
+ NULL,
+ ical_getics_backend,
+ (void *) encaps
+ );
+
+ ser = strdup(icalcomponent_as_ical_string(encaps));
+ client_write(ser, strlen(ser));
+ free(ser);
+ cprintf("\n000\n");
+ icalcomponent_free(encaps); /* Don't need this anymore. */
+
+}
+
+
+/*
+ * Delete all of the calendar items in the current room, and replace them
+ * with calendar items from a client-supplied data stream.
+ */
+void ical_putics(void)
+{
+ char *calstream = NULL;
+ icalcomponent *cal;
+ icalcomponent *c;
+
+ if ( (CC->room.QRdefaultview != VIEW_CALENDAR)
+ &&(CC->room.QRdefaultview != VIEW_TASKS) ) {
+ cprintf("%d Not a calendar room\n", ERROR+NOT_HERE);
+ return; /* Not a vCalendar-centric room */
+ }
+
+ if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
+ cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED);
+ return;
+ }
+
+ cprintf("%d Transmit data now\n", SEND_LISTING);
+ calstream = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
+ if (calstream == NULL) {
+ return;
+ }
+
+ cal = icalcomponent_new_from_string(calstream);
+ free(calstream);
+ ical_dezonify(cal);
+
+ /* We got our data stream -- now do something with it. */
+
+ /* Delete the existing messages in the room, because we are replacing
+ * the entire calendar with an entire new (or updated) calendar.
+ * (Careful: this opens an S_ROOMS critical section!)
+ */
+ CtdlDeleteMessages(CC->room.QRname, NULL, 0, "");
+
+ /* If the top-level component is *not* a VCALENDAR, we can drop it right
+ * in. This will almost never happen.
+ */
+ if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) {
+ ical_write_to_cal(&CC->user, cal);
+ }
+ /*
+ * In the more likely event that we're looking at a VCALENDAR with the VEVENT
+ * and other components encapsulated inside, we have to extract them.
+ */
+ else {
+ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
+ (c != NULL);
+ c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
+ ical_write_to_cal(&CC->user, c);
+ }
+ }
+
+ icalcomponent_free(cal);
+}
+
+
+/*
+ * All Citadel calendar commands from the client come through here.
+ */
+void cmd_ical(char *argbuf)
+{
+ char subcmd[64];
+ long msgnum;
+ char partnum[256];
+ char action[256];
+ char who[256];
+
+ extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+ /* Allow "test" and "freebusy" subcommands without logging in. */
+
+ if (!strcasecmp(subcmd, "test")) {
+ cprintf("%d This server supports calendaring\n", CIT_OK);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "freebusy")) {
+ extract_token(who, argbuf, 1, '|', sizeof who);
+ ical_freebusy(who);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "sgi")) {
+ CIT_ICAL->server_generated_invitations =
+ (extract_int(argbuf, 1) ? 1 : 0) ;
+ cprintf("%d %d\n",
+ CIT_OK, CIT_ICAL->server_generated_invitations);
+ return;
+ }
+
+ if (CtdlAccessCheck(ac_logged_in)) return;
+
+ if (!strcasecmp(subcmd, "respond")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ extract_token(action, argbuf, 3, '|', sizeof action);
+ ical_respond(msgnum, partnum, action);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "handle_rsvp")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ extract_token(action, argbuf, 3, '|', sizeof action);
+ ical_handle_rsvp(msgnum, partnum, action);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "conflicts")) {
+ msgnum = extract_long(argbuf, 1);
+ extract_token(partnum, argbuf, 2, '|', sizeof partnum);
+ ical_conflicts(msgnum, partnum);
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "getics")) {
+ ical_getics();
+ return;
+ }
+
+ if (!strcasecmp(subcmd, "putics")) {
+ ical_putics();
+ return;
+ }
+
+ cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+}