Projects
Kolab:3.4
cyrus-imapd
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 52
View file
cyrus-imapd.spec
Changed
@@ -57,14 +57,16 @@ ## ## Patches ## -Patch0001: cyrus-imapd-2.5-autoconf-2.63.patch -Patch0002: cyrus-imapd-2.5-lt-prereq.patch -Patch0003: cyrus-imapd-2.5-with-dav-ifdefs.patch -Patch0004: cyrus-imapd-2.5-return-0-to-make-function-nonvoid.patch -Patch0005: cyrus-imapd-2.5-shared-folder-deletion.patch -Patch1000: cyrus-imapd-2.5-disable-user-parameter-notifyd.patch -Patch1001: cyrus-imapd-2.5-acl-change-notifications.patch +Patch0001: 0001-There-is-no-actual-prerequisite-for-libtool-of-2.2.6.patch +Patch0002: 0002-A-non-void-function-should-return-something-so-retur.patch +Patch0003: 0003-Disable-user-parameter-check-to-notify-external.patch +Patch0004: 0004-Fix-display-of-deleted-shared-folders.patch +Patch0005: 0005-Apply-ifdefs-for-building-without-DAV-and-without-re.patch +Patch0006: 0006-Add-ACL-change-notifications.patch +Patch0007: 0007-Correct-location-of-ifdef-endif.patch +Patch0008: 0008-Extend-ifdefs-endifs-for-WITH_DAV.patch +Patch0009: 0009-Keep-the-same-order-between-.c-and-.h-perhaps.patch BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) @@ -241,14 +243,15 @@ %prep %setup -q -n %{_name}-%{real_version}%{?dot_snapshot_version} -#%patch0001 -p1 +%patch0001 -p1 %patch0002 -p1 %patch0003 -p1 %patch0004 -p1 %patch0005 -p1 - -%patch1000 -p1 -%patch1001 -p1 +%patch0006 -p1 +%patch0007 -p1 +%patch0008 -p1 +%patch0009 -p1 # only to update config.* files aclocal -I cmulocal
View file
0001-There-is-no-actual-prerequisite-for-libtool-of-2.2.6.patch
Added
@@ -0,0 +1,25 @@ +From 18c6d1d15047ce5e92b9892aeaf73e955e2b57f0 Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sat, 8 Mar 2014 23:48:30 +0100 +Subject: [PATCH 1/6] There is no actual prerequisite for libtool of >= 2.2.6 + and it is incompatible with autoconf 2.63 + +--- + configure.ac | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/configure.ac b/configure.ac +index 3c29680..61073e2 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -127,7 +127,6 @@ AH_BOTTOM([#if HAVE_VISIBILITY + #define HIDDEN + #endif]) + +-LT_PREREQ([2.2.6]) + LT_INIT([disable-static]) + AC_SUBST([LIBTOOL_DEPS]) + +-- +1.8.3.1 +
View file
0002-A-non-void-function-should-return-something-so-retur.patch
Added
@@ -0,0 +1,24 @@ +From 49a177f85e57c4a3c2613bf72ae492bc4b855caf Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sat, 8 Mar 2014 23:49:04 +0100 +Subject: [PATCH 2/6] A non void function should return something, so return 0 + +--- + imap/xapian_wrap.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/imap/xapian_wrap.cpp b/imap/xapian_wrap.cpp +index 47d904f..ea96b87 100644 +--- a/imap/xapian_wrap.cpp ++++ b/imap/xapian_wrap.cpp +@@ -291,6 +291,7 @@ xapian_query_t *xapian_query_new_match(const xapian_db_t *db, const char *prefix + catch (const Xapian::Error &err) { + syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", + err.get_context().c_str(), err.get_description().c_str()); ++ return 0; + } + } + +-- +1.8.3.1 +
View file
0003-Disable-user-parameter-check-to-notify-external.patch
Added
@@ -0,0 +1,38 @@ +From 7e8b0d9ecee648e854bc90f674f61e44f8d42c05 Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sat, 8 Mar 2014 23:49:27 +0100 +Subject: [PATCH 3/6] Disable user parameter check to notify external + +--- + notifyd/notify_external.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/notifyd/notify_external.c b/notifyd/notify_external.c +index 5a2d680..964d1ee 100644 +--- a/notifyd/notify_external.c ++++ b/notifyd/notify_external.c +@@ -71,15 +71,15 @@ char* notify_external(const char *class, const char *priority, + + /* check/parse options */ + if (!(notify = config_getstring(IMAPOPT_NOTIFY_EXTERNAL))) { +- syslog(LOG_ERR, "ERROR: recipient not specified"); +- return strdup("NO external recipient not specified"); ++ syslog(LOG_ERR, "ERROR: external recipient (program) not specified"); ++ return strdup("NO external recipient (program) not specified"); + } + +- if (!*user) { +- syslog(LOG_ERR, "ERROR: recipient not specified"); +- return strdup("NO external recipient not specified"); ++/* if (!*user) { ++ syslog(LOG_ERR, "ERROR: external recipient user not specified"); ++ return strdup("NO external recipient user not specified"); + } +- ++*/ + buf[0] = notify; + buf[1] = "-c"; + buf[2] = class; +-- +1.8.3.1 +
View file
0004-Fix-display-of-deleted-shared-folders.patch
Added
@@ -0,0 +1,32 @@ +From f931a6f37054f6f9f7638d71147c1b7367242557 Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sat, 8 Mar 2014 23:49:50 +0100 +Subject: [PATCH 4/6] Fix display of deleted shared folders + +--- + imap/mboxname.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/imap/mboxname.c b/imap/mboxname.c +index 86a755f..f3634c0 100644 +--- a/imap/mboxname.c ++++ b/imap/mboxname.c +@@ -576,7 +576,15 @@ static int mboxname_toexternal(struct namespace *namespace, const char *mboxname + + if (mbparts.box) + strcat(result, "."); ++ } else { ++ /* shared mailbox */ ++ if (mbparts.is_deleted) { ++ const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX); ++ sprintf(result, "%s.", dp); ++ } + } ++ ++ + if (mbparts.box) + strcat(result, mbparts.box); + +-- +1.8.3.1 +
View file
0005-Apply-ifdefs-for-building-without-DAV-and-without-re.patch
Added
@@ -0,0 +1,281 @@ +From af772395dc3f45dbc2347fc3611606c7242059b5 Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sat, 8 Mar 2014 23:50:55 +0100 +Subject: [PATCH 5/6] Apply ifdefs for building without DAV and without + requiring DAV-related software during build + +--- + Makefile.am | 19 ++++++++++++++----- + imap/mailbox.c | 10 ++++++++++ + imap/mailbox.h | 2 ++ + imap/mboxevent.c | 13 ++++++++++++- + imap/mboxevent.h | 2 ++ + imap/sync_server.c | 6 ++++++ + 6 files changed, 46 insertions(+), 6 deletions(-) + +diff --git a/Makefile.am b/Makefile.am +index b1790a5..48d0e8e 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -1190,7 +1190,6 @@ dist_man_MANS = \ + man/deliver.8 \ + man/fetchnews.8 \ + man/fud.8 \ +- man/httpd.8 \ + man/idled.8 \ + man/imapd.8 \ + man/ipurge.8 \ +@@ -1206,17 +1205,27 @@ dist_man_MANS = \ + man/rmnews.8 \ + man/smmapd.8 \ + man/syncnews.8 \ +- man/sync_client.8 \ +- man/sync_reset.8 \ +- man/sync_server.8 \ + man/timsieved.8 \ + man/tls_prune.8 \ + man/unexpunge.8 +-if USE_SQUAT ++ ++if SQUATTER + dist_man_MANS += \ + man/squatter.8 + endif + ++if HTTPD ++dist_man_MANS += \ ++ man/httpd.8 ++endif ++ ++if REPLICATION ++dist_man_MANS += \ ++ man/sync_client.8 \ ++ man/sync_reset.8 \ ++ man/sync_server.8 ++endif ++ + master_master_SOURCES = \ + master/cyrusMasterMIB.c \ + master/cyrusMasterMIB.h \ +diff --git a/imap/mailbox.c b/imap/mailbox.c +index 43d8f79..cc457df 100644 +--- a/imap/mailbox.c ++++ b/imap/mailbox.c +@@ -51,7 +51,9 @@ + #elif defined(HAVE_STDINT_H) + # include <stdint.h> + #endif ++#ifdef WITH_DAV + #include <libical/vcc.h> ++#endif + #include <stdio.h> + #include <stdlib.h> + #include <string.h> +@@ -81,8 +83,10 @@ + + #include "annotate.h" + #include "assert.h" ++#ifdef WITH_DAV + #include "caldav_db.h" + #include "carddav_db.h" ++#endif + #include "crc32.h" + #include "md5.h" + #include "exitcodes.h" +@@ -2660,6 +2664,7 @@ out: + return r; + } + ++#ifdef WITH_DAV + static int mailbox_update_carddav(struct mailbox *mailbox, + struct index_record *old, + struct index_record *new) +@@ -2880,6 +2885,7 @@ static int mailbox_update_dav(struct mailbox *mailbox, + return mailbox_update_caldav(mailbox, old, new); + return 0; + } ++#endif //WITH_DAV + + EXPORTED int mailbox_update_conversations(struct mailbox *mailbox, + struct index_record *old, +@@ -3106,8 +3112,10 @@ static int mailbox_update_indexes(struct mailbox *mailbox, + { + int r = 0; + ++#ifdef WITH_DAV + r = mailbox_update_dav(mailbox, old, new); + if (r) return r; ++#endif + + r = mailbox_update_conversations(mailbox, old, new); + if (r) return r; +@@ -4231,6 +4239,7 @@ static int chkchildren(char *name, + return r; + } + ++#ifdef WITH_DAV + EXPORTED int mailbox_add_dav(struct mailbox *mailbox) + { + struct index_record record; +@@ -4250,6 +4259,7 @@ EXPORTED int mailbox_add_dav(struct mailbox *mailbox) + + return 0; + } ++#endif + + EXPORTED int mailbox_add_conversations(struct mailbox *mailbox) + { +diff --git a/imap/mailbox.h b/imap/mailbox.h +index cdcf878..f6174c6 100644 +--- a/imap/mailbox.h ++++ b/imap/mailbox.h +@@ -577,7 +577,9 @@ extern int mailbox_get_annotate_state(struct mailbox *mailbox, + uint32_t mailbox_sync_crc(struct mailbox *mailbox, unsigned vers, int recalc); + unsigned mailbox_best_crcvers(unsigned minvers, unsigned maxvers); + ++#ifdef WITH_DAV + extern int mailbox_add_dav(struct mailbox *mailbox); ++#endif + + /* Rename a CID. Note - this is just one mailbox! */ + extern int mailbox_cid_rename(struct mailbox *mailbox, +diff --git a/imap/mboxevent.c b/imap/mboxevent.c +index 97882a5..0f0d298 100644 +--- a/imap/mboxevent.c ++++ b/imap/mboxevent.c +@@ -53,8 +53,10 @@ + + #include "annotate.h" + #include "assert.h" ++#ifdef WITH_DAV + #include "caldav_db.h" + #include "carddav_db.h" ++#endif + #include "exitcodes.h" + #include "imapurl.h" + #include "libconfig.h" +@@ -131,9 +133,11 @@ static struct mboxevent event_template = + { EVENT_PID, "pid", EVENT_PARAM_INT, 0, 0 }, + { EVENT_USER, "user", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_MESSAGE_SIZE, "messageSize", EVENT_PARAM_INT, 0, 0 }, ++#ifdef WITH_DAV + { EVENT_MBTYPE, "vnd.cmu.mbtype", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_DAV_FILENAME, "vnd.cmu.davFilename", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_DAV_UID, "vnd.cmu.davUid", EVENT_PARAM_STRING, 0, 0 }, ++#endif + { EVENT_MESSAGE_CID, "vnd.fastmail.cid", EVENT_PARAM_STRING, 0, 0 }, + /* always at end to let the parser to easily truncate this part */ + { EVENT_ENVELOPE, "vnd.cmu.envelope", EVENT_PARAM_STRING, 0, 0 }, +@@ -378,8 +382,10 @@ static int mboxevent_expected_param(enum event_type type, enum event_param param + return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_SESSIONID; + case EVENT_MAILBOX_ID: + return (type & MAILBOX_EVENTS); ++#ifdef WITH_DAV + case EVENT_MBTYPE: + return (type & MAILBOX_EVENTS); ++#endif + case EVENT_MAX_MESSAGES: + return type & QUOTA_EVENTS; + case EVENT_MESSAGE_CONTENT: +@@ -388,12 +394,14 @@ static int mboxevent_expected_param(enum event_type type, enum event_param param + case EVENT_MESSAGE_SIZE: + return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGESIZE) && + (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)); ++#ifdef WITH_DAV + case EVENT_DAV_FILENAME: + return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVFILENAME) && + (type & EVENT_CALENDAR); + case EVENT_DAV_UID: + return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVUID) && + (type & EVENT_CALENDAR); ++#endif + case EVENT_MESSAGE_CID: + return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CID) && + (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)); +@@ -743,6 +751,7 @@ EXPORTED void mboxevent_extract_record(struct mboxevent *event, struct mailbox * + cacheitem_size(record, CACHE_BODYSTRUCTURE))); + } + ++#ifdef WITH_DAV + /* add caldav items */ + if ((mailbox->mbtype & (MBTYPES_DAV)) && + (mboxevent_expected_param(event->type, EVENT_DAV_FILENAME) || +@@ -783,7 +792,7 @@ EXPORTED void mboxevent_extract_record(struct mboxevent *event, struct mailbox * + } + } + } +- ++#endif //WITH_DAV + void mboxevent_extract_copied_record(struct mboxevent *event, + const struct mailbox *mailbox, uint32_t uid) + { +@@ -993,8 +1002,10 @@ EXPORTED void mboxevent_extract_mailbox(struct mboxevent *event, + imapurl_toURL(url, &imapurl); + FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url)); + ++#ifdef WITH_DAV + FILL_STRING_PARAM(event, EVENT_MBTYPE, + xstrdup(mboxlist_mbtype_to_string(mailbox->mbtype))); ++#endif + + /* mailbox related events also require mailboxID */ + if (event->type & MAILBOX_EVENTS) { +diff --git a/imap/mboxevent.h b/imap/mboxevent.h +index f0977ef..4e111c7 100644 +--- a/imap/mboxevent.h ++++ b/imap/mboxevent.h +@@ -112,9 +112,11 @@ enum event_param { + EVENT_PID, + EVENT_USER, + EVENT_MESSAGE_SIZE, ++#ifdef WITH_DAV + EVENT_MBTYPE, + EVENT_DAV_FILENAME, + EVENT_DAV_UID, ++#endif + EVENT_MESSAGE_CID, + EVENT_ENVELOPE, + EVENT_BODYSTRUCTURE, +diff --git a/imap/sync_server.c b/imap/sync_server.c +index 745b3cf..e51983d 100644 +--- a/imap/sync_server.c ++++ b/imap/sync_server.c +@@ -74,9 +74,11 @@ + #include "annotate.h" + #include "append.h" + #include "auth.h" ++#ifdef WITH_DAV + #include "caldav_db.h" + #include "carddav_db.h" + #include "dav_db.h" ++#endif + #include "dlist.h" + #include "exitcodes.h" + #include "global.h" +@@ -291,9 +293,11 @@ int service_init(int argc __attribute__((unused)), + statuscache_open(); + } + ++#ifdef WITH_DAV + dav_init(); + caldav_init(); + carddav_init(); ++#endif + + return 0; + } +@@ -455,9 +459,11 @@ void shut_down(int code) + + proc_cleanup(); + ++#ifdef WITH_DAV + carddav_done(); + caldav_done(); + dav_done(); ++#endif + + if (config_getswitch(IMAPOPT_STATUSCACHE)) { + statuscache_close(); +-- +1.8.3.1 +
View file
0006-Add-ACL-change-notifications.patch
Added
@@ -0,0 +1,162 @@ +From 62103a35b782aad0d94efe21657b82eeab7d27df Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sat, 8 Mar 2014 23:52:02 +0100 +Subject: [PATCH 6/6] Add ACL change notifications + +--- + imap/mboxevent.c | 34 ++++++++++++++++++++++++++++++---- + imap/mboxevent.h | 12 +++++++++++- + imap/mboxlist.c | 9 +++++++++ + 3 files changed, 50 insertions(+), 5 deletions(-) + +diff --git a/imap/mboxevent.c b/imap/mboxevent.c +index 0f0d298..9a60b0a 100644 +--- a/imap/mboxevent.c ++++ b/imap/mboxevent.c +@@ -78,7 +78,7 @@ + EVENT_MESSAGE_TRASH) + + #define MAILBOX_EVENTS (EVENT_MAILBOX_CREATE|EVENT_MAILBOX_DELETE|\ +- EVENT_MAILBOX_RENAME) ++ EVENT_MAILBOX_RENAME|EVENT_ACL_CHANGE) + + #define SUBS_EVENTS (EVENT_MAILBOX_SUBSCRIBE|EVENT_MAILBOX_UNSUBSCRIBE) + +@@ -122,6 +122,8 @@ static struct mboxevent event_template = + { EVENT_DISK_QUOTA, "diskQuota", EVENT_PARAM_INT, 0, 0 }, + { EVENT_DISK_USED, "diskUsed", EVENT_PARAM_INT, 0, 0 }, + { EVENT_MAX_MESSAGES, "maxMessages", EVENT_PARAM_INT, 0, 0 }, ++ { EVENT_ACL_SUBJECT, "aclSubject", EVENT_PARAM_STRING, 0, 0 }, ++ { EVENT_ACL_RIGHTS, "aclRights", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_MESSAGES, "messages", EVENT_PARAM_INT, 0, 0 }, + { EVENT_UNSEEN_MESSAGES, "vnd.cmu.unseenMessages", EVENT_PARAM_INT, 0, 0 }, + { EVENT_CONVEXISTS, "vnd.fastmail.convExists", EVENT_PARAM_INT, 0, 0 }, +@@ -191,7 +193,7 @@ EXPORTED void mboxevent_init(void) + enabled_events |= FLAGS_EVENTS; + + if (groups & IMAP_ENUM_EVENT_GROUPS_ACCESS) +- enabled_events |= (EVENT_LOGIN|EVENT_LOGOUT); ++ enabled_events |= (EVENT_LOGIN|EVENT_LOGOUT|EVENT_ACL_CHANGE); + + if (groups & IMAP_ENUM_EVENT_GROUPS_SUBSCRIPTION) + enabled_events |= SUBS_EVENTS; +@@ -423,6 +425,10 @@ static int mboxevent_expected_param(enum event_type type, enum event_param param + return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_SERVICE; + case EVENT_TIMESTAMP: + return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_TIMESTAMP; ++ case EVENT_ACL_SUBJECT: ++ return type & EVENT_ACL_CHANGE; ++ case EVENT_ACL_RIGHTS: ++ return type & EVENT_ACL_CHANGE; + case EVENT_UIDNEXT: + if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_UIDNEXT)) + return 0; +@@ -679,12 +685,24 @@ EXPORTED void mboxevent_set_access(struct mboxevent *event, + if (userid && mboxevent_expected_param(event->type, EVENT_USER)) { + /* translate any separators in user */ + char *user = xstrdup(userid); +- mboxname_hiersep_toexternal(&namespace, user, +- config_virtdomains ? strcspn(user, "@") : 0); ++ if (user) { ++ mboxname_hiersep_toexternal(&namespace, user, ++ config_virtdomains ? strcspn(user, "@") : 0); ++ } + FILL_STRING_PARAM(event, EVENT_USER, user); + } + } + ++EXPORTED void mboxevent_set_acl(struct mboxevent *event, const char *identifier, ++ const char *rights) ++{ ++ if (!event) ++ return; ++ ++ FILL_STRING_PARAM(event, EVENT_ACL_SUBJECT, xstrdup(identifier)); ++ FILL_STRING_PARAM(event, EVENT_ACL_RIGHTS, xstrdup(rights)); ++} ++ + EXPORTED void mboxevent_extract_record(struct mboxevent *event, struct mailbox *mailbox, + struct index_record *record) + { +@@ -1132,6 +1150,8 @@ static const char *event_to_name(enum event_type type) + return "MailboxSubscribe"; + case EVENT_MAILBOX_UNSUBSCRIBE: + return "MailboxUnSubscribe"; ++ case EVENT_ACL_CHANGE: ++ return "AclChange"; + default: + fatal("Unknown message event", EC_SOFTWARE); + } +@@ -1335,6 +1355,12 @@ EXPORTED void mboxevent_set_access(struct mboxevent *event __attribute__((unused + { + } + ++EXPORTED void mboxevent_set_acl(struct mboxevent *event __attribute__((unused)), ++ const char *identifier __attribute__((unused)), ++>.......>.......>.......>.......const char *rights __attribute__((unused))) ++{ ++} ++ + EXPORTED void mboxevent_extract_record(struct mboxevent *event __attribute__((unused)), + struct mailbox *mailbox __attribute__((unused)), + struct index_record *record __attribute__((unused))) +diff --git a/imap/mboxevent.h b/imap/mboxevent.h +index 4e111c7..6106505 100644 +--- a/imap/mboxevent.h ++++ b/imap/mboxevent.h +@@ -80,7 +80,8 @@ enum event_type { + EVENT_MAILBOX_RENAME = (1<<17), + EVENT_MAILBOX_SUBSCRIBE = (1<<18), + EVENT_MAILBOX_UNSUBSCRIBE = (1<<19), +- EVENT_CALENDAR = (1<<20) ++ EVENT_CALENDAR = (1<<20), ++ EVENT_ACL_CHANGE = (1<<21) + }; + + /* +@@ -110,6 +111,8 @@ enum event_param { + EVENT_MIDSET, + EVENT_FLAG_NAMES, + EVENT_PID, ++ EVENT_ACL_SUBJECT, ++ EVENT_ACL_RIGHTS, + EVENT_USER, + EVENT_MESSAGE_SIZE, + #ifdef WITH_DAV +@@ -221,6 +224,13 @@ void mboxevent_add_flag(struct mboxevent *event, const char *flag); + void mboxevent_set_access(struct mboxevent *event, + const char *serveraddr, const char *clientaddr, + const char *userid, const char *mailboxname); ++ ++/* ++ * Shortcut to setting event notification parameters ++ */ ++void mboxevent_set_acl(struct mboxevent *event, const char *identifier, ++ const char *rights); ++ + /* + * Extract data from the given record to fill these event parameters : + * - uidset from UID +diff --git a/imap/mboxlist.c b/imap/mboxlist.c +index 2e4b7ee..c313ba7 100644 +--- a/imap/mboxlist.c ++++ b/imap/mboxlist.c +@@ -1854,6 +1854,15 @@ EXPORTED int mboxlist_setacl(struct namespace *namespace, const char *name, + name, cyrusdb_strerror(r)); + r = IMAP_IOERROR; + } ++ ++ /* send a AclChange event notification */ ++ struct mboxevent *mboxevent = mboxevent_new(EVENT_ACL_CHANGE); ++ mboxevent_extract_mailbox(mboxevent, mailbox); ++ mboxevent_set_acl(mboxevent, identifier, rights); ++ ++ mboxevent_notify(mboxevent); ++ mboxevent_free(&mboxevent); ++ + } + + /* 4. Change backup copy (cyrus.header) */ +-- +1.8.3.1 +
View file
0007-Correct-location-of-ifdef-endif.patch
Added
@@ -0,0 +1,27 @@ +From a5446f731ca691d8cd5eecc5ec21d81e70f5a688 Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sat, 8 Mar 2014 23:56:42 +0100 +Subject: [PATCH 7/7] Correct location of ifdef/endif + +--- + imap/mboxevent.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/imap/mboxevent.c b/imap/mboxevent.c +index 9a60b0a..feac0fd 100644 +--- a/imap/mboxevent.c ++++ b/imap/mboxevent.c +@@ -809,8 +809,9 @@ EXPORTED void mboxevent_extract_record(struct mboxevent *event, struct mailbox * + } + } + } +-} + #endif //WITH_DAV ++} ++ + void mboxevent_extract_copied_record(struct mboxevent *event, + const struct mailbox *mailbox, uint32_t uid) + { +-- +1.8.3.1 +
View file
0008-Extend-ifdefs-endifs-for-WITH_DAV.patch
Added
@@ -0,0 +1,47 @@ +From 46eb1fa3ba446dbfdb31277fb0499785394a4d72 Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sun, 9 Mar 2014 00:00:30 +0100 +Subject: [PATCH 8/8] Extend ifdefs/endifs for WITH_DAV + +--- + imap/mbdump.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/imap/mbdump.c b/imap/mbdump.c +index acabb95..352ed24 100644 +--- a/imap/mbdump.c ++++ b/imap/mbdump.c +@@ -630,6 +630,7 @@ EXPORTED int dump_mailbox(const char *tag, struct mailbox *mailbox, uint32_t uid + fname = mboxkey_getpath(userid); + ftag = "MBOXKEY"; + break; ++#ifdef WITH_DAV + case DAV_DB: { + struct buf dav_file = BUF_INITIALIZER; + +@@ -638,6 +639,7 @@ EXPORTED int dump_mailbox(const char *tag, struct mailbox *mailbox, uint32_t uid + ftag = "DAV"; + break; + } ++#endif // WITH_DAV + default: + fatal("unknown user data file", EC_OSFILE); + } +@@ -1072,12 +1074,14 @@ EXPORTED int undump_mailbox(const char *mbname, + char *s = user_hash_subs(userid); + strlcpy(fnamebuf, s, sizeof(fnamebuf)); + free(s); ++#ifdef WITHDAV + } else if (userid && !strcmp(file.s, "DAV")) { + /* overwriting this outright is absolutely what we want to do */ + struct buf dav_file = BUF_INITIALIZER; + dav_getpath_byuserid(&dav_file, userid); + strlcpy(fnamebuf, buf_cstring(&dav_file), sizeof(fnamebuf)); + buf_free(&dav_file); ++#endif // WITH_DAV + } else if (userid && !strcmp(file.s, "SEEN")) { + seen_file = seen_getpath(userid); + +-- +1.8.3.1 +
View file
0009-Keep-the-same-order-between-.c-and-.h-perhaps.patch
Added
@@ -0,0 +1,34 @@ +From c70ef68f50b32bbdad04ba4d80b9f9a8bddf8817 Mon Sep 17 00:00:00 2001 +From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> +Date: Sun, 9 Mar 2014 00:26:18 +0100 +Subject: [PATCH 9/9] Keep the same order between .c and .h perhaps? + +--- + imap/mboxevent.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/imap/mboxevent.c b/imap/mboxevent.c +index feac0fd..4fdf6a5 100644 +--- a/imap/mboxevent.c ++++ b/imap/mboxevent.c +@@ -122,8 +122,6 @@ static struct mboxevent event_template = + { EVENT_DISK_QUOTA, "diskQuota", EVENT_PARAM_INT, 0, 0 }, + { EVENT_DISK_USED, "diskUsed", EVENT_PARAM_INT, 0, 0 }, + { EVENT_MAX_MESSAGES, "maxMessages", EVENT_PARAM_INT, 0, 0 }, +- { EVENT_ACL_SUBJECT, "aclSubject", EVENT_PARAM_STRING, 0, 0 }, +- { EVENT_ACL_RIGHTS, "aclRights", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_MESSAGES, "messages", EVENT_PARAM_INT, 0, 0 }, + { EVENT_UNSEEN_MESSAGES, "vnd.cmu.unseenMessages", EVENT_PARAM_INT, 0, 0 }, + { EVENT_CONVEXISTS, "vnd.fastmail.convExists", EVENT_PARAM_INT, 0, 0 }, +@@ -133,6 +131,8 @@ static struct mboxevent event_template = + { EVENT_MIDSET, "vnd.cmu.midset", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_FLAG_NAMES, "flagNames", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_PID, "pid", EVENT_PARAM_INT, 0, 0 }, ++ { EVENT_ACL_SUBJECT, "aclSubject", EVENT_PARAM_STRING, 0, 0 }, ++ { EVENT_ACL_RIGHTS, "aclRights", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_USER, "user", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_MESSAGE_SIZE, "messageSize", EVENT_PARAM_INT, 0, 0 }, + #ifdef WITH_DAV +-- +1.8.3.1 +
View file
cyrus-imapd-2.5.tar.gz/Makefile.am
Changed
@@ -161,12 +161,15 @@ imap/http_dblookup.c \ imap/http_ischedule.c \ imap/http_rss.c \ + imap/http_timezone.c \ + imap/zoneinfo_db.c \ imap/caldav_db.c \ imap/proxy.c \ imap/smtpclient.c \ imap/spool.c \ imap/carddav_db.c \ imap/dav_db.c \ + imap/dav_util.c \ imap/jcal.c \ imap/xcal.c \ imap/httpd.c @@ -951,9 +954,12 @@ imap/http_dav.c \ imap/http_dblookup.c \ imap/dav_db.c \ + imap/dav_util.c \ imap/http_ischedule.c \ imap/http_carddav.c \ imap/http_caldav.c \ + imap/http_timezone.c \ + imap/zoneinfo_db.c \ imap/smtpclient.c \ imap/xcal.c \ imap/jcal.c \
View file
cyrus-imapd-2.5.tar.gz/cunit/annotate.testc
Changed
@@ -5,6 +5,7 @@ #include "assert.h" #include "xmalloc.h" #include "retry.h" +#include "util.h" #include "imap/global.h" #include "libcyr_cfg.h" #include "imap/annotate.h"
View file
cyrus-imapd-2.5.tar.gz/cunit/buf.testc
Changed
@@ -5,6 +5,7 @@ #include "util.h" #include "retry.h" #include "map.h" +#include "util.h" #include <sys/mman.h> static void test_simple(void) @@ -1223,7 +1224,6 @@ { static const char DATA0[] = "cardigan umami"; struct buf b = BUF_INITIALIZER; - const char *s; CU_ASSERT_EQUAL(b.len, 0); CU_ASSERT(b.alloc >= b.len); @@ -1236,14 +1236,6 @@ CU_ASSERT_EQUAL(buf_len(&b), b.len); CU_ASSERT_PTR_EQUAL(b.s, (char *)DATA0); - s = buf_cstring(&b); - CU_ASSERT_PTR_NOT_NULL(s); - CU_ASSERT_PTR_EQUAL(s, (char *)DATA0); - CU_ASSERT_EQUAL(b.len, sizeof(DATA0)-1); - CU_ASSERT_EQUAL(b.alloc, 0); - CU_ASSERT_EQUAL(buf_len(&b), b.len); - CU_ASSERT_PTR_EQUAL(b.s, (char *)DATA0); - buf_putc(&b, 'X'); CU_ASSERT_EQUAL(b.len, sizeof(DATA0)-1+1); CU_ASSERT(b.alloc >= b.len);
View file
cyrus-imapd-2.5.tar.gz/cunit/dlist.testc
Changed
@@ -380,4 +380,31 @@ buf_free(&b2); } +static void test_deepstructure(void) +{ + struct dlist *dl = NULL; + struct dlist *di = NULL; + struct buf b = BUF_INITIALIZER; + struct buf b2 = BUF_INITIALIZER; + int r; + + buf_setcstr(&b, "%(toplevel %(sub thing) ANOTHER (value is %(list with interesting things) (in it)) ExTrA ExTrA READ \"all about it\" INCLUDING 123456)"); + + r = dlist_parsemap(&dl, 0, b.s, b.len); + + CU_ASSERT_EQUAL(r, 0); + CU_ASSERT_PTR_NOT_NULL(dl); + + di = dlist_getchild(dl, "INCLUDING"); + CU_ASSERT_EQUAL(dlist_num(di), 123456); + + dlist_printbuf(dl, 0, &b2); + + CU_ASSERT_STRING_EQUAL(buf_cstring(&b2), buf_cstring(&b)); + + dlist_free(&dl); + buf_free(&b); + buf_free(&b2); +} + /* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/message.testc
Changed
@@ -3,6 +3,7 @@ #endif #include "cunit/cunit.h" #include "parseaddr.h" +#include "util.h" #include "imap/message.h" static void test_parse_trivial(void)
View file
cyrus-imapd-2.5.tar.gz/cunit/sieve.testc
Changed
@@ -16,6 +16,7 @@ #include "retry.h" #include "imap/spool.h" #include "map.h" +#include "util.h" #include "cyrusdb.h" #include "libcyr_cfg.h" #include "libconfig.h"
View file
cyrus-imapd-2.5.tar.gz/doc/changes.html
Changed
@@ -8,11 +8,41 @@ </head> <body> +<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta9</h1> +<ul> +<li>Split merging of scheduling object resources from + sched_deliver_local() into deliver_merge_request() and + deliver_merge_reply().</li> +</ul> + +<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta8</h1> +<ul> +<li>Fixed bug in parsing of Accept header (now accepts <tt>*/*</tt> + and <tt>type/*</tt>).</li> +<li>Fixed telemetry logging bug (old garbage appearing in log).</li> +<li>Added a workaround for the DELETE bug in MacOS X 10.9.0 Calendar + client.</li> +</ul> + <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta7</h1> <ul> -<li>Added support for returning jCal data in GET/PROPFIND/REPORT - response.</li> -<li>Added support for accepting jCal data in PUT request.</li> +<li>Added Timzone Service module along with associated admin tools + (ctl_zoneinfo, vzic).</li> +<li>Added support for accepting/returning jCal (requires Jansson) and + xCal data wherever iCalendar data is allowed.</li> +<li>Proxied responses (including chunked) are now piped to client + rather than being buffered and forwarded.</li> +<li>Better handling of COPY/MOVE between backends (including + LOCKs).</li> +<li>Fixed "annotation truncation" bug where the largest allowed + annotation value was slightly less than 4k (solves problem reading + CalDavZAP settings).</li> +<li>GET on ./well-known now returns a list of /.well-known/ URLs on + the server.</li> +<li>Added support for X-HTTP-Method-Override header in POST requests.</li> +<li>Added replacement functions for those not present in libxml2 < + v2.8.0</li> +<li>Plugged a few more memory leaks found by Valgrind.</li> </ul> <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta6</h1>
View file
cyrus-imapd-2.5.tar.gz/doc/install-http.html
Changed
@@ -7,7 +7,7 @@ </head> <body> -<h1>Cyrus HTTP (RSS, CalDAV, CardDAV, iSchedule, DomainKey)</h1> +<h1>Cyrus HTTP (RSS, CalDAV, CardDAV, iSchedule, Timezone Service)</h1> <b><i>Note that the HTTP service and associated modules in Cyrus are still under development. This release should be considered beta @@ -28,6 +28,10 @@ <li>Allow scheduling transactions between separate calendaring and scheduling systems via the iSchedule protocol <i>(currently only used within a Cyrus Murder)</i>.</li> +<li>Act as a Timezone Service by serving iCalendar (VTIMEZONE) + data to client systems.</li> +<li>Serve static content (such as the RSS feed list template and/or + the CalDAV/CardDAV JavaScript clients mentioned below).</li> </ul> <i>Unlike the <a href="http://httpd.apache.org/">Apache HTTP @@ -61,12 +65,13 @@ <ul> <li>Must have <a href="http://xmlsoft.org/">libxml2</a> installed.</li> -<li>Must have a recent SASL build (v2.1.26 or later) in order to - support HTTP Digest, Negotiate, and NTLM authentication. - Otherwise, only HTTP Basic authentication will be available</li> +<li>Must have a recent <a href="http://www.cyrusimap.org/">SASL</a> + release (v2.1.26 or later) in order to support HTTP Digest, + Negotiate, and NTLM authentication. Otherwise, only HTTP Basic + authentication will be available.</li> </ul> -<h3>CalDAV / CardDAV Requirements</h3> +<h3>Additional CalDAV / CardDAV Requirements</h3> <ul> <li>Must have <a href="http://freeassociation.sourceforge.net/">libical</a> @@ -74,19 +79,29 @@ <li>Must have <a href="http://www.sqlite.org/">SQLite</a> v3.x (or later) installed.</li> <li>Optionally - install <a href="https://github.com/jehiah/json-c">json-c</a> for + install <a href="http://www.digip.org/jansson/">Jansson</a> for jCal/jCard support.</li> </ul> <!-- -<h3>iSchedule Requirements</h3> +<h3>Additional iSchedule Requirements</h3> <ul> +<li>Must meet CalDAV requirements above.</li> <li>Must - have <a href="http://www.opendkim.org/">OpenDKIM v2.9.x (or higher)</a> + have <a href="http://www.opendkim.org/">OpenDKIM v2.x.x (or higher)</a> installed.</li> </ul> --> +<h3>Additional Timezone Service Requirements</h3> +<ul> +<li>Must + have <a href="http://freeassociation.sourceforge.net/">libical</a> + installed.</li> +<li>Must have <a href="http://www.digip.org/jansson/">Jansson</a> + installed.</li> +</ul> + <h2 id="config">General Configuration</h2> <p>The Cyrus <tt>httpd</tt> service is configurable via several @@ -512,6 +527,72 @@ </p> +<h2>Timezone Service Module</h2> + +<h3>Configuration</h3> + +<p>When enabled, the Timezone module allows Cyrus to function as a + timezone service, providing timezone data to client systems. This + module stores timezone data in the <tt>zoneinfo/</tt> subdirectory of + the Cyrus configuration directory (as specified by + the <tt>configdir</tt> option). The data is indexed by a database + whose location is specified by the <tt>zoneinfo_db_path</tt> option, + using the format specified by the <tt>zoneinfo_db</tt> option.</p> + +<h3>Administration</h3> + +<p>This module is designed to use the <i>Time Zone Database</i> data + (a.k.a. <i>Olson Database</i>) converted to the iCalendar format. The + steps to populate the Cyrus <tt>zoneinfo/</tt> directory are as follows: + +<ol start=0> +<li>Build the <tt>vzic</tt> utility located in + the <tt>tools/vzic/</tt> subdirectory of the Cyrus source code. + Simply running <tt>make</tt> in the <tt>tools/vzic/</tt> + subdirectory should suffice.</li> +<li>Download the latest version of the Time Zone Database data + from <a href="http://www.iana.org/time-zones">IANA</a>. <i>Only the + data is required, NOT the code</i>.</li> +<li>Expand the downloaded timezone data into the temporary directory + of your choice.</li> +<li>Populate <tt>configdir/zoneinfo/</tt> with iCalendar data: + <p><i>Initial Install Only</i></p> + <ol type=a> + <li>Convert the raw data into iCalendar format by + running <tt>vzic</tt> as follows: + <p><tt>vzic --pure --olson-dir <location-of-raw-data> + --output-dir <configdir>/zoneinfo</tt></p> + <p>This will create and install iCalendar data directly into + the <tt>configdir/zoneinfo/</tt> directory.</p> + </li> + </ol> + <p><i>Updating Data Only</i></p> + <ol type=a> + <li>Convert the raw data into iCalendar format by + running <tt>vzic</tt> as follows: + <p><tt>vzic --pure --olson-dir <location-of-raw-data></tt></p> + <p>This will create a <tt>zoneinfo/</tt> subdirectory in your + current location (preferably <tt>tools/vzic/</tt>).</p> + </li> + <li>Merge new/updated iCalendar data into + the <tt>configdir/zoneinfo/</tt> directory by + running <tt>vzic-merge.pl</tt> in your current location: + <p><tt>vzic-merge.pl</tt></p> + </li> + </ol> +</li> +<li>Rebuild the Cyrus zoneinfo index by + running <tt>ctl_zoneinfo</tt> as follows: + <p><tt>ctl_zoneinfo -r <version-string></tt></p> + <p>where <tt><version-string></tt> describes the recently + downloaded timezone data (e.g. "Time Zone Database + v.2013h").</p></li> +<li>Verify that the zoneinfo index database and all iCalendar data + files/links are readable by the <tt>cyrus</tt> user.</li> +</ol> +</p> + + <h2>iSchedule Module</h2> <p>This module will be automatically enabled if and only if both the @@ -527,6 +608,7 @@ <h3>Administration</h3> --> + <h2>DomainKey Module</h2> <p><i>Currently unavailable. Will be available once iSchedule support to
View file
cyrus-imapd-2.5.tar.gz/doc/install.html
Changed
@@ -37,7 +37,8 @@ <li><a href="install-sieve.html">Installing SIEVE</a> <li><a href="install-snmpmon.html">SNMP Monitoring</a> <li><a href="install-netnews.html">Cyrus and Netnews</a> -<li><a href="install-http.html">Cyrus HTTP (RSS, CalDAV, CardDAV)</a> +<li><a href="install-http.html">Cyrus HTTP (RSS, CalDAV, CardDAV, + Timezone Service)</a> <li><a href="install-virtdomains.html">Virtual Domains</a> <li><a href="install-replication.html">Cyrus Replication</a> <li><a href="install-murder.html">Cyrus Murder: The IMAP @@ -61,7 +62,5 @@ </ul> <P><HR> -last modified: $Date: 2006/11/30 17:11:16 $ -<br> <A HREF="index.html">Return</A> to the Cyrus IMAP Server Home Page </BODY></HTML>
View file
cyrus-imapd-2.5.tar.gz/doc/internal/caldav_scheduling_flowchart.html
Added
@@ -0,0 +1,337 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html> +<head> + <title>Cyrus CalDAV Scheduling Flowchart</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> +</head> +<body> + <h1>Cyrus CalDAV Scheduling Flowchart</h1> + + <h3 id="caldav_put">caldav_put() - create/modify via HTTP PUT on a + resource or POST (add-member) on a calendar</h3> + <ol> + <li>Check if the new resource is a scheduling resource (contains + ORGANIZER property). If not, skip to step 4.</li> + <li>Check for (and load) any existing resource.</li> + <li>Check if the authenticated user matches ORGANIZER. If yes, + goto <a href="#sched_request">sched_request()</a>, otherwise + goto <a href="#sched_reply">sched_reply()</a>.</li> + <li>Store the new/modified resource.</li> + </ol> + + <h3 id="caldav_delete_sched">caldav_delete_sched() - remove via HTTP + DELETE on a resource</h3> + <ol> + <li>Check if the existing resource is a scheduling resource (has + Schedule-Tag). If not, we are done.</li> + <li>Load the existing resource.</li> + <li>Check if the authenticated user matches ORGANIZER. If yes, + goto <a href="#sched_request">sched_request()</a>, otherwise + goto <a href="#sched_reply">sched_reply()</a>.</li> + </ol> + + <h3 id="caldav_post">caldav_post() - busytime query via HTTP POST on + Scheduling Outbox</h3> + <ol> + <li>Check the ACL on the owner's Scheduling Outbox. If the + authenticated user doesn't have the DACL_SCHEDFB right, fail.</li> + <li><a href="#sched_busytime_query">sched_busytime_query()</a>.</li> + </ol> + + <hr> + + <h3 id="sched_request">sched_request() - perform an organizer + request / attendee status update</h3> + <ol> + <li>Check the ACL on the owner's Scheduling Outbox. If the + authenticated user doesn't have the DACL_INVITE right, fail.</li> + <li>If the request includes a resource, then set METHOD:REQUEST, + otherwise set METHOD:CANCEL.</li> + <li>Create an iTIP message template, copying over any + CALSCALE property and VTIMEZONE components.</li> + <li>If not an attendee status update and the existing resource is a + scheduling resource: + Foreach component in the existing resource, add it and + its SEQUENCE to our hash table keyed by RECURRENCE-ID (for + comparison against new/modified resource).</li> + <li>Create a hash table of attendees. This will hold + attendee-specific iTIP messages.</li> + <li>Foreach component in the new/modified resource:</li> + <ol type=a> + <li>Lookup (by RECURRENCE-ID) and remove the component from the + hash table of existing components.</li> + <li>If the component exists compare all of DTSTART, DTEND, + DURATION, RRULE, RDATE, EXDATE to those of the new + component.</li> + <li>If the component is new or changed, + then <a href="#process_attendees">process_attendees()</a>.</li> + </ol> + <li>Foreach remaining component in the hash table of existing + components do <a href="#sched_cancel">sched_cancel()</a>.</li> + <li>Foreach iTIP message in our hash table of + ATTENDEES, <a href="#sched_deliver">sched_deliver()</a> the iTIP + message.</li> + <li>Foreach component in the new/modified resource update the + SCHEDULE-STATUS of each ATTENDEE.</li> + </ol> + + <h3 id="process_attendees">process_attendees() - create a suitable + iTIP request message for each attendee</h3> + <ol> + <li>Foreach ATTENDEE in the component, remove the SCHEDULE-STATUS + parameter, and set PROPSTAT=NEEDS-ACTION if required.</li> + <li>Make a copy of the component and + <a href="#clean_component">clean_component()</a>.</li> + <li>Foreach ATTENDEE in the cleaned component:</li> + <ol type=a> + <li>Check the CalDAV Scheduling parameters. If SCHEDULE-AGENT + != SERVER, skip to the next attendee.</li> + <li>Lookup attendee in our hash table.</li> + <li>If it doesn't exist, create a clone of our iTIP template and + insert it into our hash table of attendees.</li> + <li>Add the component to the attendee's iTIP message.</li> + <li>Add the component “number” to our mask of new components + appearing in the attendee's iTIP message.</li> + </ol> + <li>If the component is not the "master", foreach attendee do + <a href="#sched_exclude">sched_exclude()</a>.</li> + </ol> + + <h3 id="sched_exclude">sched_exclude() - exclude an attendee from a + recurrence instance</h3> + <ol> + <li>If the component did not appear in the attendee's iTIP + message, add an EXDATE property (based on the RECURRENCE-ID of + the component) to the master component of the attendee's iTIP + message.</li> + </ol> + + <h3 id="sched_cancel">sched_cancel() - cancel an organizer event/task</h3> + <ol> + <li>Set STATUS:CANCELLED on the component.</li> + <li><a href="#process_attendees">process_attendees()</a>.</li> + </ol> + + <hr> + + <h3 id="sched_reply">sched_reply() - perform an attendee reply</h3> + <ol> + <li>Check the CalDAV Scheduling parameters on ORGANIZER. If + SCHEDULE-AGENT != SERVER, we are done.</li> + <li>Check the ACL on the owner's Scheduling Outbox. If the + authenticated user doesn't have the DACL_REPLY right, fail.</li> + <li>Create a new iTIP (METHOD:REPLY) message, copying over any + CALSCALE property and VTIMEZONE components.</li> + <li>Foreach component in the existing resource:</li> + <ol type=a> + <li><a href="#trim_attendees">trim_attendees()</a>.</li> + <li>Add the trimmed component and the attendee's PARTSTAT to our + hash table keyed by RECURRENCE-ID (for comparison against + new/modified resource).</li> + </ol> + <li>Foreach component in the new/modified resource:</li> + <ol type=a> + <li><a href="#trim_attendees">trim_attendees()</a>.</li> + <li>Lookup (by RECURRENCE-ID) and remove the component from the + hash table of existing components.</li> + <li>If the component exists, compare the PARTSTAT of the + ATTENDEE to that of the new component.</li> + <li>If the component is new or the PARTSTAT has changed:</li> + <ol type=i> + <li><a href="#clean_component">clean_component()</a>.</li> + <li>Add the component to our iTIP message.</li> + <li>Add the component “number” to our mask of new components + appearing in our iTIP message.</li> + </ol> + </ol> + <li>Foreach remaining component in the hash table of existing + components do <a href="#sched_decline">sched_decline()</a>.</li> + <li><a href="#sched_deliver">sched_deliver()</a> our iTIP + message.</li> + <li>Foreach component in the new/modified resource that appeared + in our iTIP message, update the SCHEDULE-STATUS of the ORGANIZER.</li> + </ol> + + <h3 id="trim_attendees">trim_attendees() - remove all attendees + other than the one replying</h3> + <ol> + <li>Clone the component and remove all ATTENDEE properties other + than the one corresponding to the owner of the calendar.</li> + <li>Return the ATTENDEE property of owner, his/her PARTSTAT + parameter, and the RECURRENCE-ID of the component.</li> + </ol> + + <h3 id="sched_decline">sched_decline() - decline a recurrence + instance for an attendee</h3> + <ol> + <li>Set PARTSTAT of ATTENDEE to DECLINED.</li> + <li><a href="#clean_component">clean_component()</a>.</li> + <li>Add the component to our iTIP message.</li> + </ol> + + <hr> + + <h3 id="clean_component">clean_component() - sanitize a component + for use in an iTIP message</h3> + <ol> + <li>Update DTSTAMP.</li> + <li>Remove any VALARM components.</li> + <li>For a reply/decline only, remove scheduling parameters from + ORGANIZER.</li> + </ol> + + <h3 id="sched_deliver">sched_deliver() - deliver an iTIP message to + a recipient</h3> + <ol> + <li>Lookup the recipient.</li> + <li>If local to our server goto + <a href="#sched_deliver_local">sched_deliver_local()</a>, + otherwise goto + <a href="#sched_deliver_remote">sched_deliver_remote()</a>.</li> + </ol> + + <hr> + + <h3 id="sched_deliver_local">sched_deliver_local() - deliver an + iTIP message to a local user</h3> + <ol> + <li>Check the ACL on the owner's Scheduling Inbox. If the + sender doesn't have the proper right (DACL_INVITE for + request/cancel, DACL_REPLY for reply), fail.</li> + <li>Search the recipient's calendars for a resource having the + specified UID.</li> + <li>If the resource doesn't exist:</li> + <ol type=a> + <li>If the iTIP method is REPLY, fail (we are done).</li> + <li>If the iTIP method is CANCEL, ignore it (we are done).</li> + <li>Otherwise, create a new (empty) attendee object and target + the recipient's Default calendar.</li> + </ol> + <li>Otherwise, load the existing resource.</li> + <li>Update the new/existing resource:</li> + <ol type=a> + <li>If the iTIP method is CANCEL, set STATUS:CANCELLED on all + existing components.</li> + <li>If the iTIP method is REPLY, do + <a href="#deliver_merge_reply">deliver_merge_reply()</a>.</li> + <li>If the iTIP method is REQUEST, do + <a href="#deliver_merge_request">deliver_merge_request()</a>.</li> + </ol> + <li>Store the new/updated resource in the recipient's target + calendar.</li> + <li>Record the delivery status (SCHEDULE-STATUS).</li> + <li>If the iTIP message is something other than just a PARTSTAT + update from an attendee, store the iTIP message as a new + resource in the recipient's Inbox.</li> + <li>If the iTIP method is REPLY, send an update other attendees + via <a href="#sched_request">sched_request()</a>.</li> + </ol> + + <h3 id="deliver_merge_reply">deliver_merge_reply() - update + an organizer resource with an attendee reply</h3> + <ol> + <li>Foreach component in the existing resource, add it to our + hash table keyed by RECURRENCE-ID (for comparison against + iTIP message).</li> + <li>Foreach component in the iTIP message:</li> + <ol type=a> + <li>Lookup (by RECURRENCE-ID) the component from the + hash table of existing components.</li> + <li>If the component doesn't exist (new recurrence overridden by + ATTENDEE) create a new recurring component:</li> + <ol type=i> + <li>Clone the existing master component.</li> + <li>Remove the RRULE property.</li> + <li>Add the RECURRENCE-ID from the iTIP message.</li> + <li>Replace the DTSTART, DTEND, SEQUENCE properties with those + from the iTIP message.</li> + <li>Add the new component to our existing resource.</li> + </ol> + <li>Get the sending ATTENDEE from the iTIP message.</li> + <li>Find the matching ATTENDEE in the existing component.</li> + <li>If not found (ATTENDEE added themselves to this recurrence), + add new ATTENDEE to the component.</li> + <li>Set the ATTENDEE PARTSTAT, RSVP, and SCHEDULE-STATUS + parameters in the existing component.</li> + </ol> + <li>Return the sending ATTENDEE.</li> + </ol> + + <h3 id="deliver_merge_request">deliver_merge_request() - + create/update an attendee resource with an organizer request</h3> + <ol> + <li>Foreach VTIMEZONE component in the existing resource, add it + to our hash table keyed by TZID (for comparison against iTIP + message).</li> + <li>Foreach VTIMEZONE component in the iTIP message:</li> + <ol type=a> + <li>Lookup (by TZID) the VTIMEZONE component from the hash table of + existing components.</li> + <li>If the component exists, remove it from the existing + object.</li> + <li>Add the VTIMEZONE from the iTIP message to our existing + object.</li> + </ol> + <li>Foreach component in the existing resource, add it to our + hash table keyed by RECURRENCE-ID (for comparison against + iTIP message).</li> + <li>Foreach component in the iTIP message:</li> + <ol type=a> + <li>Clone a new component from the iTIP component.</li> + <li>Lookup (by RECURRENCE-ID) the component from the + hash table of existing components.</li> + <li>If the component exists:</li> + <ol type=i> + <li>Compare the SEQUENCE of the new component to the existing + component to see if it has changed.</li> + <li>Copy any COMPLETED, PERCENT-COMPLETE, or TRANSP properties + from the existing component to the new component.</li> + <li>Copy any ORGANIZER SCHEDULE-STATUS parameter + from the existing component to the new component.</li> + <li>Remove the existing component from the existing object.</li> + </ol> + <li>Add the new component to the existing object.</li> + </ol> + </ol> + + <hr> + + <h3 id="sched_deliver_remote">sched_deliver_remote() - deliver an + iTIP message to a remote user</h3> + <ol> + <li>If the recipient is local to our Murder, goto + <a href="#isched_send">isched_send()</a>, otherwise + goto <a href="#imip_send">imip_send()</a>.</li> + <li>Retrieve status of iTIP message delivery.</li> + </ol> + + <h3 id="isched_send">isched_send() - deliver an iTIP message to a + remote user via iSchedule (HTTP)</h3> + <ol> + </ol> + + <h3 id="imip_send">imip_send() - deliver an iTIP message to a + remote user via iMIP (SMTP)</h3> + <ol> + </ol> + + <hr> + + <h3 id="sched_busytime_query">sched_busytime_query() - perform a + busytime query</h3> + <ol> + </ol> + + <h3 id="busytime_query_local">busytime_query_local() - perform a + busytime query on a local user</h3> + <ol> + </ol> + + <h3 id="busytime_query_remote">busytime_query_remote() - perform a + busytime query on a remote user</h3> + <ol> + </ol> + +</body> +</html>
View file
cyrus-imapd-2.5.tar.gz/doc/internal/database-formats.html
Changed
@@ -158,6 +158,21 @@ Data: <Version>TAB<Deny List (comma-separated wildmat patterns)>TAB<Deny Message> </pre> +<h2>Timezone Info (zoneinfo.db)</h2> + +<p>This database is used for the timezone service and contains records + relating to timezones and their aliases. The database is indexed by + timezone ID and each data record contains the database version + number, a record type, a timestamp, and an optional list of strings + (either aliases for a timezone or the reference timezone for an + alias). The format of each record is as follows:</p> + +<pre> +Key: <TZID> + +Data: <Version>SP<Record Type>SP<Timestamp>SP<Data Strings (TAB-separated)> +</pre> + <h2>Seen State (<userid>.seen)</h2> <p>This database is a per-user database and maintains the list of
View file
cyrus-imapd-2.5.tar.gz/doc/specs.html
Changed
@@ -255,6 +255,8 @@ <TD>Locating Services for Calendaring Extensions to WebDAV (CalDAV) and vCard Extensions to WebDAV (CardDAV)</TD></TR> <TR><TD><A HREF="http://tools.ietf.org/html/draft-desruisseaux-ischedule">draft-desruisseaux-ischedule</A></TD> <TD>Internet Calendar Scheduling Protocol (iSchedule)</TD></TR> +<TR><TD><A HREF="http://tools.ietf.org/html/draft-douglass-timezone-service">draft-douglass-timezone-service</A></TD> +<TD>Timezone Service Protocol</TD></TR> <TR><TD><A HREF="http://tools.ietf.org/html/draft-thomson-hybi-http-timeout">draft-thomson-hybi-http-timeout</A></TD> <TD>Hypertext Transfer Protocol (HTTP) Keep-Alive Header</TD></TR> <TR><TD><A HREF="http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded">draft-ietf-appsawg-http-forwarded</A></TD>
View file
cyrus-imapd-2.5.tar.gz/imap/Makefile.in
Added
@@ -0,0 +1,407 @@ +# Makefile for cyrus imap server and associated programs +# +# Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. The name "Carnegie Mellon University" must not be used to +# endorse or promote products derived from this software without +# prior written permission. For permission or any legal +# details, please contact +# Carnegie Mellon University +# Center for Technology Transfer and Enterprise Creation +# 4615 Forbes Avenue +# Suite 302 +# Pittsburgh, PA 15213 +# (412) 268-7393, fax: (412) 268-7395 +# innovation@andrew.cmu.edu +# +# 4. Redistributions of any form whatsoever must retain the following +# acknowledgment: +# "This product includes software developed by Computing Services +# at Carnegie Mellon University (http://www.cmu.edu/computing/)." +# +# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO +# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE +# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING +# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +DEFINES=-DSETPROCTITLE + +# \Seen state database. Either 'db' (berkeley db) or +# 'local' (legacy flat file). +SEEN=seen_db.o + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +CC = @CC@ +INSTALL = @INSTALL@ +RANLIB = @RANLIB@ +AWK = @AWK@ + +CYRUS_USER=@cyrus_user@ +CYRUS_GROUP=@cyrus_group@ + +DEFS = @DEFS@ @LOCALDEFS@ +CPPFLAGS = -I.. -I$(srcdir)/../lib @COM_ERR_CPPFLAGS@ @SIEVE_CPPFLAGS@ @CPPFLAGS@ @SASLFLAGS@ @XML2_CFLAGS@ @ICAL_CFLAGS@ @SQLITE3_CFLAGS@ @DKIM_CFLAGS@ +IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ + +RSS_OBJS = http_rss.o +DAV_OBJS = http_dav.o dav_db.o +CALDAV_OBJS = http_caldav.o caldav_db.o smtpclient.o xcal.o jcal.o +CARDDAV_OBJS = http_carddav.o carddav_db.o +ISCHED_OBJS = http_ischedule.o +TIMEZONE_OBJS = http_timezone.o zoneinfo_db.o +HTTP_OBJS = httpd.o http_err.o http_proxy.o @HTTP_OBJS@ +HTTP_LIBS = @XML2_LIBS@ @ICAL_LIBS@ @SQLITE3_LIBS@ @DKIM_LIBS@ @JSON_LIBS@ + +SIEVE_OBJS = @SIEVE_OBJS@ +SIEVE_LIBS = @SIEVE_LIBS@ +IMAP_COM_ERR_LIBS = @IMAP_COM_ERR_LIBS@ +LIB_WRAP = @LIB_WRAP@ +LIBS = $(IMAP_LIBS) $(IMAP_COM_ERR_LIBS) +DEPLIBS = ../lib/libcyrus.a ../lib/libcyrus_min.a @DEPLIBS@ + +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ @COM_ERR_LDFLAGS@ + +SHELL = /bin/sh +MAKEDEPEND = @MAKEDEPEND@ + +COMPILE_ET=@COMPILE_ET@ + +# +# Some notes on purify -- +# you probably want to run the make as the cyrus user as +# purify sets the cache directory based on the user. So, +# if you don't, purify can't find the instrumented libraries +# and so you don't get any useful information. +# It may also help to run purify by hand to instrument any of +# the dynamic libraries that may crop up during run time. +# +PURIFY=/usr/local/bin/purify +PUREOPT= -best-effort -logfile=/tmp/pure/%v.%p.log -always_use_cache_dir -cachedir=/usr/tmp/$(USER) +QUANTIFY=/usr/local/bin/quantify +QUANTOPT=-windows=no -filename-prefix=/tmp/quant/%v.%p -write-summary-file= -logfile=/tmp/quant/%v.%p.log + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +cyrus_prefix = @cyrus_prefix@ +service_path = @service_path@ + +LOBJS= append.o mailbox.o mboxlist.o mupdate-client.o mboxname.o message.o \ + global.o imap_err.o mupdate_err.o proc.o setproctitle.o \ + convert_code.o duplicate.o saslclient.o saslserver.o ../lib/signals.o \ + annotate.o search_engines.o squat.o squat_internal.o mbdump.o \ + imapparse.o telemetry.o user.o notify.o idle.o quota_db.o \ + sync_log.o $(SEEN) mboxkey.o backend.o tls.o message_guid.o \ + statuscache_db.o userdeny_db.o sequence.o upgrade_index.o \ + dlist.o version.o dav_util.o + +IMAPDOBJS=pushstats.o imapd.o proxy.o imap_proxy.o index.o + +LMTPOBJS=lmtpstats.o lmtpengine.o spool.o + +# Your typical objects for the command line utilities +CLIOBJS=cli_fatal.o mutex_fake.o + +SERVICE=../master/service.o +SERVICETHREAD=../master/service-thread.o + +PROGS = imapd lmtpd pop3d \ + fud smmapd reconstruct quota mbpath ipurge cyr_dbtool cyr_synclog \ + cyrdump chk_cyrus cvt_cyrusdb deliver ctl_mboxlist \ + ctl_deliver ctl_cyrusdb squatter mbexamine cyr_expire arbitron \ + unexpunge cyr_df cyr_sequence cyr_userseen \ + @IMAP_PROGS@ + +BUILTSOURCES = imap_err.c imap_err.h pushstats.c pushstats.h \ + lmtpstats.c lmtpstats.h mupdate_err.c mupdate_err.h \ + nntp_err.c nntp_err.h http_err.c http_err.h + +all: $(BUILTSOURCES) $(PROGS) $(SUIDPROGS) + +pure: imapd.pure lmtpd.pure mupdate.pure + + +install: + $(srcdir)/../install-sh -d ${DESTDIR}$(service_path) + for file in $(PROGS); \ + do \ + $(INSTALL) -m 755 $$file $(DESTDIR)$(service_path) || exit 1; \ + done + ln -f $(DESTDIR)$(service_path)/pop3d $(DESTDIR)$(service_path)/pop3proxyd + ln -f $(DESTDIR)$(service_path)/imapd $(DESTDIR)$(service_path)/proxyd + ln -f $(DESTDIR)$(service_path)/lmtpd $(DESTDIR)$(service_path)/lmtpproxyd + +.c.o: + $(CC) -c $(CPPFLAGS) $(DEFS) $(CFLAGS) \ + $< + +### libimap + +libimap.a: $(LOBJS) + rm -f libimap.a + ar cr libimap.a $(LOBJS) + $(RANLIB) libimap.a + +### Built Source Files + +version.o: ../xversion.h + +pushstats.c: pushstats.snmp $(srcdir)/../snmp/snmpgen + $(srcdir)/../snmp/snmpgen $(srcdir)/pushstats.snmp + +pushstats.h: pushstats.c + +lmtpstats.c: lmtpstats.snmp $(srcdir)/../snmp/snmpgen + $(srcdir)/../snmp/snmpgen $(srcdir)/lmtpstats.snmp + +lmtpstats.h: lmtpstats.c + +imap_err.c: imap_err.et + $(COMPILE_ET) $(srcdir)/imap_err.et + +imap_err.h: imap_err.c + +nntp_err.c: nntp_err.et + $(COMPILE_ET) $(srcdir)/nntp_err.et + +nntp_err.h: nntp_err.c + +mupdate_err.c: mupdate_err.et + $(COMPILE_ET) $(srcdir)/mupdate_err.et + +mupdate_err.h: mupdate_err.c + +http_err.c: http_err.et + $(COMPILE_ET) $(srcdir)/http_err.et + +http_err.h: http_err.c + +### Services +idled: idled.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o idled \ + idled.o mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) + +lmtpd: lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) mutex_fake.o \ + libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o lmtpd \ + $(SERVICE) lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \ + mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +lmtpd.pure: lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \ + mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(SERVICE) + $(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o lmtpd.pure \ + $(SERVICE) lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \ + mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +imapd: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o imapd \ + $(SERVICE) $(IMAPDOBJS) mutex_fake.o \ + libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +imapd.pure: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) + $(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o imapd.pure \ + $(SERVICE) $(IMAPDOBJS) mutex_fake.o libimap.a \ + $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +imapd.quant: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) + $(QUANTIFY) $(QUANTOPT) $(CC) $(LDFLAGS) -o imapd.quant \ + $(SERVICE) $(IMAPDOBJS) mutex_fake.o libimap.a \ + $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +mupdate: mupdate.o mupdate-slave.o mupdate-client.o mutex_pthread.o tls.o \ + libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o mupdate \ + $(SERVICETHREAD) mupdate.o mupdate-slave.o mupdate-client.o \ + mutex_pthread.o tls.o libimap.a \ + $(DEPLIBS) $(LIBS) $(LIB_WRAP) -lpthread + +mupdate.pure: mupdate.o mupdate-slave.o mupdate-client.o mutex_pthread.o \ + libimap.a $(DEPLIBS) + $(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o mupdate.pure \ + $(SERVICETHREAD) mupdate.o mupdate-slave.o mupdate-client.o \ + mutex_pthread.o libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) -lpthread + +pop3d: pop3d.o proxy.o backend.o tls.o mutex_fake.o libimap.a \ + $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o pop3d pop3d.o proxy.o backend.o tls.o $(SERVICE) \ + mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +nntpd: nntpd.o proxy.o backend.o index.o smtpclient.o spool.o tls.o \ + mutex_fake.o nntp_err.o libimap.a $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o nntpd nntpd.o proxy.o backend.o index.o spool.o \ + smtpclient.o tls.o $(SERVICE) mutex_fake.o nntp_err.o \ + libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +fud: fud.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o fud $(SERVICE) fud.o mutex_fake.o libimap.a \ + $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +smmapd: smmapd.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o smmapd $(SERVICE) smmapd.o mutex_fake.o libimap.a \ + $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +sync_server: sync_server.o sync_support.o \ + imapparse.o tls.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o \ + sync_server sync_server.o sync_support.o \ + imapparse.o tls.o $(SERVICE) libimap.a mutex_fake.o \ + $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +httpd: $(HTTP_OBJS) proxy.o backend.o index.o spool.o tls.o mutex_fake.o \ + libimap.a $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o httpd $(HTTP_OBJS) \ + proxy.o backend.o index.o spool.o tls.o mutex_fake.o libimap.a \ + $(SERVICE) $(DEPLIBS) $(LIBS) $(LIB_WRAP) $(HTTP_LIBS) + +### Command Line Utilities +arbitron: arbitron.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o arbitron arbitron.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +cyr_userseen: cyr_userseen.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o cyr_userseen cyr_userseen.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +cyr_sequence: cyr_sequence.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o cyr_sequence cyr_sequence.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +cyr_dbtool: cyr_dbtool.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o cyr_dbtool cyr_dbtool.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +cyr_synclog: cyr_synclog.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o cyr_synclog cyr_synclog.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +cvt_cyrusdb: cvt_cyrusdb.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o cvt_cyrusdb cvt_cyrusdb.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +chk_cyrus: chk_cyrus.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o chk_cyrus chk_cyrus.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +deliver: deliver.o $(LMTPOBJS) proxy.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o deliver deliver.o $(LMTPOBJS) proxy.o \ + mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) + +ctl_deliver: ctl_deliver.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + $@ ctl_deliver.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +ctl_mboxlist: ctl_mboxlist.o mupdate-client.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o $@ ctl_mboxlist.o mupdate-client.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +ctl_cyrusdb: ctl_cyrusdb.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + $@ ctl_cyrusdb.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +cyr_expire: cyr_expire.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o $@ cyr_expire.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +fetchnews: fetchnews.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + $@ fetchnews.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +squatter: squatter.o index.o squat_build.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o squatter squatter.o index.o squat_build.o \ + $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +mbpath: mbpath.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o mbpath mbpath.o $(CLIOBJS) libimap.a \ + $(DEPLIBS) $(LIBS) + +ipurge: ipurge.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o ipurge ipurge.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +cyr_virusscan: cyr_virusscan.o index.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o cyr_virusscan cyr_virusscan.o index.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) -lclamav + +cyrdump: cyrdump.o index.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o cyrdump cyrdump.o index.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +cyr_df: cyr_df.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + cyr_df cyr_df.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +mbexamine: mbexamine.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + mbexamine mbexamine.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +reconstruct: reconstruct.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + reconstruct reconstruct.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +dav_reconstruct: dav_reconstruct.o caldav_db.o dav_db.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + $@ dav_reconstruct.o caldav_db.o dav_db.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) $(HTTP_LIBS) + +ctl_zoneinfo: ctl_zoneinfo.o zoneinfo_db.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + $@ ctl_zoneinfo.o zoneinfo_db.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) $(HTTP_LIBS) + +quota: quota.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o quota quota.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +tls_prune: tls_prune.o tls.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + $@ tls_prune.o tls.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) + +unexpunge: unexpunge.o $(CLIOBJS) libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o $@ unexpunge.o $(CLIOBJS) \ + libimap.a $(DEPLIBS) $(LIBS) + +sync_client: sync_client.o sync_support.o \ + backend.o tls.o imapparse.o libimap.a mutex_fake.o $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + sync_client sync_client.o sync_support.o \ + backend.o tls.o imapparse.o libimap.a mutex_fake.o $(DEPLIBS) $(LIBS) + +sync_reset: sync_reset.o sync_support.o \ + libimap.a mutex_fake.o $(DEPLIBS) + $(CC) $(LDFLAGS) -o \ + sync_reset sync_reset.o sync_support.o \ + libimap.a mutex_fake.o $(DEPLIBS) $(LIBS) + +### Other Misc Targets + +clean: + rm -f *.o *.a Makefile.bak makedepend.log \ + $(BUILTSOURCES) $(PROGS) $(SUIDPROGS) cyr_virusscan + +distclean: clean + rm -f Makefile + +depend: imap_err.h + ${MAKEDEPEND} $(CPPFLAGS) $(DEFS) $(CFLAGS) *.c $(srcdir)/*.c 1>makedepend.log 2>&1 + +# DO NOT DELETE THIS LINE -- make depend depends on it.
View file
cyrus-imapd-2.5.tar.gz/imap/annotate.c
Changed
@@ -811,56 +811,45 @@ const char **entryp, const char **useridp) { -#define NFIELDS 3 - const char *fields[NFIELDS]; - int nfields = 0; + static struct buf keybuf; const char *p; - unsigned int uid = 0; - const char *mboxname = ""; - - /* paranoia: ensure the last character in the key is - * a NUL, which it should be because of the way we - * always build keys */ - if (key[keysize-1]) - return IMAP_ANNOTATION_BADENTRY; - keysize--; + const char *end; + + buf_setmap(&keybuf, key, keysize); + buf_putc(&keybuf, '\0'); /* safety tricks due to broken FM code */ + p = buf_cstring(&keybuf); + end = p + keysize; + /* * paranoia: split the key into fields on NUL characters. * We would use strarray_nsplit() for this, except that * by design that function cannot split on NULs and does * not handle embedded NULs. */ - fields[nfields++] = key; - for (p = key ; (p-key) < keysize ; p++) { - if (!*p) { - if (nfields == NFIELDS) - return IMAP_ANNOTATION_BADENTRY; - fields[nfields++] = p+1; - } - } - if (nfields != NFIELDS) - return IMAP_ANNOTATION_BADENTRY; if (d->mboxname) { - /* per-folder db for message scope annotations */ - char *end = NULL; - uid = strtoul(fields[0], &end, 10); - if (uid == 0 || end == NULL || *end) - return IMAP_ANNOTATION_BADENTRY; - mboxname = d->mboxname; + *mboxnamep = d->mboxname; + *uidp = 0; + while (*p && p < end) *uidp = (10 * (*uidp)) + (*p++ - '0'); + if (p < end) p++; + else return IMAP_ANNOTATION_BADENTRY; } else { /* global db for mailnbox & server scope annotations */ - uid = 0; - mboxname = fields[0]; + *uidp = 0; + *mboxnamep = p; + while (*p && p < end) p++; + if (p < end) p++; + else return IMAP_ANNOTATION_BADENTRY; } - if (mboxnamep) *mboxnamep = mboxname; - if (uidp) *uidp = uid; - if (entryp) *entryp = fields[1]; - if (useridp) *useridp = fields[2]; + *entryp = p; /* XXX: trailing NULLs on non-userid keys? Bogus just at FM */ + while (*p && p < end) p++; + if (p < end && !*p) + *useridp = p+1; + else + *useridp = NULL; return 0; -#undef NFIELDS } #if DEBUG @@ -949,26 +938,28 @@ struct find_rock *frock = (struct find_rock *) rock; const char *mboxname, *entry, *userid; unsigned int uid; + char newkey[MAX_MAILBOX_NAME+1]; + size_t newkeylen; struct buf value = BUF_INITIALIZER; - char keycopy[MAX_MAILBOX_PATH+1]; int r; assert(keylen < MAX_MAILBOX_PATH); - /* take a copy, we may be deleting this record, so the key - * pointer will no longer be valid */ - memcpy(keycopy, key, keylen); - #if DEBUG syslog(LOG_ERR, "find_cb: found key %s in %s", - key_as_string(frock->d, keycopy, keylen), frock->d->filename); + key_as_string(frock->d, key, keylen), frock->d->filename); #endif - r = split_key(frock->d, keycopy, keylen, &mboxname, + r = split_key(frock->d, key, keylen, &mboxname, &uid, &entry, &userid); if (r) return r; + newkeylen = make_key(mboxname, uid, entry, userid, newkey, sizeof(newkey)); + if (keylen != newkeylen || strncmp(newkey, key, keylen)) { + syslog(LOG_ERR, "find_cb: bogus key %s %d %s %s (%d %d)", mboxname, uid, entry, userid, (int)keylen, (int)newkeylen); + } + r = split_attribs(data, datalen, &value); if (!r) r = frock->proc(mboxname, uid, entry, userid, &value, frock->rock); @@ -2498,8 +2489,8 @@ static int annotate_canon_value(struct buf *value, int type) { char *p = NULL; - unsigned long uwhatever; - long whatever; + unsigned long uwhatever = 0; + long whatever = 0; /* check for NIL */ if (value->s == NULL) @@ -3035,7 +3026,7 @@ const char *newuserid = userid; if (rrock->olduserid && rrock->newuserid && - !strcmp(rrock->olduserid, userid)) { + !strcmpsafe(rrock->olduserid, userid)) { /* renaming a user, so change the userid for priv annots */ newuserid = rrock->newuserid; }
View file
cyrus-imapd-2.5.tar.gz/imap/append.c
Changed
@@ -1369,3 +1369,8 @@ seen_close(&seendb); return r; } + +EXPORTED const char *append_stagefname(struct stagemsg *stage) +{ + return strarray_nth(&stage->parts, 0); +}
View file
cyrus-imapd-2.5.tar.gz/imap/append.h
Changed
@@ -149,4 +149,6 @@ extern int append_run_annotator(struct appendstate *as, struct index_record *record); +extern const char *append_stagefname(struct stagemsg *stage); + #endif /* INCLUDED_APPEND_H */
View file
cyrus-imapd-2.5.tar.gz/imap/arbitron.c
Changed
@@ -135,7 +135,7 @@ break; case 'D': { - unsigned month, day, year; + unsigned month = 0, day = 0, year = 0; struct tm date; if (strlen(optarg) < 8 ||
View file
cyrus-imapd-2.5.tar.gz/imap/caldav_db.c
Changed
@@ -138,8 +138,7 @@ " UNIQUE( mailbox, resource ) );" \ "CREATE INDEX IF NOT EXISTS idx_ical_uid ON ical_objs ( ical_uid );" -/* Open DAV DB corresponding to userid */ -EXPORTED struct caldav_db *caldav_open(const char *userid, int flags) +static struct caldav_db *caldav_open_fname(const char *fname, int flags) { sqlite3 *db; struct caldav_db *caldavdb = NULL; @@ -147,14 +146,45 @@ if (flags & CALDAV_TRUNC) cmds = CMD_DROP CMD_CREATE; - db = dav_open(userid, cmds); + db = dav_open(fname, cmds); if (db) { caldavdb = xzmalloc(sizeof(struct caldav_db)); caldavdb->db = db; + } + + return caldavdb; +} + +EXPORTED struct caldav_db *caldav_open_userid(const char *userid, int flags) +{ + struct caldav_db *caldavdb = NULL; + struct buf fname = BUF_INITIALIZER; + + dav_getpath_byuserid(&fname, userid); + caldavdb = caldav_open_fname(buf_cstring(&fname), flags); + buf_free(&fname); + + /* Construct mbox name corresponding to userid's scheduling Inbox */ + strncpy(caldavdb->sched_inbox, caldav_mboxname(userid, SCHED_INBOX), sizeof(caldavdb->sched_inbox)); - /* Construct mailbox name corresponding to userid's scheduling Inbox */ - strcpy(caldavdb->sched_inbox, caldav_mboxname(userid, SCHED_INBOX)); + return caldavdb; +} + +/* Open DAV DB corresponding to userid */ +EXPORTED struct caldav_db *caldav_open_mailbox(struct mailbox *mailbox, int flags) +{ + struct caldav_db *caldavdb = NULL; + const char *userid = mboxname_to_userid(mailbox->name); + + if (userid) { + caldavdb = caldav_open_userid(userid, flags); + } + else { + struct buf fname = BUF_INITIALIZER; + dav_getpath(&fname, mailbox); + caldavdb = caldav_open_fname(buf_cstring(&fname), flags); + buf_free(&fname); } return caldavdb; @@ -723,6 +753,7 @@ case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break; case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break; case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break; + case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break; default: break; } cdata->comp_type = mykind; @@ -730,6 +761,7 @@ /* Get organizer */ prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); if (prop) cdata->organizer = icalproperty_get_organizer(prop)+7; + else cdata->organizer = NULL; /* Get transparency */ prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
View file
cyrus-imapd-2.5.tar.gz/imap/caldav_db.h
Changed
@@ -50,6 +50,27 @@ #include "dav_db.h" +#ifndef HAVE_VAVAILABILITY +/* Allow us to compile without #ifdef HAVE_VAVAILABILITY everywhere */ +#define ICAL_VAVAILABILITY_COMPONENT ICAL_XLICINVALID_COMPONENT +#endif + +/* Bitmask of calendar components */ +enum { + /* "Real" components - MUST remain in this order (values used in DAV DB) */ + CAL_COMP_VEVENT = (1<<0), + CAL_COMP_VTODO = (1<<1), + CAL_COMP_VJOURNAL = (1<<2), + CAL_COMP_VFREEBUSY = (1<<3), + CAL_COMP_VAVAILABILITY = (1<<4), + /* Append additional "real" components here */ + + /* Other components - values don't matter - prepend here */ + CAL_COMP_VALARM = (1<<13), + CAL_COMP_VTIMEZONE = (1<<14), + CAL_COMP_VCALENDAR = (1<<15) +}; + struct caldav_db; #define CALDAV_CREATE 0x01 @@ -73,8 +94,9 @@ /* done with all caldav operations for this process */ int caldav_done(void); -/* get a database handle corresponding to userid */ -struct caldav_db *caldav_open(const char *userid, int flags); +/* get a database handle corresponding to mailbox */ +struct caldav_db *caldav_open_mailbox(struct mailbox *mailbox, int flags); +struct caldav_db *caldav_open_userid(const char *userid, int flags); /* close this handle */ int caldav_close(struct caldav_db *caldavdb);
View file
cyrus-imapd-2.5.tar.gz/imap/carddav_db.c
Changed
@@ -141,8 +141,8 @@ #define CMD_DROP CMD_DROP_OBJ CMD_DROP_EM CMD_DROP_GR #define CMD_CREATE CMD_CREATE_OBJ CMD_CREATE_EM CMD_CREATE_GR -/* Open DAV DB corresponding to userid */ -struct carddav_db *carddav_open(const char *userid, int flags) +/* Open DAV DB corresponding to mailbox */ +static struct carddav_db *carddav_open_fname(const char *fname, int flags) { sqlite3 *db; struct carddav_db *carddavdb = NULL; @@ -150,7 +150,7 @@ if (flags & CARDDAV_TRUNC) cmds = CMD_DROP CMD_CREATE; - db = dav_open(userid, cmds); + db = dav_open(fname, cmds); if (db) { carddavdb = xzmalloc(sizeof(struct carddav_db)); @@ -160,6 +160,30 @@ return carddavdb; } +EXPORTED struct carddav_db *carddav_open_userid(const char *userid, int flags) +{ + struct buf fname = BUF_INITIALIZER; + struct carddav_db *carddavdb = NULL; + + dav_getpath_byuserid(&fname, userid); + carddavdb = carddav_open_fname(buf_cstring(&fname), flags); + buf_free(&fname); + + return carddavdb; +} + +EXPORTED struct carddav_db *carddav_open_mailbox(struct mailbox *mailbox, int flags) +{ + struct buf fname = BUF_INITIALIZER; + struct carddav_db *carddavdb = NULL; + + dav_getpath(&fname, mailbox); + carddavdb = carddav_open_fname(buf_cstring(&fname), flags); + buf_free(&fname); + + return carddavdb; +} + /* Close DAV DB */ EXPORTED int carddav_close(struct carddav_db *carddavdb)
View file
cyrus-imapd-2.5.tar.gz/imap/carddav_db.h
Changed
@@ -72,8 +72,9 @@ /* done with all carddav operations for this process */ int carddav_done(void); -/* get a database handle corresponding to userid */ -struct carddav_db *carddav_open(const char *userid, int flags); +/* get a database handle corresponding to mailbox */ +struct carddav_db *carddav_open_mailbox(struct mailbox *mailbox, int flags); +struct carddav_db *carddav_open_userid(const char *userid, int flags); /* close this handle */ int carddav_close(struct carddav_db *carddavdb);
View file
cyrus-imapd-2.5.tar.gz/imap/ctl_conversationsdb.c
Changed
@@ -778,7 +778,7 @@ { char *inboxname; char *fname; - int r; + int r = 0; fname = conversations_getuserpath(userid); if (fname == NULL) {
View file
cyrus-imapd-2.5.tar.gz/imap/ctl_zoneinfo.c
Added
@@ -0,0 +1,310 @@ +/* ctl_zoneinfo.c -- Program to perform operations on zoneinfo db + * + * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <config.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <libical/ical.h> + +#include "annotate.h" /* for strlist functionality */ +#include "global.h" +#include "hash.h" +#include "map.h" +#include "util.h" +#include "xmalloc.h" +#include "zoneinfo_db.h" + +extern int optind; +extern char *optarg; + +/* config.c stuff */ +const int config_need_data = 0; + +int verbose = 0; + +/* forward declarations */ +void usage(void); +void free_zoneinfo(void *data); +void store_zoneinfo(const char *tzid, void *data, void *rock); +void do_zonedir(const char *prefix, struct hash_table *tzentries, + struct zoneinfo *info); +void shut_down(int code); + + +int main(int argc, char **argv) +{ + int opt, r = 0; + char *alt_config = NULL, *version = NULL; + enum { REBUILD, NONE } op = NONE; + + if ((geteuid()) == 0 && (become_cyrus() != 0)) { + fatal("must run as the Cyrus user", EC_USAGE); + } + + while ((opt = getopt(argc, argv, "C:r:v")) != EOF) { + switch (opt) { + case 'C': /* alt config file */ + alt_config = optarg; + break; + + case 'r': + if (op == NONE) { + op = REBUILD; + version = optarg; + } + else usage(); + break; + + case 'v': + verbose = 1; + break; + + default: + usage(); + } + } + + cyrus_init(alt_config, "ctl_zoneinfo", 0); + + signals_set_shutdown(&shut_down); + signals_add_handlers(0); + + switch (op) { + case REBUILD: { + struct hash_table tzentries; + struct zoneinfo *zi; + struct txn *tid = NULL; + char prefix[2048]; + + construct_hash_table(&tzentries, 500, 1); + + /* Add INFO record (overall lastmod and TZ DB source version) */ + zi = xzmalloc(sizeof(struct zoneinfo)); + zi->type = ZI_INFO; + appendstrlist(&zi->data, version); + hash_insert(INFO_TZID, zi, &tzentries); + + snprintf(prefix, sizeof(prefix), "%s%s", config_dir, FNAME_ZONEINFODIR); + + do_zonedir(prefix, &tzentries, zi); + + zoneinfo_open(NULL); + + hash_enumerate(&tzentries, &store_zoneinfo, &tid); + + zoneinfo_close(tid); + + free_hash_table(&tzentries, &free_zoneinfo); + break; + } + + case NONE: + r = 2; + usage(); + break; + } + + cyrus_done(); + + return r; +} + + +void usage(void) +{ + fprintf(stderr, + "usage: zoneinfo_reconstruct [-C <alt_config>] [-v]" + " -r <version-string>\n"); + exit(EC_USAGE); +} + + +/* Add all ZONEs and LINKs in the given directory to the hash table */ +void do_zonedir(const char *dir, struct hash_table *tzentries, + struct zoneinfo *info) +{ + DIR *dirp; + struct dirent *dirent; + + signals_poll(); + + if (verbose) printf("Rebuilding %s\n", dir); + + dirp = opendir(dir); + if (!dirp) { + fprintf(stderr, "can't open zoneinfo directory %s\n", dir); + } + + while ((dirent = readdir(dirp))) { + char path[2048], *tzid; + int plen; + struct stat sbuf; + struct zoneinfo *zi; + + if (*dirent->d_name == '.') continue; + + plen = snprintf(path, sizeof(path), "%s/%s", dir, dirent->d_name); + lstat(path, &sbuf); + + if (S_ISDIR(sbuf.st_mode)) { + /* Path is a directory (region) */ + do_zonedir(path, tzentries, info); + } + else if (S_ISLNK(sbuf.st_mode)) { + /* Path is a symlink (alias) */ + char link[1024], *alias; + ssize_t llen; + + /* Isolate tzid in path */ + if ((llen = readlink(path, link, sizeof(link))) < 0) continue; + link[llen-4] = '\0'; /* Trim ".ics" */ + for (tzid = link; !strncmp(tzid, "../", 3); tzid += 3); + + /* Isolate alias in path */ + path[plen-4] = '\0'; /* Trim ".ics" */ + alias = path + strlen(config_dir) + strlen("zoneinfo") + 2; + + if (verbose) printf("\tLINK: %s -> %s\n", alias, tzid); + + /* Create hash entry for alias */ + if (!(zi = hash_lookup(alias, tzentries))) { + zi = xzmalloc(sizeof(struct zoneinfo)); + hash_insert(alias, zi, tzentries); + } + zi->type = ZI_LINK; + appendstrlist(&zi->data, tzid); + + /* Create/update hash entry for tzid */ + if (!(zi = hash_lookup(tzid, tzentries))) { + zi = xzmalloc(sizeof(struct zoneinfo)); + hash_insert(tzid, zi, tzentries); + } + zi->type = ZI_ZONE; + appendstrlist(&zi->data, alias); + } + else if (S_ISREG(sbuf.st_mode)) { + /* Path is a regular file (zone) */ + int fd; + const char *base = NULL; + unsigned long len = 0; + icalcomponent *ical, *comp; + icalproperty *prop; + + /* Parse the iCalendar file for important properties */ + if ((fd = open(path, O_RDONLY)) == -1) continue; + map_refresh(fd, 1, &base, &len, MAP_UNKNOWN_LEN, path, NULL); + close(fd); + + ical = icalparser_parse_string(base); + map_free(&base, &len); + + comp = icalcomponent_get_first_component(ical, + ICAL_VTIMEZONE_COMPONENT); + prop = icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY); + tzid = (char *) icalproperty_get_value_as_string(prop); + + if (verbose) printf("\tZONE: %s\n", tzid); + + /* Create/update hash entry for tzid */ + if (!(zi = hash_lookup(tzid, tzentries))) { + zi = xzmalloc(sizeof(struct zoneinfo)); + hash_insert(tzid, zi, tzentries); + } + zi->type = ZI_ZONE; + prop = icalcomponent_get_first_property(comp, + ICAL_LASTMODIFIED_PROPERTY); + zi->dtstamp = icaltime_as_timet(icalproperty_get_lastmodified(prop)); + + icalcomponent_free(ical); + + /* Check overall lastmod */ + if (zi->dtstamp > info->dtstamp) info->dtstamp = zi->dtstamp; + } + else { + fprintf(stderr, "unknown path type %s\n", path); + } + } + + closedir(dirp); +} + + +/* Free a malloc'd struct zoneinfo */ +void free_zoneinfo(void *data) +{ + struct zoneinfo *zi = (struct zoneinfo *) data; + + freestrlist(zi->data); + free(zi); +} + + +/* Store a struct zoneinfo into zoneinfo.db using the given txn */ +void store_zoneinfo(const char *tzid, void *data, void *rock) +{ + struct zoneinfo *zi = (struct zoneinfo *) data; + struct txn **tid = (struct txn **) rock; + + zoneinfo_store(tzid, zi, tid); +} + + +/* + * Cleanly shut down and exit + */ +void shut_down(int code) __attribute__((noreturn)); +void shut_down(int code) +{ + in_shutdown = 1; + + exit(code); +}
View file
cyrus-imapd-2.5.tar.gz/imap/cyr_dbtool.c
Changed
@@ -63,11 +63,14 @@ #include "global.h" #include "mailbox.h" #include "util.h" +#include "retry.h" #include "xmalloc.h" #define STACKSIZE 64000 static char stack[STACKSIZE+1]; +int outfd; + static struct db *db = NULL; static int read_key_value(char **keyptr, size_t *keylen, char **valptr, size_t *vallen) { @@ -108,7 +111,16 @@ const char *key, size_t keylen, const char *data, size_t datalen) { - printf("%.*s\t%.*s\n", (int)keylen, key, (int)datalen, data); + struct iovec io[4]; + io[0].iov_base = (char *)key; + io[0].iov_len = keylen; + io[1].iov_base = "\t"; + io[1].iov_len = 1; + io[2].iov_base = (char *)data; + io[2].iov_len = datalen; + io[3].iov_base = "\n"; + io[3].iov_len = 1; + retry_writev(outfd, io, 4); return 0; } @@ -294,6 +306,8 @@ exit(EC_OSERR); } + outfd = fileno(stdout); + cyrus_init(alt_config, "cyr_dbtool", 0, 0); r = cyrusdb_open(argv[optind+1], fname, db_flags, &db);
View file
cyrus-imapd-2.5.tar.gz/imap/cyr_expire.c
Changed
@@ -58,6 +58,7 @@ #include "annotate.h" #include "duplicate.h" #include "exitcodes.h" +#include "imap_err.h" #include "global.h" #include "hash.h" #include "imap_err.h"
View file
cyrus-imapd-2.5.tar.gz/imap/dav_db.c
Changed
@@ -102,32 +102,9 @@ } -/* Create filename corresponding to userid's DAV DB */ -EXPORTED void dav_getpath(struct buf *fname, const char *userid) +static void dav_debug(void *fname, const char *sql) { - char c, *domain; - - buf_reset(fname); - if (config_virtdomains && (domain = strchr(userid, '@'))) { - char d = (char) dir_hash_c(domain+1, config_fulldirhash); - *domain = '\0'; /* split user@domain */ - c = (char) dir_hash_c(userid, config_fulldirhash); - buf_printf(fname, "%s%s%c/%s%s%c/%s%s", config_dir, FNAME_DOMAINDIR, d, - domain+1, FNAME_USERDIR, c, userid, FNAME_DAVSUFFIX); - *domain = '@'; /* reassemble user@domain */ - } - else { - c = (char) dir_hash_c(userid, config_fulldirhash); - buf_printf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid, - FNAME_DAVSUFFIX); - } -} - - -static void dav_debug(void *userid, const char *sql) -{ - syslog(LOG_DEBUG, "dav_exec(%s%s): %s", - (const char *) userid, FNAME_DAVSUFFIX, sql); + syslog(LOG_DEBUG, "dav_exec(%s): %s", (const char *) fname, sql); } static void free_dav_open(struct open_davdb *open) @@ -136,19 +113,15 @@ free(open); } - -/* Open DAV DB corresponding to userid */ -EXPORTED sqlite3 *dav_open(const char *userid, const char *cmds) +/* Open DAV DB corresponding in file */ +EXPORTED sqlite3 *dav_open(const char *fname, const char *cmds) { int rc = SQLITE_OK; - struct buf fname = BUF_INITIALIZER; struct stat sbuf; struct open_davdb *open; - dav_getpath(&fname, userid); - for (open = open_davdbs; open; open = open->next) { - if (!strcmp(open->path, buf_cstring(&fname))) { + if (!strcmp(open->path, fname)) { /* already open! */ open->refcount++; goto docmds; @@ -156,7 +129,7 @@ } open = xzmalloc(sizeof(struct open_davdb)); - open->path = buf_release(&fname); + open->path = xstrdup(fname); rc = stat(open->path, &sbuf); if (rc == -1 && errno == ENOENT) { @@ -180,7 +153,7 @@ #if SQLITE_VERSION_NUMBER >= 3006000 sqlite3_extended_result_codes(open->db, 1); #endif - sqlite3_trace(open->db, dav_debug, (void *) userid); + sqlite3_trace(open->db, dav_debug, open->path); } /* stitch on up */ @@ -188,7 +161,7 @@ open->next = open_davdbs; open_davdbs = open; -docmds: + docmds: if (cmds) { rc = sqlite3_exec(open->db, cmds, NULL, NULL, NULL); if (rc != SQLITE_OK) { @@ -198,8 +171,6 @@ } } - buf_free(&fname); - return open->db; } @@ -207,7 +178,7 @@ /* Close DAV DB */ EXPORTED int dav_close(sqlite3 *davdb) { - int rc, r = 0;; + int rc, r = 0; struct open_davdb *open, *prev = NULL; if (!davdb) return 0; @@ -292,12 +263,12 @@ } -EXPORTED int dav_delete(const char *userid) +EXPORTED int dav_delete(struct mailbox *mailbox) { struct buf fname = BUF_INITIALIZER; int r = 0; - dav_getpath(&fname, userid); + dav_getpath(&fname, mailbox); if (unlink(buf_cstring(&fname)) && errno != ENOENT) { syslog(LOG_ERR, "dav_db: error unlinking %s: %m", buf_cstring(&fname)); r = CYRUSDB_INTERNAL;
View file
cyrus-imapd-2.5.tar.gz/imap/dav_db.h
Changed
@@ -45,8 +45,7 @@ #define DAV_DB_H #include <sqlite3.h> - -#include "util.h" +#include "dav_util.h" struct dav_data { unsigned rowid; @@ -75,10 +74,8 @@ /* done with all DAV operations for this process */ int dav_done(void); -void dav_getpath(struct buf *buf, const char *userid); - -/* get a database handle corresponding to userid */ -sqlite3 *dav_open(const char *userid, const char *cmds); +/* get a database handle corresponding to mailbox */ +sqlite3 *dav_open(const char *fname, const char *cmds); /* close this handle */ int dav_close(sqlite3 *davdb); @@ -89,7 +86,7 @@ int (*cb)(sqlite3_stmt *stmt, void *rock), void *rock, sqlite3_stmt **stmt); -/* delete database corresponding to userid */ -int dav_delete(const char *userid); +/* delete database corresponding to mailbox */ +int dav_delete(struct mailbox *mailbox); #endif /* DAV_DB_H */
View file
cyrus-imapd-2.5.tar.gz/imap/dav_reconstruct.c
Changed
@@ -68,6 +68,7 @@ #include "mboxlist.h" #include "util.h" #include "xmalloc.h" +#include "xstrlcat.h" extern int optind; extern char *optarg; @@ -140,7 +141,7 @@ /* remove existing database entirely */ /* XXX - build a new file and rename into place? */ - dav_getpath(&fnamebuf, userid); + dav_getpath_byuserid(&fnamebuf, userid); if (buf_len(&fnamebuf)) unlink(buf_cstring(&fnamebuf));
View file
cyrus-imapd-2.5.tar.gz/imap/dav_util.c
Added
@@ -0,0 +1,84 @@ +/* dav_util.c -- utility functions for dealing with DAV database + * + * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <config.h> + +#include <string.h> + +#include "dav_util.h" +#include "global.h" +#include "mailbox.h" +#include "mboxname.h" +#include "util.h" + +/* Create filename corresponding to DAV DB for mailbox */ +EXPORTED void dav_getpath(struct buf *fname, struct mailbox *mailbox) +{ + const char *userid; + + userid = mboxname_to_userid(mailbox->name); + + if (userid) dav_getpath_byuserid(fname, userid); + else buf_setcstr(fname, mailbox_meta_fname(mailbox, META_DAV)); +} + +/* Create filename corresponding to DAV DB for userid */ +EXPORTED void dav_getpath_byuserid(struct buf *fname, const char *userid) +{ + char c, *domain; + + buf_reset(fname); + if (config_virtdomains && (domain = strchr(userid, '@'))) { + char d = (char) dir_hash_c(domain+1, config_fulldirhash); + *domain = '\0'; /* split user@domain */ + c = (char) dir_hash_c(userid, config_fulldirhash); + buf_printf(fname, "%s%s%c/%s%s%c/%s%s", config_dir, FNAME_DOMAINDIR, d, + domain+1, FNAME_USERDIR, c, userid, FNAME_DAVSUFFIX); + *domain = '@'; /* reassemble user@domain */ + } + else { + c = (char) dir_hash_c(userid, config_fulldirhash); + buf_printf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid, + FNAME_DAVSUFFIX); + } +}
View file
cyrus-imapd-2.5.tar.gz/imap/dav_util.h
Added
@@ -0,0 +1,58 @@ +/* dav_util.h -- utility functions for dealing with DAV database + * + * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef DAV_UTIL_H +#define DAV_UTIL_H + +#include "mailbox.h" +#include "util.h" + +#define FNAME_DAVSUFFIX ".dav" /* per-user DAV DB extension */ + +/* Create filename corresponding to DAV DB for mailbox */ +void dav_getpath(struct buf *fname, struct mailbox *mailbox); + +/* Create filename corresponding to DAV DB for userid */ +void dav_getpath_byuserid(struct buf *fname, const char *userid); + +#endif /* DAV_UTIL_H */
View file
cyrus-imapd-2.5.tar.gz/imap/dlist.c
Changed
@@ -591,7 +591,10 @@ printfile(out, dl); break; case DL_BUF: - prot_printliteral(out, dl->sval, dl->nval); + if (strlen(dl->sval) == dl->nval) + prot_printastring(out, dl->sval); + else + prot_printliteral(out, dl->sval, dl->nval); break; case DL_GUID: prot_printf(out, "%s", message_guid_encode(dl->gval)); @@ -644,6 +647,191 @@ *dlp = NULL; } +struct dlistsax_state { + const char *base; + const char *p; + const char *end; + dlistsax_cb_t *proc; + int depth; + struct dlistsax_data d; + struct buf gbuf; +}; + +static int _parseqstring(struct dlistsax_state *s, struct buf *buf) +{ + buf->len = 0; + + /* get over the first quote */ + if (*s->p++ != '"') return IMAP_INVALID_IDENTIFIER; + + while (s->p < s->end) { + /* found the end quote */ + if (*s->p == '"') { + s->p++; + return 0; + } + /* backslash just quotes the next char, no matter what it is */ + if (*s->p == '\\') { + s->p++; + if (s->p == s->end) break; + /* fall through */ + } + + buf_putc(buf, *s->p++); + } + + return IMAP_INVALID_IDENTIFIER; +} + +static int _parseliteral(struct dlistsax_state *s, struct buf *buf) +{ + size_t len = 0; + + if (*s->p++ != '{') return IMAP_INVALID_IDENTIFIER; + + while (s->p < s->end) { + if (cyrus_isdigit(*s->p)) { + len = (len * 10) + (*s->p++ - '0'); + } + else if (*s->p == '}') { + if (s->p + 3 + len >= s->end) break; + if (s->p[1] != '\r') break; + if (s->p[2] != '\n') break; + buf_setmap(buf, s->p+3, len); + s->p += len = 3; + return 0; + } + } + + return IMAP_INVALID_IDENTIFIER; +} + +static int _parseitem(struct dlistsax_state *s, struct buf *buf) +{ + const char *sp; + if (*s->p == '"') + return _parseqstring(s, buf); + else if (*s->p == '{') + return _parseliteral(s, buf); + + sp = memchr(s->p, ' ', s->end - s->p); + if (!sp) sp = s->end; + while (sp[-1] == ')' && sp > s->p) sp--; + /* this is much faster because it doesn't do a reset and check the MMAP flag */ + buf->len = 0; + buf_appendmap(buf, s->p, sp - s->p); + s->p = sp; + return 0; /* this could be the last thing, so end is OK */ +} + +static int _parsesax(struct dlistsax_state *s, int parsekey) +{ + int r = 0; + + s->depth++; + + /* handle the key if wanted */ + if (parsekey) { + r = _parseitem(s, &s->d.kbuf); + if (r) return r; + if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER; + if (*s->p == ' ') s->p++; + else return IMAP_INVALID_IDENTIFIER; + } + else { + s->d.kbuf.len = 0; + } + + if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER; + + /* check what sort of value we have */ + if (*s->p == '(') { + r = s->proc(DLISTSAX_LISTSTART, &s->d); + if (r) return r; + + s->p++; + if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER; + + while (*s->p != ')') { + r = _parsesax(s, 0); + if (r) return r; + if (*s->p == ')') break; + if (*s->p == ' ') s->p++; + else return IMAP_INVALID_IDENTIFIER; + if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER; + } + + r = s->proc(DLISTSAX_LISTEND, &s->d); + if (r) return r; + + s->p++; + } + else if (*s->p == '%') { + s->p++; + if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER; + /* no whitespace allowed here */ + if (*s->p == '(') { + r = s->proc(DLISTSAX_KVLISTSTART, &s->d); + if (r) return r; + + s->p++; + if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER; + + while (*s->p != ')') { + r = _parsesax(s, 1); + if (r) return r; + if (*s->p == ')') break; + if (*s->p == ' ') s->p++; + else return IMAP_INVALID_IDENTIFIER; + if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER; + } + + r = s->proc(DLISTSAX_KVLISTEND, &s->d); + if (r) return r; + + s->p++; + } + else { + /* unknown percent type */ + return IMAP_INVALID_IDENTIFIER; + } + } + else { + r = _parseitem(s, &s->d.buf); + if (r) return r; + + r = s->proc(DLISTSAX_STRING, &s->d); + if (r) return r; + } + + s->depth--; + + /* success */ + return 0; +} + +EXPORTED int dlist_parsesax(const char *base, size_t len, int parsekey, + dlistsax_cb_t *proc, void *rock) +{ + static struct dlistsax_state state; + int r; + + state.base = base; + state.p = base; + state.end = base + len; + state.proc = proc; + state.d.rock = rock; + + r = _parsesax(&state, parsekey); + + if (r) return r; + + if (state.p < state.end) + return IMAP_IOERROR; + + return 0; +} + static char next_nonspace(struct protstream *in, char c) { while (Uisspace(c)) c = prot_getc(in); @@ -900,6 +1088,14 @@ const char *str; size_t len; + if (!dl) return 0; + + /* atom can be NULL */ + if (dl->type == DL_NIL) { + *valp = NULL; + return 1; + } + /* tomap always adds a trailing \0 */ if (!dlist_tomap(dl, &str, &len)) return 0;
View file
cyrus-imapd-2.5.tar.gz/imap/dlist.h
Changed
@@ -51,6 +51,25 @@ #include "mailbox.h" #include "message_guid.h" +enum dlistsax_t { + DLISTSAX_LISTSTART, + DLISTSAX_LISTEND, + DLISTSAX_KVLISTSTART, + DLISTSAX_KVLISTEND, + DLISTSAX_RESERVE, + DLISTSAX_LITERAL, + DLISTSAX_FLAG, + DLISTSAX_STRING, + /* error callbacks */ + DLISTSAX_ERROR +}; + +struct dlistsax_data { + struct buf kbuf; + struct buf buf; + void *rock; +}; + enum dlist_t { DL_NIL = 0, DL_ATOM, @@ -203,6 +222,11 @@ int dlist_parsemap(struct dlist **dlp, int parsekeys, const char *base, unsigned len); +typedef int dlistsax_cb_t(int type, struct dlistsax_data *data); + +int dlist_parsesax(const char *base, size_t len, int parsekey, + dlistsax_cb_t *proc, void *rock); + void dlist_stitch(struct dlist *parent, struct dlist *child); void dlist_unstitch(struct dlist *parent, struct dlist *child); struct dlist *dlist_splice(struct dlist *parent, int num);
View file
cyrus-imapd-2.5.tar.gz/imap/global.c
Changed
@@ -100,6 +100,7 @@ EXPORTED const char *config_ptscache_db; EXPORTED const char *config_statuscache_db; HIDDEN const char *config_userdeny_db; +EXPORTED const char *config_zoneinfo_db; EXPORTED const char *config_conversations_db; EXPORTED int charset_flags; @@ -264,6 +265,7 @@ config_ptscache_db = config_getstring(IMAPOPT_PTSCACHE_DB); config_statuscache_db = config_getstring(IMAPOPT_STATUSCACHE_DB); config_userdeny_db = config_getstring(IMAPOPT_USERDENY_DB); + config_zoneinfo_db = config_getstring(IMAPOPT_ZONEINFO_DB); config_conversations_db = config_getstring(IMAPOPT_CONVERSATIONS_DB); /* configure libcyrus as needed */
View file
cyrus-imapd-2.5.tar.gz/imap/global.h
Changed
@@ -146,6 +146,7 @@ extern const char *config_ptscache_db; extern const char *config_statuscache_db; extern const char *config_userdeny_db; +extern const char *config_zoneinfo_db; extern const char *config_conversations_db; extern int charset_flags;
View file
cyrus-imapd-2.5.tar.gz/imap/http_caldav.c
Changed
@@ -81,6 +81,7 @@ #include "xcal.h" #include "mailbox.h" #include "mboxlist.h" +#include "md5.h" #include "message.h" #include "message_guid.h" #include "proxy.h" @@ -113,9 +114,12 @@ struct busytime busytime; /* array of found busytime periods */ }; +static unsigned config_allowsched = IMAP_ENUM_CALDAV_ALLOWSCHEDULING_OFF; static struct caldav_db *auth_caldavdb = NULL; static time_t compile_time; +static struct caldav_db *my_caldav_open(struct mailbox *mailbox); +static void my_caldav_close(struct caldav_db *caldavdb); static void my_caldav_init(struct buf *serverinfo); static void my_caldav_auth(const char *userid); static void my_caldav_reset(void); @@ -131,6 +135,7 @@ static int caldav_copy(struct transaction_t *txn, struct mailbox *src_mbox, struct index_record *src_rec, struct mailbox *dest_mbox, const char *dest_rsrc, + struct caldav_db *dest_davdb, unsigned overwrite, unsigned flags); static int caldav_delete_sched(struct transaction_t *txn, struct mailbox *mailbox, @@ -139,7 +144,9 @@ static int caldav_post(struct transaction_t *txn); static int caldav_put(struct transaction_t *txn, struct mime_type_t *mime, - struct mailbox *mailbox, unsigned flags); + struct mailbox *mailbox, + struct caldav_db *caldavdb, + unsigned flags); static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns, struct propfind_ctx *fctx, xmlNodePtr resp, @@ -171,9 +178,15 @@ static int proppatch_caltransp(xmlNodePtr prop, unsigned set, struct proppatch_ctx *pctx, struct propstat propstat[], void *rock); +static int propfind_tz_avail(const xmlChar *name, xmlNsPtr ns, + struct propfind_ctx *fctx, xmlNodePtr resp, + struct propstat propstat[], void *rock); static int proppatch_timezone(xmlNodePtr prop, unsigned set, struct proppatch_ctx *pctx, struct propstat propstat[], void *rock); +static int proppatch_availability(xmlNodePtr prop, unsigned set, + struct proppatch_ctx *pctx, + struct propstat propstat[], void *rock); static int report_cal_query(struct transaction_t *txn, xmlNodePtr inroot, struct propfind_ctx *fctx); @@ -199,23 +212,23 @@ static struct mime_type_t caldav_mime_types[] = { /* First item MUST be the default type and storage format */ { "text/calendar; charset=utf-8", "2.0", "ics", "ifb", - (const char* (*)(void *)) &icalcomponent_as_ical_string, + (char* (*)(void *)) &icalcomponent_as_ical_string_r, (void * (*)(const char*)) &icalparser_parse_string, - &begin_icalendar, &end_icalendar + (void (*)(void *)) &icalcomponent_free, &begin_icalendar, &end_icalendar }, { "application/calendar+xml; charset=utf-8", NULL, "xcs", "xfb", - (const char* (*)(void *)) &icalcomponent_as_xcal_string, + (char* (*)(void *)) &icalcomponent_as_xcal_string, (void * (*)(const char*)) &xcal_string_as_icalcomponent, - &begin_xcal, &end_xcal + NULL, &begin_xcal, &end_xcal }, #ifdef WITH_JSON { "application/calendar+json; charset=utf-8", NULL, "jcs", "jfb", - (const char* (*)(void *)) &icalcomponent_as_jcal_string, + (char* (*)(void *)) &icalcomponent_as_jcal_string, (void * (*)(const char*)) &jcal_string_as_icalcomponent, - &begin_jcal, &end_jcal + NULL, &begin_jcal, &end_jcal }, #endif - { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; /* Array of known "live" properties */ @@ -293,8 +306,9 @@ propfind_caldata, NULL, NULL }, { "calendar-description", NS_CALDAV, PROP_COLLECTION, propfind_fromdb, proppatch_todb, NULL }, - { "calendar-timezone", NS_CALDAV, PROP_COLLECTION, - propfind_fromdb, proppatch_timezone, NULL }, + { "calendar-timezone", NS_CALDAV, + PROP_COLLECTION | PROP_PRESCREEN | PROP_NEEDPROP, + propfind_tz_avail, proppatch_timezone, NULL }, { "supported-calendar-component-set", NS_CALDAV, PROP_COLLECTION, propfind_calcompset, proppatch_calcompset, NULL }, { "supported-calendar-data", NS_CALDAV, PROP_COLLECTION, @@ -313,6 +327,11 @@ { "schedule-calendar-transp", NS_CALDAV, PROP_COLLECTION, propfind_caltransp, proppatch_caltransp, NULL }, + /* Calendar Availability (draft-daboo-calendar-availability) properties */ + { "calendar-availability", NS_CALDAV, + PROP_COLLECTION | PROP_PRESCREEN | PROP_NEEDPROP, + propfind_tz_avail, proppatch_availability, NULL }, + /* Apple Calendar Server properties */ { "getctag", NS_CS, PROP_ALLPROP | PROP_COLLECTION, propfind_sync_token, NULL, NULL }, @@ -325,18 +344,19 @@ caldav_mime_types, &caldav_parse_path, &caldav_check_precond, - { (void **) &auth_caldavdb, + { (db_open_proc_t) &my_caldav_open, + (db_close_proc_t) &my_caldav_close, (db_lookup_proc_t) &caldav_lookup_resource, (db_foreach_proc_t) &caldav_foreach, (db_write_proc_t) &caldav_write, (db_delete_proc_t) &caldav_delete, (db_delmbox_proc_t) &caldav_delmbox }, &caldav_acl, - &caldav_copy, + (copy_proc_t) &caldav_copy, &caldav_delete_sched, { MBTYPE_CALENDAR, "mkcalendar", "mkcalendar-response", NS_CALDAV }, &caldav_post, - { CALDAV_SUPP_DATA, &caldav_put }, + { CALDAV_SUPP_DATA, (put_proc_t) &caldav_put }, caldav_props, { { "calendar-query", &report_cal_query, DACL_READ, REPORT_NEED_MBOX | REPORT_MULTISTATUS }, @@ -354,6 +374,9 @@ struct namespace_t namespace_calendar = { URL_NS_CALENDAR, 0, "/dav/calendars", "/.well-known/caldav", 1 /* auth */, (ALLOW_READ | ALLOW_POST | ALLOW_WRITE | ALLOW_DELETE | +#ifdef HAVE_VAVAILABILITY + ALLOW_CAL_AVAIL | +#endif ALLOW_DAV | ALLOW_WRITECOL | ALLOW_CAL ), &my_caldav_init, &my_caldav_auth, my_caldav_reset, &my_caldav_shutdown, { @@ -378,6 +401,23 @@ }; +static struct caldav_db *my_caldav_open(struct mailbox *mailbox) +{ + if (httpd_userid && mboxname_userownsmailbox(httpd_userid, mailbox->name)) { + return auth_caldavdb; + } + else { + return caldav_open_mailbox(mailbox, CALDAV_CREATE); + } +} + + +static void my_caldav_close(struct caldav_db *caldavdb) +{ + if (caldavdb && (caldavdb != auth_caldavdb)) caldav_close(caldavdb); +} + + static void my_caldav_init(struct buf *serverinfo) { namespace_calendar.enabled = @@ -398,7 +438,8 @@ #endif } - if (config_getswitch(IMAPOPT_CALDAV_ALLOWSCHEDULING)) { + config_allowsched = config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING); + if (config_allowsched) { namespace_calendar.allow |= ALLOW_CAL_SCHED; /* Need to set this to parse CalDAV Scheduling parameters */ @@ -414,6 +455,9 @@ const char *mailboxname; int r; + /* Generate mailboxname of calendar-home-set */ + mailboxname = caldav_mboxname(userid, NULL); + if (httpd_userisadmin || global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) { /* admin or proxy from frontend - won't have DAV database */ @@ -425,14 +469,13 @@ else { /* Open CalDAV DB for 'userid' */ my_caldav_reset(); - auth_caldavdb = caldav_open(userid, CALDAV_CREATE); + auth_caldavdb = caldav_open_userid(userid, CALDAV_CREATE); if (!auth_caldavdb) fatal("Unable to open CalDAV DB", EC_IOERR); } /* Auto-provision calendars for 'userid' */ /* calendar-home-set */ - mailboxname = caldav_mboxname(userid, NULL); r = mboxlist_lookup(mailboxname, NULL, NULL); if (r == IMAP_MAILBOX_NONEXISTENT) { if (config_mupdate_server) { @@ -657,6 +700,9 @@ /* Per RFC 6638, check Schedule-Tag */ if ((hdr = spool_getheader(txn->req_hdrs, "If-Schedule-Tag-Match"))) { + /* Special case for Apple 'If-Schedule-Tag-Match:' with no value + * and also no schedule tag on the record - let that match */ + if (cdata && !stag && !hdr[0][0]) return precond; if (etagcmp(hdr[0], stag)) return HTTP_PRECOND_FAILED; } @@ -769,6 +815,7 @@ static int caldav_copy(struct transaction_t *txn, struct mailbox *src_mbox, struct index_record *src_rec, struct mailbox *dest_mbox, const char *dest_rsrc, + struct caldav_db *dest_davdb, unsigned overwrite, unsigned flags) { int r; @@ -800,8 +847,8 @@ } /* Store source resource at destination */ - r = store_resource(txn, ical, dest_mbox, dest_rsrc, auth_caldavdb, - overwrite, flags); + r = store_resource(txn, ical, dest_mbox, dest_rsrc, dest_davdb, + overwrite, flags); icalcomponent_free(ical); @@ -819,7 +866,7 @@ if (!(namespace_calendar.allow & ALLOW_CAL_SCHED)) return 0; - if (!mailbox) { + if (!record) { /* XXX DELETE collection - check all resources for sched objects */ } else if (cdata->sched_tag) { @@ -851,7 +898,12 @@ prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); organizer = icalproperty_get_organizer(prop); - if (caladdress_lookup(organizer, &sparam)) { + r = caladdress_lookup(organizer, &sparam); + if (r == HTTP_NOT_FOUND) { + r = 0; + goto done; + } + if (r) { syslog(LOG_ERR, "meth_delete: failed to process scheduling message in %s" " (org=%s, att=%s)", @@ -911,8 +963,16 @@ const char **hdr, *sep; struct mime_type_t *mime = NULL; + /* Check requested MIME type: + 1st entry in caldav_mime_types array MUST be default MIME type */ + if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) + mime = get_accept_type(hdr, caldav_mime_types); + else mime = caldav_mime_types; + if (!mime) return HTTP_NOT_ACCEPTABLE; + /* Open mailbox for reading */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -942,23 +1002,6 @@ goto done; } - /* Check requested MIME type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) { - struct accept *e, *enc = parse_accept(hdr); - - for (e = enc; e && e->token; e++) { - struct mime_type_t *m; - - for (m = caldav_mime_types; !mime && m->content_type; m++) { - if (is_mediatype(e->token, m->content_type)) mime = m; - } - - free(e->token); - } - if (enc) free(enc); - } - if (!mime) mime = caldav_mime_types; /* 1st in array MUST default type */ - /* Setup for chunked response */ txn->flags.te |= TE_CHUNKED; txn->flags.vary |= VARY_ACCEPT; @@ -1009,7 +1052,7 @@ comp; comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) { - const char *cal_str; + char *cal_str; icalcomponent_kind kind = icalcomponent_isa(comp); /* Don't duplicate any TZIDs in our iCalendar */ @@ -1032,6 +1075,7 @@ } cal_str = mime->to_string(comp); write_body(0, txn, cal_str, strlen(cal_str)); + free(cal_str); } icalcomponent_free(ical); @@ -1048,7 +1092,7 @@ write_body(0, txn, NULL, 0); done: - if (mailbox) mailbox_unlock_index(mailbox, NULL); + mailbox_close(&mailbox); buf_free(&attrib); return ret; @@ -1130,7 +1174,7 @@ static char etag[63]; unsigned level = 0; struct buf *body = &txn->resp_body.payload; - const char *host = NULL; + const char *proto = NULL, *host = NULL; /* stat() mailboxes.db for Last-Modified and ETag */ snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST); @@ -1182,9 +1226,9 @@ buf_reset(body); /* Create base URL for calendars */ - http_proto_host(txn->req_hdrs, NULL, &host); + http_proto_host(txn->req_hdrs, &proto, &host); assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "webcal://%s%s", host, txn->req_tgt.path); + buf_printf(&txn->buf, "%s://%s%s", proto, host, txn->req_tgt.path); /* Generate list of calendars */ strlcat(txn->req_tgt.mboxname, ".%", sizeof(txn->req_tgt.mboxname)); @@ -1301,7 +1345,7 @@ /* Check Content-Type */ if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) { for (mime = caldav_mime_types; mime->content_type; mime++) { - if (is_mediatype(hdr[0], mime->content_type)) break; + if (is_mediatype(mime->content_type, hdr[0])) break; } } if (!mime || !mime->content_type) { @@ -1440,7 +1484,9 @@ */ static int caldav_put(struct transaction_t *txn, struct mime_type_t *mime, - struct mailbox *mailbox, unsigned flags) + struct mailbox *mailbox, + struct caldav_db *davdb, + unsigned flags) { int ret; icalcomponent *ical = NULL, *comp, *nextcomp; @@ -1496,90 +1542,101 @@ } } - if ((namespace_calendar.allow & ALLOW_CAL_SCHED) && organizer) { - /* Scheduling object resource */ - const char *userid; - struct caldav_data *cdata; - struct sched_param sparam; - icalcomponent *oldical = NULL; - - /* Construct userid corresponding to mailbox */ - userid = mboxname_to_userid(txn->req_tgt.mboxname); - - /* Make sure iCal UID is unique for this user */ - caldav_lookup_uid(auth_caldavdb, uid, 0, &cdata); - /* XXX Check errors */ - - if (cdata->dav.mailbox && - (strcmp(cdata->dav.mailbox, txn->req_tgt.mboxname) || - strcmp(cdata->dav.resource, txn->req_tgt.resource))) { - /* CALDAV:unique-scheduling-object-resource */ - char ext_userid[MAX_MAILBOX_NAME+1]; - - strlcpy(ext_userid, userid, sizeof(ext_userid)); - mboxname_hiersep_toexternal(&httpd_namespace, ext_userid, 0); + switch (kind) { + case ICAL_VEVENT_COMPONENT: + case ICAL_VTODO_COMPONENT: + if ((namespace_calendar.allow & ALLOW_CAL_SCHED) && organizer && + icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY)) { + /* Scheduling object resource */ + const char *userid; + struct caldav_data *cdata; + struct sched_param sparam; + icalcomponent *oldical = NULL; + int r; - txn->error.precond = CALDAV_UNIQUE_OBJECT; - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%s/user/%s/%s/%s", - namespace_calendar.prefix, - ext_userid, strrchr(cdata->dav.mailbox, '.')+1, - cdata->dav.resource); - txn->error.resource = buf_cstring(&txn->buf); - ret = HTTP_FORBIDDEN; - goto done; - } + /* Construct userid corresponding to mailbox */ + userid = mboxname_to_userid(txn->req_tgt.mboxname); - /* Lookup the organizer */ - if (caladdress_lookup(organizer, &sparam)) { - syslog(LOG_ERR, - "meth_delete: failed to process scheduling message in %s" - " (org=%s, att=%s)", - txn->req_tgt.mboxname, organizer, userid); - txn->error.desc = "Failed to lookup organizer address\r\n"; - ret = HTTP_SERVER_ERROR; - goto done; - } + /* Make sure iCal UID is unique for this user */ + caldav_lookup_uid(davdb, uid, 0, &cdata); + /* XXX Check errors */ - if (cdata->dav.imap_uid) { - /* Update existing object */ - struct index_record record; - struct buf msg_buf = BUF_INITIALIZER; - int r; + if (cdata->dav.mailbox && + (strcmp(cdata->dav.mailbox, txn->req_tgt.mboxname) || + strcmp(cdata->dav.resource, txn->req_tgt.resource))) { + + buf_printf(&txn->buf, "%s/user/%s/%s/%s", + namespace_calendar.prefix, + userid, strrchr(cdata->dav.mailbox, '.')+1, + cdata->dav.resource); + txn->error.resource = buf_cstring(&txn->buf); + ret = HTTP_FORBIDDEN; + goto done; + } - /* Load message containing the resource and parse iCal data */ - r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record, NULL); - if (!r) r = mailbox_map_record(mailbox, &record, &msg_buf); + /* Lookup the organizer */ + r = caladdress_lookup(organizer, &sparam); + if (r == HTTP_NOT_FOUND) + break; /* not a local organiser? Just skip it */ if (r) { - txn->error.desc = "Failed to read record \r\n"; + syslog(LOG_ERR, + "meth_put: failed to process scheduling message in %s" + " (org=%s)", + txn->req_tgt.mboxname, organizer); + txn->error.desc = "Failed to lookup organizer address\r\n"; ret = HTTP_SERVER_ERROR; goto done; } - oldical = icalparser_parse_string(buf_base(&msg_buf) + record.header_size); - buf_free(&msg_buf); - } - if (!strcmp(sparam.userid, userid)) { - /* Organizer scheduling object resource */ - sched_request(organizer, &sparam, oldical, ical, 0); - } - else { - /* Attendee scheduling object resource */ - sched_reply(userid, oldical, ical); - } + if (cdata->dav.imap_uid) { + /* Update existing object */ + struct index_record record; + struct buf msg_buf = BUF_INITIALIZER; + + /* Load message containing the resource and parse iCal data */ + r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record, NULL); + if (!r) r = mailbox_map_record(mailbox, &record, &msg_buf); + if (r) { + txn->error.desc = "Failed to read record \r\n"; + ret = HTTP_SERVER_ERROR; + goto done; + } + oldical = icalparser_parse_string(buf_base(&msg_buf) + record.header_size); + buf_free(&msg_buf); + } - if (oldical) icalcomponent_free(oldical); + if (!strcmp(sparam.userid, userid)) { + /* Organizer scheduling object resource */ + sched_request(organizer, &sparam, oldical, ical, 0); + } + else { + /* Attendee scheduling object resource */ + sched_reply(userid, oldical, ical); + } - flags |= NEW_STAG; + if (oldical) icalcomponent_free(oldical); + + flags |= NEW_STAG; + } + break; + + default: + /* Nothing else to do */ + break; } /* Store resource at target */ ret = store_resource(txn, ical, mailbox, txn->req_tgt.resource, - auth_caldavdb, OVERWRITE_CHECK, flags); + davdb, OVERWRITE_CHECK, flags); if (flags & PREFER_REP) { struct resp_body_t *resp_body = &txn->resp_body; - const char *data; + const char **hdr; + char *data; + + if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) + mime = get_accept_type(hdr, caldav_mime_types); + if (!mime) goto done; switch (ret) { case HTTP_NO_CONTENT: @@ -1604,6 +1661,8 @@ /* Output current representation */ write_body(ret, txn, data, resp_body->len); + + free(data); ret = 0; break; @@ -1843,6 +1902,8 @@ filter->comp |= CAL_COMP_VFREEBUSY; else if (!xmlStrcmp(name, BAD_CAST "VTIMEZONE")) filter->comp |= CAL_COMP_VTIMEZONE; + else if (!xmlStrcmp(name, BAD_CAST "VAVAILABILITY")) + filter->comp |= CAL_COMP_VAVAILABILITY; else { error->precond = CALDAV_SUPP_FILTER; ret = HTTP_FORBIDDEN; @@ -1930,6 +1991,7 @@ case CAL_COMP_VTODO: comp = "VTODO"; break; case CAL_COMP_VJOURNAL: comp = "VJOURNAL"; break; case CAL_COMP_VFREEBUSY: comp = "VFREEBUSY"; break; + case CAL_COMP_VAVAILABILITY: comp = "VAVAILABILITY"; break; } if (comp) buf_printf(&fctx->buf, "; component=%s", comp); @@ -2012,96 +2074,31 @@ } -/* Callback to fetch CALDAV:calendar-data */ +/* Callback to prescreen/fetch CALDAV:calendar-data */ static int propfind_caldata(const xmlChar *name, xmlNsPtr ns, struct propfind_ctx *fctx, - xmlNodePtr resp, + xmlNodePtr resp __attribute__((unused)), struct propstat propstat[], void *rock) { xmlNodePtr prop = (xmlNodePtr) rock; - xmlChar *attr; - const char *data; - unsigned long datalen; - xmlNodePtr node; - - if (!resp || !propstat) { - /* Prescreen calendar-data "property" request */ - unsigned allowed = 1; - - if ((attr = xmlGetProp(prop, BAD_CAST "content-type"))) { - struct mime_type_t *mime; - - /* Check requested MIME type */ - for (mime = caldav_mime_types; mime->content_type; mime++) { - if (is_mediatype((const char *) attr, mime->content_type)) { - xmlFree(attr); - - if ((attr = xmlGetProp(prop, BAD_CAST "version")) && - (!mime->version || - xmlStrcmp(attr, BAD_CAST mime->version))) { - allowed = 0; - } - break; - } - } - if (!mime->content_type) allowed = 0; - - if (attr) xmlFree(attr); - } - - if (!allowed) { - fctx->err->precond = CALDAV_SUPP_DATA; - *fctx->ret = HTTP_FORBIDDEN; - } - - return allowed; - } - - - if (!fctx->record) return HTTP_NOT_FOUND; - - if (!buf_base(&fctx->msg_buf)) { - if (mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf)) - return HTTP_SERVER_ERROR; - } + const char *data = NULL; + unsigned long datalen = 0; - data = buf_base(&fctx->msg_buf) + fctx->record->header_size; - datalen = fctx->record->size - fctx->record->header_size; + if (propstat) { + if (!fctx->record) return HTTP_NOT_FOUND; - node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK], - name, ns, NULL, 0); - - if ((attr = xmlGetProp(prop, BAD_CAST "content-type"))) { - struct mime_type_t *mime = caldav_mime_types; - - /* Find requested MIME type */ - while (!is_mediatype((const char *) attr, mime->content_type)) mime++; - - if (mime != caldav_mime_types) { - /* Not the storage format - convert into requested MIME type */ - icalcomponent *ical = icalparser_parse_string(data); - - data = mime->to_string(ical); - datalen = strlen(data); - icalcomponent_free(ical); + if (!fctx->msg_buf.len) { + mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf); } + if (!fctx->msg_buf.len) return HTTP_SERVER_ERROR; - xmlSetProp(node, BAD_CAST "content-type", attr); - xmlFree(attr); - - if ((attr = xmlGetProp(prop, BAD_CAST "version"))) { - xmlSetProp(node, BAD_CAST "version", attr); - xmlFree(attr); - } + data = fctx->msg_buf.s + fctx->record->header_size; + datalen = fctx->record->size - fctx->record->header_size; } - xmlAddChild(node, - xmlNewCDataBlock(fctx->root->doc, BAD_CAST data, datalen)); - - fctx->fetcheddata = 1; - - return 0; + return propfind_getdata(name, ns, fctx, propstat, prop, caldav_mime_types, + CALDAV_SUPP_DATA, data, datalen); } @@ -2135,8 +2132,10 @@ namespace_calendar.prefix, fctx->userid, cal ? cal : ""); } else { - buf_printf(&fctx->buf, "%s/user/%s@internal/%s", - namespace_calendar.prefix, fctx->userid, cal ? cal : ""); + buf_printf(&fctx->buf, "%s/user/%s@%s/%s", + namespace_calendar.prefix, fctx->userid, + httpd_extradomain ? httpd_extradomain : "internal", + cal ? cal : ""); } xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf)); @@ -2150,12 +2149,15 @@ const char *name; unsigned long type; } cal_comps[] = { - { "VEVENT", CAL_COMP_VEVENT }, - { "VTODO", CAL_COMP_VTODO }, - { "VJOURNAL", CAL_COMP_VJOURNAL }, - { "VFREEBUSY", CAL_COMP_VFREEBUSY }, -// { "VTIMEZONE", CAL_COMP_VTIMEZONE }, -// { "VALARM", CAL_COMP_VALARM }, + { "VEVENT", CAL_COMP_VEVENT }, + { "VTODO", CAL_COMP_VTODO }, + { "VJOURNAL", CAL_COMP_VJOURNAL }, + { "VFREEBUSY", CAL_COMP_VFREEBUSY }, +#ifdef HAVE_VAVAILABILITY + { "VAVAILABILITY", CAL_COMP_VAVAILABILITY }, +#endif +// { "VTIMEZONE", CAL_COMP_VTIMEZONE }, +// { "VALARM", CAL_COMP_VALARM }, { NULL, 0 } }; @@ -2457,6 +2459,51 @@ } +/* Callback to prescreen/fetch CALDAV:calendar-timezone/availability */ +static int propfind_tz_avail(const xmlChar *name, xmlNsPtr ns, + struct propfind_ctx *fctx, + xmlNodePtr resp __attribute__((unused)), + struct propstat propstat[], + void *rock) +{ + xmlNodePtr prop = (xmlNodePtr) rock; + struct buf attrib = BUF_INITIALIZER; + int ret = 0; + + if (propstat) { + int r = 0; + buf_reset(&fctx->buf); + buf_printf(&fctx->buf, ANNOT_NS "<%s>%s", + (const char *) ns->href, name); + + if (fctx->mailbox && !fctx->record) { + r = annotatemore_lookup(fctx->mailbox->name, + buf_cstring(&fctx->buf), + /* shared */ NULL, &attrib); + } + + if (r) { + ret = HTTP_SERVER_ERROR; + goto done; + } + + if (!attrib.len) { + ret = HTTP_NOT_FOUND; + goto done; + } + } + + buf_cstring(&attrib); /* ensure a valid pointer */ + + ret = propfind_getdata(name, ns, fctx, propstat, prop, caldav_mime_types, + CALDAV_SUPP_DATA, attrib.s, attrib.len); + +done: + buf_free(&attrib); + return ret; +} + + /* Callback to write calendar-timezone property */ static int proppatch_timezone(xmlNodePtr prop, unsigned set, struct proppatch_ctx *pctx, @@ -2466,18 +2513,47 @@ int r; if (pctx->req_tgt->collection && !pctx->req_tgt->resource) { - xmlChar *freeme = NULL; + xmlChar *type, *ver = NULL, *freeme = NULL; struct buf buf = BUF_INITIALIZER; + struct mime_type_t *mime; + icalcomponent *ical = NULL; unsigned valid = 1; - if (set) { - icalcomponent *ical = NULL; + type = xmlGetProp(prop, BAD_CAST "content-type"); + if (type) ver = xmlGetProp(prop, BAD_CAST "version"); + /* Check/find requested MIME type */ + for (mime = caldav_mime_types; type && mime->content_type; mime++) { + if (is_mediatype(mime->content_type, (const char *) type)) { + if (ver && + (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) { + continue; + } + break; + } + } + + if (!mime) { + xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], + &propstat[PROPSTAT_FORBID], + prop->name, prop->ns, NULL, + CALDAV_SUPP_DATA); + *pctx->ret = HTTP_FORBIDDEN; + valid = 0; + } + else if (!mime->content_type) { + xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], + &propstat[PROPSTAT_FORBID], + prop->name, prop->ns, NULL, + CALDAV_SUPP_DATA); + *pctx->ret = HTTP_FORBIDDEN; + valid = 0; + } + else if (set) { freeme = xmlNodeGetContent(prop); - buf_setcstr(&buf, (const char*)freeme); /* Parse and validate the iCal data */ - ical = icalparser_parse_string(buf_cstring(&buf)); + ical = mime->from_string((const char *)freeme); if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) { xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], &propstat[PROPSTAT_FORBID], @@ -2496,6 +2572,12 @@ *pctx->ret = HTTP_FORBIDDEN; valid = 0; } + else if (mime != caldav_mime_types) { + buf_setcstr(&buf, icalcomponent_as_ical_string(ical)); + } + else { + buf_setcstr(&buf, (const char *)freeme); + } } if (valid) { @@ -2506,6 +2588,109 @@ (const char *) prop->ns->href, prop->name); r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate); + if (!r) r = annotate_state_write(astate, buf_cstring(&pctx->buf), /*userid*/NULL, &buf); + if (!r) { + xml_add_prop(HTTP_OK, pctx->ns[NS_DAV], &propstat[PROPSTAT_OK], + prop->name, prop->ns, NULL, 0); + } + else { + xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV], + &propstat[PROPSTAT_ERROR], + prop->name, prop->ns, NULL, 0); + } + } + + if (ical) icalcomponent_free(ical); + if (freeme) xmlFree(freeme); + if (type) xmlFree(type); + if (ver) xmlFree(ver); + buf_free(&buf); + } + else { + xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], + &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0); + + *pctx->ret = HTTP_FORBIDDEN; + } + + return 0; +} + + +/* Callback to write calendar-availability property */ +static int proppatch_availability(xmlNodePtr prop, unsigned set, + struct proppatch_ctx *pctx, + struct propstat propstat[], + void *rock __attribute__((unused))) +{ + /* XXX - fixme */ + if (pctx->req_tgt->collection && !pctx->req_tgt->resource) { + struct buf buf = BUF_INITIALIZER; + xmlChar *type, *ver = NULL, *freeme = NULL; + struct mime_type_t *mime; + icalcomponent *ical = NULL; + unsigned valid = 1; + + type = xmlGetProp(prop, BAD_CAST "content-type"); + if (type) ver = xmlGetProp(prop, BAD_CAST "version"); + + /* Check/find requested MIME type */ + for (mime = caldav_mime_types; type && mime->content_type; mime++) { + if (is_mediatype(mime->content_type, (const char *) type)) { + if (ver && + (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) { + continue; + } + break; + } + } + + if (!mime->content_type) { + xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], + &propstat[PROPSTAT_FORBID], + prop->name, prop->ns, NULL, + CALDAV_SUPP_DATA); + *pctx->ret = HTTP_FORBIDDEN; + valid = 0; + } + else if (set) { + freeme = xmlNodeGetContent(prop); + + /* Parse and validate the iCal data */ + ical = mime->from_string((const char *)freeme); + if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) { + xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], + &propstat[PROPSTAT_FORBID], + prop->name, prop->ns, NULL, + CALDAV_VALID_DATA); + *pctx->ret = HTTP_FORBIDDEN; + valid = 0; + } + else if (!icalcomponent_get_first_component(ical, + ICAL_VAVAILABILITY_COMPONENT)) { + xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], + &propstat[PROPSTAT_FORBID], + prop->name, prop->ns, NULL, + CALDAV_VALID_OBJECT); + *pctx->ret = HTTP_FORBIDDEN; + valid = 0; + } + else if (mime != caldav_mime_types) { + buf_setcstr(&buf, icalcomponent_as_ical_string(ical)); + } + else { + buf_setcstr(&buf, (const char *)freeme); + } + } + + if (valid) { + struct annotate_state *astate = NULL; + int r; + buf_reset(&pctx->buf); + buf_printf(&pctx->buf, ANNOT_NS "<%s>%s", + (const char *) prop->ns->href, prop->name); + + r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate); r = annotate_state_write(astate, buf_cstring(&pctx->buf), /*userid*/NULL, &buf); if (!r) { xml_add_prop(HTTP_OK, pctx->ns[NS_DAV], &propstat[PROPSTAT_OK], @@ -2518,7 +2703,11 @@ } } + if (ical) icalcomponent_free(ical); if (freeme) xmlFree(freeme); + if (type) xmlFree(type); + if (ver) xmlFree(ver); + buf_free(&buf); } else { xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], @@ -2538,7 +2727,11 @@ xmlNodePtr node; struct calquery_filter calfilter; - fctx->davdb = auth_caldavdb; + memset(&calfilter, 0, sizeof(struct calquery_filter)); + calfilter.save_busytime = 0; + + fctx->open_db = (db_open_proc_t) &my_caldav_open; + fctx->close_db = (db_close_proc_t) &my_caldav_close; fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource; fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach; fctx->proc_by_resource = &propfind_by_resource; @@ -2547,7 +2740,6 @@ for (node = inroot->children; node; node = node->next) { if (node->type == XML_ELEMENT_NODE) { if (!xmlStrcmp(node->name, BAD_CAST "filter")) { - memset(&calfilter, 0, sizeof(struct calquery_filter)); ret = parse_comp_filter(node->children, &calfilter, &txn->error); if (ret) return ret; else { @@ -2592,9 +2784,14 @@ httpd_authstate, propfind_by_collection, fctx); } + if (fctx->davdb) my_caldav_close(fctx->davdb); + ret = *fctx->ret; } + /* RRULEs still populate busytime array */ + if (calfilter.busytime.busy) free(calfilter.busytime.busy); + return ret; } @@ -2633,10 +2830,11 @@ /* Check if we already have this mailbox open */ if (!mailbox || strcmp(mailbox->name, tgt.mboxname)) { - if (mailbox) mailbox_unlock_index(mailbox, NULL); + if (mailbox) mailbox_close(&mailbox); /* Open mailbox for reading */ - if ((r = http_mailbox_open(tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -2647,9 +2845,12 @@ fctx->mailbox = mailbox; } + /* Open the DAV DB corresponding to the mailbox */ + fctx->davdb = my_caldav_open(fctx->mailbox); + /* Find message UID for the resource */ - r = caldav_lookup_resource(auth_caldavdb, - tgt.mboxname, tgt.resource, 0, &cdata); + r = caldav_lookup_resource(fctx->davdb, + tgt.mboxname, tgt.resource, 0, &cdata); if (r) { ret = HTTP_NOT_FOUND; goto done; @@ -2658,11 +2859,13 @@ /* XXX Check errors */ propfind_by_resource(fctx, cdata); + + my_caldav_close(fctx->davdb); } } done: - if (mailbox) mailbox_unlock_index(mailbox, NULL); + mailbox_close(&mailbox); buf_free(&uri); return ret; @@ -2745,6 +2948,8 @@ struct busytime *busytime = &calfilter->busytime; icalcomponent *cal = NULL; + fctx->open_db = (db_open_proc_t) &my_caldav_open; + fctx->close_db = (db_close_proc_t) &my_caldav_close; fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource; fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach; fctx->proc_by_resource = &busytime_by_resource; @@ -2761,11 +2966,13 @@ } else { /* Get busytime for all contained calendar collections */ - strlcat(mailboxname, ".%", sizeof(mailboxname)); + strlcat(mailboxname, ".%", MAX_MAILBOX_PATH); mboxlist_findall(NULL, /* internal namespace */ mailboxname, 1, httpd_userid, httpd_authstate, busytime_by_collection, fctx); } + + if (fctx->davdb) my_caldav_close(fctx->davdb); } if (!*fctx->ret) { @@ -2842,6 +3049,8 @@ xmlNodePtr inroot, struct propfind_ctx *fctx) { int ret = 0; + const char **hdr; + struct mime_type_t *mime; struct calquery_filter calfilter; xmlNodePtr node; icalcomponent *cal; @@ -2849,6 +3058,13 @@ /* Can not be run against a resource */ if (txn->req_tgt.resource) return HTTP_FORBIDDEN; + /* Check requested MIME type: + 1st entry in caldav_mime_types array MUST be default MIME type */ + if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) + mime = get_accept_type(hdr, caldav_mime_types); + else mime = caldav_mime_types; + if (!mime) return HTTP_NOT_ACCEPTABLE; + memset(&calfilter, 0, sizeof(struct calquery_filter)); calfilter.comp = CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY; calfilter.start = icaltime_from_timet_with_zone(INT_MIN, 0, NULL); @@ -2856,7 +3072,6 @@ calfilter.save_busytime = 1; fctx->filter = apply_calfilter; fctx->filter_crit = &calfilter; - fctx->davdb = auth_caldavdb; /* Parse children element of report */ for (node = inroot->children; node; node = node->next) { @@ -2889,28 +3104,8 @@ if (calfilter.busytime.busy) free(calfilter.busytime.busy); if (cal) { - struct mime_type_t *mime = NULL; - const char **hdr, *cal_str; - - /* Check requested MIME type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) { - struct accept *e, *enc = parse_accept(hdr); - - for (e = enc; e && e->token; e++) { - struct mime_type_t *m; - - for (m = caldav_mime_types; !mime && m->content_type; m++) { - if (is_mediatype(e->token, m->content_type)) mime = m; - } - - free(e->token); - } - if (enc) free(enc); - } - if (!mime) mime = caldav_mime_types; /* 1st in array is default type */ - /* Output the iCalendar object as text/calendar */ - cal_str = mime->to_string(cal); + char *cal_str = mime->to_string(cal); icalcomponent_free(cal); txn->resp_body.type = mime->content_type; @@ -2919,6 +3114,7 @@ txn->flags.cc |= CC_NOTRANSFORM; write_body(HTTP_OK, txn, cal_str, strlen(cal_str)); + free(cal_str); } else ret = HTTP_NOT_FOUND; @@ -2964,6 +3160,7 @@ case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break; case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break; case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break; + case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break; default: txn->error.precond = CALDAV_SUPP_COMP; return HTTP_FORBIDDEN; @@ -3203,21 +3400,17 @@ int caladdress_lookup(const char *addr, struct sched_param *param) { - char *p; + const char *userid = addr; int islocal = 1, found = 1; - static char userid[MAX_MAILBOX_BUFFER]; memset(param, 0, sizeof(struct sched_param)); if (!addr) return HTTP_NOT_FOUND; - p = (char *) addr; - if (!strncmp(addr, "mailto:", 7)) p += 7; + if (!strncasecmp(userid, "mailto:", 7)) userid += 7; /* XXX Do LDAP/DB/socket lookup to see if user is local */ /* XXX Hack until real lookup stuff is written */ - strlcpy(userid, p, sizeof(userid)); - if ((p = strchr(userid, '@'))) *p++ = '\0'; if (islocal) { /* User is in a local domain */ @@ -3227,7 +3420,7 @@ struct mboxname_parts parts; if (!found) return HTTP_NOT_FOUND; - else param->userid = userid; + else param->userid = xstrdupnull(userid); /* XXX - memleak */ /* Lookup user's cal-home-set to see if its on this server */ @@ -3237,8 +3430,9 @@ r = http_mlookup(mailboxname, &mbentry, NULL); if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - mailboxname, error_message(r)); + if (r != IMAP_MAILBOX_NONEXISTENT) + syslog(LOG_ERR, "mlookup(%s) failed: %s", + mailboxname, error_message(r)); mboxname_free_parts(&parts); switch (r) { @@ -3632,7 +3826,6 @@ const char *userid = sparam.userid; mbentry_t *mbentry = NULL; icalcomponent *busy = NULL; - int rights = 0; resp = xml_add_schedresponse(root, @@ -3640,6 +3833,7 @@ ns[NS_DAV] : NULL, BAD_CAST attendee, NULL); + /* XXX - BROKEN WITH DOMAIN SPLIT, POS */ /* Check ACL of ORGANIZER on attendee's Scheduling Inbox */ snprintf(mailboxname, sizeof(mailboxname), "user.%s.%s.Inbox", userid, calendarprefix); @@ -3652,31 +3846,20 @@ BAD_CAST REQSTAT_REJECTED); } else { - rights = cyrus_acl_myrights(org_authstate, mbentry->acl); - mboxlist_entry_free(&mbentry); - if (!(rights & DACL_SCHEDFB)) { - xmlNewChild(resp, NULL, BAD_CAST "request-status", - BAD_CAST REQSTAT_NOPRIVS); - } - else { - /* Start query at attendee's calendar-home-set */ - snprintf(mailboxname, sizeof(mailboxname), - "user.%s.%s", userid, calendarprefix); - - fctx.davdb = caldav_open(userid, CALDAV_CREATE); - fctx.req_tgt->collection = NULL; - calfilter.busytime.len = 0; - busy = busytime_query_local(txn, &fctx, mailboxname, - ICAL_METHOD_REPLY, uid, - organizer, attendee); - - caldav_close(fctx.davdb); - } + /* Start query at attendee's calendar-home-set */ + snprintf(mailboxname, sizeof(mailboxname), + "user.%s.%s", userid, calendarprefix); + + fctx.req_tgt->collection = NULL; + calfilter.busytime.len = 0; + busy = busytime_query_local(txn, &fctx, mailboxname, + ICAL_METHOD_REPLY, uid, + organizer, attendee); } if (busy) { xmlNodePtr cdata; - const char *fb_str = mime->to_string(busy); + char *fb_str = mime->to_string(busy); icalcomponent_free(busy); xmlNewChild(resp, NULL, BAD_CAST "request-status", @@ -3701,6 +3884,7 @@ xmlAddChild(cdata, xmlNewCDataBlock(root->doc, BAD_CAST fb_str, strlen(fb_str))); + free(fb_str); /* iCalendar data in response should not be transformed */ txn->flags.cc |= CC_NOTRANSFORM; @@ -3830,13 +4014,345 @@ } } + +static const char *deliver_merge_reply(icalcomponent *ical, + icalcomponent *reply) +{ + struct hash_table comp_table; + icalcomponent *comp, *itip; + icalcomponent_kind kind; + icalproperty *prop, *att; + icalparameter *param; + icalparameter_partstat partstat; + icalparameter_rsvp rsvp = ICAL_RSVP_NONE; + const char *recurid, *attendee = NULL, *req_stat = SCHEDSTAT_SUCCESS; + + /* Add each component of old object to hash table for comparison */ + construct_hash_table(&comp_table, 10, 1); + comp = icalcomponent_get_first_real_component(ical); + kind = icalcomponent_isa(comp); + do { + prop = + icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY); + if (prop) recurid = icalproperty_get_value_as_string(prop); + else recurid = ""; + + hash_insert(recurid, comp, &comp_table); + + } while ((comp = icalcomponent_get_next_component(ical, kind))); + + /* Process each component in the iTIP reply */ + for (itip = icalcomponent_get_first_component(reply, kind); + itip; + itip = icalcomponent_get_next_component(reply, kind)) { + + /* Lookup this comp in the hash table */ + prop = + icalcomponent_get_first_property(itip, ICAL_RECURRENCEID_PROPERTY); + if (prop) recurid = icalproperty_get_value_as_string(prop); + else recurid = ""; + + comp = hash_lookup(recurid, &comp_table); + if (!comp) { + /* New recurrence overridden by attendee. + Create a new recurrence from master component. */ + comp = icalcomponent_new_clone(hash_lookup("", &comp_table)); + + /* Add RECURRENCE-ID */ + icalcomponent_add_property(comp, icalproperty_new_clone(prop)); + + /* Remove RRULE */ + prop = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY); + if (prop) { + icalcomponent_remove_property(comp, prop); + icalproperty_free(prop); + } + + /* Replace DTSTART, DTEND, SEQUENCE */ + prop = + icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY); + if (prop) { + icalcomponent_remove_property(comp, prop); + icalproperty_free(prop); + } + prop = + icalcomponent_get_first_property(itip, ICAL_DTSTART_PROPERTY); + if (prop) + icalcomponent_add_property(comp, icalproperty_new_clone(prop)); + + prop = + icalcomponent_get_first_property(comp, ICAL_DTEND_PROPERTY); + if (prop) { + icalcomponent_remove_property(comp, prop); + icalproperty_free(prop); + } + prop = + icalcomponent_get_first_property(itip, ICAL_DTEND_PROPERTY); + if (prop) + icalcomponent_add_property(comp, icalproperty_new_clone(prop)); + + prop = + icalcomponent_get_first_property(comp, ICAL_SEQUENCE_PROPERTY); + if (prop) { + icalcomponent_remove_property(comp, prop); + icalproperty_free(prop); + } + prop = + icalcomponent_get_first_property(itip, ICAL_SEQUENCE_PROPERTY); + if (prop) + icalcomponent_add_property(comp, icalproperty_new_clone(prop)); + + icalcomponent_add_component(ical, comp); + } + + /* Get the sending attendee */ + att = icalcomponent_get_first_property(itip, ICAL_ATTENDEE_PROPERTY); + attendee = icalproperty_get_attendee(att); + param = icalproperty_get_first_parameter(att, ICAL_PARTSTAT_PARAMETER); + partstat = icalparameter_get_partstat(param); + param = icalproperty_get_first_parameter(att, ICAL_RSVP_PARAMETER); + if (param) rsvp = icalparameter_get_rsvp(param); + + prop = + icalcomponent_get_first_property(itip, ICAL_REQUESTSTATUS_PROPERTY); + if (prop) { + struct icalreqstattype rq = icalproperty_get_requeststatus(prop); + req_stat = icalenum_reqstat_code(rq.code); + } + + /* Find matching attendee in existing object */ + for (prop = + icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); + prop && strcmp(attendee, icalproperty_get_attendee(prop)); + prop = + icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)); + if (!prop) { + /* Attendee added themselves to this recurrence */ + prop = icalproperty_new_clone(att); + icalcomponent_add_property(comp, prop); + } + + /* Find and set PARTSTAT */ + param = icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER); + if (!param) { + param = icalparameter_new(ICAL_PARTSTAT_PARAMETER); + icalproperty_add_parameter(prop, param); + } + icalparameter_set_partstat(param, partstat); + + /* Find and set RSVP */ + param = icalproperty_get_first_parameter(prop, ICAL_RSVP_PARAMETER); + if (param) icalproperty_remove_parameter_by_ref(prop, param); + if (rsvp != ICAL_RSVP_NONE) { + param = icalparameter_new(ICAL_RSVP_PARAMETER); + icalproperty_add_parameter(prop, param); + icalparameter_set_rsvp(param, rsvp); + } + + /* Find and set SCHEDULE-STATUS */ + for (param = + icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER); + param && strcmp(icalparameter_get_iana_name(param), + "SCHEDULE-STATUS"); + param = + icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER)); + if (!param) { + param = icalparameter_new(ICAL_IANA_PARAMETER); + icalproperty_add_parameter(prop, param); + icalparameter_set_iana_name(param, "SCHEDULE-STATUS"); + } + icalparameter_set_iana_value(param, req_stat); + } + + free_hash_table(&comp_table, NULL); + + return attendee; +} + + +static int deliver_merge_request(const char *attendee, + icalcomponent *ical, icalcomponent *request) +{ + int deliver_inbox = 0; + struct hash_table comp_table; + icalcomponent *comp, *itip; + icalcomponent_kind kind = ICAL_NO_COMPONENT; + icalproperty *prop; + icalparameter *param; + const char *tzid, *recurid; + + /* Add each VTIMEZONE of old object to hash table for comparison */ + construct_hash_table(&comp_table, 10, 1); + for (comp = + icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT); + comp; + comp = + icalcomponent_get_next_component(ical, ICAL_VTIMEZONE_COMPONENT)) { + prop = icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY); + tzid = icalproperty_get_tzid(prop); + + hash_insert(tzid, comp, &comp_table); + } + + /* Process each VTIMEZONE in the iTIP request */ + for (itip = icalcomponent_get_first_component(request, + ICAL_VTIMEZONE_COMPONENT); + itip; + itip = icalcomponent_get_next_component(request, + ICAL_VTIMEZONE_COMPONENT)) { + /* Lookup this TZID in the hash table */ + prop = icalcomponent_get_first_property(itip, ICAL_TZID_PROPERTY); + tzid = icalproperty_get_tzid(prop); + + comp = hash_lookup(tzid, &comp_table); + if (comp) { + /* Remove component from old object */ + icalcomponent_remove_component(ical, comp); + icalcomponent_free(comp); + } + + /* Add new/modified component from iTIP request */ + icalcomponent_add_component(ical, icalcomponent_new_clone(itip)); + } + + free_hash_table(&comp_table, NULL); + + /* Add each component of old object to hash table for comparison */ + construct_hash_table(&comp_table, 10, 1); + comp = icalcomponent_get_first_real_component(ical); + if (comp) kind = icalcomponent_isa(comp); + for (; comp; comp = icalcomponent_get_next_component(ical, kind)) { + prop = + icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY); + if (prop) recurid = icalproperty_get_value_as_string(prop); + else recurid = ""; + + hash_insert(recurid, comp, &comp_table); + } + + /* Process each component in the iTIP request */ + itip = icalcomponent_get_first_real_component(request); + if (kind == ICAL_NO_COMPONENT) kind = icalcomponent_isa(itip); + for (; itip; itip = icalcomponent_get_next_component(request, kind)) { + icalcomponent *new_comp = icalcomponent_new_clone(itip); + + /* Lookup this comp in the hash table */ + prop = + icalcomponent_get_first_property(itip, ICAL_RECURRENCEID_PROPERTY); + if (prop) recurid = icalproperty_get_value_as_string(prop); + else recurid = ""; + + comp = hash_lookup(recurid, &comp_table); + if (comp) { + int old_seq, new_seq; + + /* Check if this is something more than an update */ + /* XXX Probably need to check PARTSTAT=NEEDS-ACTION + and RSVP=TRUE as well */ + old_seq = icalcomponent_get_sequence(comp); + new_seq = icalcomponent_get_sequence(itip); + if (new_seq > old_seq) deliver_inbox = 1; + + /* Copy over any COMPLETED, PERCENT-COMPLETE, + or TRANSP properties */ + prop = + icalcomponent_get_first_property(comp, ICAL_COMPLETED_PROPERTY); + if (prop) { + icalcomponent_add_property(new_comp, + icalproperty_new_clone(prop)); + } + prop = + icalcomponent_get_first_property(comp, + ICAL_PERCENTCOMPLETE_PROPERTY); + if (prop) { + icalcomponent_add_property(new_comp, + icalproperty_new_clone(prop)); + } + prop = + icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY); + if (prop) { + icalcomponent_add_property(new_comp, + icalproperty_new_clone(prop)); + } + + /* Copy over any ORGANIZER;SCHEDULE-STATUS */ + /* XXX Do we only do this iff PARTSTAT!=NEEDS-ACTION */ + prop = + icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); + for (param = icalproperty_get_first_parameter(prop, + ICAL_IANA_PARAMETER); + param; + param = icalproperty_get_next_parameter(prop, + ICAL_IANA_PARAMETER)) { + if (!strcmp(icalparameter_get_iana_name(param), + "SCHEDULE-STATUS")) { + const char *sched_stat = + icalparameter_get_iana_value(param); + + prop = + icalcomponent_get_first_property(new_comp, + ICAL_ORGANIZER_PROPERTY); + param = icalparameter_new(ICAL_IANA_PARAMETER); + icalproperty_add_parameter(prop, param); + icalparameter_set_iana_name(param, + "SCHEDULE-STATUS"); + icalparameter_set_iana_value(param, sched_stat); + } + } + + /* Remove component from old object */ + icalcomponent_remove_component(ical, comp); + icalcomponent_free(comp); + } + else { + /* New component */ + deliver_inbox = 1; + } + + if (config_allowsched == IMAP_ENUM_CALDAV_ALLOWSCHEDULING_APPLE && + kind == ICAL_VEVENT_COMPONENT) { + /* Make VEVENT component transparent if recipient ATTENDEE + PARTSTAT=NEEDS-ACTION (for compatibility with CalendarServer) */ + for (prop = + icalcomponent_get_first_property(new_comp, + ICAL_ATTENDEE_PROPERTY); + prop && strcmp(icalproperty_get_attendee(prop), attendee); + prop = + icalcomponent_get_next_property(new_comp, + ICAL_ATTENDEE_PROPERTY)); + param = + icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER); + if (icalparameter_get_partstat(param) == + ICAL_PARTSTAT_NEEDSACTION) { + prop = + icalcomponent_get_first_property(new_comp, + ICAL_TRANSP_PROPERTY); + if (prop) + icalproperty_set_transp(prop, ICAL_TRANSP_TRANSPARENT); + else { + prop = icalproperty_new_transp(ICAL_TRANSP_TRANSPARENT); + icalcomponent_add_property(new_comp, prop); + } + } + } + + /* Add new/modified component from iTIP request */ + icalcomponent_add_component(ical, new_comp); + } + + free_hash_table(&comp_table, NULL); + + return deliver_inbox; +} + + /* Deliver scheduling object to local recipient */ static void sched_deliver_local(const char *recipient, struct sched_param *sparam, struct sched_data *sched_data, struct auth_state *authstate) { - int r = 0, rights, reqd_privs, deliver_inbox = 0; + int r = 0, rights, reqd_privs, deliver_inbox = 1; const char *userid = sparam->userid, *attendee = NULL; static struct buf resource = BUF_INITIALIZER; static unsigned sched_count = 0; @@ -3846,6 +4362,9 @@ struct caldav_db *caldavdb = NULL; struct caldav_data *cdata; icalcomponent *ical = NULL; + icalproperty_method method; + icalcomponent_kind kind; + icalcomponent *comp; icalproperty *prop; struct transaction_t txn; @@ -3879,8 +4398,11 @@ goto done; } + /* Get METHOD of the iTIP message */ + method = icalcomponent_get_method(sched_data->itip); + /* Search for iCal UID in recipient's calendars */ - caldavdb = caldav_open(userid, CALDAV_CREATE); + caldavdb = caldav_open_userid(userid, CALDAV_CREATE); if (!caldavdb) { sched_data->status = sched_data->ischedule ? REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; @@ -3900,12 +4422,39 @@ sched_data->ischedule ? REQSTAT_PERMFAIL : SCHEDSTAT_PERMFAIL; goto done; } + else if (method == ICAL_METHOD_CANCEL) { + /* Can't find object belonging to attendee - we're done */ + sched_data->status = + sched_data->ischedule ? REQSTAT_SUCCESS : SCHEDSTAT_DELIVERED; + goto done; + } else { /* Can't find object belonging to attendee - use default calendar */ mailboxname = caldav_mboxname(userid, SCHED_DEFAULT); buf_reset(&resource); buf_printf(&resource, "%s.ics", icalcomponent_get_uid(sched_data->itip)); + + /* Create new attendee object */ + ical = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT, 0); + + /* Copy over VERSION property */ + prop = icalcomponent_get_first_property(sched_data->itip, + ICAL_VERSION_PROPERTY); + icalcomponent_add_property(ical, icalproperty_new_clone(prop)); + + /* Copy over PRODID property */ + prop = icalcomponent_get_first_property(sched_data->itip, + ICAL_PRODID_PROPERTY); + icalcomponent_add_property(ical, icalproperty_new_clone(prop)); + + /* Copy over any CALSCALE property */ + prop = icalcomponent_get_first_property(sched_data->itip, + ICAL_CALSCALE_PROPERTY); + if (prop) { + icalcomponent_add_property(ical, + icalproperty_new_clone(prop)); + } } /* Open recipient's calendar for reading */ @@ -3918,23 +4467,9 @@ goto done; } - if (!cdata->dav.imap_uid) { - /* Create new object (copy of request w/o METHOD) */ - ical = icalcomponent_new_clone(sched_data->itip); - - prop = icalcomponent_get_first_property(ical, ICAL_METHOD_PROPERTY); - icalcomponent_remove_property(ical, prop); - icalproperty_free(prop); - - deliver_inbox = 1; - } - else { - /* Update existing object */ + if (cdata->dav.imap_uid) { struct index_record record; struct buf msg_buf = BUF_INITIALIZER; - icalcomponent *comp; - icalcomponent_kind kind; - icalproperty_method method; /* Load message containing the resource and parse iCal data */ r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record, NULL); @@ -3946,371 +4481,40 @@ } ical = icalparser_parse_string(buf_base(&msg_buf) + record.header_size); buf_free(&msg_buf); + } + switch (method) { + case ICAL_METHOD_CANCEL: /* Get component type */ comp = icalcomponent_get_first_real_component(ical); kind = icalcomponent_isa(comp); - /* Get METHOD of the iTIP message */ - method = icalcomponent_get_method(sched_data->itip); - - switch (method) { - case ICAL_METHOD_CANCEL: - /* Set STATUS:CANCELLED on all components */ - do { - icalcomponent_set_status(comp, ICAL_STATUS_CANCELLED); - icalcomponent_set_sequence(comp, - icalcomponent_get_sequence(comp)+1); - } while ((comp = icalcomponent_get_next_component(ical, kind))); - - deliver_inbox = 1; - - break; - - case ICAL_METHOD_REPLY: { - struct hash_table comp_table; - icalcomponent *itip; - icalproperty *att; - icalparameter *param; - icalparameter_partstat partstat; - icalparameter_rsvp rsvp = ICAL_RSVP_NONE; - const char *recurid, *req_stat = SCHEDSTAT_SUCCESS; - - /* Add each component of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - comp = icalcomponent_get_first_real_component(ical); - do { - prop = - icalcomponent_get_first_property(comp, - ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - hash_insert(recurid, comp, &comp_table); - - } while ((comp = icalcomponent_get_next_component(ical, kind))); - - /* Process each component in the iTIP reply */ - itip = icalcomponent_get_first_component(sched_data->itip, kind); - do { - /* Lookup this comp in the hash table */ - prop = - icalcomponent_get_first_property(itip, - ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - comp = hash_lookup(recurid, &comp_table); - if (!comp) { - /* New recurrence overridden by attendee. - Create a new recurrence from master component. */ - comp = - icalcomponent_new_clone(hash_lookup("", &comp_table)); - - /* Add RECURRENCE-ID */ - icalcomponent_add_property(comp, - icalproperty_new_clone(prop)); - - /* Remove RRULE */ - prop = - icalcomponent_get_first_property(comp, - ICAL_RRULE_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - - /* Replace DTSTART, DTEND, SEQUENCE */ - prop = - icalcomponent_get_first_property(comp, - ICAL_DTSTART_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - prop = - icalcomponent_get_first_property(itip, - ICAL_DTSTART_PROPERTY); - if (prop) - icalcomponent_add_property(comp, - icalproperty_new_clone(prop)); - - prop = - icalcomponent_get_first_property(comp, - ICAL_DTEND_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - prop = - icalcomponent_get_first_property(itip, - ICAL_DTEND_PROPERTY); - if (prop) - icalcomponent_add_property(comp, - icalproperty_new_clone(prop)); - - prop = - icalcomponent_get_first_property(comp, - ICAL_SEQUENCE_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - prop = - icalcomponent_get_first_property(itip, - ICAL_SEQUENCE_PROPERTY); - if (prop) - icalcomponent_add_property(comp, - icalproperty_new_clone(prop)); - - icalcomponent_add_component(ical, comp); - } - - /* Get the sending attendee */ - att = icalcomponent_get_first_property(itip, - ICAL_ATTENDEE_PROPERTY); - attendee = icalproperty_get_attendee(att); - param = - icalproperty_get_first_parameter(att, - ICAL_PARTSTAT_PARAMETER); - partstat = icalparameter_get_partstat(param); - param = - icalproperty_get_first_parameter(att, - ICAL_RSVP_PARAMETER); - if (param) rsvp = icalparameter_get_rsvp(param); - - prop = - icalcomponent_get_first_property(itip, - ICAL_REQUESTSTATUS_PROPERTY); - if (prop) { - struct icalreqstattype rq = - icalproperty_get_requeststatus(prop); - req_stat = - icalenum_reqstat_code(rq.code); - } - - /* Find matching attendee in existing object */ - for (prop = - icalcomponent_get_first_property(comp, - ICAL_ATTENDEE_PROPERTY); - prop && strcmp(attendee, icalproperty_get_attendee(prop)); - prop = - icalcomponent_get_next_property(comp, - ICAL_ATTENDEE_PROPERTY)); - if (!prop) { - /* Attendee added themselves to this recurrence */ - prop = icalproperty_new_clone(att); - icalcomponent_add_property(comp, prop); - } - - /* Find and set PARTSTAT */ - param = - icalproperty_get_first_parameter(prop, - ICAL_PARTSTAT_PARAMETER); - if (!param) { - param = icalparameter_new(ICAL_PARTSTAT_PARAMETER); - icalproperty_add_parameter(prop, param); - } - icalparameter_set_partstat(param, partstat); - - /* Find and set RSVP */ - param = - icalproperty_get_first_parameter(prop, - ICAL_RSVP_PARAMETER); - if (param) icalproperty_remove_parameter_by_ref(prop, param); - if (rsvp != ICAL_RSVP_NONE) { - param = icalparameter_new(ICAL_RSVP_PARAMETER); - icalproperty_add_parameter(prop, param); - icalparameter_set_rsvp(param, rsvp); - } - - /* Find and set SCHEDULE-STATUS */ - for (param = - icalproperty_get_first_parameter(prop, - ICAL_IANA_PARAMETER); - param && strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-STATUS"); - param = - icalproperty_get_next_parameter(prop, - ICAL_IANA_PARAMETER)); - if (!param) { - param = icalparameter_new(ICAL_IANA_PARAMETER); - icalproperty_add_parameter(prop, param); - icalparameter_set_iana_name(param, "SCHEDULE-STATUS"); - } - icalparameter_set_iana_value(param, req_stat); - - } while ((itip = icalcomponent_get_next_component(sched_data->itip, - kind))); - - free_hash_table(&comp_table, NULL); - - deliver_inbox = 1; - - break; - } - - case ICAL_METHOD_REQUEST: { - struct hash_table comp_table; - icalcomponent *itip; - const char *tzid, *recurid; - - /* Add each VTIMEZONE of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - for (comp = icalcomponent_get_first_component(ical, - ICAL_VTIMEZONE_COMPONENT); - comp; - comp = - icalcomponent_get_next_component(ical, - ICAL_VTIMEZONE_COMPONENT)) { - prop = - icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY); - tzid = icalproperty_get_tzid(prop); - - hash_insert(tzid, comp, &comp_table); - } - - /* Process each VTIMEZONE in the iTIP request */ - for (itip = - icalcomponent_get_first_component(sched_data->itip, - ICAL_VTIMEZONE_COMPONENT); - itip; - itip = - icalcomponent_get_next_component(sched_data->itip, - ICAL_VTIMEZONE_COMPONENT)) { - /* Lookup this TZID in the hash table */ - prop = - icalcomponent_get_first_property(itip, - ICAL_TZID_PROPERTY); - tzid = icalproperty_get_tzid(prop); - - comp = hash_lookup(tzid, &comp_table); - if (comp) { - /* Remove component from old object */ - icalcomponent_remove_component(ical, comp); - icalcomponent_free(comp); - } - - /* Add new/modified component from iTIP request*/ - icalcomponent_add_component(ical, - icalcomponent_new_clone(itip)); - } - - free_hash_table(&comp_table, NULL); - - /* Add each component of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - comp = icalcomponent_get_first_real_component(ical); - do { - prop = - icalcomponent_get_first_property(comp, - ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - hash_insert(recurid, comp, &comp_table); - - } while ((comp = icalcomponent_get_next_component(ical, kind))); - - /* Process each component in the iTIP request */ - itip = icalcomponent_get_first_component(sched_data->itip, kind); - do { - icalcomponent *new_comp = icalcomponent_new_clone(itip); - - /* Lookup this comp in the hash table */ - prop = - icalcomponent_get_first_property(itip, - ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - comp = hash_lookup(recurid, &comp_table); - if (comp) { - int old_seq, new_seq; - icalparameter *param; - - /* Check if this is something more than an update */ - /* XXX Probably need to check PARTSTAT=NEEDS-ACTION - and RSVP=TRUE as well */ - old_seq = icalcomponent_get_sequence(comp); - new_seq = icalcomponent_get_sequence(itip); - if (new_seq > old_seq) deliver_inbox = 1; - - /* Copy over any COMPLETED, PERCENT-COMPLETE, - or TRANSP properties */ - prop = - icalcomponent_get_first_property(comp, - ICAL_COMPLETED_PROPERTY); - if (prop) { - icalcomponent_add_property(new_comp, - icalproperty_new_clone(prop)); - } - prop = - icalcomponent_get_first_property(comp, - ICAL_PERCENTCOMPLETE_PROPERTY); - if (prop) { - icalcomponent_add_property(new_comp, - icalproperty_new_clone(prop)); - } - prop = - icalcomponent_get_first_property(comp, - ICAL_TRANSP_PROPERTY); - if (prop) { - icalcomponent_add_property(new_comp, - icalproperty_new_clone(prop)); - } - - /* Copy over any ORGANIZER;SCHEDULE-STATUS */ - /* XXX Do we only do this iff PARTSTAT!=NEEDS-ACTION */ - prop = - icalcomponent_get_first_property(comp, - ICAL_ORGANIZER_PROPERTY); - for (param = - icalproperty_get_first_parameter(prop, - ICAL_IANA_PARAMETER); - param; - param = - icalproperty_get_next_parameter(prop, - ICAL_IANA_PARAMETER)) { - if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-STATUS")) { - const char *sched_stat = - icalparameter_get_iana_value(param); - - prop = - icalcomponent_get_first_property(new_comp, - ICAL_ORGANIZER_PROPERTY); - param = icalparameter_new(ICAL_IANA_PARAMETER); - icalproperty_add_parameter(prop, param); - icalparameter_set_iana_name(param, - "SCHEDULE-STATUS"); - icalparameter_set_iana_value(param, sched_stat); - } - } + /* Set STATUS:CANCELLED on all components */ + do { + icalcomponent_set_status(comp, ICAL_STATUS_CANCELLED); + icalcomponent_set_sequence(comp, + icalcomponent_get_sequence(comp)+1); + } while ((comp = icalcomponent_get_next_component(ical, kind))); - /* Remove component from old object */ - icalcomponent_remove_component(ical, comp); - icalcomponent_free(comp); - } - else deliver_inbox = 1; + break; - /* Add new/modified component from iTIP request*/ - icalcomponent_add_component(ical, new_comp); + case ICAL_METHOD_REPLY: + attendee = deliver_merge_reply(ical, sched_data->itip); - } while ((itip = icalcomponent_get_next_component(sched_data->itip, - kind))); + break; - free_hash_table(&comp_table, NULL); + case ICAL_METHOD_REQUEST: + deliver_inbox = deliver_merge_request(recipient, + ical, sched_data->itip); + break; - break; - } + default: + /* Unknown METHOD -- ignore it */ + syslog(LOG_ERR, "Unknown iTIP method: %s", + icalenum_method_to_string(method)); - default: - /* Unknown METHOD -- ignore it */ - syslog(LOG_ERR, "Unknown iTIP method: %s", - icalenum_method_to_string(method)); - goto inbox; - } + sched_data->is_reply = 0; + goto inbox; } /* Store the (updated) object in the recipients's calendar */ @@ -4349,17 +4553,14 @@ /* XXX Should this be a config option? - it might have perf implications */ if (sched_data->is_reply) { - /* Send updates to attendees */ + /* Send updates to attendees - skipping sender of reply */ sched_request(recipient, sparam, NULL, ical, attendee); } done: if (ical) icalcomponent_free(ical); - if (inbox) { - mailbox_unlock_index(inbox, NULL); - mailbox_close(&inbox); - } - if (mailbox) mailbox_close(&mailbox); + mailbox_close(&inbox); + mailbox_close(&mailbox); if (caldavdb) caldav_close(caldavdb); } @@ -4665,27 +4866,51 @@ } +/* + * Compare the properties of the given kind in two components. + * Returns 0 if equal, 1 otherwise. + * + * If the property exists in neither comp, then they are equal. + * If the property exists in only 1 comp, then they are not equal. + * if the property is RDATE or EXDATE, create an MD5 hash of all + * property strings for each component and compare the hashes. + * Otherwise compare the two property strings. + */ static unsigned propcmp(icalcomponent *oldical, icalcomponent *newical, icalproperty_kind kind) { - icalproperty *oldprop, *newprop; + icalproperty *oldprop = icalcomponent_get_first_property(oldical, kind); + icalproperty *newprop = icalcomponent_get_first_property(newical, kind); - oldprop = icalcomponent_get_first_property(oldical, kind); - newprop = icalcomponent_get_first_property(newical, kind); + if (!oldprop) return (newprop != NULL); + else if (!newprop) return 1; + else if ((kind == ICAL_RDATE_PROPERTY) || (kind == ICAL_EXDATE_PROPERTY)) { + MD5_CTX ctx; + const char *str; + unsigned char old_md5[MD5_DIGEST_LENGTH], new_md5[MD5_DIGEST_LENGTH]; - if (!oldprop) { - if (newprop) return 1; + MD5Init(&ctx); + do { + str = icalproperty_get_value_as_string(oldprop); + MD5Update(&ctx, str, strlen(str)); + } while ((oldprop = icalcomponent_get_next_property(oldical, kind))); + + MD5Final(old_md5, &ctx); + + MD5Init(&ctx); + do { + str = icalproperty_get_value_as_string(newprop); + MD5Update(&ctx, str, strlen(str)); + } while ((newprop = icalcomponent_get_next_property(newical, kind))); + + MD5Final(new_md5, &ctx); + + return (memcmp(old_md5, new_md5, MD5_DIGEST_LENGTH) != 0); } - else if (!newprop) return 1; else { - /* XXX Do something smarter based on property type */ - const char *oldstr = icalproperty_get_value_as_string(oldprop); - const char *newstr = icalproperty_get_value_as_string(newprop); - - if (strcmp(oldstr, newstr)) return 1; + return (strcmp(icalproperty_get_value_as_string(oldprop), + icalproperty_get_value_as_string(newprop)) != 0); } - - return 0; } @@ -4777,21 +5002,27 @@ /* Add each component of old object to hash table for comparison */ construct_hash_table(&comp_table, 10, 1); - if (oldical) { + if (!att_update && oldical) { comp = icalcomponent_get_first_real_component(oldical); - do { - old_data = xzmalloc(sizeof(struct comp_data)); - old_data->comp = comp; - old_data->sequence = icalcomponent_get_sequence(comp); - prop = icalcomponent_get_first_property(comp, - ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; + /* If the existing object isn't a scheduling object, + we don't need to compare components, treat them as new */ + if (icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY)) { + do { + old_data = xzmalloc(sizeof(struct comp_data)); + old_data->comp = comp; + old_data->sequence = icalcomponent_get_sequence(comp); - hash_insert(recurid, old_data, &comp_table); + prop = + icalcomponent_get_first_property(comp, + ICAL_RECURRENCEID_PROPERTY); + if (prop) recurid = icalproperty_get_value_as_string(prop); + else recurid = ""; + + hash_insert(recurid, old_data, &comp_table); - } while ((comp = icalcomponent_get_next_component(oldical, kind))); + } while ((comp = icalcomponent_get_next_component(oldical, kind))); + } } /* Create hash table of attendees */ @@ -4829,6 +5060,7 @@ ICAL_RDATE_PROPERTY); needs_action += propcmp(old_data->comp, comp, ICAL_EXDATE_PROPERTY); + /* XXX Should we check STATUS here? */ if (old_data->sequence >= icalcomponent_get_sequence(comp)) { /* Make sure SEQUENCE is set properly */
View file
cyrus-imapd-2.5.tar.gz/imap/http_carddav.c
Changed
@@ -85,6 +85,8 @@ static struct carddav_db *auth_carddavdb = NULL; +static struct carddav_db *my_carddav_open(struct mailbox *mailbox); +static void my_carddav_close(struct carddav_db *carddavdb); static void my_carddav_init(struct buf *serverinfo); static void my_carddav_auth(const char *userid); static void my_carddav_reset(void); @@ -96,10 +98,13 @@ static int carddav_copy(struct transaction_t *txn, struct mailbox *src_mbox, struct index_record *src_rec, struct mailbox *dest_mbox, const char *dest_rsrc, + struct carddav_db *dest_davdb, unsigned overwrite, unsigned flags); static int carddav_put(struct transaction_t *txn, struct mime_type_t *mime, - struct mailbox *mailbox, unsigned flags); + struct mailbox *mailbox, + struct carddav_db *carddavdb, + unsigned flags); static VObject *vcard_string_as_vobject(const char *str) { return Parse_MIME(str, strlen(str)); @@ -135,9 +140,9 @@ /* First item MUST be the default type and storage format */ { "text/vcard; charset=utf-8", "3.0", NULL, "vcf", NULL, (void * (*)(const char*)) &vcard_string_as_vobject, - NULL, NULL + (void (*)(void *)) &cleanVObject, NULL, NULL }, - { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; /* Array of known "live" properties */ @@ -230,18 +235,19 @@ carddav_mime_types, &carddav_parse_path, &check_precond, - { (void **) &auth_carddavdb, + { (db_open_proc_t) &my_carddav_open, + (db_close_proc_t) &my_carddav_close, (db_lookup_proc_t) &carddav_lookup_resource, (db_foreach_proc_t) &carddav_foreach, (db_write_proc_t) &carddav_write, (db_delete_proc_t) &carddav_delete, (db_delmbox_proc_t) &carddav_delmbox }, NULL, /* No ACL extensions */ - &carddav_copy, + (copy_proc_t) &carddav_copy, NULL, /* No special DELETE handling */ { MBTYPE_ADDRESSBOOK, NULL, NULL, 0 }, /* No special MK* method */ NULL, /* No special POST handling */ - { CARDDAV_SUPP_DATA, &carddav_put }, + { CARDDAV_SUPP_DATA, (put_proc_t) &carddav_put }, carddav_props, { { "addressbook-query", &report_card_query, DACL_READ, REPORT_NEED_MBOX | REPORT_MULTISTATUS }, @@ -291,12 +297,25 @@ }; -static struct namespace carddav_namespace; +static struct carddav_db *my_carddav_open(struct mailbox *mailbox) +{ + if (httpd_userid && mboxname_userownsmailbox(httpd_userid, mailbox->name)) { + return auth_carddavdb; + } + else { + return carddav_open_mailbox(mailbox, CALDAV_CREATE); + } +} -static void my_carddav_init(struct buf *serverinfo) + +static void my_carddav_close(struct carddav_db *carddavdb) { - int r; + if (carddavdb && (carddavdb != auth_carddavdb)) carddav_close(carddavdb); +} + +static void my_carddav_init(struct buf *serverinfo) +{ namespace_addressbook.enabled = config_httpmodules & IMAP_ENUM_HTTPMODULES_CARDDAV; @@ -306,12 +325,6 @@ fatal("Required 'addressbookprefix' option is not set", EC_CONFIG); } - /* Set namespace -- force standard (internal) */ - if ((r = mboxname_init_namespace(&carddav_namespace, 1))) { - syslog(LOG_ERR, "%s", error_message(r)); - fatal(error_message(r), EC_CONFIG); - } - carddav_init(); if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON && @@ -329,6 +342,8 @@ struct buf boxbuf = BUF_INITIALIZER; const char *mailboxname; + mailboxname = mboxname_user_mbox(userid, NULL); + if (httpd_userisadmin || global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) { /* admin or proxy from frontend - won't have DAV database */ @@ -340,7 +355,7 @@ else { /* Open CardDAV DB for 'userid' */ my_carddav_reset(); - auth_carddavdb = carddav_open(userid, CARDDAV_CREATE); + auth_carddavdb = carddav_open_userid(userid, CARDDAV_CREATE); if (!auth_carddavdb) fatal("Unable to open CardDAV DB", EC_IOERR); } @@ -541,6 +556,7 @@ static int carddav_copy(struct transaction_t *txn, struct mailbox *src_mbox, struct index_record *src_rec, struct mailbox *dest_mbox, const char *dest_rsrc, + struct carddav_db *dest_davdb, unsigned overwrite, unsigned flags) { int r; @@ -563,8 +579,8 @@ mailbox_unlock_index(src_mbox, NULL); /* Store source resource at destination */ - r = store_resource(txn, vcard, dest_mbox, dest_rsrc, auth_carddavdb, - overwrite, flags); + r = store_resource(txn, vcard, dest_mbox, dest_rsrc, dest_davdb, + overwrite, flags); cleanVObject(vcard); cleanStrTbl(); @@ -582,7 +598,9 @@ */ static int carddav_put(struct transaction_t *txn, struct mime_type_t *mime, - struct mailbox *mailbox, unsigned flags) + struct mailbox *mailbox, + struct carddav_db *davdb, + unsigned flags) { int ret; VObject *vcard = NULL; @@ -597,7 +615,7 @@ /* Store resource at target */ ret = store_resource(txn, vcard, mailbox, txn->req_tgt.resource, - auth_carddavdb, OVERWRITE_CHECK, flags); + davdb, OVERWRITE_CHECK, flags); if (flags & PREFER_REP) { struct resp_body_t *resp_body = &txn->resp_body; @@ -718,90 +736,30 @@ } -/* Callback to fetch CARDDAV:address-data */ +/* Callback to prescreen/fetch CARDDAV:address-data */ static int propfind_addrdata(const xmlChar *name, xmlNsPtr ns, struct propfind_ctx *fctx, - xmlNodePtr resp, + xmlNodePtr resp __attribute__((unused)), struct propstat propstat[], void *rock) { xmlNodePtr prop = (xmlNodePtr) rock; - xmlChar *attr; - const char *data; - unsigned long datalen; - xmlNodePtr node; - - if (!resp || !propstat) { - /* Prescreen address-data "property" request */ - unsigned allowed = 1; - - if ((attr = xmlGetProp(prop, BAD_CAST "content-type"))) { - struct mime_type_t *mime; - - /* Check requested MIME type */ - for (mime = carddav_mime_types; mime->content_type; mime++) { - if (is_mediatype((const char *) attr, mime->content_type)) { - xmlFree(attr); - - if ((attr = xmlGetProp(prop, BAD_CAST "version")) && - (!mime->version || - xmlStrcmp(attr, BAD_CAST mime->version))) { - allowed = 0; - } - break; - } - } - if (!mime->content_type) allowed = 0; + const char *data = NULL; + size_t datalen = 0; - if (attr) xmlFree(attr); - } + if (propstat) { + if (!fctx->record) return HTTP_NOT_FOUND; - if (!allowed) { - fctx->err->precond = CARDDAV_SUPP_DATA; - *fctx->ret = HTTP_FORBIDDEN; - } + if (!fctx->msg_buf.len) + mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf); + if (!fctx->msg_buf.len) return HTTP_SERVER_ERROR; - return allowed; + data = fctx->msg_buf.s + fctx->record->header_size; + datalen = fctx->record->size - fctx->record->header_size; } - - if (!fctx->record) return HTTP_NOT_FOUND; - - if (!buf_base(&fctx->msg_buf)) - mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf); - - data = buf_base(&fctx->msg_buf) + fctx->record->header_size; - datalen = fctx->record->size - fctx->record->header_size; - - node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK], - name, ns, NULL, 0); - - if ((attr = xmlGetProp(prop, BAD_CAST "content-type"))) { - struct mime_type_t *mime = carddav_mime_types; - - /* Find requested MIME type */ - while (!is_mediatype((const char *) attr, mime->content_type)) mime++; - - if (mime != carddav_mime_types) { - /* Not the storage format - convert into requested MIME type */ - /* XXX TODO */ - } - - xmlSetProp(node, BAD_CAST "content-type", attr); - xmlFree(attr); - - if ((attr = xmlGetProp(prop, BAD_CAST "version"))) { - xmlSetProp(node, BAD_CAST "version", attr); - xmlFree(attr); - } - } - - xmlAddChild(node, - xmlNewCDataBlock(fctx->root->doc, BAD_CAST data, datalen)); - - fctx->fetcheddata = 1; - - return 0; + return propfind_getdata(name, ns, fctx, propstat, prop, carddav_mime_types, + CARDDAV_SUPP_DATA, data, datalen); } @@ -873,7 +831,8 @@ int ret = 0; xmlNodePtr node; - fctx->davdb = auth_carddavdb; + fctx->open_db = (db_open_proc_t) &my_carddav_open; + fctx->close_db = (db_close_proc_t) &my_carddav_close; fctx->lookup_resource = (db_lookup_proc_t) &carddav_lookup_resource; fctx->foreach_resource = (db_foreach_proc_t) &carddav_foreach; fctx->proc_by_resource = &propfind_by_resource; @@ -902,6 +861,8 @@ httpd_authstate, propfind_by_collection, fctx); } + if (fctx->davdb) my_carddav_close(fctx->davdb); + ret = *fctx->ret; } @@ -946,7 +907,8 @@ if (mailbox) mailbox_unlock_index(mailbox, NULL); /* Open mailbox for reading */ - if ((r = http_mailbox_open(tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -957,18 +919,23 @@ fctx->mailbox = mailbox; } + /* Open the DAV DB corresponding to the mailbox */ + fctx->davdb = my_carddav_open(fctx->mailbox); + /* Find message UID for the resource */ - carddav_lookup_resource(auth_carddavdb, + carddav_lookup_resource(fctx->davdb, tgt.mboxname, tgt.resource, 0, &cdata); cdata->dav.resource = tgt.resource; /* XXX Check errors */ propfind_by_resource(fctx, cdata); + + my_carddav_close(fctx->davdb); } } done: - if (mailbox) mailbox_unlock_index(mailbox, NULL); + mailbox_close(&mailbox); buf_free(&uri); return ret;
View file
cyrus-imapd-2.5.tar.gz/imap/http_dav.c
Changed
@@ -210,7 +210,7 @@ { &meth_propfind, &princ_params }, /* PROPFIND */ { NULL, NULL }, /* PROPPATCH */ { NULL, NULL }, /* PUT */ - { &meth_report, NULL }, /* REPORT */ + { &meth_report, &princ_params }, /* REPORT */ { &meth_trace, NULL }, /* TRACE */ { NULL, NULL } /* UNLOCK */ } @@ -222,7 +222,7 @@ if (config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV) { namespace_principal.enabled = 1; namespace_principal.allow |= ALLOW_CAL; - if (config_getswitch(IMAPOPT_CALDAV_ALLOWSCHEDULING)) + if (config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING)) namespace_principal.allow |= ALLOW_CAL_SCHED; } if (config_httpmodules & IMAP_ENUM_HTTPMODULES_CARDDAV) { @@ -454,6 +454,29 @@ } +/* Check requested MIME type */ +struct mime_type_t *get_accept_type(const char **hdr, struct mime_type_t *types) +{ + struct mime_type_t *ret = NULL; + struct accept *e, *enc = parse_accept(hdr); + + for (e = enc; e && e->token; e++) { + if (!ret && e->qual > 0.0) { + struct mime_type_t *m; + + for (m = types; !ret && m->content_type; m++) { + if (is_mediatype(e->token, m->content_type)) ret = m; + } + } + + free(e->token); + } + if (enc) free(enc); + + return ret; +} + + static int add_privs(int rights, unsigned flags, xmlNodePtr parent, xmlNodePtr root, xmlNsPtr *ns); @@ -803,6 +826,74 @@ } +/* Helper function to prescreen/fetch resource data */ +int propfind_getdata(const xmlChar *name, xmlNsPtr ns, + struct propfind_ctx *fctx, + struct propstat propstat[], xmlNodePtr prop, + struct mime_type_t *mime_types, int precond, + const char *data, unsigned long datalen) +{ + int ret = 0; + xmlChar *type, *ver = NULL; + struct mime_type_t *mime; + + type = xmlGetProp(prop, BAD_CAST "content-type"); + if (type) ver = xmlGetProp(prop, BAD_CAST "version"); + + /* Check/find requested MIME type */ + for (mime = mime_types; type && mime->content_type; mime++) { + if (is_mediatype((const char *) type, mime->content_type)) { + if (ver && + (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) { + continue; + } + break; + } + } + + if (!propstat) { + /* Prescreen "property" request */ + if (!mime->content_type) { + fctx->err->precond = precond; + ret = *fctx->ret = HTTP_FORBIDDEN; + } + } + else { + /* Add "property" */ + char *freeme = NULL; + + prop = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], + &propstat[PROPSTAT_OK], name, ns, NULL, 0); + + if (mime != mime_types) { + /* Not the storage format - convert into requested MIME type */ + void *obj = mime_types->from_string(data); + + data = freeme = mime->to_string(obj); + datalen = strlen(data); + mime_types->free(obj); + } + + if (type) { + xmlSetProp(prop, BAD_CAST "content-type", type); + if (ver) xmlSetProp(prop, BAD_CAST "version", ver); + } + + xmlAddChild(prop, + xmlNewCDataBlock(fctx->root->doc, BAD_CAST data, datalen)); + + fctx->fetcheddata = 1; + + if (freeme) free(freeme); + } + + if (type) xmlFree(type); + if (ver) xmlFree(ver); + + return ret; +} + + /* Callback to fetch DAV:creationdate */ int propfind_creationdate(const xmlChar *name, xmlNsPtr ns, struct propfind_ctx *fctx, @@ -811,7 +902,7 @@ void *rock __attribute__((unused))) { time_t t = 0; - struct tm *tm; + char datestr[21]; if (fctx->data) { struct dav_data *ddata = (struct dav_data *) fctx->data; @@ -828,15 +919,10 @@ if (!t) return HTTP_NOT_FOUND; - tm = gmtime(&t); - - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%4d-%02d-%02dT%02d:%02d:%02dZ", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); + rfc3339date_gen(datestr, sizeof(datestr), t); xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK], - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); + name, ns, BAD_CAST datestr, 0); return 0; } @@ -1549,8 +1635,14 @@ if (fctx->userid) { buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%s/user/%s/", - namespace_principal.prefix, fctx->userid); + if (strchr(fctx->userid, '@')) { + buf_printf(&fctx->buf, "%s/user/%s/", + namespace_principal.prefix, fctx->userid); + } + else { + buf_printf(&fctx->buf, "%s/user/%s@%s/", + namespace_principal.prefix, fctx->userid, httpd_extradomain); + } xml_add_href(node, NULL, buf_cstring(&fctx->buf)); } else { @@ -1789,7 +1881,7 @@ else if (entry->flags & PROP_PRESCREEN) { void *rock = (entry->flags & PROP_NEEDPROP) ? prop : entry->rock; - allowed = entry->get(prop->name, NULL, fctx, NULL, NULL, rock); + allowed = !entry->get(prop->name, NULL, fctx, NULL, NULL, rock); } return allowed; @@ -2008,8 +2100,8 @@ /* Check Content-Type */ if (!(hdr = spool_getheader(txn->req_hdrs, "Content-Type")) || - (!is_mediatype(hdr[0], "text/xml") && - !is_mediatype(hdr[0], "application/xml"))) { + (!is_mediatype("text/xml", hdr[0]) && + !is_mediatype("application/xml", hdr[0]))) { txn->error.desc = "This method requires an XML body\r\n"; return HTTP_BAD_MEDIATYPE; } @@ -2119,7 +2211,7 @@ /* Local Mailbox */ /* Open mailbox for writing */ - r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_EXCLUSIVE); + r = mailbox_open_iwl(txn->req_tgt.mboxname, &mailbox); if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); @@ -2349,7 +2441,7 @@ done: buf_free(&acl); if (indoc) xmlFreeDoc(indoc); - if (mailbox) mailbox_unlock_index(mailbox, NULL); + mailbox_close(&mailbox); return ret; } @@ -2375,6 +2467,7 @@ const char *etag = NULL; time_t lastmod = 0; unsigned flags = 0; + void *src_davdb = NULL, *dest_davdb = NULL; /* Response should not be cached */ txn->flags.cc |= CC_NOCACHE; @@ -2508,16 +2601,9 @@ /* Local Mailbox */ - if (!*cparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } - /* Open dest mailbox for reading */ - if ((r = mailbox_open_irl(dest_tgt.mboxname, &dest_mbox))) { + r = mailbox_open_irl(dest_tgt.mboxname, &dest_mbox); + if (r) { syslog(LOG_ERR, "mailbox_open_irl(%s) failed: %s", dest_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -2525,8 +2611,11 @@ goto done; } + /* Open the DAV DB corresponding to the dest mailbox */ + dest_davdb = cparams->davdb.open_db(dest_mbox); + /* Find message UID for the dest resource, if exists */ - cparams->davdb.lookup_resource(*cparams->davdb.db, dest_tgt.mboxname, + cparams->davdb.lookup_resource(dest_davdb, dest_tgt.mboxname, dest_tgt.resource, 0, (void **) &ddata); /* XXX Check errors */ @@ -2546,7 +2635,8 @@ } /* Open source mailbox for reading */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &src_mbox, LOCK_SHARED))) { + r = mailbox_open_irl(txn->req_tgt.mboxname, &src_mbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -2554,8 +2644,11 @@ goto done; } + /* Open the DAV DB corresponding to the src mailbox */ + src_davdb = cparams->davdb.open_db(src_mbox); + /* Find message UID for the source resource */ - cparams->davdb.lookup_resource(*cparams->davdb.db, txn->req_tgt.mboxname, + cparams->davdb.lookup_resource(src_davdb, txn->req_tgt.mboxname, txn->req_tgt.resource, 0, (void **) &ddata); if (!ddata->rowid) { ret = HTTP_NOT_FOUND; @@ -2612,7 +2705,7 @@ /* Parse, validate, and store the resource */ ret = cparams->copy(txn, src_mbox, &src_rec, dest_mbox, dest_tgt.resource, - overwrite, flags); + dest_davdb, overwrite, flags); /* we're done, no need to keep this */ mailbox_unlock_index(dest_mbox, NULL); @@ -2624,7 +2717,7 @@ mailbox_lock_index(src_mbox, LOCK_EXCLUSIVE); /* Find message UID for the source resource */ - cparams->davdb.lookup_resource(*cparams->davdb.db, txn->req_tgt.mboxname, + cparams->davdb.lookup_resource(src_davdb, txn->req_tgt.mboxname, txn->req_tgt.resource, 0, (void **) &ddata); /* XXX Check errors */ @@ -2654,8 +2747,10 @@ txn->resp_body.etag = NULL; } - if (dest_mbox) mailbox_close(&dest_mbox); - if (src_mbox) mailbox_unlock_index(src_mbox, NULL); + if (dest_davdb) cparams->davdb.close_db(dest_davdb); + mailbox_close(&dest_mbox); + if (src_davdb) cparams->davdb.close_db(src_davdb); + mailbox_close(&src_mbox); return ret; } @@ -2665,20 +2760,23 @@ int meth_delete(struct transaction_t *txn, void *params) { struct meth_params *dparams = (struct meth_params *) params; - int ret = HTTP_NO_CONTENT, r, precond, rights; + int ret = HTTP_NO_CONTENT, r = 0, precond, rights; + struct mboxevent *mboxevent = NULL; struct mailbox *mailbox = NULL; mbentry_t *mbentry = NULL; struct dav_data *ddata; struct index_record record; const char *etag = NULL; time_t lastmod = 0; + void *davdb = NULL; /* Response should not be cached */ txn->flags.cc |= CC_NOCACHE; /* Parse the path */ - if ((r = dparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; + r = dparams->parse_path(txn->req_uri->path, + &txn->req_tgt, &txn->error.desc); + if (r) return r; /* Make sure method is allowed */ if (!(txn->req_tgt.allow & ALLOW_DELETE)) return HTTP_NOT_ALLOWED; @@ -2725,39 +2823,48 @@ /* Local Mailbox */ - if (!*dparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } - if (!txn->req_tgt.resource) { /* DELETE collection */ + /* Open mailbox for reading */ + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); + if (r) { + syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", + txn->req_tgt.mboxname, error_message(r)); + txn->error.desc = error_message(r); + return HTTP_SERVER_ERROR; + } + + /* Open the DAV DB corresponding to the mailbox */ + davdb = dparams->davdb.open_db(mailbox); + /* Do any special processing */ - if (dparams->delete) dparams->delete(txn, NULL, NULL, NULL); + if (dparams->delete) dparams->delete(txn, mailbox, NULL, NULL); + + mailbox_close(&mailbox); + + mboxevent = mboxevent_new(EVENT_MAILBOX_DELETE); + /* XXX - delayed delete? */ r = mboxlist_deletemailbox(txn->req_tgt.mboxname, httpd_userisadmin || httpd_userisproxyadmin, - httpd_userid, httpd_authstate, - /*mboxevent*/NULL, + httpd_userid, httpd_authstate, mboxevent, /*checkack*/1, /*localonly*/0, /*force*/0); - - if (!r) dparams->davdb.delete_mbox(*dparams->davdb.db, txn->req_tgt.mboxname, 0); + if (!r) dparams->davdb.delete_mbox(davdb, txn->req_tgt.mboxname, 0); else if (r == IMAP_PERMISSION_DENIED) ret = HTTP_FORBIDDEN; else if (r == IMAP_MAILBOX_NONEXISTENT) ret = HTTP_NOT_FOUND; else if (r) ret = HTTP_SERVER_ERROR; - return ret; - } + dparams->davdb.close_db(davdb); + goto done; + } /* DELETE resource */ /* Open mailbox for writing */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_EXCLUSIVE))) { + r = mailbox_open_iwl(txn->req_tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -2765,8 +2872,11 @@ goto done; } + /* Open the DAV DB corresponding to the mailbox */ + davdb = dparams->davdb.open_db(mailbox); + /* Find message UID for the resource, if exists */ - dparams->davdb.lookup_resource(*dparams->davdb.db, txn->req_tgt.mboxname, + dparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, txn->req_tgt.resource, 0, (void **) &ddata); if (!ddata->rowid) { ret = HTTP_NOT_FOUND; @@ -2813,20 +2923,32 @@ /* Expunge the resource */ record.system_flags |= FLAG_EXPUNGED; - if ((r = mailbox_rewrite_index_record(mailbox, &record))) { + mboxevent = mboxevent_new(EVENT_MESSAGE_EXPUNGE); + + r = mailbox_rewrite_index_record(mailbox, &record); + + if (r) { syslog(LOG_ERR, "expunging record (%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); ret = HTTP_SERVER_ERROR; goto done; } + + mboxevent_extract_record(mboxevent, mailbox, &record); + mboxevent_extract_mailbox(mboxevent, mailbox); } /* Do any special processing */ if (dparams->delete) dparams->delete(txn, mailbox, &record, ddata); done: - if (mailbox) mailbox_unlock_index(mailbox, NULL); + if (davdb) dparams->davdb.close_db(davdb); + mailbox_close(&mailbox); + + if (!r) + mboxevent_notify(mboxevent); + mboxevent_free(&mboxevent); return ret; } @@ -2837,7 +2959,7 @@ { struct meth_params *gparams = (struct meth_params *) params; const char **hdr; - struct mime_type_t *mime = NULL; + struct mime_type_t *mime; int ret = 0, r, precond, rights; const char *data = NULL; unsigned long datalen, offset; @@ -2849,6 +2971,8 @@ struct index_record record; const char *etag = NULL; time_t lastmod = 0; + void *davdb = NULL; + char *freeme = NULL; /* Parse the path */ ret = gparams->parse_path(txn->req_uri->path, @@ -2858,22 +2982,12 @@ /* We don't handle GET on a collection (yet) */ if (!txn->req_tgt.resource) return HTTP_NO_CONTENT; - /* Check requested MIME type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) { - struct accept *e, *enc = parse_accept(hdr); - - for (e = enc; e && e->token; e++) { - struct mime_type_t *m; - - for (m = gparams->mime_types; !mime && m->content_type; m++) { - if (is_mediatype(e->token, m->content_type)) mime = m; - } - - free(e->token); - } - if (enc) free(enc); - } - if (!mime) mime = gparams->mime_types; /* 1st in array MUST default type */ + /* Check requested MIME type: + 1st entry in gparams->mime_types array MUST be default MIME type */ + if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) + mime = get_accept_type(hdr, gparams->mime_types); + else mime = gparams->mime_types; + if (!mime) return HTTP_NOT_ACCEPTABLE; /* Locate the mailbox */ r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); @@ -2916,24 +3030,19 @@ /* Local Mailbox */ - if (!*gparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } - /* Open mailbox for reading */ - r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED); + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); goto done; } + /* Open the DAV DB corresponding to the mailbox */ + davdb = gparams->davdb.open_db(mailbox); + /* Find message UID for the resource */ - gparams->davdb.lookup_resource(*gparams->davdb.db, txn->req_tgt.mboxname, + gparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, txn->req_tgt.resource, 0, (void **) &ddata); if (!ddata->rowid) { ret = HTTP_NOT_FOUND; @@ -3003,11 +3112,11 @@ if (mime != gparams->mime_types) { /* Not the storage format - convert into requested MIME type */ - icalcomponent *ical = icalparser_parse_string(data); + void *obj = gparams->mime_types[0].from_string(data); - data = mime->to_string(ical); + data = freeme = mime->to_string(obj); datalen = strlen(data); - icalcomponent_free(ical); + gparams->mime_types[0].free(obj); } } } @@ -3017,11 +3126,13 @@ buf_free(&msg_buf); done: + if (davdb) gparams->davdb.close_db(davdb); if (r) { txn->error.desc = error_message(r); ret = HTTP_SERVER_ERROR; } - if (mailbox) mailbox_unlock_index(mailbox, NULL); + mailbox_close(&mailbox); + free(freeme); return ret; } @@ -3049,6 +3160,7 @@ xmlNsPtr ns[NUM_NAMESPACE]; xmlChar *owner = NULL; time_t now = time(NULL); + void *davdb = NULL; /* XXX We ignore Depth and Timeout header fields */ @@ -3104,16 +3216,9 @@ /* Local Mailbox */ - if (!*lparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } - /* Open mailbox for reading */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -3121,8 +3226,11 @@ goto done; } + /* Open the DAV DB corresponding to the mailbox */ + davdb = lparams->davdb.open_db(mailbox); + /* Find message UID for the resource, if exists */ - lparams->davdb.lookup_resource(*lparams->davdb.db, txn->req_tgt.mboxname, + lparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, txn->req_tgt.resource, 1, (void *) &ddata); if (ddata->imap_uid) { @@ -3266,7 +3374,7 @@ root = xmlNewChild(root, NULL, BAD_CAST "lockdiscovery", NULL); xml_add_lockdisc(root, txn->req_tgt.path, (struct dav_data *) ddata); - lparams->davdb.write_resource(*lparams->davdb.db, ddata, 1); + lparams->davdb.write_resource(davdb, ddata, 1); txn->resp_body.lock = ddata->lock_token; @@ -3279,12 +3387,14 @@ /* Tell client where to find the new resource */ txn->location = txn->req_tgt.path; } + else ret = HTTP_OK; xml_response(ret, txn, outdoc); ret = 0; done: - if (mailbox) mailbox_unlock_index(mailbox, NULL); + if (davdb) lparams->davdb.close_db(davdb); + mailbox_close(&mailbox); if (outdoc) xmlFreeDoc(outdoc); if (indoc) xmlFreeDoc(indoc); if (owner) xmlFree(owner); @@ -3586,6 +3696,9 @@ if (fctx->depth > 1) { /* Resource(s) */ + /* Open the DAV DB corresponding to the mailbox */ + if (!fctx->davdb) fctx->davdb = fctx->open_db(mailbox); + if (fctx->req_tgt->resource) { /* Add response for target resource */ void *data; @@ -3695,13 +3808,6 @@ mboxlist_entry_free(&mbentry); /* Local Mailbox */ - if (!*fparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } } /* Principal or Local Mailbox */ @@ -3797,11 +3903,10 @@ fctx.reqd_privs = DACL_READ; fctx.filter = NULL; fctx.filter_crit = NULL; - if (fparams->davdb.db) { - fctx.davdb = *fparams->davdb.db; - fctx.lookup_resource = fparams->davdb.lookup_resource; - fctx.foreach_resource = fparams->davdb.foreach_resource; - } + fctx.open_db = fparams->davdb.open_db; + fctx.close_db = fparams->davdb.close_db; + fctx.lookup_resource = fparams->davdb.lookup_resource; + fctx.foreach_resource = fparams->davdb.foreach_resource; fctx.proc_by_resource = &propfind_by_resource; fctx.elist = NULL; fctx.lprops = fparams->lprops; @@ -3852,6 +3957,8 @@ httpd_authstate, propfind_by_collection, &fctx); } + if (fctx.davdb) fctx.close_db(fctx.davdb); + ret = *fctx.ret; } @@ -4090,6 +4197,7 @@ quota_t qdiffs[QUOTA_NUMRESOURCES] = QUOTA_DIFFS_INITIALIZER; time_t lastmod; unsigned flags = 0; + void *davdb = NULL; if (txn->meth == METH_PUT) { /* Response should not be cached */ @@ -4112,7 +4220,7 @@ /* Check Content-Type */ if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) { for (mime = pparams->mime_types; mime->content_type; mime++) { - if (is_mediatype(hdr[0], mime->content_type)) break; + if (is_mediatype(mime->content_type, hdr[0])) break; } } if (!mime || !mime->content_type) { @@ -4162,16 +4270,9 @@ /* Local Mailbox */ - if (!*pparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } - /* Open mailbox for reading */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -4179,8 +4280,11 @@ goto done; } + /* Open the DAV DB corresponding to the mailbox */ + davdb = pparams->davdb.open_db(mailbox); + /* Find message UID for the resource, if exists */ - pparams->davdb.lookup_resource(*pparams->davdb.db, txn->req_tgt.mboxname, + pparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, txn->req_tgt.resource, 0, (void *) &ddata); /* XXX Check errors */ @@ -4269,10 +4373,11 @@ } /* Parse, validate, and store the resource */ - ret = pparams->put.proc(txn, mime, mailbox, flags); + ret = pparams->put.proc(txn, mime, mailbox, davdb, flags); done: - if (mailbox) mailbox_unlock_index(mailbox, NULL); + if (davdb) pparams->davdb.close_db(davdb); + mailbox_close(&mailbox); return ret; } @@ -4309,7 +4414,8 @@ istate.map = NULL; /* Open mailbox for reading */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -4489,7 +4595,7 @@ done: if (istate.map) free(istate.map); - if (mailbox) mailbox_unlock_index(mailbox, NULL); + mailbox_close(&mailbox); return ret; } @@ -4607,13 +4713,6 @@ mboxlist_entry_free(&mbentry); /* Local Mailbox */ - if (!*rparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } } /* Principal or Local Mailbox */ @@ -4727,6 +4826,7 @@ const char *etag; time_t lastmod; size_t len; + void *davdb = NULL; /* Response should not be cached */ txn->flags.cc |= CC_NOCACHE; @@ -4777,16 +4877,9 @@ /* Local Mailbox */ - if (!*lparams->davdb.db) { - syslog(LOG_ERR, "DAV database for user '%s' is not opened. " - "Check 'configdirectory' permissions or " - "'proxyservers' option on backend server.", proxy_userid); - txn->error.desc = "DAV database is not opened"; - return HTTP_SERVER_ERROR; - } - /* Open mailbox for reading */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -4794,8 +4887,11 @@ goto done; } + /* Open the DAV DB corresponding to the mailbox */ + davdb = lparams->davdb.open_db(mailbox); + /* Find message UID for the resource, if exists */ - lparams->davdb.lookup_resource(*lparams->davdb.db, txn->req_tgt.mboxname, + lparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, txn->req_tgt.resource, 1, (void **) &ddata); if (!ddata->rowid) { ret = HTTP_NOT_FOUND; @@ -4867,15 +4963,16 @@ ddata->lock_ownerid = NULL; ddata->lock_expire = 0; - lparams->davdb.write_resource(*lparams->davdb.db, ddata, 1); + lparams->davdb.write_resource(davdb, ddata, 1); } else { /* Unmapped URL - Treat as lock-null and delete mapping entry */ - lparams->davdb.delete_resource(lparams->davdb.db, ddata->rowid, 1); + lparams->davdb.delete_resource(davdb, ddata->rowid, 1); } done: - if (mailbox) mailbox_unlock_index(mailbox, NULL); + if (davdb) lparams->davdb.close_db(davdb); + mailbox_close(&mailbox); return ret; }
View file
cyrus-imapd-2.5.tar.gz/imap/http_dav.h
Changed
@@ -151,17 +151,6 @@ schedule-send-reply, schedule-send-freebusy) */ -/* Bitmask of calendar components */ -enum { - CAL_COMP_VCALENDAR = 0xf000, - CAL_COMP_VEVENT = (1<<0), - CAL_COMP_VTODO = (1<<1), - CAL_COMP_VJOURNAL = (1<<2), - CAL_COMP_VFREEBUSY = (1<<3), - CAL_COMP_VTIMEZONE = (1<<4), - CAL_COMP_VALARM = (1<<5) -}; - /* Index into preconditions array */ enum { /* WebDAV (RFC 4918) preconditons */ @@ -241,6 +230,9 @@ #define NO_DUP_CHECK (1<<7) +typedef void *(*db_open_proc_t)(struct mailbox *mailbox); +typedef void (*db_close_proc_t)(void *davdb); + /* Function to lookup DAV 'resource' in 'mailbox', with optional 'lock', * placing the record in 'data' */ @@ -265,7 +257,7 @@ const char *int_userid; /* internal userid */ int userisadmin; /* is userid an admin */ struct auth_state *authstate; /* authorization state for userid */ - void *davdb; /* DAV DB corresponding to userid */ + void *davdb; /* DAV DB corresponding to collection */ struct mailbox *mailbox; /* mailbox correspondng to collection */ struct quota quota; /* quota info for collection */ struct index_record *record; /* cyrus.index record for resource */ @@ -275,8 +267,10 @@ int (*filter)(struct propfind_ctx *, void *data); /* callback to filter resources */ void *filter_crit; /* criteria to filter resources */ - db_lookup_proc_t lookup_resource; - db_foreach_proc_t foreach_resource; + db_open_proc_t open_db; /* open DAV DB for a given mailbox */ + db_close_proc_t close_db; /* close DAV DB for a given mailbox */ + db_lookup_proc_t lookup_resource; /* lookup a specific resource */ + db_foreach_proc_t foreach_resource; /* process all resources in a mailbox */ int (*proc_by_resource)(void *rock, /* Callback to process a resource */ void *data); struct propfind_entry_list *elist; /* List of props to fetch w/callbacks */ @@ -366,7 +360,8 @@ typedef int (*db_delmbox_proc_t)(void *davdb, const char *mailbox, int commit); struct davdb_params { - void **db; /* DAV DB to use for resources */ + db_open_proc_t open_db; /* open DAV DB for a given mailbox */ + db_close_proc_t close_db; /* close DAV DB for a given mailbox */ db_lookup_proc_t lookup_resource; /* lookup a specific resource */ db_foreach_proc_t foreach_resource; /* process all resources in a mailbox */ db_write_proc_t write_resource; /* write a specific resource */ @@ -386,6 +381,7 @@ typedef int (*copy_proc_t)(struct transaction_t *txn, struct mailbox *src_mbox, struct index_record *src_rec, struct mailbox *dest_mbox, const char *dest_rsrc, + void *dest_davdb, unsigned overwrite, unsigned flags); /* Function to do special processing for DELETE method (optional) */ @@ -398,8 +394,9 @@ const char *version; const char *file_ext; const char *file_ext2; - const char* (*to_string)(void *); + char* (*to_string)(void *); void* (*from_string)(const char *); + void (*free)(void *); const char* (*begin_stream)(struct buf *); void (*end_stream)(struct buf *); }; @@ -420,9 +417,8 @@ typedef int (*post_proc_t)(struct transaction_t *txn); /* meth_put() parameters */ -typedef int (*put_proc_t)(struct transaction_t *txn, - struct mime_type_t *mime, - struct mailbox *mailbox, unsigned flags); +typedef int (*put_proc_t)(struct transaction_t *txn, struct mime_type_t *mime, + struct mailbox *mailbox, void *davdb, unsigned flags); struct put_params { unsigned supp_data_precond; /* precond code for unsupported data */ @@ -476,6 +472,7 @@ int parse_path(struct request_target_t *tgt, const char **errstr); int target_to_mboxname(struct request_target_t *req_tgt, char *mboxname); unsigned get_preferences(struct transaction_t *txn); +struct mime_type_t *get_accept_type(const char **hdr, struct mime_type_t *types); int parse_xml_body(struct transaction_t *txn, xmlNodePtr *root); @@ -514,6 +511,11 @@ /* PROPFIND callbacks */ +int propfind_getdata(const xmlChar *name, xmlNsPtr ns, + struct propfind_ctx *fctx, + struct propstat propstat[], xmlNodePtr prop, + struct mime_type_t *mime_types, int precond, + const char *data, unsigned long datalen); int propfind_fromdb(const xmlChar *name, xmlNsPtr ns, struct propfind_ctx *fctx, xmlNodePtr resp, struct propstat propstat[], void *rock);
View file
cyrus-imapd-2.5.tar.gz/imap/http_dblookup.c
Changed
@@ -77,13 +77,13 @@ }; static int get_email(struct transaction_t *txn __attribute__((unused)), - const char *user, const char *key) + const char *userid, const char *key) { struct carddav_db *db = NULL; int ret = HTTP_NOT_FOUND; /* XXX init just incase carddav not enabled? */ - db = carddav_open(user, /*flags*/0); + db = carddav_open_userid(userid, /*flags*/0); if (!db) goto done; if (carddav_getemail(db, key)) @@ -94,7 +94,7 @@ return ret; } -static int get_group(struct transaction_t *txn, const char *user, const char *key) +static int get_group(struct transaction_t *txn, const char *userid, const char *key) { struct carddav_db *db = NULL; strarray_t *array = NULL; @@ -104,7 +104,7 @@ int i; /* XXX init just incase carddav not enabled? */ - db = carddav_open(user, /*flags*/0); + db = carddav_open_userid(userid, /*flags*/0); if (!db) goto done; array = carddav_getgroup(db, key);
View file
cyrus-imapd-2.5.tar.gz/imap/http_ischedule.c
Changed
@@ -63,6 +63,7 @@ #include "tok.h" #include "util.h" #include "xmalloc.h" +#include "xcal.h" #include "xstrlcpy.h" #include <sasl/saslutil.h> @@ -93,18 +94,23 @@ static struct mime_type_t isched_mime_types[] = { /* First item MUST be the default type and storage format */ { "text/calendar; charset=utf-8", "2.0", "ics", "ifb", - (const char* (*)(void *)) &icalcomponent_as_ical_string, + (char* (*)(void *)) &icalcomponent_as_ical_string_r, (void * (*)(const char*)) &icalparser_parse_string, - NULL, NULL + (void (*)(void *)) &icalcomponent_free, NULL, NULL + }, + { "application/calendar+xml; charset=utf-8", NULL, "xcs", "xfb", + (char* (*)(void *)) &icalcomponent_as_xcal_string, + (void * (*)(const char*)) &xcal_string_as_icalcomponent, + NULL, NULL, NULL }, #ifdef WITH_JSON { "application/calendar+json; charset=utf-8", NULL, "jcs", "jfb", - (const char* (*)(void *)) &icalcomponent_as_jcal_string, + (char* (*)(void *)) &icalcomponent_as_jcal_string, (void * (*)(const char*)) &jcal_string_as_icalcomponent, - NULL, NULL, + NULL, NULL, NULL, }, #endif - { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; struct namespace_t namespace_ischedule = { @@ -171,7 +177,8 @@ /* We don't handle GET on a anything other than ?action=capabilities */ if (!URI_QUERY(txn->req_uri) || strcmp(URI_QUERY(txn->req_uri), "action=capabilities")) { - return HTTP_NOT_FOUND; + txn->error.desc = "Invalid action"; + return HTTP_BAD_REQUEST; } /* Generate ETag based on compile date/time of this source file. @@ -207,6 +214,7 @@ if (txn->resp_body.lastmod > lastmod) { xmlNodePtr root, capa, node, comp, meth; xmlNsPtr ns[NUM_NAMESPACE]; + struct mime_type_t *mime; /* Start construction of our query-result */ if (!(root = init_xml_response("query-result", NS_ISCHED, NULL, ns))) { @@ -247,9 +255,22 @@ node = xmlNewChild(capa, NULL, BAD_CAST "calendar-data-types", NULL); - node = xmlNewChild(node, NULL, BAD_CAST "calendar-data-type", NULL); - xmlNewProp(node, BAD_CAST "content-type", BAD_CAST "text/calendar"); - xmlNewProp(node, BAD_CAST "version", BAD_CAST "2.0"); + for (mime = isched_mime_types; mime->content_type; mime++) { + xmlNodePtr type = xmlNewChild(node, NULL, + BAD_CAST "calendar-data-type", NULL); + + /* Trim any charset from content-type */ + buf_reset(&txn->buf); + buf_printf(&txn->buf, "%.*s", + (int) strcspn(mime->content_type, ";"), + mime->content_type); + + xmlNewProp(type, BAD_CAST "content-type", + BAD_CAST buf_cstring(&txn->buf)); + + if (mime->version) + xmlNewProp(type, BAD_CAST "version", BAD_CAST mime->version); + } node = xmlNewChild(capa, NULL, BAD_CAST "attachments", NULL); node = xmlNewChild(node, NULL, BAD_CAST "inline", NULL); @@ -302,7 +323,7 @@ /* Check Content-Type */ if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) { for (mime = isched_mime_types; mime->content_type; mime++) { - if (is_mediatype(hdr[0], mime->content_type)) break; + if (is_mediatype(mime->content_type, hdr[0])) break; } } if (!mime || !mime->content_type) { @@ -823,17 +844,70 @@ static int meth_get_domainkey(struct transaction_t *txn, void *params __attribute__((unused))) { - txn->flags.cc |= CC_REVALIDATE; - txn->resp_body.type = "text/plain"; + int ret = 0, r, fd = -1, precond; + const char *path; + static struct buf pathbuf = BUF_INITIALIZER; + struct stat sbuf; + const char *msg_base = NULL; + unsigned long msg_size = 0; + struct resp_body_t *resp_body = &txn->resp_body; + + /* See if file exists and get Content-Length & Last-Modified time */ + buf_setcstr(&pathbuf, config_dir); + buf_appendcstr(&pathbuf, txn->req_uri->path); + path = buf_cstring(&pathbuf); + r = stat(path, &sbuf); + if (r || !S_ISREG(sbuf.st_mode)) return HTTP_NOT_FOUND; + + /* Generate Etag */ + assert(!buf_len(&txn->buf)); + buf_printf(&txn->buf, "%ld-%ld", (long) sbuf.st_mtime, (long) sbuf.st_size); - return meth_get_doc(txn, NULL); + /* Check any preconditions, including range request */ + txn->flags.ranges = 1; + precond = check_precond(txn, NULL, buf_cstring(&txn->buf), sbuf.st_mtime); + + switch (precond) { + case HTTP_OK: + case HTTP_PARTIAL: + case HTTP_NOT_MODIFIED: + /* Fill in Content-Type, ETag, Last-Modified, and Expires */ + resp_body->type = "text/plain"; + resp_body->etag = buf_cstring(&txn->buf); + resp_body->lastmod = sbuf.st_mtime; + resp_body->maxage = 86400; /* 24 hrs */ + txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; + if (httpd_userid) txn->flags.cc |= CC_PUBLIC; + + if (precond != HTTP_NOT_MODIFIED) break; + + default: + /* We failed a precondition - don't perform the request */ + resp_body->type = NULL; + return precond; + } + + if (txn->meth == METH_GET) { + /* Open and mmap the file */ + if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR; + map_refresh(fd, 1, &msg_base, &msg_size, sbuf.st_size, path, NULL); + } + + write_body(precond, txn, msg_base, sbuf.st_size); + + if (fd != -1) { + map_free(&msg_base, &msg_size); + close(fd); + } + + return ret; } static void isched_init(struct buf *serverinfo) { if (!(config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV) || - !config_getswitch(IMAPOPT_CALDAV_ALLOWSCHEDULING)) { + !config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING)) { /* Need CALDAV and CALDAV_SCHED in order to have ISCHEDULE */ return; }
View file
cyrus-imapd-2.5.tar.gz/imap/http_proxy.c
Changed
@@ -46,6 +46,7 @@ #ifdef HAVE_UNISTD_H #include <unistd.h> #endif +#include <assert.h> #include <ctype.h> #include <syslog.h> #include <sasl/sasl.h> @@ -628,6 +629,7 @@ if (*code < 200) return 0; /* Final response */ + if (!body) return 0; /* body will be piped */ if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload); /* Check connection persistence */ @@ -762,7 +764,7 @@ switch (resp_body->framing) { case FRAMING_LENGTH: /* Read 'len' octets */ - if (!pipe_chunk(pin, pout, resp_body->len)) { + if (resp_body->len && !pipe_chunk(pin, pout, resp_body->len)) { syslog(LOG_ERR, "prot_read() error"); *errstr = "Unable to read body data"; return HTTP_BAD_GATEWAY; @@ -851,7 +853,7 @@ int r = 0, sent_body = 0; xmlChar *uri; unsigned code; - const char *statline; + const char **hdr, *statline; hdrcache_t resp_hdrs = NULL; struct body_t resp_body; @@ -875,6 +877,9 @@ write_forwarding_hdrs(be->out, txn->req_hdrs, txn->req_line.ver, https ? "https" : "http"); spool_enum_hdrcache(txn->req_hdrs, &write_cachehdr, be->out); + if ((hdr = spool_getheader(txn->req_hdrs, "TE"))) { + for (; *hdr; hdr++) prot_printf(be->out, "TE: %s\r\n", *hdr); + } if (http_methods[txn->meth].flags & METH_NOBODY) prot_puts(be->out, "Content-Length: 0\r\n"); else if (spool_getheader(txn->req_hdrs, "Transfer-Encoding") || @@ -887,11 +892,10 @@ /* Read response(s) from backend until final response or error */ memset(&resp_body, 0, sizeof(struct body_t)); - resp_body.flags = BODY_DONE; /* Don't read body, we want to pipe it */ do { r = http_read_response(be, txn->meth, &code, &statline, - &resp_hdrs, &resp_body, &txn->error.desc); + &resp_hdrs, NULL, &txn->error.desc); if (r) break; if (code == 100) { /* Continue */ @@ -938,8 +942,11 @@ default: if (txn->meth == METH_HEAD) break; - r = pipe_resp_body(be->in, httpd_out, resp_hdrs, &resp_body, - txn->flags.ver1_0, &txn->error.desc); + if (pipe_resp_body(be->in, httpd_out, resp_hdrs, &resp_body, + txn->flags.ver1_0, &txn->error.desc)) { + /* Couldn't pipe the body and can't finish response */ + txn->flags.conn = CONN_CLOSE; + } } } @@ -956,159 +963,268 @@ * on different backends. This is handled as a GET from the source and * PUT on the destination, while obeying any Overwrite header. * - * XXX This function currently buffers the response headers and body. - * Should work on sending them to the client on-the-fly. + * For a MOVE request, we also LOCK, DELETE, and possibly UNLOCK the source. + * + * XXX This function buffers the response bodies of the LOCK & GET requests. + * The response body of the PUT request is piped to the client. */ EXPORTED int http_proxy_copy(struct backend *src_be, struct backend *dest_be, struct transaction_t *txn) { - int r = 0; + int r = 0, sent_body; unsigned code; - char *etag = NULL, *lastmod = NULL;; + char *lock = NULL; const char **hdr, *statline; hdrcache_t resp_hdrs = NULL; struct body_t resp_body; +#define write_hdr(pout, name, hdrs) \ + if ((hdr = spool_getheader(hdrs, name))) \ + for (; *hdr; hdr++) prot_printf(pout, "%s: %s\r\n", name, *hdr) + + + resp_body.payload = txn->resp_body.payload; + + if (txn->meth == METH_MOVE) { + /* + * Send a LOCK request to source backend: + * + * - Use any relevant conditional headers specified by client + */ + prot_printf(src_be->out, "LOCK %s %s\r\n" + "Host: %s\r\n" + "User-Agent: %s\r\n", + txn->req_tgt.path, HTTP_VERSION, + src_be->hostname, buf_cstring(&serverinfo)); + write_hdr(src_be->out, "If", txn->req_hdrs); + write_hdr(src_be->out, "If-Match", txn->req_hdrs); + write_hdr(src_be->out, "If-Unmodified-Since", txn->req_hdrs); + write_hdr(src_be->out, "If-Schedule-Tag-Match", txn->req_hdrs); + + assert(!buf_len(&txn->buf)); + buf_printf_markup(&txn->buf, 0, + "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"); + buf_printf_markup(&txn->buf, 0, "<D:lockinfo xmlns:D='DAV:'>"); + buf_printf_markup(&txn->buf, 1, + "<D:lockscope><D:exclusive/></D:lockscope>"); + buf_printf_markup(&txn->buf, 1, + "<D:locktype><D:write/></D:locktype>"); + buf_printf_markup(&txn->buf, 1, "<D:owner>%s</D:owner>", httpd_userid); + buf_printf_markup(&txn->buf, 0, "</D:lockinfo>"); + + prot_printf(src_be->out, + "Content-Type: application/xml; charset=utf-8\r\n" + "Content-Length: %u\r\n\r\n%s", + (unsigned)buf_len(&txn->buf), buf_cstring(&txn->buf)); + buf_reset(&txn->buf); + + prot_flush(src_be->out); + + /* Read response(s) from source backend until final response or error */ + resp_body.flags = 0; + + do { + r = http_read_response(src_be, METH_LOCK, &code, &statline, + &resp_hdrs, &resp_body, &txn->error.desc); + if (r) { + proxy_downserver(src_be); + goto done; + } + } while (code < 200); + + /* Get lock token */ + if ((hdr = spool_getheader(resp_hdrs, "Lock-Token"))) + lock = xstrdup(*hdr); + + switch (code) { + case 200: + /* Success, continue */ + break; + + case 201: + /* Created empty resource, treat as 404 (Not Found) */ + r = HTTP_NOT_FOUND; + goto delete; + + case 409: + /* Failed to create resource, treat as 404 (Not Found) */ + r = HTTP_NOT_FOUND; + goto done; + + default: + /* Send failure response to client */ + send_response(statline, resp_hdrs, &resp_body.payload, &txn->flags); + goto done; + } + } + + /* * Send a GET request to source backend to fetch body: * * - Use any relevant conditional headers specified by client + * (if not already sent in LOCK request) */ - prot_printf(src_be->out, "GET %s %s\r\n", txn->req_tgt.path, HTTP_VERSION); - prot_printf(src_be->out, "Host: %s\r\n", src_be->hostname); - prot_printf(src_be->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo)); - if ((hdr = spool_getheader(txn->req_hdrs, "If"))) - prot_printf(src_be->out, "If: %s\r\n", hdr[0]); - if ((hdr = spool_getheader(txn->req_hdrs, "If-Match"))) { - for (; *hdr; hdr++) prot_printf(src_be->out, "If-Match: %s\r\n", *hdr); + prot_printf(src_be->out, "GET %s %s\r\n" + "Host: %s\r\n" + "User-Agent: %s\r\n", + txn->req_tgt.path, HTTP_VERSION, + src_be->hostname, buf_cstring(&serverinfo)); + if (txn->meth != METH_MOVE) { + write_hdr(src_be->out, "If", txn->req_hdrs); + write_hdr(src_be->out, "If-Match", txn->req_hdrs); + write_hdr(src_be->out, "If-Unmodified-Since", txn->req_hdrs); + write_hdr(src_be->out, "If-Schedule-Tag-Match", txn->req_hdrs); } - if ((hdr = spool_getheader(txn->req_hdrs, "If-Unmodified-Since"))) - prot_printf(src_be->out, "If-Unmodified-Since: %s\r\n", hdr[0]); prot_puts(src_be->out, "\r\n"); prot_flush(src_be->out); /* Read response(s) from source backend until final response or error */ resp_body.flags = 0; - resp_body.payload = txn->resp_body.payload; do { r = http_read_response(src_be, METH_GET, &code, &statline, &resp_hdrs, &resp_body, &txn->error.desc); if (r || (resp_body.flags & BODY_CLOSE)) { proxy_downserver(src_be); - break; + goto done; } } while (code < 200); - if (!r && (code == 200)) { /* OK */ - int sent_body = 0; + if (code != 200) { + /* Send failure response to client */ + send_response(statline, resp_hdrs, &resp_body.payload, &txn->flags); + goto done; + } - /* For MOVE, make a copy of the ETag or Last-Modified for later use */ - if (txn->meth == METH_MOVE) { - if ((hdr = spool_getheader(resp_hdrs, "ETag"))) - etag = xstrdup(hdr[0]); - else if ((hdr = spool_getheader(resp_hdrs, "Last-Modified"))) - lastmod = xstrdup(hdr[0]); + + /* + * Send a synchonizing PUT request to dest backend: + * + * - Add Expect:100-continue header (for synchonicity) + * - Obey Overwrite by adding If-None-Match header + * - Use any TE, Prefer, Accept* headers specified by client + * - Use Content-Type, -Encoding, -Language headers from GET response + * - Body is buffered, so send using "identity" TE + */ + prot_printf(dest_be->out, "PUT %s %s\r\n" + "Host: %s\r\n" + "User-Agent: %s\r\n" + "Expect: 100-continue\r\n", + *spool_getheader(txn->req_hdrs, "Destination"), HTTP_VERSION, + dest_be->hostname, buf_cstring(&serverinfo)); + hdr = spool_getheader(txn->req_hdrs, "Overwrite"); + if (hdr && !strcmp(*hdr, "F")) + prot_puts(dest_be->out, "If-None-Match: *\r\n"); + write_hdr(dest_be->out, "TE", txn->req_hdrs); + write_hdr(dest_be->out, "Prefer", txn->req_hdrs); + write_hdr(dest_be->out, "Accept", txn->req_hdrs); + write_hdr(dest_be->out, "Accept-Charset", txn->req_hdrs); + write_hdr(dest_be->out, "Accept-Encoding", txn->req_hdrs); + write_hdr(dest_be->out, "Accept-Language", txn->req_hdrs); + write_hdr(dest_be->out, "Content-Type", resp_hdrs); + write_hdr(dest_be->out, "Content-Encoding", resp_hdrs); + write_hdr(dest_be->out, "Content-Language", resp_hdrs); + prot_printf(dest_be->out, "Content-Length: %u\r\n\r\n", + (unsigned)buf_len(&resp_body.payload)); + prot_flush(dest_be->out); + + /* Read response(s) from dest backend until final response or error */ + sent_body = 0; + + do { + r = http_read_response(dest_be, METH_PUT, &code, &statline, + &resp_hdrs, NULL, &txn->error.desc); + if (r) { + proxy_downserver(dest_be); + goto done; } + if ((code == 100) /* Continue */ && !sent_body++) { + /* Send body to dest backend to complete the PUT */ + prot_putbuf(dest_be->out, &resp_body.payload); + prot_flush(dest_be->out); + } + } while (code < 200); + + /* Send response to client */ + send_response(statline, resp_hdrs, NULL, &txn->flags); + if (code != 204) { + resp_body.framing = FRAMING_UNKNOWN; + if (pipe_resp_body(dest_be->in, httpd_out, resp_hdrs, &resp_body, + 0, &txn->error.desc)) { + /* Couldn't pipe the body and can't finish response */ + txn->flags.conn = CONN_CLOSE; + proxy_downserver(dest_be); + goto done; + } + } + + + delete: + if ((txn->meth == METH_MOVE) && (code < 300)) { /* - * Send a synchonizing PUT request to dest backend: + * Send a DELETE request to source backend: * - * - Add Expect:100-continue header (for synchonicity) - * - Use any Prefer headers specified by client - * - Obey Overwrite:F by adding If-None-Match:* header - * - Use Content-Type, -Encoding, -Language and -Length from GET resp - * - Body is buffered, so send using "identity" TE + * - Add If header with lock token */ - hdr = spool_getheader(txn->req_hdrs, "Destination"); - prot_printf(dest_be->out, "PUT %s %s\r\n", hdr[0], HTTP_VERSION); - prot_printf(dest_be->out, "Host: %s\r\n", dest_be->hostname); - prot_printf(dest_be->out, "User-Agent: %s\r\n", - buf_cstring(&serverinfo)); - prot_puts(dest_be->out, "Expect: 100-continue\r\n"); - if ((hdr = spool_getheader(txn->req_hdrs, "Prefer"))) { - for (; *hdr; hdr++) - prot_printf(dest_be->out, "Prefer: %s\r\n", *hdr); - } - if ((hdr = spool_getheader(txn->req_hdrs, "Overwrite")) && - !strcmp(hdr[0], "F")) { - prot_printf(dest_be->out, "If-None-Match: *\r\n"); - } - hdr = spool_getheader(resp_hdrs, "Content-Type"); - prot_printf(dest_be->out, "Content-Type: %s\r\n", hdr[0]); - if ((hdr = spool_getheader(resp_hdrs, "Content-Encoding"))) - prot_printf(dest_be->out, "Content-Encoding: %s\r\n", hdr[0]); - if ((hdr = spool_getheader(resp_hdrs, "Content-Language"))) - prot_printf(dest_be->out, "Content-Language: %s\r\n", hdr[0]); - prot_printf(dest_be->out, "Content-Length: %u\r\n", - (unsigned)buf_len(&resp_body.payload)); - prot_puts(dest_be->out, "\r\n"); - prot_flush(dest_be->out); - - /* Read response(s) from dest backend until final response or error */ - resp_body.flags = 0; + prot_printf(src_be->out, "DELETE %s %s\r\n" + "Host: %s\r\n" + "User-Agent: %s\r\n", + txn->req_tgt.path, HTTP_VERSION, + src_be->hostname, buf_cstring(&serverinfo)); + if (lock) prot_printf(src_be->out, "If: (%s)\r\n", lock); + prot_puts(src_be->out, "\r\n"); + prot_flush(src_be->out); + + /* Read response(s) from source backend until final resp or error */ + resp_body.flags = BODY_DISCARD; do { - r = http_read_response(dest_be, METH_PUT, &code, &statline, - &resp_hdrs, &resp_body, &txn->error.desc); - if (r || (resp_body.flags & BODY_CLOSE)) { - proxy_downserver(dest_be); + if (http_read_response(src_be, METH_DELETE, &code, NULL, + &resp_hdrs, &resp_body, &txn->error.desc) + || (resp_body.flags & BODY_CLOSE)) { + proxy_downserver(src_be); break; } - - if ((code == 100) /* Continue */ && !sent_body++) { - /* Send body to dest backend to complete the PUT */ - prot_putbuf(dest_be->out, &resp_body.payload); - prot_flush(dest_be->out); - } } while (code < 200); + + if (code < 300 && lock) { + free(lock); + lock = NULL; + } } - txn->resp_body.payload = resp_body.payload; - if (!r) { - /* Send response to client */ - send_response(statline, resp_hdrs, &resp_body.payload, &txn->flags); + done: + if (lock) { + /* + * Something failed - Send an UNLOCK request to source backend: + */ + prot_printf(src_be->out, "UNLOCK %s %s\r\n" + "Host: %s\r\n" + "User-Agent: %s\r\n" + "Lock-Token: %s\r\n\r\n", + txn->req_tgt.path, HTTP_VERSION, + src_be->hostname, buf_cstring(&serverinfo), lock); + prot_flush(src_be->out); + + /* Read response(s) from source backend until final resp or error */ + resp_body.flags = BODY_DISCARD; - if ((txn->meth == METH_MOVE) && (code < 300)) { - /* - * Send a DELETE request to source backend: - * - * - Add If-Match header with ETag from GET - * - Add If-Unmodified-Since header with Last-Modified from GET - * - * XXX This clearly isn't an atomic MOVE. - * Either try to fix this (LOCK?), or don't allow MOVE - */ - prot_printf(src_be->out, "DELETE %s %s\r\n", - txn->req_tgt.path, HTTP_VERSION); - prot_printf(src_be->out, "Host: %s\r\n", src_be->hostname); - prot_printf(src_be->out, "User-Agent: %s\r\n", - buf_cstring(&serverinfo)); - if (etag) prot_printf(src_be->out, "If-Match: %s\r\n", etag); - else if (lastmod) prot_printf(src_be->out, - "If-Unmodified-Since: %s\r\n", - lastmod); - prot_puts(src_be->out, "\r\n"); - prot_flush(src_be->out); - - /* Read response(s) from source backend until final resp or error */ - resp_body.flags = BODY_DISCARD; + do { + if (http_read_response(src_be, METH_UNLOCK, &code, NULL, + &resp_hdrs, &resp_body, &txn->error.desc)) { + proxy_downserver(src_be); + break; + } + } while (code < 200); - do { - if (http_read_response(src_be, METH_DELETE, &code, NULL, - &resp_hdrs, &resp_body, &txn->error.desc) - || (resp_body.flags & BODY_CLOSE)) { - proxy_downserver(src_be); - break; - } - } while (code < 200); - } + free(lock); } + txn->resp_body.payload = resp_body.payload; if (resp_hdrs) spool_free_hdrcache(resp_hdrs); - if (etag) free(etag); - if (lastmod) free(lastmod); return r; }
View file
cyrus-imapd-2.5.tar.gz/imap/http_rss.c
Changed
@@ -195,7 +195,8 @@ /* Local Mailbox */ /* Open mailbox for reading */ - if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) { + r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); + if (r) { syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", txn->req_tgt.mboxname, error_message(r)); txn->error.desc = error_message(r); @@ -293,7 +294,7 @@ } } - if (mailbox) mailbox_unlock_index(mailbox, NULL); + mailbox_close(&mailbox); return ret; @@ -710,6 +711,7 @@ if (!replace && config_httpprettytelemetry) buf_appendcstr(buf, "\n"); } + /* List messages as an RSS feed */ static int list_messages(struct transaction_t *txn, struct mailbox *mailbox) {
View file
cyrus-imapd-2.5.tar.gz/imap/http_timezone.c
Added
@@ -0,0 +1,1169 @@ +/* http_timezone.c -- Routines for handling timezone service requests in httpd + * + * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/* + * TODO: + * - Implement localized names and "lang" parameter + * - Implement action=find with case-insensitive match - strncasecmp()? + */ + +#include <config.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <ctype.h> +#include <string.h> +#include <syslog.h> +#include <assert.h> + +#include "global.h" +#include "hash.h" +#include "httpd.h" +#include "http_dav.h" +#include "http_err.h" +#include "http_proxy.h" +#include "jcal.h" +#include "map.h" +#include "tok.h" +#include "strhash.h" +#include "util.h" +#include "version.h" +#include "xcal.h" +#include "xstrlcpy.h" +#include "zoneinfo_db.h" + + +#define TIMEZONE_WELLKNOWN_URI "/.well-known/timezone" + +static time_t compile_time; +static void timezone_init(struct buf *serverinfo); +static void timezone_shutdown(void); +static int meth_get(struct transaction_t *txn, void *params); +static int action_capa(struct transaction_t *txn, struct hash_table *params); +static int action_list(struct transaction_t *txn, struct hash_table *params); +static int action_get(struct transaction_t *txn, struct hash_table *params); +static int action_expand(struct transaction_t *txn, struct hash_table *params); +static int json_response(int code, struct transaction_t *txn, json_t *root, + char **resp); +static int json_error_response(struct transaction_t *txn, const char *err); + +struct observance { + const char *name; + icaltimetype onset; + int offset_from; + int offset_to; + int is_daylight; +}; + +static const struct action_t { + const char *name; + int (*proc)(struct transaction_t *txn, struct hash_table *params); +} actions[] = { + { "capabilities", &action_capa }, + { "list", &action_list }, + { "get", &action_get }, + { "expand", &action_expand }, + { "find", &action_list }, + { NULL, NULL} +}; + + +static struct mime_type_t tz_mime_types[] = { + /* First item MUST be the default type and storage format */ + { "text/calendar; charset=utf-8", "2.0", "ics", "ifb", + (char* (*)(void *)) &icalcomponent_as_ical_string_r, + NULL, NULL, NULL, NULL + }, + { "application/calendar+xml; charset=utf-8", NULL, "xcs", "xfb", + (char* (*)(void *)) &icalcomponent_as_xcal_string, + NULL, NULL, NULL, NULL + }, + { "application/calendar+json; charset=utf-8", NULL, "jcs", "jfb", + (char* (*)(void *)) &icalcomponent_as_jcal_string, + NULL, NULL, NULL, NULL + }, + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + + +/* Namespace for TIMEZONE feeds of mailboxes */ +struct namespace_t namespace_timezone = { + URL_NS_TIMEZONE, 0, "/timezone", TIMEZONE_WELLKNOWN_URI, 0 /* auth */, ALLOW_READ, + timezone_init, NULL, NULL, timezone_shutdown, + { + { NULL, NULL }, /* ACL */ + { NULL, NULL }, /* COPY */ + { NULL, NULL }, /* DELETE */ + { &meth_get, NULL }, /* GET */ + { &meth_get, NULL }, /* HEAD */ + { NULL, NULL }, /* LOCK */ + { NULL, NULL }, /* MKCALENDAR */ + { NULL, NULL }, /* MKCOL */ + { NULL, NULL }, /* MOVE */ + { &meth_options, NULL }, /* OPTIONS */ + { NULL, NULL }, /* POST */ + { NULL, NULL }, /* PROPFIND */ + { NULL, NULL }, /* PROPPATCH */ + { NULL, NULL }, /* PUT */ + { NULL, NULL }, /* REPORT */ + { &meth_trace, NULL }, /* TRACE */ + { NULL, NULL } /* UNLOCK */ + } +}; + + +static void timezone_init(struct buf *serverinfo) +{ + namespace_timezone.enabled = + config_httpmodules & IMAP_ENUM_HTTPMODULES_TIMEZONE; + + if (!namespace_timezone.enabled) return; + + /* Open zoneinfo db */ + if (zoneinfo_open(NULL)) { + namespace_timezone.enabled = 0; + return; + } + + if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON && + !strstr(buf_cstring(serverinfo), " Jansson/")) { + buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION); + } + + compile_time = calc_compile_time(__TIME__, __DATE__); +} + + +static void timezone_shutdown(void) +{ + zoneinfo_close(NULL); +} + + +/* Perform a GET/HEAD request */ +static int meth_get(struct transaction_t *txn, + void *params __attribute__((unused))) +{ + int ret; + tok_t tok; + char *param; + struct strlist *action; + struct hash_table query_params; + const struct action_t *ap = NULL; + + /* Parse the query string and add param/value pairs to hash table */ + construct_hash_table(&query_params, 10, 1); + tok_initm(&tok, URI_QUERY(txn->req_uri), "&=", TOK_TRIMLEFT|TOK_TRIMRIGHT); + while ((param = tok_next(&tok))) { + struct strlist *vals; + char *value = tok_next(&tok); + if (!value) break; + + vals = hash_lookup(param, &query_params); + appendstrlist(&vals, xmlURIUnescapeString(value, strlen(value), NULL)); + hash_insert(param, vals, &query_params); + } + tok_fini(&tok); + + action = hash_lookup("action", &query_params); + if (action && !action->next /* mandatory, once only */) { + for (ap = actions; ap->name && strcmp(action->s, ap->name); ap++); + } + + if (!ap || !ap->name) ret = json_error_response(txn, "invalid-action"); + else ret = ap->proc(txn, &query_params); + + free_hash_table(&query_params, (void (*)(void *)) &freestrlist); + + return ret; +} + + +/* Perform a capabilities action */ +static int action_capa(struct transaction_t *txn, + struct hash_table *params __attribute__((unused))) +{ + int precond; + struct message_guid guid; + const char *etag; + static time_t lastmod = 0; + static char *resp = NULL; + json_t *root = NULL; + + /* Generate ETag based on compile date/time of this source file. + * Extend this to include config file size/mtime if we add run-time options. + */ + assert(!buf_len(&txn->buf)); + buf_printf(&txn->buf, "%ld", (long) compile_time); + message_guid_generate(&guid, buf_cstring(&txn->buf), buf_len(&txn->buf)); + etag = message_guid_encode(&guid); + + /* Check any preconditions, including range request */ + txn->flags.ranges = 1; + precond = check_precond(txn, NULL, etag, compile_time); + + switch (precond) { + case HTTP_OK: + case HTTP_PARTIAL: + case HTTP_NOT_MODIFIED: + /* Fill in Etag, Last-Modified, Expires */ + txn->resp_body.etag = etag; + txn->resp_body.lastmod = compile_time; + txn->resp_body.maxage = 86400; /* 24 hrs */ + txn->flags.cc |= CC_MAXAGE; + + if (precond != HTTP_NOT_MODIFIED) break; + + default: + /* We failed a precondition - don't perform the request */ + return precond; + } + + if (txn->resp_body.lastmod > lastmod) { + struct zoneinfo info; + int r; + + /* Get info record from the database */ + if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR; + + /* Construct our response */ + root = json_pack("{s:i" /* version */ + " s:{s:s s:{s:b s:b} s:[]}" /* info */ + " s:[" /* actions */ + " {s:s s:[]}" /* capabilities */ + " {s:s s:[" /* list */ +// " {s:s s:b s:b}" /* lang */ + " {s:s s:b s:b}" /* tzid */ + " {s:s s:b s:b}" /* changedsince */ + " ]}" + " {s:s s:[" /* get */ +// " {s:s s:b s:b}" /* lang */ + " {s:s s:b s:b}" /* tzid */ + " {s:s s:b s:b s:[s s s]}"/* format */ + " {s:s s:b s:b}" /* truncate */ + " ]}" + " {s:s s:[" /* expand */ +// " {s:s s:b s:b}" /* lang */ + " {s:s s:b s:b}" /* tzid */ + " {s:s s:b s:b}" /* changedsince */ + " {s:s s:b s:b}" /* start */ + " {s:s s:b s:b}" /* end */ + " ]}" + " {s:s s:[" /* find */ +// " {s:s s:b s:b}" /* lang */ + " {s:s s:b s:b}" /* name */ + " ]}" + " ]}", + "version", 1, + "info", "primary-source", info.data->s, + "truncated", "any", 1, "untruncated", 1, "contacts", + "actions", + "name", "capabilities", "parameters", + + "name", "list", "parameters", +// "name", "lang", "required", 0, "multi", 1, + "name", "tzid", "required", 0, "multi", 1, + "name", "changedsince", "required", 0, "multi", 0, + + "name", "get", "parameters", +// "name", "lang", "required", 0, "multi", 1, + "name", "tzid", "required", 1, "multi", 0, + "name", "format", "required", 0, "multi", 0, + "values", "text/calendar", "application/calendar+xml", + "application/calendar+json", + "name", "truncate", "required", 0, "multi", 0, + + "name", "expand", "parameters", +// "name", "lang", "required", 0, "multi", 1, + "name", "tzid", "required", 1, "multi", 0, + "name", "changedsince", "required", 0, "multi", 0, + "name", "start", "required", 0, "multi", 0, + "name", "end", "required", 0, "multi", 0, + + "name", "find", "parameters", +// "name", "lang", "required", 0, "multi", 1, + "name", "name", "required", 1, "multi", 0); + freestrlist(info.data); + + if (!root) { + txn->error.desc = "Unable to create JSON response"; + return HTTP_SERVER_ERROR; + } + + /* Update lastmod */ + lastmod = txn->resp_body.lastmod; + } + + /* Output the JSON object */ + return json_response(precond, txn, root, &resp); +} + + +static int list_cb(const char *tzid, int tzidlen, + struct zoneinfo *zi, void *rock) +{ + json_t *tzarray = (json_t *) rock, *tz; + char tzidbuf[200], lastmod[21]; + + strlcpy(tzidbuf, tzid, tzidlen+1); + rfc3339date_gen(lastmod, sizeof(lastmod), zi->dtstamp); + + tz = json_pack("{s:s s:s}", "tzid", tzidbuf, "last-modified", lastmod); + json_array_append_new(tzarray, tz); + + if (zi->data) { + struct strlist *sl; + json_t *aliases = json_array(); + + json_object_set_new(tz, "aliases", aliases); + + for (sl = zi->data; sl; sl = sl->next) + json_array_append_new(aliases, json_string(sl->s)); + } + + return 0; +} + + +/* Perform a list action */ +static int action_list(struct transaction_t *txn, struct hash_table *params) +{ + int r, precond, tzid_only = 1; + struct strlist *param, *name = NULL; + struct resp_body_t *resp_body = &txn->resp_body; + struct zoneinfo info; + time_t lastmod, changedsince = 0; + json_t *root = NULL; + + /* Sanity check the parameters */ + param = hash_lookup("action", params); + if (!strcmp("find", param->s)) { + name = hash_lookup("name", params); + if (!name || name->next /* mandatory, once only */) { + return json_error_response(txn, "invalid-name"); + } + tzid_only = 0; + } + else { + param = hash_lookup("changedsince", params); + if (param) { + changedsince = icaltime_as_timet(icaltime_from_string(param->s)); + if (!changedsince || param->next /* once only */) + return json_error_response(txn, "invalid-changedsince"); + } + + name = hash_lookup("tzid", params); + if (name) { + if (changedsince) return json_error_response(txn, "invalid-tzid"); + else { + /* Check for tzid=*, and revert to empty list */ + struct strlist *sl; + + for (sl = name; sl && strcmp(sl->s, "*"); sl = sl->next); + if (sl) name = NULL; + } + } + } + + /* Get info record from the database */ + if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR; + + /* Generate ETag & Last-Modified from info record */ + assert(!buf_len(&txn->buf)); + buf_printf(&txn->buf, "%u-%ld", strhash(info.data->s), info.dtstamp); + lastmod = info.dtstamp; + freestrlist(info.data); + + /* Check any preconditions, including range request */ + txn->flags.ranges = 1; + precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod); + + switch (precond) { + case HTTP_OK: + case HTTP_PARTIAL: + case HTTP_NOT_MODIFIED: + /* Fill in ETag, Last-Modified, and Expires */ + resp_body->etag = buf_cstring(&txn->buf); + resp_body->lastmod = lastmod; + resp_body->maxage = 86400; /* 24 hrs */ + txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; + if (httpd_userid) txn->flags.cc |= CC_PUBLIC; + + if (precond != HTTP_NOT_MODIFIED) break; + + default: + /* We failed a precondition - don't perform the request */ + resp_body->type = NULL; + return precond; + } + + + if (txn->meth == METH_GET) { + char dtstamp[21]; + + /* Start constructing our response */ + rfc3339date_gen(dtstamp, sizeof(dtstamp), lastmod); + root = json_pack("{s:s s:[]}", "dtstamp", dtstamp, "timezones"); + if (!root) { + txn->error.desc = "Unable to create JSON response"; + return HTTP_SERVER_ERROR; + } + + /* Add timezones to array */ + do { + zoneinfo_find(name ? name->s : NULL, tzid_only, changedsince, + &list_cb, json_object_get(root, "timezones")); + } while (name && (name = name->next)); + } + + /* Output the JSON object */ + return json_response(precond, txn, root, NULL); +} + + +static void check_tombstone(struct observance *tombstone, + struct observance *obs, icaltimetype *recur) +{ + icaltimetype *onset = recur ? recur : &obs->onset; + + if (icaltime_compare(*onset, tombstone->onset) > 0) { + /* onset is closer to cutoff than existing tombstone */ + tombstone->name = icalmemory_tmp_copy(obs->name); + tombstone->offset_from = tombstone->offset_to = obs->offset_to; + tombstone->is_daylight = obs->is_daylight; + memcpy(&tombstone->onset, onset, sizeof(icaltimetype)); + } +} + +struct rdate { + icalproperty *prop; + struct icaldatetimeperiodtype date; +}; + +static int rdate_compare(const void *rdate1, const void *rdate2) +{ + return icaltime_compare(((struct rdate *) rdate1)->date.time, + ((struct rdate *) rdate2)->date.time); +} + +static void truncate_vtimezone(icalcomponent *vtz, icaltimetype *truncate) +{ + icalcomponent *comp, *nextc; + struct observance tombstone; + + memset(&tombstone, 0, sizeof(struct observance)); + + /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */ + for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT); + comp; comp = nextc) { + icalproperty *prop, *dtstart_prop = NULL, *rrule_prop = NULL; + icalarray *rdate_array = icalarray_new(sizeof(struct rdate), 20); + struct observance obs; + unsigned n; + int r; + + nextc = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT); + + memset(&obs, 0, sizeof(struct observance)); + obs.is_daylight = (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT); + + /* Grab the properties that we require to expand recurrences */ + for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); + prop; + prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) { + + switch (icalproperty_isa(prop)) { + case ICAL_TZNAME_PROPERTY: + obs.name = icalproperty_get_tzname(prop); + break; + + case ICAL_DTSTART_PROPERTY: + dtstart_prop = prop; + obs.onset = icalproperty_get_dtstart(prop); + break; + + case ICAL_TZOFFSETFROM_PROPERTY: + obs.offset_from = icalproperty_get_tzoffsetfrom(prop); + break; + + case ICAL_TZOFFSETTO_PROPERTY: + obs.offset_to = icalproperty_get_tzoffsetto(prop); + break; + + case ICAL_RRULE_PROPERTY: + rrule_prop = prop; + break; + + case ICAL_RDATE_PROPERTY: { + struct rdate rdate = { prop, icalproperty_get_rdate(prop) }; + + icalarray_append(rdate_array, &rdate); + break; + } + + default: + /* ignore all other properties */ + break; + } + } + + /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */ + if (!dtstart_prop || !obs.name || !obs.offset_from || !obs.offset_to) + continue; + + r = icaltime_compare(obs.onset, *truncate); + if (r <= 0) { + /* Check DTSTART vs tombstone */ + check_tombstone(&tombstone, &obs, NULL); + } + + if (r >= 0) { + /* All observances occur on or after our cutoff, nothing to do */ + icalarray_free(rdate_array); + continue; + } + + /* Check RRULE */ + if (rrule_prop) { + struct icalrecurrencetype rrule; + + rrule = icalproperty_get_rrule(rrule_prop); + + /* Check RRULE duration */ + if (!icaltime_is_null_time(rrule.until)) { + if (rrule.until.is_utc) { + /* Adjust UNTIL to local time */ + icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from); + rrule.until.is_utc = 0; + } + + if (icaltime_compare(rrule.until, *truncate) < 0) { + /* RRULE ends prior to our cutoff - remove it */ + icalcomponent_remove_property(comp, rrule_prop); + icalproperty_free(rrule_prop); + rrule_prop = NULL; + + /* Check UNTIL vs tombstone */ + check_tombstone(&tombstone, &obs, &rrule.until); + } + } + + if (rrule_prop) { + icalrecur_iterator *ritr; + + /* Set iterator to start 1 year prior to our cutoff */ + obs.onset.year = truncate->year - 1; + obs.onset.month = truncate->month; + obs.onset.day = truncate->day; + + ritr = icalrecur_iterator_new(rrule, obs.onset); + + /* Check last recurrence prior to our cutoff vs tombstone */ + obs.onset = icalrecur_iterator_next(ritr); + check_tombstone(&tombstone, &obs, NULL); + + /* Use first recurrence after our cutoff as new DTSTART */ + obs.onset = icalrecur_iterator_next(ritr); + icalproperty_set_dtstart(dtstart_prop, obs.onset); + dtstart_prop = NULL; + + icalrecur_iterator_free(ritr); + } + } + + /* Sort the RDATEs by onset */ + icalarray_sort(rdate_array, &rdate_compare); + + /* Check RDATEs */ + for (n = 0; n < rdate_array->num_elements; n++) { + struct rdate *rdate = icalarray_element_at(rdate_array, n); + + r = icaltime_compare(rdate->date.time, *truncate); + if (r <= 0) { + /* Check RDATE vs tombstone */ + check_tombstone(&tombstone, &obs, &rdate->date.time); + } + + if (r < 0) { + /* RDATE occurs prior to our cutoff - remove it */ + icalcomponent_remove_property(comp, rdate->prop); + icalproperty_free(rdate->prop); + } + else { + if (dtstart_prop) { + /* Make this RDATE the new DTSTART */ + icalproperty_set_dtstart(dtstart_prop, rdate->date.time); + dtstart_prop = NULL; + + icalcomponent_remove_property(comp, rdate->prop); + icalproperty_free(rdate->prop); + } + break; + } + } + icalarray_free(rdate_array); + + /* Final check */ + if (dtstart_prop) { + /* All observances occur prior to our cutoff, remove comp */ + icalcomponent_remove_component(vtz, comp); + icalcomponent_free(comp); + } + } + + if (icaltime_compare(tombstone.onset, *truncate) < 0) { + /* Need to add a tombstone component starting at our cutoff */ + comp = icalcomponent_vanew( + tombstone.is_daylight ? + ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT, + icalproperty_new_tzoffsetfrom(tombstone.offset_from), + icalproperty_new_tzoffsetto(tombstone.offset_to), + icalproperty_new_tzname(tombstone.name), + icalproperty_new_dtstart(*truncate), + 0); + icalcomponent_add_component(vtz, comp); + } +} + +/* Perform a get action */ +static int action_get(struct transaction_t *txn, struct hash_table *params) +{ + int r, precond; + struct strlist *param; + const char *tzid; + struct zoneinfo zi; + time_t lastmod; + icaltimetype truncate = icaltime_null_time(); + char *data = NULL; + unsigned long datalen = 0; + struct resp_body_t *resp_body = &txn->resp_body; + struct mime_type_t *mime = NULL; + + /* Sanity check the parameters */ + param = hash_lookup("tzid", params); + if (!param || param->next /* mandatory, once only */ + || strchr(param->s, '.') /* paranoia */) { + return json_error_response(txn, "invalid-tzid"); + } + tzid = param->s; + + /* Check/find requested MIME type */ + param = hash_lookup("format", params); + if (param && !param->next /* optional, once only */) { + for (mime = tz_mime_types; mime->content_type; mime++) { + if (is_mediatype(param->s, mime->content_type)) break; + } + } + else mime = tz_mime_types; + + if (!mime || !mime->content_type) { + return json_error_response(txn, "invalid-format"); + } + + /* Check for any truncation */ + param = hash_lookup("truncate", params); + if (param) { + truncate = icaltime_from_day_of_year(1, atoi(param->s)); + truncate.is_date = truncate.hour = truncate.minute = truncate.second = 0; + if (icaltime_is_null_time(truncate) || param->next /* once only */) + return json_error_response(txn, "invalid-truncate"); + } + + /* Get info record from the database */ + if ((r = zoneinfo_lookup(tzid, &zi))) + return (r == CYRUSDB_NOTFOUND ? HTTP_NOT_FOUND : HTTP_SERVER_ERROR); + + /* Generate ETag & Last-Modified from info record */ + assert(!buf_len(&txn->buf)); + buf_printf(&txn->buf, "%u-%ld", strhash(tzid), zi.dtstamp); + lastmod = zi.dtstamp; + freestrlist(zi.data); + + /* Check any preconditions, including range request */ + txn->flags.ranges = 1; + precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod); + + switch (precond) { + case HTTP_OK: + case HTTP_PARTIAL: + case HTTP_NOT_MODIFIED: + /* Fill in Content-Type, ETag, Last-Modified, and Expires */ + resp_body->type = mime->content_type; + resp_body->etag = buf_cstring(&txn->buf); + resp_body->lastmod = lastmod; + resp_body->maxage = 86400; /* 24 hrs */ + txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; + if (httpd_userid) txn->flags.cc |= CC_PUBLIC; + + if (precond != HTTP_NOT_MODIFIED) break; + + default: + /* We failed a precondition - don't perform the request */ + resp_body->type = NULL; + return precond; + } + + + if (txn->meth == METH_GET) { + static struct buf pathbuf = BUF_INITIALIZER; + const char *path, *proto, *host, *msg_base = NULL; + unsigned long msg_size = 0; + icalcomponent *ical, *vtz; + icalproperty *prop; + int fd; + + /* Open, mmap, and parse the file */ + buf_reset(&pathbuf); + buf_printf(&pathbuf, "%s%s/%s.ics", + config_dir, FNAME_ZONEINFODIR, tzid); + path = buf_cstring(&pathbuf); + if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR; + + map_refresh(fd, 1, &msg_base, &msg_size, MAP_UNKNOWN_LEN, path, NULL); + if (!msg_base) return HTTP_SERVER_ERROR; + + ical = icalparser_parse_string(msg_base); + map_free(&msg_base, &msg_size); + close(fd); + + vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT); + prop = icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY); + + if (zi.type == ZI_LINK) { + const char *equiv = icalproperty_get_tzid(prop); + + /* Substitute TZID alias */ + icalproperty_set_tzid(prop, tzid); + + /* Add EQUIVALENT-TZID */ + prop = icalproperty_new_x(equiv); + icalproperty_set_x_name(prop, "EQUIVALENT-TZID"); + icalcomponent_add_property(vtz, prop); + } + + /* Start constructing TZURL */ + buf_reset(&pathbuf); + http_proto_host(txn->req_hdrs, &proto, &host); + buf_printf(&pathbuf, "%s://%s%s?action=get&tzid=%s", + proto, host, namespace_timezone.prefix, tzid); + if (mime != tz_mime_types) { + buf_printf(&pathbuf, "&format=%.*s", + (int) strcspn(mime->content_type, ";"), + mime->content_type); + } + if (!icaltime_is_null_time(truncate)) { + buf_printf(&pathbuf, "&truncate=%d", truncate.year); + + /* Truncate the VTIMEZONE */ + truncate_vtimezone(vtz, &truncate); + } + + /* Set TZURL property */ + prop = icalproperty_new_tzurl(buf_cstring(&pathbuf)); + icalcomponent_add_property(vtz, prop); + + /* Convert to requested MIME type */ + data = mime->to_string(ical); + datalen = strlen(data); + + /* Set Content-Disposition filename */ + buf_reset(&pathbuf); + buf_printf(&pathbuf, "%s.%s", tzid, mime->file_ext); + resp_body->fname = buf_cstring(&pathbuf); + + icalcomponent_free(ical); + } + + write_body(precond, txn, data, datalen); + + if (data) free(data); + + return 0; +} + + +static int observance_compare(const void *obs1, const void *obs2) +{ + return icaltime_compare(((struct observance *) obs1)->onset, + ((struct observance *) obs2)->onset); +} + +/* Perform an expand action */ +static int action_expand(struct transaction_t *txn, struct hash_table *params) +{ + int r, precond; + struct strlist *param; + const char *tzid; + struct zoneinfo zi; + time_t lastmod, changedsince = 0; + icaltimetype start, end; + struct resp_body_t *resp_body = &txn->resp_body; + json_t *root = NULL; + + /* Sanity check the parameters */ + param = hash_lookup("tzid", params); + if (!param || param->next /* mandatory, once only */ + || strchr(param->s, '.') /* paranoia */) { + return json_error_response(txn, "invalid-tzid"); + } + tzid = param->s; + + param = hash_lookup("changedsince", params); + if (param) { + changedsince = icaltime_as_timet(icaltime_from_string(param->s)); + if (!changedsince || param->next /* once only */) + return json_error_response(txn, "invalid-changedsince"); + } + + param = hash_lookup("start", params); + if (param) { + start = icaltime_from_string(param->s); + if (icaltime_is_null_time(start) || param->next /* once only */) + return json_error_response(txn, "invalid-start"); + } + else { + /* Default to current year */ + time_t now = time(0); + struct tm *tm = gmtime(&now); + + start = icaltime_from_day_of_year(1, tm->tm_year + 1900); + } + + param = hash_lookup("end", params); + if (param) { + end = icaltime_from_string(param->s); + if (icaltime_compare(end, start) <= 0 /* end MUST be > start */ + || param->next /* once only */) + return json_error_response(txn, "invalid-end"); + } + else { + /* Default to start year + 10 */ + memcpy(&end, &start, sizeof(icaltimetype)); + end.year += 10; + } + + /* Get info record from the database */ + if ((r = zoneinfo_lookup(tzid, &zi))) + return (r == CYRUSDB_NOTFOUND ? HTTP_NOT_FOUND : HTTP_SERVER_ERROR); + + /* Generate ETag & Last-Modified from info record */ + assert(!buf_len(&txn->buf)); + buf_printf(&txn->buf, "%u-%ld", strhash(tzid), zi.dtstamp); + lastmod = zi.dtstamp; + freestrlist(zi.data); + + /* Check any preconditions, including range request */ + txn->flags.ranges = 1; + if (lastmod <= changedsince) precond = HTTP_NOT_MODIFIED; + else precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod); + + switch (precond) { + case HTTP_OK: + case HTTP_PARTIAL: + case HTTP_NOT_MODIFIED: + /* Fill in ETag, Last-Modified, and Expires */ + resp_body->etag = buf_cstring(&txn->buf); + resp_body->lastmod = lastmod; + resp_body->maxage = 86400; /* 24 hrs */ + txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; + if (httpd_userid) txn->flags.cc |= CC_PUBLIC; + + if (precond != HTTP_NOT_MODIFIED) break; + + default: + /* We failed a precondition - don't perform the request */ + resp_body->type = NULL; + return precond; + } + + + if (txn->meth == METH_GET) { + static struct buf pathbuf = BUF_INITIALIZER; + const char *path, *msg_base = NULL; + unsigned long msg_size = 0; + icalcomponent *ical, *vtz, *comp; + char dtstamp[21]; + icalarray *obsarray; + json_t *jobsarray; + unsigned n; + int fd; + + /* Open, mmap, and parse the file */ + buf_reset(&pathbuf); + buf_printf(&pathbuf, "%s%s/%s.ics", + config_dir, FNAME_ZONEINFODIR, tzid); + path = buf_cstring(&pathbuf); + if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR; + + map_refresh(fd, 1, &msg_base, &msg_size, MAP_UNKNOWN_LEN, path, NULL); + if (!msg_base) return HTTP_SERVER_ERROR; + + ical = icalparser_parse_string(msg_base); + map_free(&msg_base, &msg_size); + close(fd); + + /* Start constructing our response */ + rfc3339date_gen(dtstamp, sizeof(dtstamp), lastmod); + root = json_pack("{s:s s:[]}", "dtstamp", dtstamp, "observances"); + if (!root) { + txn->error.desc = "Unable to create JSON response"; + return HTTP_SERVER_ERROR; + } + + /* Create an array of observances */ + obsarray = icalarray_new(sizeof(struct observance), 20); + + /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */ + vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT); + for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT); + comp; + comp = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT)) { + + icaltimetype dtstart = icaltime_null_time(); + struct observance obs; + icalproperty *prop, *rrule_prop = NULL; + + /* Grab the properties that we require to expand recurrences */ + memset(&obs, 0, sizeof(struct observance)); + for (prop = icalcomponent_get_first_property(comp, + ICAL_ANY_PROPERTY); + prop; + prop = icalcomponent_get_next_property(comp, + ICAL_ANY_PROPERTY)) { + + switch (icalproperty_isa(prop)) { + case ICAL_TZNAME_PROPERTY: + obs.name = icalproperty_get_tzname(prop); + break; + + case ICAL_DTSTART_PROPERTY: + dtstart = icalproperty_get_dtstart(prop); + break; + + case ICAL_TZOFFSETFROM_PROPERTY: + obs.offset_from = icalproperty_get_tzoffsetfrom(prop); + break; + + case ICAL_TZOFFSETTO_PROPERTY: + obs.offset_to = icalproperty_get_tzoffsetto(prop); + break; + + case ICAL_RRULE_PROPERTY: + rrule_prop = prop; + break; + + default: + /* ignore all other properties */ + break; + } + } + + /* We MUST have TZNAME, DTSTART, TZOFFSETFROM and TZOFFSETTO */ + if (!obs.name || !obs.offset_from || !obs.offset_to || + icaltime_is_null_time(dtstart)) continue; + + /* Adjust DTSTART to UTC */ + memcpy(&obs.onset, &dtstart, sizeof(icaltimetype)); + icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); + obs.onset.is_utc = 1; + + if (icaltime_compare(obs.onset, end) > 0) { + /* Skip observance(s) after our window */ + } + else if (rrule_prop) { + /* Add any RRULE observances within our window */ + struct icalrecurrencetype rrule; + icalrecur_iterator *ritr; + icaltimetype recur; + + rrule = icalproperty_get_rrule(rrule_prop); + + if (!icaltime_is_null_time(rrule.until) && rrule.until.is_utc) { + /* Adjust UNTIL to local time */ + icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from); + rrule.until.is_utc = 0; + } + + if (icaltime_compare(start, obs.onset) > 0) { + /* Set iterator dtstart to be 1 day prior to our window */ + obs.onset.year = start.year; + obs.onset.month = start.month; + obs.onset.day = start.day - 1; + } + + /* Adjust iterator dtstart to local time */ + icaltime_adjust(&obs.onset, 0, 0, 0, obs.offset_from); + obs.onset.is_utc = 0; + + ritr = icalrecur_iterator_new(rrule, obs.onset); + while (!icaltime_is_null_time(recur = + icalrecur_iterator_next(ritr))) { + /* Adjust observance to UTC */ + memcpy(&obs.onset, &recur, sizeof(icaltimetype)); + icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); + obs.onset.is_utc = 1; + + if (icaltime_compare(obs.onset, end) > 0) { + /* Quit if we've gone past our window */ + break; + } + else if (icaltime_compare(obs.onset, start) < 0) { + /* Skip observances prior to our window */ + } + else { + /* Add the observance to our array */ + icalarray_append(obsarray, &obs); + } + } + icalrecur_iterator_free(ritr); + } + else if (icaltime_compare(obs.onset, start) < 0) { + /* Skip observances prior to our window */ + } + else { + /* Add the DTSTART observance to our array */ + icalarray_append(obsarray, &obs); + } + + /* Add any RDATE observances within our window */ + for (prop = icalcomponent_get_first_property(comp, + ICAL_RDATE_PROPERTY); + prop; + prop = icalcomponent_get_next_property(comp, + ICAL_RDATE_PROPERTY)) { + struct icaldatetimeperiodtype rdate = + icalproperty_get_rdate(prop); + + /* Adjust RDATE to UTC */ + memcpy(&obs.onset, &rdate.time, sizeof(icaltimetype)); + icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); + obs.onset.is_utc = 1; + + if (icaltime_compare(obs.onset, start) < 0) { + /* Skip observances prior to our window */ + } + else if (icaltime_compare(obs.onset, end) > 0) { + /* Skip observances after our window */ + } + else if (icaltime_compare(obs.onset, dtstart) == 0) { + /* Skip duplicates of DTSTART observance */ + } + else { + /* Add the RDATE observance to our array */ + icalarray_append(obsarray, &obs); + } + } + } + + /* Sort the observances by onset */ + icalarray_sort(obsarray, &observance_compare); + + /* Add observances to JSON array */ + jobsarray = json_object_get(root, "observances"); + for (n = 0; n < obsarray->num_elements; n++) { + struct observance *obs = icalarray_element_at(obsarray, n); + + json_array_append_new(jobsarray, + json_pack("{s:s s:s s:i s:i}", + "name", obs->name, + "onset", + icaltime_as_ical_string(obs->onset), + "utc-offset-from", obs->offset_from, + "utc-offset-to", obs->offset_to)); + } + icalarray_free(obsarray); + + icalcomponent_free(ical); + } + + /* Output the JSON object */ + return json_response(precond, txn, root, NULL); +} + + +static int json_response(int code, struct transaction_t *txn, json_t *root, + char **resp) +{ + size_t flags = JSON_PRESERVE_ORDER; + char *buf = NULL; + + if (root) { + /* Dump JSON object into a text buffer */ + flags |= (config_httpprettytelemetry ? JSON_INDENT(2) : JSON_COMPACT); + buf = json_dumps(root, flags); + json_decref(root); + + if (!buf) { + txn->error.desc = "Error dumping JSON object"; + return HTTP_SERVER_ERROR; + } + else if (resp) { + if (*resp) free(*resp); + *resp = buf; + } + } + else if (resp) buf = *resp; + + /* Output the JSON object */ + txn->resp_body.type = "application/json; charset=utf-8"; + write_body(code, txn, buf, buf ? strlen(buf) : 0); + + if (!resp && buf) free(buf); + + return 0; +} + + +static int json_error_response(struct transaction_t *txn, const char *err) +{ + json_t *root; + + root = json_pack("{s:s}", "error", err); + if (!root) { + txn->error.desc = "Unable to create JSON response"; + return HTTP_SERVER_ERROR; + } + + return json_response(HTTP_BAD_REQUEST, txn, root, NULL); +}
View file
cyrus-imapd-2.5.tar.gz/imap/httpd.c
Changed
@@ -126,10 +126,10 @@ sasl_conn_t *httpd_saslconn; /* the sasl connection context */ -static struct mailbox *httpd_mailbox = NULL; static struct wildmat *allow_cors = NULL; int httpd_timeout, httpd_keepalive; char *httpd_userid = NULL, *proxy_userid = NULL; +char *httpd_extradomain = NULL; struct auth_state *httpd_authstate = 0; int httpd_userisadmin = 0; int httpd_userisproxyadmin = 0; @@ -149,6 +149,7 @@ unsigned long config_httpmodules; int config_httpprettytelemetry; +static time_t compile_time; struct buf serverinfo = BUF_INITIALIZER; static void digest_send_success(const char *name __attribute__((unused)), @@ -208,6 +209,7 @@ static int http_auth(const char *creds, struct transaction_t *txn); static void keep_alive(int sig); +static int meth_get(struct transaction_t *txn, void *params); static int meth_propfind_root(struct transaction_t *txn, void *params); @@ -255,8 +257,8 @@ { NULL, NULL }, /* ACL */ { NULL, NULL }, /* COPY */ { NULL, NULL }, /* DELETE */ - { &meth_get_doc, NULL }, /* GET */ - { &meth_get_doc, NULL }, /* HEAD */ + { &meth_get, NULL }, /* GET */ + { &meth_get, NULL }, /* HEAD */ { NULL, NULL }, /* LOCK */ { NULL, NULL }, /* MKCALENDAR */ { NULL, NULL }, /* MKCOL */ @@ -277,9 +279,12 @@ #ifdef WITH_DAV &namespace_principal, &namespace_calendar, + &namespace_addressbook, &namespace_ischedule, &namespace_domainkey, - &namespace_addressbook, +#ifdef WITH_JSON + &namespace_timezone, +#endif #endif #ifdef WITH_RSS &namespace_rss, @@ -316,9 +321,6 @@ backend_cached = NULL; backend_current = NULL; - if (httpd_mailbox) mailbox_close(&httpd_mailbox); - httpd_mailbox = NULL; - if (httpd_in) { prot_NONBLOCK(httpd_in); prot_fill(httpd_in); @@ -360,6 +362,10 @@ free(httpd_userid); httpd_userid = NULL; } + if (httpd_extradomain != NULL) { + free(httpd_extradomain); + httpd_extradomain = NULL; + } if (proxy_userid != NULL) { free(proxy_userid); proxy_userid = NULL; @@ -486,7 +492,7 @@ #ifdef HAVE_ZLIB buf_printf(&serverinfo, " zlib/%s", ZLIB_VERSION); #endif - buf_printf(&serverinfo, " libxml/%s", LIBXML_DOTTED_VERSION); + buf_printf(&serverinfo, " libxml2/%s", LIBXML_DOTTED_VERSION); } /* Do any namespace specific initialization */ @@ -496,6 +502,8 @@ if (namespaces[i]->init) namespaces[i]->init(&serverinfo); } + compile_time = calc_compile_time(__TIME__, __DATE__); + return 0; } @@ -696,8 +704,6 @@ } if (backend_cached) free(backend_cached); - if (httpd_mailbox) mailbox_close(&httpd_mailbox); - sync_log_done(); mboxlist_close(); @@ -932,10 +938,12 @@ txn.auth_chal.param = NULL; txn.req_hdrs = NULL; txn.req_body.flags = 0; + buf_reset(&txn.req_body.payload); txn.location = NULL; memset(&txn.error, 0, sizeof(struct error_t)); memset(&txn.resp_body, 0, /* Don't zero the response payload buffer */ sizeof(struct resp_body_t) - sizeof(struct buf)); + buf_reset(&txn.resp_body.payload); buf_reset(&txn.buf); ret = empty = 0; @@ -1078,6 +1086,13 @@ txn.flags.conn &= ~CONN_UPGRADE; } + /* Check for HTTP method override */ + if (!strcmp(req_line->meth, "POST") && + (hdr = spool_getheader(txn.req_hdrs, "X-HTTP-Method-Override"))) { + txn.flags.override = 1; + req_line->meth = (char *) hdr[0]; + } + /* Check Method against our list of known methods */ for (txn.meth = 0; (txn.meth < METH_UNKNOWN) && strcmp(http_methods[txn.meth].name, req_line->meth); @@ -1335,8 +1350,9 @@ struct accept *e, *enc = parse_accept(hdr); for (e = enc; e && e->token; e++) { - if (!strcasecmp(e->token, "gzip") || - !strcasecmp(e->token, "x-gzip")) { + if (e->qual > 0.0 && + (!strcasecmp(e->token, "gzip") || + !strcasecmp(e->token, "x-gzip"))) { txn.flags.te = TE_GZIP; } free(e->token); @@ -1347,8 +1363,9 @@ struct accept *e, *enc = parse_accept(hdr); for (e = enc; e && e->token; e++) { - if (!strcasecmp(e->token, "gzip") || - !strcasecmp(e->token, "x-gzip")) { + if (e->qual > 0.0 && + (!strcasecmp(e->token, "gzip") || + !strcasecmp(e->token, "x-gzip"))) { txn.resp_body.enc = CE_GZIP; } free(e->token); @@ -1452,12 +1469,31 @@ /* Compare Content-Types */ -EXPORTED int is_mediatype(const char *hdr, const char *type) +EXPORTED int is_mediatype(const char *pat, const char *type) { - size_t tlen = strcspn(type, "; \r\n\0"); - size_t hlen = strcspn(hdr, "; \r\n\0"); + const char *psep = strchr(pat, '/'); + const char *tsep = strchr(type, '/'); + size_t plen; + size_t tlen; + int alltypes; + + /* Check type */ + if (!psep || !tsep) return 0; + plen = psep - pat; + tlen = tsep - type; + + alltypes = !strncmp(pat, "*", plen); + + if (!alltypes && ((tlen != plen) || strncasecmp(pat, type, tlen))) return 0; + + /* Check subtype */ + pat = ++psep; + plen = strcspn(pat, "; \r\n\0"); + type = ++tsep; + tlen = strcspn(type, "; \r\n\0"); - return ((tlen == hlen) && !strncasecmp(hdr, type, tlen)); + return (!strncmp(pat, "*", plen) || + (!alltypes && (tlen == plen) && !strncasecmp(pat, type, tlen))); } @@ -1907,20 +1943,34 @@ /**************************** Response Routines *****************************/ +/* Create RFC3339 date ('buf' must be at least 21 characters) */ +EXPORTED char *rfc3339date_gen(char *buf, size_t len, time_t t) +{ + struct tm *tm = gmtime(&t); + + snprintf(buf, len, "%4d-%02d-%02dT%02d:%02d:%02dZ", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + return buf; +} + + /* Create HTTP-date ('buf' must be at least 30 characters) */ -EXPORTED void httpdate_gen(char *buf, size_t len, time_t t) +EXPORTED char *httpdate_gen(char *buf, size_t len, time_t t) { - struct tm *tm; static char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - tm = gmtime(&t); + struct tm *tm = gmtime(&t); snprintf(buf, len, "%3s, %02d %3s %4d %02d:%02d:%02d GMT", wday[tm->tm_wday], tm->tm_mday, month[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec); + + return buf; } @@ -2138,6 +2188,9 @@ if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { prot_printf(httpd_out, "Server: %s\r\n", buf_cstring(&serverinfo)); } + if (txn->flags.mime) { + prot_puts(httpd_out, "MIME-Version: 1.0\r\n"); + } if (txn->req_tgt.allow & ALLOW_ISCHEDULE) { prot_puts(httpd_out, "iSchedule-Version: 1.0\r\n"); if (resp_body->iserial) { @@ -2172,7 +2225,9 @@ (txn->req_tgt.allow & ALLOW_WRITECOL) ? ", extended-mkcol" : ""); if (txn->req_tgt.allow & ALLOW_CAL) { - prot_printf(httpd_out, "DAV: calendar-access%s\r\n", + prot_printf(httpd_out, "DAV: calendar-access%s%s\r\n", + (txn->req_tgt.allow & ALLOW_CAL_AVAIL) ? + ", calendar-availability" : "", (txn->req_tgt.allow & ALLOW_CAL_SCHED) ? ", calendar-auto-schedule" : ""); } @@ -2342,7 +2397,9 @@ } } } - else prot_printf(httpd_out, "Content-Length: %lu\r\n", resp_body->len); + else if (resp_body->len || txn->meth != METH_HEAD) { + prot_printf(httpd_out, "Content-Length: %lu\r\n", resp_body->len); + } } @@ -2366,7 +2423,8 @@ /* Add request-line */ buf_appendcstr(&log, "; \""); if (txn->req_line.meth) { - buf_printf(&log, "%s", txn->req_line.meth); + buf_printf(&log, "%s", + txn->flags.override ? "POST" : txn->req_line.meth); if (txn->req_line.uri) { buf_printf(&log, " %s", txn->req_line.uri); if (txn->req_line.ver) { @@ -2383,6 +2441,10 @@ /* Add any request modifying headers */ const char *sep = " ("; + if (txn->flags.override) { + buf_printf(&log, "%smethod-override=%s", sep, txn->req_line.meth); + sep = "; "; + } if ((hdr = spool_getheader(txn->req_hdrs, "Origin"))) { buf_printf(&log, "%sorigin=%s", sep, hdr[0]); sep = "; "; @@ -2447,6 +2509,8 @@ const char *preamble = "This is a message with multiple parts in MIME format.\r\n"; + txn->flags.mime = 1; + /* Create multipart boundary */ snprintf(boundary, sizeof(boundary), "%s-%ld-%ld-%ld", *spool_getheader(txn->req_hdrs, "Host"), @@ -2590,7 +2654,7 @@ if (code) { /* Initial call - prepare response header based on CE, TE and version */ - if (do_md5) MD5_Init(&ctx); + if (do_md5) MD5Init(&ctx); if (txn->flags.te & ~TE_CHUNKED) { /* Transfer-Encoded content MUST be chunked */ @@ -2646,8 +2710,8 @@ } if (outlen && do_md5) { - MD5_Update(&ctx, buf+offset, outlen); - MD5_Final(md5, &ctx); + MD5Update(&ctx, buf+offset, outlen); + MD5Final(md5, &ctx); txn->resp_body.md5 = md5; } } @@ -2681,7 +2745,7 @@ prot_write(httpd_out, buf, outlen); prot_puts(httpd_out, "\r\n"); - if (do_md5) MD5_Update(&ctx, buf, outlen); + if (do_md5) MD5Update(&ctx, buf, outlen); } if (!len) { /* Terminate the HTTP/1.1 body with a zero-length chunk */ @@ -2689,7 +2753,7 @@ /* Trailer */ if (do_md5) { - MD5_Final(md5, &ctx); + MD5Final(md5, &ctx); Content_MD5(md5); } @@ -2877,6 +2941,10 @@ free(httpd_userid); httpd_userid = NULL; } + if (httpd_extradomain) { + free(httpd_extradomain); + httpd_extradomain = NULL; + } if (httpd_authstate) { auth_freestate(httpd_authstate); httpd_authstate = NULL; @@ -2947,9 +3015,10 @@ proc_register("httpd", httpd_clienthost, httpd_userid, NULL, NULL); - syslog(LOG_NOTICE, "login: %s %s %s%s %s", + syslog(LOG_NOTICE, "login: %s %s %s%s %s SESSIONID=<%s>", httpd_clienthost, httpd_userid, scheme->name, - httpd_tls_done ? "+TLS" : "", "User logged in"); + httpd_tls_done ? "+TLS" : "", "User logged in", + session_id()); /* Recreate telemetry log entry for request (w/ credentials redacted) */ @@ -3038,6 +3107,10 @@ free(httpd_userid); httpd_userid = NULL; } + if (httpd_extradomain) { + free(httpd_extradomain); + httpd_extradomain = NULL; + } if (httpd_authstate) { auth_freestate(httpd_authstate); httpd_authstate = NULL; @@ -3128,6 +3201,7 @@ if (scheme->idx == AUTH_BASIC) { /* Basic (plaintext) authentication */ char *pass; + char *extra; if (!clientin) { /* Create initial challenge (base64 buffer is static) */ @@ -3147,7 +3221,8 @@ return SASL_BADPARAM; } *pass++ = '\0'; - + extra = strchr(user, '%'); + if (extra) *extra++ = '\0'; /* Verify the password */ status = sasl_checkpass(httpd_saslconn, user, strlen(user), pass, strlen(pass)); @@ -3163,6 +3238,7 @@ } /* Successful authentication - fall through */ + httpd_extradomain = xstrdupnull(extra); } else { /* SASL-based authentication (Digest, Negotiate, NTLM) */ @@ -3247,32 +3323,6 @@ /************************* Method Execution Routines ************************/ -/* "Open" the requested mailbox. Either return the existing open - * mailbox if it matches, or close the existing and open the requested. - */ -EXPORTED int http_mailbox_open(const char *name, struct mailbox **mailbox, int locktype) -{ - int r; - - if (httpd_mailbox && !strcmp(httpd_mailbox->name, name)) { - r = mailbox_lock_index(httpd_mailbox, locktype); - } - else { - if (httpd_mailbox) { - mailbox_close(&httpd_mailbox); - httpd_mailbox = NULL; - } - if (locktype == LOCK_EXCLUSIVE) - r = mailbox_open_iwl(name, &httpd_mailbox); - else - r = mailbox_open_irl(name, &httpd_mailbox); - } - - *mailbox = httpd_mailbox; - return r; -} - - /* Compare an etag in a header to a resource etag. * Returns 0 if a match, non-zero otherwise. */ @@ -3612,11 +3662,92 @@ }; +static int list_well_known(struct transaction_t *txn) +{ + static struct buf body = BUF_INITIALIZER; + static time_t lastmod = 0; + struct stat sbuf; + int precond; + + /* stat() imapd.conf for Last-Modified and ETag */ + stat(config_filename, &sbuf); + assert(!buf_len(&txn->buf)); + buf_printf(&txn->buf, "%ld-%ld-%ld", + compile_time, sbuf.st_mtime, sbuf.st_size); + sbuf.st_mtime = MAX(compile_time, sbuf.st_mtime); + + /* Check any preconditions, including range request */ + txn->flags.ranges = 1; + precond = check_precond(txn, NULL, buf_cstring(&txn->buf), sbuf.st_mtime); + + switch (precond) { + case HTTP_OK: + case HTTP_NOT_MODIFIED: + /* Fill in ETag, Last-Modified, and Expires */ + txn->resp_body.etag = buf_cstring(&txn->buf); + txn->resp_body.lastmod = sbuf.st_mtime; + txn->resp_body.maxage = 86400; /* 24 hrs */ + txn->flags.cc |= CC_MAXAGE; + + if (precond != HTTP_NOT_MODIFIED) break; + + default: + /* We failed a precondition - don't perform the request */ + return precond; + } + + if (txn->resp_body.lastmod > lastmod) { + const char *proto = NULL, *host = NULL; + unsigned i, level = 0; + + /* Start HTML */ + buf_reset(&body); + buf_printf_markup(&body, level, HTML_DOCTYPE); + buf_printf_markup(&body, level++, "<html>"); + buf_printf_markup(&body, level++, "<head>"); + buf_printf_markup(&body, level, + "<title>%s</title>", "Well-Known Locations"); + buf_printf_markup(&body, --level, "</head>"); + buf_printf_markup(&body, level++, "<body>"); + buf_printf_markup(&body, level, + "<h2>%s</h2>", "Well-Known Locations"); + buf_printf_markup(&body, level++, "<ul>"); + + /* Add the list of enabled /.well-known/ URLs */ + http_proto_host(txn->req_hdrs, &proto, &host); + for (i = 0; namespaces[i]; i++) { + + if (namespaces[i]->enabled && namespaces[i]->well_known) { + buf_printf_markup(&body, level, + "<li><a href=\"%s://%s%s\">%s</a></li>", + proto, host, namespaces[i]->prefix, + namespaces[i]->well_known); + } + } + + /* Finish HTML */ + buf_printf_markup(&body, --level, "</ul>"); + buf_printf_markup(&body, --level, "</body>"); + buf_printf_markup(&body, --level, "</html>"); + + lastmod = txn->resp_body.lastmod; + } + + /* Output the HTML response */ + txn->resp_body.type = "text/html; charset=utf-8"; + write_body(precond, txn, buf_cstring(&body), buf_len(&body)); + + return 0; +} + + +#define WELL_KNOWN_PREFIX "/.well-known" + /* Perform a GET/HEAD request */ -int meth_get_doc(struct transaction_t *txn, - void *params __attribute__((unused))) +static int meth_get(struct transaction_t *txn, + void *params __attribute__((unused))) { - int ret = 0, r, fd = -1, precond; + int ret = 0, r, fd = -1, precond, len; const char *prefix, *urls, *path, *ext; static struct buf pathbuf = BUF_INITIALIZER; struct stat sbuf; @@ -3624,6 +3755,14 @@ unsigned long msg_size = 0; struct resp_body_t *resp_body = &txn->resp_body; + /* Check if this is a request for /.well-known/ listing */ + len = strlen(WELL_KNOWN_PREFIX); + if (!strncmp(txn->req_uri->path, WELL_KNOWN_PREFIX, len)) { + if (txn->req_uri->path[len] == '/') len++; + if (txn->req_uri->path[len] == '\0') return list_well_known(txn); + else return HTTP_NOT_FOUND; + } + /* Serve up static pages */ prefix = config_getstring(IMAPOPT_HTTPDOCROOT); if (!prefix) return HTTP_NOT_FOUND; @@ -3777,8 +3916,8 @@ /* Perform an PROPFIND request on "/" iff we support CalDAV */ -int meth_propfind_root(struct transaction_t *txn, - void *params __attribute__((unused))) +static int meth_propfind_root(struct transaction_t *txn, + void *params __attribute__((unused))) { assert(txn);
View file
cyrus-imapd-2.5.tar.gz/imap/httpd.h
Changed
@@ -130,9 +130,10 @@ URL_NS_PRINCIPAL, URL_NS_CALENDAR, URL_NS_ADDRESSBOOK, - URL_NS_RSS, URL_NS_ISCHEDULE, URL_NS_DOMAINKEY, + URL_NS_TIMEZONE, + URL_NS_RSS, URL_NS_DBLOOKUP }; @@ -146,9 +147,10 @@ ALLOW_DAV = (1<<5), /* WebDAV specific methods/features */ ALLOW_WRITECOL = (1<<6), /* Create/modify collections */ ALLOW_CAL = (1<<7), /* CalDAV specific methods/features */ - ALLOW_CAL_SCHED = (1<<8), /* CalDAV Scheduling specific features */ - ALLOW_CARD = (1<<9), /* CardDAV specific methods/features */ - ALLOW_ISCHEDULE = (1<<10) /* iSchedule specific methods/features */ + ALLOW_CAL_AVAIL = (1<<8), /* CalDAV Availability specific features */ + ALLOW_CAL_SCHED = (1<<9), /* CalDAV Scheduling specific features */ + ALLOW_CARD = (1<<10),/* CardDAV specific methods/features */ + ALLOW_ISCHEDULE = (1<<11) /* iSchedule specific methods/features */ }; struct auth_scheme_t { @@ -282,7 +284,9 @@ struct txn_flags_t { unsigned char ver1_0; /* Request from HTTP/1.0 client */ unsigned char conn; /* Connection opts on req/resp */ + unsigned char override; /* HTTP method override */ unsigned char cors; /* Cross-Origin Resource Sharing */ + unsigned char mime; /* MIME-conformant response */ unsigned char te; /* Transfer-Encoding for resp */ unsigned char cc; /* Cache-Control directives for resp */ unsigned char ranges; /* Accept range requests for resource */ @@ -421,14 +425,15 @@ struct accept *next; }; +extern struct namespace_t namespace_default; extern struct namespace_t namespace_principal; extern struct namespace_t namespace_calendar; extern struct namespace_t namespace_addressbook; extern struct namespace_t namespace_ischedule; extern struct namespace_t namespace_domainkey; +extern struct namespace_t namespace_timezone; extern struct namespace_t namespace_rss; extern struct namespace_t namespace_dblookup; -extern struct namespace_t namespace_default; /* XXX These should be included in struct transaction_t */ @@ -442,6 +447,7 @@ extern int httpd_userisadmin; extern int httpd_userisproxyadmin; extern char *httpd_userid, *proxy_userid; +extern char *httpd_extradomain; extern struct auth_state *httpd_authstate; extern struct namespace httpd_namespace; extern struct sockaddr_storage httpd_localaddr, httpd_remoteaddr; @@ -451,12 +457,11 @@ extern xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd, const char **errstr); extern struct accept *parse_accept(const char **hdr); -extern int is_mediatype(const char *hdr, const char *type); +extern int is_mediatype(const char *pat, const char *type); extern time_t calc_compile_time(const char *time, const char *date); -extern int http_mailbox_open(const char *name, struct mailbox **mailbox, - int locktype); extern const char *http_statusline(long code); -extern void httpdate_gen(char *buf, size_t len, time_t t); +extern char *rfc3339date_gen(char *buf, size_t len, time_t t); +extern char *httpdate_gen(char *buf, size_t len, time_t t); extern void comma_list_hdr(const char *hdr, const char *vals[], unsigned flags, ...); extern void response_header(long code, struct transaction_t *txn); @@ -469,7 +474,6 @@ const char *buf, unsigned len); extern void write_multipart_body(long code, struct transaction_t *txn, const char *buf, unsigned len); -extern int meth_get_doc(struct transaction_t *txn, void *params); extern int meth_options(struct transaction_t *txn, void *params); extern int meth_trace(struct transaction_t *txn, void *params); extern int etagcmp(const char *hdr, const char *etag);
View file
cyrus-imapd-2.5.tar.gz/imap/imapd.c
Changed
@@ -5422,19 +5422,17 @@ searchargs = new_searchargs(tag, GETSEARCH_CHARSET_KEYWORD|GETSEARCH_RETURN, &imapd_namespace, imapd_userid, imapd_authstate, imapd_userisadmin || imapd_userisproxyadmin); + + /* special case quirk for iPhones */ if (imapd_id.quirks & QUIRK_SEARCHFUZZY) searchargs->fuzzy_depth++; + c = get_search_program(imapd_in, imapd_out, searchargs); if (c == EOF) { eatline(imapd_in, ' '); freesearchargs(searchargs); return; } - if (imapd_id.quirks & QUIRK_SEARCHFUZZY) { - char *expr = search_expr_serialise(searchargs->root); - syslog(LOG_NOTICE, "fuzzy search %s", expr); - free(expr); - } if (c == '\r') c = prot_getc(imapd_in); if (c != '\n') { @@ -10098,10 +10096,9 @@ int i=0, j=0; char tagbuf[128]; int c; /* getword() returns an int */ - struct buf tag, cmd, tmp, user; + struct buf cmd, tmp, user; int r = 0; - memset(&tag, 0, sizeof(struct buf)); memset(&cmd, 0, sizeof(struct buf)); memset(&tmp, 0, sizeof(struct buf)); memset(&user, 0, sizeof(struct buf)); @@ -10110,28 +10107,21 @@ strlen(mailbox), mailbox); while(1) { - c = getword(pin, &tag); - if (c == EOF) { - r = IMAP_SERVER_UNAVAILABLE; + c = prot_getc(pin); + if (c != '*') { + prot_ungetc(c, pin); + r = getresult(pin, "ACL0"); break; } + c = prot_getc(pin); /* skip SP */ c = getword(pin, &cmd); if (c == EOF) { r = IMAP_SERVER_UNAVAILABLE; break; } - if(c == '\r') { - c = prot_getc(pin); - if(c != '\n') { - r = IMAP_SERVER_UNAVAILABLE; - goto cleanup; - } - } - if(c == '\n') goto cleanup; - - if (tag.s[0] == '*' && !strncmp(cmd.s, "ACL", 3)) { + if (!strncmp(cmd.s, "ACL", 3)) { while(c != '\n') { /* An ACL response, we should send a DELETEACL command */ c = getastring(pin, pout, &tmp); @@ -10147,7 +10137,7 @@ goto cleanup; } } - if(c == '\n') goto cleanup; + if(c == '\n') break; /* end of * ACL */ c = getastring(pin, pout, &user); if (c == EOF) { @@ -10170,13 +10160,10 @@ } /* if the next character is \n, we'll exit the loop */ } - continue; - } else if (!strncmp(tag.s, "ACL0", 4)) { - /* end of this command */ - if (!strcasecmp(cmd.s, "OK")) { break; } - if (!strcasecmp(cmd.s, "NO")) { r = IMAP_REMOTE_DENIED; break; } - r = IMAP_SERVER_UNAVAILABLE; - break; + } + else { + /* skip this line, we don't really care */ + eatline(pin, c); } } @@ -10185,17 +10172,9 @@ /* Now cleanup after all the DELETEACL commands */ if(!r) { while(j < i) { - c = getword(pin, &tag); - if (c == EOF) { - r = IMAP_SERVER_UNAVAILABLE; - break; - } - - eatline(pin, c); - - if(!strncmp("ACL", tag.s, 3)) { - j++; - } + snprintf(tagbuf, sizeof(tagbuf), "ACL%d", ++j); + r = getresult(pin, tagbuf); + if (r) break; } } @@ -10204,7 +10183,6 @@ buf_free(&user); buf_free(&tmp); buf_free(&cmd); - buf_free(&tag); return r; } @@ -10213,15 +10191,11 @@ const char *mboxname, const char *acl_in) { int r = 0; - int c; /* getword() returns an int */ char tag[128]; int tagnum = 1; char *rights, *nextid; char *acl_safe = acl_in ? xstrdup(acl_in) : NULL; char *acl = acl_safe; - struct buf inbuf; - - memset(&inbuf, 0, sizeof(struct buf)); while (acl) { rights = strchr(acl, '\t'); @@ -10241,40 +10215,12 @@ strlen(acl), acl, strlen(rights), rights); - while(1) { - c = getword(pin, &inbuf); - if (c == EOF) { - r = IMAP_SERVER_UNAVAILABLE; - break; - } - if(strncmp(tag, inbuf.s, strlen(tag))) { - eatline(pin, c); - continue; - } else { - /* this is our line */ - break; - } - } - - /* Are we OK? */ - - c = getword(pin, &inbuf); - if (c == EOF) { - r = IMAP_SERVER_UNAVAILABLE; - break; - } - - if(strncmp("OK", inbuf.s, 2)) { - r = IMAP_REMOTE_DENIED; - break; - } + r = getresult(pin, tag); + if (r) break; - /* Eat the line and get the next one */ - eatline(pin, c); acl = nextid; } - buf_free(&inbuf); if(acl_safe) free(acl_safe); return r; @@ -10582,6 +10528,7 @@ "Could not move mailbox: %s, mboxlist_update() failed %s", item->mbentry->name, error_message(r)); } + else item->state = XFER_LOCAL_MOVING; if (!r && xfer->seendb) { /* Backport the user's seendb on-the-fly */
View file
cyrus-imapd-2.5.tar.gz/imap/jcal.c
Changed
@@ -381,21 +381,18 @@ /* * Construct a jCal string for an iCalendar component. */ -const char *icalcomponent_as_jcal_string(icalcomponent *ical) +char *icalcomponent_as_jcal_string(icalcomponent *ical) { json_t *jcal; size_t flags = JSON_PRESERVE_ORDER; - char *freeme; - const char *buf; + char *buf; if (!ical) return NULL; jcal = icalcomponent_as_json_array(ical); flags |= (config_httpprettytelemetry ? JSON_INDENT(2) : JSON_COMPACT); - freeme = json_dumps(jcal, flags); - buf = icalmemory_tmp_copy(freeme); - free(freeme); + buf = json_dumps(jcal, flags); json_decref(jcal); @@ -761,16 +758,14 @@ buf_printf_markup(buf, 1, "["); buf_printf_markup(buf, 2, "["); buf_printf_markup(buf, 3, "\"prodid\","); - buf_printf_markup(buf, 3, "{"); - buf_printf_markup(buf, 3, "},"); + buf_printf_markup(buf, 3, "{},"); buf_printf_markup(buf, 3, "\"text\","); buf_printf_markup(buf, 3, "\"-//CyrusIMAP.org/Cyrus %s//EN\"", cyrus_version()); buf_printf_markup(buf, 2, "],"); buf_printf_markup(buf, 2, "["); buf_printf_markup(buf, 3, "\"version\","); - buf_printf_markup(buf, 3, "{"); - buf_printf_markup(buf, 3, "},"); + buf_printf_markup(buf, 3, "{},"); buf_printf_markup(buf, 3, "\"text\","); buf_printf_markup(buf, 3, "\"2.0\""); buf_printf_markup(buf, 2, "]");
View file
cyrus-imapd-2.5.tar.gz/imap/jcal.h
Changed
@@ -50,7 +50,7 @@ #include "util.h" -extern const char *icalcomponent_as_jcal_string(icalcomponent* comp); +extern char *icalcomponent_as_jcal_string(icalcomponent* comp); extern icalcomponent *jcal_string_as_icalcomponent(const char *str); extern const char *begin_jcal(struct buf *buf); extern void end_jcal(struct buf *buf);
View file
cyrus-imapd-2.5.tar.gz/imap/lmtp_sieve.c
Changed
@@ -118,6 +118,16 @@ } } +static int getfname(void *v, const char **fnamep) +{ + deliver_data_t *d = (deliver_data_t *)v; + *fnamep = NULL; + if (d->stage) + *fnamep = append_stagefname(d->stage); + /* XXX GLOBAL STUFF HERE */ + return 0; +} + static int getsize(void *mc, int *size) { message_data_t *m = ((deliver_data_t *) mc)->m; @@ -487,7 +497,7 @@ static int sieve_fileinto(void *ac, void *ic __attribute__((unused)), void *sc, - void *mc __attribute__((unused)), + void *mc, const char **errmsg __attribute__((unused))) { sieve_fileinto_context_t *fc = (sieve_fileinto_context_t *) ac; @@ -573,9 +583,9 @@ /* "default" is a magic value that implies the default */ notify(!strcmp("default",nc->method) ? notifier : nc->method, "SIEVE", nc->priority, sd->username, NULL, - nopt, nc->options, nc->message); + nopt, nc->options, nc->message, nc->fname); } - + return SIEVE_OK; } @@ -781,6 +791,7 @@ sieve_register_notify(interp, &sieve_notify); sieve_register_size(interp, &getsize); sieve_register_header(interp, &getheader); + sieve_register_fname(interp, &getfname); sieve_register_envelope(interp, &getenvelope); sieve_register_body(interp, &getbody);
View file
cyrus-imapd-2.5.tar.gz/imap/lmtpd.c
Changed
@@ -638,7 +638,7 @@ config_virtdomains ? strcspn(userbuf, "@") : 0); notify(notifier, "MAIL", NULL, userbuf, namebuf, 0, NULL, - notifyheader ? notifyheader : ""); + notifyheader ? notifyheader : "", /*fname*/NULL); } }
View file
cyrus-imapd-2.5.tar.gz/imap/mailbox.c
Changed
@@ -921,7 +921,7 @@ return IMAP_MAILBOX_LOCKED; /* can't reuse an already locked index */ if (listitem->m.index_locktype) - return IMAP_MAILBOX_LOCKED; + return IMAP_MAILBOX_LOCKED; listitem->nopen++; mailbox = &listitem->m; @@ -970,19 +970,7 @@ } lockindex: - /* this will open, map and parse the header file */ - r = mailbox_lock_index_internal(mailbox, index_locktype); - if (r) { - syslog(LOG_ERR, "IOERROR: locking index %s: %s", - mailbox->name, error_message(r)); - goto done; - } - - /* oops, a race, it got deleted meanwhile. That's OK */ - if (mailbox->i.options & OPT_MAILBOX_DELETED) { - r = IMAP_MAILBOX_NONEXISTENT; - goto done; - } + r = mailbox_lock_index(mailbox, index_locktype); done: if (r) mailbox_close(&mailbox); @@ -1723,9 +1711,6 @@ mailbox->is_readonly = 0; r = mailbox_open_index(mailbox); } - /* XXX - this could be handled out a layer, but we always - * need to have a locked conversations DB */ - if (!r) r = mailbox_lock_conversations(mailbox); if (!r) r = lock_blocking(mailbox->index_fd, index_fname); } else if (locktype == LOCK_SHARED) { @@ -1846,7 +1831,16 @@ EXPORTED int mailbox_lock_index(struct mailbox *mailbox, int locktype) { - int r = mailbox_lock_index_internal(mailbox, locktype); + int r = 0; + + /* XXX: only lock convdb if we're in read-write mode. This is kinda + * bogus really, but there's no way to get a read lock on convdb */ + if (locktype != LOCK_SHARED) { + r = mailbox_lock_conversations(mailbox); + if (r) return r; + } + + r = mailbox_lock_index_internal(mailbox, locktype); if (r) return r; /* otherwise, sanity checks for regular use, but not for internal @@ -2683,7 +2677,7 @@ if (!userid) goto done; /* phantom record - never really existed here */ - if (!old && (new->system_flags && FLAG_EXPUNGED)) + if (!old && (new->system_flags & FLAG_EXPUNGED)) goto done; r = mailbox_cacherecord(mailbox, new); @@ -2699,7 +2693,7 @@ assert(resource); - carddavdb = carddav_open(userid, 0); + carddavdb = carddav_open_mailbox(mailbox, 0); /* Find existing record for this resource */ carddav_lookup_resource(carddavdb, mailbox->name, resource, 1, &cdata); @@ -2803,7 +2797,7 @@ if (!userid) goto done; /* phantom record - never really existed here */ - if (!old && (new->system_flags && FLAG_EXPUNGED)) + if (!old && (new->system_flags & FLAG_EXPUNGED)) goto done; r = mailbox_cacherecord(mailbox, new); @@ -2820,7 +2814,7 @@ } } - caldavdb = caldav_open(userid, 0); + caldavdb = caldav_open_mailbox(mailbox, 0); /* Find existing record for this resource */ caldav_lookup_resource(caldavdb, mailbox->name, resource, 1, &cdata); @@ -3116,15 +3110,14 @@ if (r) return r; r = mailbox_update_conversations(mailbox, old, new); + if (r) return r; - /* NOTE - we do these last */ + /* NOTE - we do these last, once the counts are updated */ - if (old) { + if (old) mailbox_index_update_counts(mailbox, old, 0); - } - if (new) { + if (new) mailbox_index_update_counts(mailbox, new, 1); - } return 0; } @@ -4244,7 +4237,7 @@ uint32_t recno; int r = 0; - if (!(mailbox->mbtype & (MBTYPE_ADDRESSBOOK|MBTYPE_CALENDAR))) + if (!(mailbox->mbtype & (MBTYPES_DAV))) return 0; for (recno = 1; recno <= mailbox->i.num_records; recno++) { @@ -4434,7 +4427,7 @@ for (i = 0; i < paths.count; i++) { char *path = paths.data[i]; /* need direct reference, because we're fiddling */ r = rmdir(path); - if (r && errno != -ENOENT) + if (r && errno != ENOENT) syslog(LOG_NOTICE, "Remove of supposedly empty directory %s failed: %m", path);
View file
cyrus-imapd-2.5.tar.gz/imap/mailbox.h
Changed
@@ -76,6 +76,7 @@ #define FNAME_SQUAT "/cyrus.squat" #define FNAME_EXPUNGE "/cyrus.expunge" #define FNAME_ANNOTATIONS "/cyrus.annotations" +#define FNAME_DAV "/cyrus.dav" enum meta_filename { META_HEADER = 1, @@ -84,6 +85,7 @@ META_SQUAT, META_EXPUNGE, META_ANNOTATIONS, + META_DAV, META_ARCHIVECACHE }; @@ -575,6 +577,8 @@ uint32_t mailbox_sync_crc(struct mailbox *mailbox, unsigned vers, int recalc); unsigned mailbox_best_crcvers(unsigned minvers, unsigned maxvers); +extern int mailbox_add_dav(struct mailbox *mailbox); + /* Rename a CID. Note - this is just one mailbox! */ extern int mailbox_cid_rename(struct mailbox *mailbox, conversation_id_t from_cid, @@ -587,6 +591,4 @@ extern int mailbox_update_xconvmodseq(struct mailbox *mailbox, modseq_t, int force); extern int mailbox_has_conversations(struct mailbox *mailbox); -extern int mailbox_add_dav(struct mailbox *mailbox); - #endif /* INCLUDED_MAILBOX_H */
View file
cyrus-imapd-2.5.tar.gz/imap/mbdump.c
Changed
@@ -59,6 +59,7 @@ #include <utime.h> #include "annotate.h" +#include "dav_util.h" #include "exitcodes.h" #include "global.h" #include "imap/imap_err.h" @@ -458,11 +459,12 @@ { META_INDEX, "cyrus.index" }, { META_CACHE, "cyrus.cache" }, { META_EXPUNGE, "cyrus.expunge" }, + { META_DAV, "cyrus.dav" }, { 0, NULL } }; -enum { SEEN_DB = 0, SUBS_DB = 1, MBOXKEY_DB = 2 }; -static int NUM_USER_DATA_FILES = 3; +enum { SEEN_DB = 0, SUBS_DB = 1, MBOXKEY_DB = 2, DAV_DB = 3 }; +static int NUM_USER_DATA_FILES = 4; EXPORTED int dump_mailbox(const char *tag, struct mailbox *mailbox, uint32_t uid_start, int oldversion, @@ -628,6 +630,14 @@ fname = mboxkey_getpath(userid); ftag = "MBOXKEY"; break; + case DAV_DB: { + struct buf dav_file = BUF_INITIALIZER; + + dav_getpath_byuserid(&dav_file, userid); + fname = (char *) buf_cstring(&dav_file); + ftag = "DAV"; + break; + } default: fatal("unknown user data file", EC_OSFILE); } @@ -1062,6 +1072,12 @@ char *s = user_hash_subs(userid); strlcpy(fnamebuf, s, sizeof(fnamebuf)); free(s); + } else if (userid && !strcmp(file.s, "DAV")) { + /* overwriting this outright is absolutely what we want to do */ + struct buf dav_file = BUF_INITIALIZER; + dav_getpath_byuserid(&dav_file, userid); + strlcpy(fnamebuf, buf_cstring(&dav_file), sizeof(fnamebuf)); + buf_free(&dav_file); } else if (userid && !strcmp(file.s, "SEEN")) { seen_file = seen_getpath(userid);
View file
cyrus-imapd-2.5.tar.gz/imap/mboxevent.c
Changed
@@ -76,8 +76,9 @@ EVENT_MESSAGE_TRASH) #define MAILBOX_EVENTS (EVENT_MAILBOX_CREATE|EVENT_MAILBOX_DELETE|\ - EVENT_MAILBOX_RENAME|EVENT_MAILBOX_SUBSCRIBE|\ - EVENT_MAILBOX_UNSUBSCRIBE) + EVENT_MAILBOX_RENAME) + +#define SUBS_EVENTS (EVENT_MAILBOX_SUBSCRIBE|EVENT_MAILBOX_UNSUBSCRIBE) #define QUOTA_EVENTS (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN|EVENT_QUOTA_CHANGE) @@ -130,10 +131,10 @@ { EVENT_PID, "pid", EVENT_PARAM_INT, 0, 0 }, { EVENT_USER, "user", EVENT_PARAM_STRING, 0, 0 }, { EVENT_MESSAGE_SIZE, "messageSize", EVENT_PARAM_INT, 0, 0 }, - { EVENT_MESSAGE_CID, "vnd.fastmail.cid", EVENT_PARAM_STRING, 0, 0 }, { EVENT_MBTYPE, "vnd.cmu.mbtype", EVENT_PARAM_STRING, 0, 0 }, { EVENT_DAV_FILENAME, "vnd.cmu.davFilename", EVENT_PARAM_STRING, 0, 0 }, { EVENT_DAV_UID, "vnd.cmu.davUid", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_MESSAGE_CID, "vnd.fastmail.cid", EVENT_PARAM_STRING, 0, 0 }, /* always at end to let the parser to easily truncate this part */ { EVENT_ENVELOPE, "vnd.cmu.envelope", EVENT_PARAM_STRING, 0, 0 }, { EVENT_BODYSTRUCTURE, "bodyStructure", EVENT_PARAM_STRING, 0, 0 }, @@ -188,6 +189,9 @@ if (groups & IMAP_ENUM_EVENT_GROUPS_ACCESS) enabled_events |= (EVENT_LOGIN|EVENT_LOGOUT); + if (groups & IMAP_ENUM_EVENT_GROUPS_SUBSCRIPTION) + enabled_events |= SUBS_EVENTS; + if (groups & IMAP_ENUM_EVENT_GROUPS_MAILBOX) enabled_events |= MAILBOX_EVENTS; } @@ -384,15 +388,15 @@ case EVENT_MESSAGE_SIZE: return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGESIZE) && (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)); - case EVENT_MESSAGE_CID: - return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CID) && - (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)); case EVENT_DAV_FILENAME: return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVFILENAME) && (type & EVENT_CALENDAR); case EVENT_DAV_UID: return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVUID) && (type & EVENT_CALENDAR); + case EVENT_MESSAGE_CID: + return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CID) && + (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)); case EVENT_MESSAGES: if (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN)) return 1; @@ -453,6 +457,7 @@ struct mboxevent *event; char stimestamp[TIMESTAMP_MAX+1]; char *formatted_message; + const char *fname = NULL; /* nothing to notify */ if (!mboxevents) @@ -549,7 +554,7 @@ /* notification is ready to send */ formatted_message = json_formatter(type, event->params); - notify(notifier, "EVENT", NULL, NULL, NULL, 0, NULL, formatted_message); + notify(notifier, "EVENT", NULL, NULL, NULL, 0, NULL, formatted_message, fname); free(formatted_message); } @@ -759,12 +764,10 @@ FILL_STRING_PARAM(event, EVENT_DAV_FILENAME, xstrdup(resource)); if (mboxevent_expected_param(event->type, EVENT_DAV_UID)) { - const char *userid = mboxname_to_userid(mailbox->name); - if (!userid) return; if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) { struct carddav_db *carddavdb = NULL; struct carddav_data *cdata = NULL; - carddavdb = carddav_open(userid, 0); + carddavdb = carddav_open_mailbox(mailbox, 0); carddav_lookup_resource(carddavdb, mailbox->name, resource, 0, &cdata); FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->vcard_uid)); carddav_close(carddavdb); @@ -772,14 +775,13 @@ if (mailbox->mbtype & MBTYPE_CALENDAR) { struct caldav_db *caldavdb = NULL; struct caldav_data *cdata = NULL; - caldavdb = caldav_open(userid, 0); + caldavdb = caldav_open_mailbox(mailbox, 0); caldav_lookup_resource(caldavdb, mailbox->name, resource, 0, &cdata); FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->ical_uid)); caldav_close(caldavdb); } } } - } void mboxevent_extract_copied_record(struct mboxevent *event,
View file
cyrus-imapd-2.5.tar.gz/imap/mboxevent.h
Changed
@@ -112,10 +112,10 @@ EVENT_PID, EVENT_USER, EVENT_MESSAGE_SIZE, - EVENT_MESSAGE_CID, EVENT_MBTYPE, EVENT_DAV_FILENAME, EVENT_DAV_UID, + EVENT_MESSAGE_CID, EVENT_ENVELOPE, EVENT_BODYSTRUCTURE, EVENT_CLIENT_ID,
View file
cyrus-imapd-2.5.tar.gz/imap/mboxlist.c
Changed
@@ -254,20 +254,6 @@ /* never get here */ } -static char *_parse_acl(struct dlist *di) -{ - struct buf buf = BUF_INITIALIZER; - struct dlist *ai; - - /* gotta make it all tabby again */ - for (ai = di->head; ai; ai = ai->next) { - buf_printf(&buf, "%s\t", ai->name); - buf_printf(&buf, "%s\t", dlist_cstring(ai)); - } - - return buf_release(&buf); -} - EXPORTED uint32_t mboxlist_string_to_mbtype(const char *string) { uint32_t mbtype = 0; @@ -303,6 +289,58 @@ return mbtype; } +struct parseentry_rock { + struct mboxlist_entry *mbentry; + struct buf *aclbuf; + int doingacl; +}; + +int parseentry_cb(int type, struct dlistsax_data *d) +{ + struct parseentry_rock *rock = (struct parseentry_rock *)d->rock; + + switch(type) { + case DLISTSAX_KVLISTSTART: + if (!strcmp(buf_cstring(&d->kbuf), "A")) { + rock->doingacl = 1; + } + break; + case DLISTSAX_KVLISTEND: + rock->doingacl = 0; + break; + case DLISTSAX_STRING: + if (rock->doingacl) { + buf_append(rock->aclbuf, &d->kbuf); + buf_putc(rock->aclbuf, '\t'); + buf_append(rock->aclbuf, &d->buf); + buf_putc(rock->aclbuf, '\t'); + } + else { + const char *key = buf_cstring(&d->kbuf); + if (!strcmp(key, "I")) { + rock->mbentry->uniqueid = buf_newcstring(&d->buf); + } + else if (!strcmp(key, "M")) { + rock->mbentry->mtime = atoi(buf_cstring(&d->buf)); + } + else if (!strcmp(key, "P")) { + rock->mbentry->partition = buf_newcstring(&d->buf); + } + else if (!strcmp(key, "S")) { + rock->mbentry->server = buf_newcstring(&d->buf); + } + else if (!strcmp(key, "T")) { + rock->mbentry->mbtype = mboxlist_string_to_mbtype(buf_cstring(&d->buf)); + } + else if (!strcmp(key, "V")) { + rock->mbentry->uidvalidity = atoi(buf_cstring(&d->buf)); + } + } + } + + return 0; +} + /* * parse a record read from the mailboxes.db into its parts. * @@ -319,6 +357,7 @@ const char *name, size_t namelen, const char *data, size_t datalen) { + static struct buf aclbuf; int r = IMAP_MAILBOX_BADFORMAT; char *freeme = NULL; char **target; @@ -336,39 +375,13 @@ /* check for DLIST mboxlist */ if (*data == '%') { - struct dlist *dl = NULL; - struct dlist *di; - r = dlist_parsemap(&dl, 0, data, datalen); - if (r) goto done; - if (!dl) goto done; - for (di = dl->head; di; di = di->next) { - switch(di->name[0]) { - case 'A': - mbentry->acl = _parse_acl(di); - break; - case 'I': - mbentry->uniqueid = xstrdupnull(dlist_cstring(di)); - break; - case 'M': - mbentry->mtime = dlist_num(di); - break; - case 'P': - mbentry->partition = xstrdupnull(dlist_cstring(di)); - break; - case 'S': - mbentry->server = xstrdupnull(dlist_cstring(di)); - break; - case 'T': - mbentry->mbtype = mboxlist_string_to_mbtype(dlist_cstring(di)); - break; - case 'V': - mbentry->uidvalidity = dlist_num(di); - break; - } - } - dlist_free(&dl); - - r = 0; + struct parseentry_rock rock; + memset(&rock, 0, sizeof(struct parseentry_rock)); + rock.mbentry = mbentry; + rock.aclbuf = &aclbuf; + aclbuf.len = 0; + r = dlist_parsesax(data, datalen, 0, parseentry_cb, &rock); + if (!r) mbentry->acl = buf_newcstring(&aclbuf); goto done; } @@ -1789,7 +1802,7 @@ mode = ACL_MODE_REMOVE; } /* do not allow non-admin user to remove the admin rights from mailbox owner */ - if (!isadmin && isidentifiermbox) { + if (!isadmin && isidentifiermbox && mode != ACL_MODE_ADD) { int has_admin_rights = mboxlist_have_admin_rights(rights); if ((has_admin_rights && mode == ACL_MODE_REMOVE) || (!has_admin_rights && mode != ACL_MODE_REMOVE)) {
View file
cyrus-imapd-2.5.tar.gz/imap/mboxname.c
Changed
@@ -1521,6 +1521,11 @@ metaflag = IMAP_ENUM_METAPARTITION_FILES_ANNOTATIONS; filename = FNAME_ANNOTATIONS; break; + case META_DAV: + snprintf(confkey, 256, "metadir-dav-%s", partition); + metaflag = IMAP_ENUM_METAPARTITION_FILES_DAV; + filename = FNAME_DAV; + break; case META_ARCHIVECACHE: snprintf(confkey, 256, "metadir-archivecache-%s", partition); metaflag = IMAP_ENUM_METAPARTITION_FILES_ARCHIVECACHE;
View file
cyrus-imapd-2.5.tar.gz/imap/nntpd.c
Changed
@@ -1023,13 +1023,12 @@ be = backend_current; if (arg1.len && - (!is_newsgroup(arg1.s) || - (r = open_group(arg1.s, 0, &be, NULL)))) goto nogroup; + (r = open_group(arg1.s, 0, &be, NULL))) goto nogroup; else if (be) { prot_printf(be->out, "%s", cmd.s); if (arg1.len) { prot_printf(be->out, " %s", arg1.s); - if (LISTGROUP) prot_printf(be->out, " %s", arg2.s); + if (LISTGROUP) prot_printf(be->out, " %s", arg2.s); } prot_printf(be->out, "\r\n"); @@ -1741,6 +1740,8 @@ if (!has_prefix) { snprintf(mailboxname, sizeof(mailboxname), "%s%s", newsprefix, name); name = mailboxname; + + if (!is_newsgroup(name)) return IMAP_MAILBOX_NONEXISTENT; } if (!r) r = mlookup(name, &mbentry);
View file
cyrus-imapd-2.5.tar.gz/imap/notify.c
Changed
@@ -77,19 +77,94 @@ return 0; } +static void notify_dlist(const char *sockpath, const char *method, + const char *class, const char *priority, + const char *user, const char *mailbox, + int nopt, const char **options, + const char *message, const char *fname) +{ + struct sockaddr_un sun_data; + struct protstream *in = NULL, *out = NULL; + struct dlist *dl = dlist_newkvlist(NULL, "NOTIFY"); + struct dlist *res = NULL; + struct dlist *il; + int c; + int soc = -1; + int i; + + dlist_setatom(dl, "METHOD", method); + dlist_setatom(dl, "CLASS", class); + dlist_setatom(dl, "PRIORITY", priority); + dlist_setatom(dl, "USER", user); + dlist_setatom(dl, "MAILBOX", mailbox); + il = dlist_newlist(dl, "OPTIONS"); + for (i = 0; i < nopt; i++) + dlist_setatom(il, NULL, options[i]); + dlist_setatom(dl, "MESSAGE", message); + dlist_setatom(dl, "FILEPATH", fname); + + memset((char *)&sun_data, 0, sizeof(sun_data)); + sun_data.sun_family = AF_UNIX; + strlcpy(sun_data.sun_path, sockpath, sizeof(sun_data.sun_path)); + + soc = socket(PF_UNIX, SOCK_STREAM, 0); + if (soc < 0) { + syslog(LOG_ERR, "unable to create notify socket(): %m"); + goto out; + } + + if (connect(soc, (struct sockaddr *)&sun_data, sizeof(sun_data)) < 0) { + syslog(LOG_ERR, "failed to connect to %s: %m", sockpath); + goto out; + } + + in = prot_new(soc, 0); + out = prot_new(soc, 1); + /* Force use of LITERAL+ */ + prot_setisclient(in, 1); + prot_setisclient(out, 1); + + dlist_print(dl, 1, out); + prot_printf(out, "\r\n"); + prot_flush(out); + + c = dlist_parse(&res, 1, in); + if (c == '\r') c = prot_getc(in); + /* XXX - do something with the response? Like have NOTIFY answer */ + if (c == '\n' && res && res->name) { + syslog(LOG_NOTICE, "NOTIFY: response %s to method %s", res->name, method); + } + else { + syslog(LOG_ERR, "NOTIFY: error sending %s to %s", method, sockpath); + } + +out: + if (in) prot_free(in); + if (out) prot_free(out); + if (soc >= 0) close(soc); + dlist_free(&dl); + dlist_free(&res); +} + EXPORTED void notify(const char *method, const char *class, const char *priority, const char *user, const char *mailbox, int nopt, const char **options, - const char *message) + const char *message, const char *fname) { - const char *notify_sock; + const char *notify_sock = config_getstring(IMAPOPT_NOTIFYSOCKET); int soc = -1; struct sockaddr_un sun_data; char buf[NOTIFY_MAXSIZE] = "", noptstr[20]; int buflen = 0; int i, r = 0; + if (!strncmp(notify_sock, "dlist:", 6)) { + return notify_dlist(notify_sock+6, method, class, priority, + user, mailbox, nopt, options, + message, fname); + } + soc = socket(AF_UNIX, SOCK_DGRAM, 0); if (soc == -1) { syslog(LOG_ERR, "unable to create notify socket(): %m"); @@ -98,8 +173,7 @@ memset((char *)&sun_data, 0, sizeof(sun_data)); sun_data.sun_family = AF_UNIX; - notify_sock = config_getstring(IMAPOPT_NOTIFYSOCKET); - if (notify_sock) { + if (notify_sock) { strlcpy(sun_data.sun_path, notify_sock, sizeof(sun_data.sun_path)); } else { @@ -129,6 +203,7 @@ } if (!r) r = add_arg(buf, sizeof(buf), message, &buflen); + if (!r && fname) r = add_arg(buf, sizeof(buf), fname, &buflen); if (r) { syslog(LOG_ERR, "notify datagram too large, %s, %s", @@ -150,5 +225,4 @@ out: xclose(soc); - return; }
View file
cyrus-imapd-2.5.tar.gz/imap/notify.h
Changed
@@ -47,6 +47,6 @@ const char *class, const char *priority, const char *user, const char *mailbox, int nopt, const char **options, - const char *message); + const char *message, const char *fname); #endif /* NOTIFY_H */
View file
cyrus-imapd-2.5.tar.gz/imap/search_engines.c
Changed
@@ -207,7 +207,7 @@ int incremental = (flags & SEARCH_UPDATE_INCREMENTAL); r = rx->begin_mailbox(rx, mailbox, flags); - if (r) return r; + if (r) goto done; first = mailbox_finduid(mailbox, rx->first_unindexed_uid(rx)); if (!first) first = 1; /* empty mailbox */ @@ -235,6 +235,7 @@ if (batch.count) r = flush_batch(rx, mailbox, &batch); + done: ptrarray_fini(&batch); r2 = rx->end_mailbox(rx, mailbox); if (r) return r;
View file
cyrus-imapd-2.5.tar.gz/imap/search_expr.c
Changed
@@ -1759,7 +1759,7 @@ struct conv_rock *rock = (struct conv_rock *)internalised; conversation_id_t cid = NULLCONVERSATION; conversation_t *conv = NULL; - int r; + int r = 0; /* invalid flag name */ if (!rock->cstate) return 0; @@ -1767,9 +1767,7 @@ if (conversation_load(rock->cstate, cid, &conv)) return 0; if (!conv) return 0; - if (rock->num < 0) - r = 0; /* invalid flag name */ - else if (rock->num == 0) + if (rock->num == 0) r = !conv->unseen; else if (rock->num > 0) r = !!conv->counts[rock->num-1];
View file
cyrus-imapd-2.5.tar.gz/imap/search_xapian.c
Changed
@@ -1012,8 +1012,8 @@ if (!dirs || !dirs->count) goto out; /* if there are directories, open the databases */ - bb->db = xapian_db_open((const char **)dirs->data); - if (!bb->db) goto out; + r = xapian_db_open((const char **)dirs->data, &bb->db); + if (r) goto out; /* read the list of all indexed messages to allow (optional) false positives * for unindexed messages */ @@ -1029,6 +1029,7 @@ out: strarray_free(dirs); strarray_free(active); + /* XXX - error return? */ return &bb->super; }
View file
cyrus-imapd-2.5.tar.gz/imap/sync_support.c
Changed
@@ -1130,8 +1130,8 @@ { struct sync_annot *item = xzmalloc(sizeof(struct sync_annot)); - item->entry = xstrdup(entry); - item->userid = xstrdup(userid); + item->entry = xstrdupnull(entry); + item->userid = xstrdupnull(userid); buf_copy(&item->value, value); item->mark = 0; @@ -1153,8 +1153,8 @@ current = l->head; while (current) { next = current->next; - if (current->entry) free(current->entry); - if (current->userid) free(current->userid); + free(current->entry); + free(current->userid); buf_free(¤t->value); free(current); current = next; @@ -1367,6 +1367,7 @@ struct dlist *kl, struct dlist *kupload, int printrecords) { + struct sync_annot_list *annots = NULL; modseq_t xconvmodseq = 0; int r = 0; @@ -1394,6 +1395,15 @@ dlist_setnum64(kl, "XCONVMODSEQ", xconvmodseq); } + /* always send mailbox annotations */ + r = read_annotations(mailbox, NULL, &annots); + if (r) goto done; + + if (annots) { + encode_annotations(kl, annots); + sync_annot_list_free(&annots); + } + if (printrecords) { struct index_record record; struct dlist *il; @@ -1401,7 +1411,6 @@ uint32_t recno; int send_file; uint32_t prevuid = 0; - struct sync_annot_list *annots = NULL; for (recno = 1; recno <= mailbox->i.num_records; recno++) { /* we can't send bogus records */ @@ -1461,14 +1470,6 @@ sync_annot_list_free(&annots); } } - - r = read_annotations(mailbox, NULL, &annots); - if (r) goto done; - - if (annots) { - encode_annotations(kl, annots); - sync_annot_list_free(&annots); - } } done: @@ -1645,13 +1646,13 @@ struct dlist *annots = NULL; struct dlist *aa; - if (!sal) + if (!sal) return; for (sa = sal->head ; sa ; sa = sa->next) { if (!annots) annots = dlist_newlist(parent, "ANNOTATIONS"); - aa = dlist_newkvlist(annots, "A"); + aa = dlist_newkvlist(annots, NULL); dlist_setatom(aa, "ENTRY", sa->entry); dlist_setatom(aa, "USERID", sa->userid); dlist_setmap(aa, "VALUE", sa->value.s, sa->value.len); @@ -1716,9 +1717,9 @@ diff++; if (!diff) - diff = strcmp(a->entry, b->entry); + diff = strcmpnull(a->entry, b->entry); if (!diff) - diff = strcmp(a->userid, b->userid); + diff = strcmpnull(a->userid, b->userid); if (!diff && diff_value) diff = buf_cmp(&a->value, &b->value);
View file
cyrus-imapd-2.5.tar.gz/imap/tls.c
Changed
@@ -674,53 +674,6 @@ SSL_CTX_set_options(s_ctx, off); SSL_CTX_set_info_callback(s_ctx, apps_ssl_info_callback); - /* Don't use an internal session cache */ - SSL_CTX_sess_set_cache_size(s_ctx, 1); /* 0 is unlimited, so use 1 */ - SSL_CTX_set_session_cache_mode(s_ctx, SSL_SESS_CACHE_SERVER | - SSL_SESS_CACHE_NO_AUTO_CLEAR | - SSL_SESS_CACHE_NO_INTERNAL_LOOKUP); - - /* Get the session timeout from the config file (in minutes) */ - timeout = config_getint(IMAPOPT_TLS_SESSION_TIMEOUT); - if (timeout < 0) timeout = 0; - if (timeout > 1440) timeout = 1440; /* 24 hours max */ - - /* A timeout of zero disables session caching */ - if (timeout) { - const char *fname = NULL; - char *tofree = NULL; - int r; - - /* Set the context for session reuse -- use the service ident */ - SSL_CTX_set_session_id_context(s_ctx, (void*) ident, strlen(ident)); - - /* Set the timeout for the internal/external cache (in seconds) */ - SSL_CTX_set_timeout(s_ctx, timeout*60); - - /* Set the callback functions for the external session cache */ - SSL_CTX_sess_set_new_cb(s_ctx, new_session_cb); - SSL_CTX_sess_set_remove_cb(s_ctx, remove_session_cb); - SSL_CTX_sess_set_get_cb(s_ctx, get_session_cb); - - fname = config_getstring(IMAPOPT_TLSCACHE_DB_PATH); - - /* create the name of the db file */ - if (!fname) { - tofree = strconcat(config_dir, FNAME_TLSSESSIONS, (char *)NULL); - fname = tofree; - } - - r = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &sessdb); - if (r != 0) { - syslog(LOG_ERR, "DBERROR: opening %s: %s", - fname, cyrusdb_strerror(ret)); - } - else - sess_dbopen = 1; - - free(tofree); - } - cipher_list = config_getstring(IMAPOPT_TLS_CIPHER_LIST); if (!SSL_CTX_set_cipher_list(s_ctx, cipher_list)) { syslog(LOG_ERR,"TLS server engine: cannot load cipher list '%s'", @@ -776,6 +729,53 @@ } } + /* Don't use an internal session cache */ + SSL_CTX_sess_set_cache_size(s_ctx, 1); /* 0 is unlimited, so use 1 */ + SSL_CTX_set_session_cache_mode(s_ctx, SSL_SESS_CACHE_SERVER | + SSL_SESS_CACHE_NO_AUTO_CLEAR | + SSL_SESS_CACHE_NO_INTERNAL_LOOKUP); + + /* Get the session timeout from the config file (in minutes) */ + timeout = config_getint(IMAPOPT_TLS_SESSION_TIMEOUT); + if (timeout < 0) timeout = 0; + if (timeout > 1440) timeout = 1440; /* 24 hours max */ + + /* A timeout of zero disables session caching */ + if (timeout) { + const char *fname = NULL; + char *tofree = NULL; + int r; + + /* Set the context for session reuse -- use the service ident */ + SSL_CTX_set_session_id_context(s_ctx, (void*) ident, strlen(ident)); + + /* Set the timeout for the internal/external cache (in seconds) */ + SSL_CTX_set_timeout(s_ctx, timeout*60); + + /* Set the callback functions for the external session cache */ + SSL_CTX_sess_set_new_cb(s_ctx, new_session_cb); + SSL_CTX_sess_set_remove_cb(s_ctx, remove_session_cb); + SSL_CTX_sess_set_get_cb(s_ctx, get_session_cb); + + fname = config_getstring(IMAPOPT_TLSCACHE_DB_PATH); + + /* create the name of the db file */ + if (!fname) { + tofree = strconcat(config_dir, FNAME_TLSSESSIONS, (char *)NULL); + fname = tofree; + } + + r = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &sessdb); + if (r != 0) { + syslog(LOG_ERR, "DBERROR: opening %s: %s", + fname, cyrusdb_strerror(ret)); + } + else + sess_dbopen = 1; + + free(tofree); + } + tls_serverengine = 1; return (0); }
View file
cyrus-imapd-2.5.tar.gz/imap/xapian_wrap.cpp
Changed
@@ -69,11 +69,10 @@ int xapian_dbw_open(const char *path, xapian_dbw_t **dbwp) { - xapian_dbw_t *dbw = 0; + xapian_dbw_t *dbw = (xapian_dbw_t *)xzmalloc(sizeof(xapian_dbw_t)); int r = 0; try { - dbw = (xapian_dbw_t *)xzmalloc(sizeof(xapian_dbw_t)); int action = Xapian::DB_CREATE_OR_OPEN; dbw->database = new Xapian::WritableDatabase(path, action); dbw->term_generator = new Xapian::TermGenerator(); @@ -93,7 +92,7 @@ } if (r) - free(dbw); + xapian_dbw_close(dbw); else *dbwp = dbw; @@ -221,13 +220,13 @@ Xapian::QueryParser *parser; }; -xapian_db_t *xapian_db_open(const char **paths) +int xapian_db_open(const char **paths, xapian_db_t **dbp) { - xapian_db_t *db = NULL; + xapian_db_t *db = (xapian_db_t *)xzmalloc(sizeof(xapian_db_t)); const char *thispath = "(unknown)"; + int r = 0; try { - db = (xapian_db_t *)xzmalloc(sizeof(xapian_db_t)); db->paths = new std::string(); db->database = new Xapian::Database(); while (*paths) { @@ -247,9 +246,15 @@ catch (const Xapian::Error &err) { syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", thispath, err.get_description().c_str()); - db = NULL; + r = IMAP_IOERROR; } - return db; + + if (r) + xapian_db_close(db); + else + *dbp = db; + + return r; } void xapian_db_close(xapian_db_t *db) @@ -262,6 +267,7 @@ free(db); } catch (const Xapian::Error &err) { + /* XXX - memory leak? */ syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", err.get_context().c_str(), err.get_description().c_str()); }
View file
cyrus-imapd-2.5.tar.gz/imap/xapian_wrap.h
Changed
@@ -66,7 +66,7 @@ extern int xapian_dbw_end_doc(xapian_dbw_t *dbw); /* query-side interface */ -extern xapian_db_t *xapian_db_open(const char **paths); +extern int xapian_db_open(const char **paths, xapian_db_t **dbp); extern void xapian_db_close(xapian_db_t *); extern xapian_query_t *xapian_query_new_match(const xapian_db_t *, const char *prefix, const char *term); extern xapian_query_t *xapian_query_new_compound(const xapian_db_t *, int is_or, xapian_query_t **children, int n);
View file
cyrus-imapd-2.5.tar.gz/imap/xcal.c
Changed
@@ -579,12 +579,12 @@ /* * Construct a xcal string for an iCalendar component. */ -const char *icalcomponent_as_xcal_string(icalcomponent *ical) +char *icalcomponent_as_xcal_string(icalcomponent *ical) { xmlDocPtr doc; xmlNodePtr root, xcomp; + xmlChar *buf; int bufsiz; - const char *xcal = NULL; if (!ical) return NULL; @@ -599,13 +599,8 @@ if (!xmlStrcmp(xcomp->name, BAD_CAST "vcalendar")) { /* Complete iCalendar stream */ - xmlChar *buf; - xmlDocDumpFormatMemoryEnc(doc, &buf, &bufsiz, "utf-8", config_httpprettytelemetry); - xcal = icalmemory_tmp_copy((char *) buf); - - xmlFree(buf); } else { /* Single iCalendar object */ @@ -613,14 +608,13 @@ bufsiz = xmlNodeDump(xbuf, doc, xcomp, 0, config_httpprettytelemetry); - xcal = icalmemory_tmp_copy((char *) xbuf); - + buf = xmlBufferDetach(xbuf); xmlBufferFree(xbuf); } xmlFreeDoc(doc); - return xcal; + return (char *) buf; } @@ -1166,3 +1160,46 @@ buf_printf_markup(buf, 1, "</vcalendar>"); buf_printf_markup(buf, 0, "</icalendar>"); } + + +/* libxml2 replacement functions for those missing in older versions */ +#if (LIBXML_VERSION < 20800) +xmlChar *xmlBufferDetach(xmlBufferPtr buf) +{ + xmlChar *ret; + + if (!buf) return NULL; + + ret = buf->content; + buf->content = NULL; + buf->use = buf->size = 0; + + return ret; +} + + +#if (LIBXML_VERSION < 20703) +xmlNodePtr xmlFirstElementChild(xmlNodePtr node) +{ + if (!node) return NULL; + + for (node = node->children; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE) return (node); + } + + return NULL; +} + + +xmlNodePtr xmlNextElementSibling(xmlNodePtr node) +{ + if (!node) return NULL; + + for (node = node->next; node; node = node->next) { + if (node->type == XML_ELEMENT_NODE) return (node); + } + + return NULL; +} +#endif /* < 2.7.3 */ +#endif /* < 2.8.0 */
View file
cyrus-imapd-2.5.tar.gz/imap/xcal.h
Changed
@@ -64,7 +64,19 @@ int (*get_int)(void *), const char* (*get_str)(void *)); -extern const char *icalcomponent_as_xcal_string(icalcomponent* comp); +extern char *icalcomponent_as_xcal_string(icalcomponent* comp); extern icalcomponent *xcal_string_as_icalcomponent(const char *str); extern const char *begin_xcal(struct buf *buf); extern void end_xcal(struct buf *buf); + +/* libxml2 replacement functions for those missing in older versions */ +#if (LIBXML_VERSION < 20800) +#include <libxml/tree.h> + +extern xmlChar *xmlBufferDetach(xmlBufferPtr buf); + +#if (LIBXML_VERSION < 20703) +extern xmlNodePtr xmlFirstElementChild(xmlNodePtr parent); +extern xmlNodePtr xmlNextElementSibling(xmlNodePtr node); +#endif /* < 2.7.3 */ +#endif /* < 2.8.0 */
View file
cyrus-imapd-2.5.tar.gz/imap/zoneinfo_db.c
Added
@@ -0,0 +1,323 @@ +/* zoneinfo_db.c -- zoneinfo DB routines + * + * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <config.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "assert.h" +#include "cyrusdb.h" +#include "exitcodes.h" +#include "global.h" +#include "tok.h" +#include "util.h" +#include "xmalloc.h" + +#include "zoneinfo_db.h" + +#define DB config_zoneinfo_db + +struct db *zoneinfodb; +static int zoneinfo_dbopen = 0; +static struct buf databuf = BUF_INITIALIZER; + +EXPORTED int zoneinfo_open(const char *fname) +{ + int ret; + char *tofree = NULL; + + if (!fname) fname = config_getstring(IMAPOPT_ZONEINFO_DB_PATH); + + /* create db file name */ + if (!fname) { + tofree = strconcat(config_dir, FNAME_ZONEINFODB, (char *)NULL); + fname = tofree; + } + + ret = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &zoneinfodb); + if (ret != 0) { + syslog(LOG_ERR, "DBERROR: opening %s: %s", fname, + cyrusdb_strerror(ret)); + } + else zoneinfo_dbopen = 1; + + free(tofree); + + return ret; +} + +void zoneinfo_close(struct txn *tid) +{ + if (zoneinfo_dbopen) { + int r; + + if (tid) { + r = cyrusdb_commit(zoneinfodb, tid); + if (r) { + syslog(LOG_ERR, "DBERROR: error committing zoneinfo: %s", + cyrusdb_strerror(r)); + } + } + r = cyrusdb_close(zoneinfodb); + if (r) { + syslog(LOG_ERR, "DBERROR: error closing zoneinfo: %s", + cyrusdb_strerror(r)); + } + zoneinfo_dbopen = 0; + + buf_free(&databuf); + } +} + +void zoneinfo_done(void) +{ + /* DB->done() handled by cyrus_done() */ +} + +static int parse_zoneinfo(const char *data, int datalen, + struct zoneinfo *zi, int all) +{ + const char *dend = data + datalen; + unsigned version; + char *p; + + memset(zi, 0, sizeof(struct zoneinfo)); + + /* version SP type SP dtstamp SP (string *(TAB string)) */ + + version = strtoul(data, &p, 10); + if (version != ZONEINFO_VERSION) return CYRUSDB_IOERROR; + + if (p < dend) zi->type = strtoul(p, &p, 10); + if (p < dend) zi->dtstamp = strtol(p, &p, 10); + + if (all && p < dend) { + size_t len = dend - ++p; + char *str = xstrndup(p, len); + tok_t tok; + + tok_initm(&tok, str, "\t", TOK_FREEBUFFER); + while ((str = tok_next(&tok))) appendstrlist(&zi->data, str); + tok_fini(&tok); + } + + return 0; +} + +EXPORTED int zoneinfo_lookup(const char *tzid, struct zoneinfo *zi) +{ + const char *data = NULL; + size_t datalen; + int r; + + /* Don't access DB if it hasn't been opened */ + if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL; + + assert(tzid); + + /* Check if there is an entry in the database */ + do { + r = cyrusdb_fetch(zoneinfodb, tzid, strlen(tzid), &data, &datalen, NULL); + } while (r == CYRUSDB_AGAIN); + + if (r || !data || (datalen < 6)) return r ? r : CYRUSDB_IOERROR; + + return parse_zoneinfo(data, datalen, zi, 1); +} + +EXPORTED int zoneinfo_store(const char *tzid, struct zoneinfo *zi, struct txn **tid) +{ + struct strlist *sl; + const char *sep; + int r; + + /* Don't access DB if it hasn't been opened */ + if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL; + + assert(tzid && zi); + + /* version SP type SP dtstamp SP (string *(TAB string)) */ + buf_reset(&databuf); + buf_printf(&databuf, "%u %u %ld ", ZONEINFO_VERSION, zi->type, zi->dtstamp); + for (sl = zi->data, sep = ""; sl; sl = sl->next, sep = "\t") + buf_printf(&databuf, "%s%s", sep, sl->s); + + r = cyrusdb_store(zoneinfodb, tzid, strlen(tzid), + buf_cstring(&databuf), buf_len(&databuf), tid); + + if (r != CYRUSDB_OK) { + syslog(LOG_ERR, "DBERROR: error updating zoneinfo: %s (%s)", + tzid, cyrusdb_strerror(r)); + } + + return r; +} + + +static int tzmatch(const char *str, const char *pat) +{ + for ( ; *pat; str++, pat++) { + /* End of string and more pattern */ + if (!*str && *pat != '*') return 0; + + switch (*pat) { + case '*': + /* Collapse consecutive stars */ + while (*++pat == '*') continue; + + /* Trailing star matches anything */ + if (!*pat) return 1; + + while (*str) if (tzmatch(str++, pat)) return 1; + return 0; + + case ' ': + case '_': + /* Treat ' ' == '_' */ + if (*str != ' ' && *str != '_') return 0; + break; + + default: + /* Case-insensitive comparison */ + if (tolower(*str) != tolower(*pat)) return 0; + break; + } + } + + /* Did we reach end of string? */ + return (!*str); +} + + +struct findrock { + const char *find; + int tzid_only; + time_t changedsince; + int (*proc)(); + void *rock; +}; + +static int find_p(void *rock, + const char *tzid, size_t tzidlen, + const char *data, size_t datalen) +{ + struct findrock *frock = (struct findrock *) rock; + struct zoneinfo zi; + + if (parse_zoneinfo(data, datalen, &zi, 0)) return 0; + + switch (zi.type) { + case ZI_INFO: return 0; + + case ZI_LINK: + if (frock->tzid_only) return 0; + break; + + case ZI_ZONE: + if (zi.dtstamp <= frock->changedsince) return 0; + break; + } + + if (!frock->find) return 1; + if (frock->tzid_only) return (tzidlen == strlen(frock->find)); + return tzmatch(tzid, frock->find); +} + +static int find_cb(void *rock, + const char *tzid, size_t tzidlen, + const char *data, size_t datalen) +{ + struct findrock *frock = (struct findrock *) rock; + struct zoneinfo zi; + int r; + + r = parse_zoneinfo(data, datalen, &zi, 1); + if (!r) { + struct strlist *linkto = NULL; + + if (zi.type == ZI_LINK) { + linkto = zi.data; + zi.data = NULL; + + tzid = linkto->s; + tzidlen = strlen(tzid); + r = zoneinfo_lookup(tzid, &zi); + } + if (!r) r = (*frock->proc)(tzid, tzidlen, &zi, frock->rock); + freestrlist(zi.data); + freestrlist(linkto); + } + + return r; +} + +EXPORTED int zoneinfo_find(const char *find, int tzid_only, time_t changedsince, + int (*proc)(), void *rock) +{ + struct findrock frock; + + /* Don't access DB if it hasn't been opened */ + if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL; + + assert(proc); + + frock.find = find; + frock.tzid_only = tzid_only; + frock.changedsince = changedsince; + frock.proc = proc; + frock.rock = rock; + + if (!find || !tzid_only) find = ""; + + /* process each matching entry in our database */ + return cyrusdb_foreach(zoneinfodb, (char *) find, strlen(find), + &find_p, &find_cb, &frock, NULL); +}
View file
cyrus-imapd-2.5.tar.gz/imap/zoneinfo_db.h
Added
@@ -0,0 +1,97 @@ +/* zoneinfo_db.h -- zoneinfo DB routines + * + * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * innovation@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef ZONEINFO_DB_H +#define ZONEINFO_DB_H + +#include <time.h> + +#include "annotate.h" /* for strlist functionality */ + +/* name of the zoneinfo directory */ +#define FNAME_ZONEINFODIR "/zoneinfo" + +/* name of the zoneinfo database */ +#define FNAME_ZONEINFODB "/zoneinfo.db" +#define ZONEINFO_VERSION 1 + +#define INFO_TZID ".info" +#define zoneinfo_lookup_info(zi) zoneinfo_lookup(INFO_TZID, zi) + +struct zoneinfo { + unsigned type; + time_t dtstamp; + struct strlist *data; +}; + +/* zoneinfo record types */ +enum { + ZI_ZONE = 0, + ZI_LINK, + ZI_INFO +}; + +/* open the zoneinfo db */ +extern int zoneinfo_open(const char *name); + +/* lookup a single zoneinfo entry and return result, or error if it + doesn't exist or doesn't have the fields we need */ +extern int zoneinfo_lookup(const char *tzid, struct zoneinfo *zi); + +/* store a zoneinfo entry */ +extern int zoneinfo_store(const char *tzid, struct zoneinfo *zi, + struct txn **tid); + +/* process all zoneinfo entries (optionally matching 'find') */ +extern int zoneinfo_find(const char *find, int tzid_only, time_t changedsince, + int (*proc)(const char *tzid, int tzidlen, + struct zoneinfo *zi, void *rock), + void *rock); + +/* close the database (optionally committing txn) */ +extern void zoneinfo_close(struct txn *tid); + +/* done with database stuff */ +extern void zoneinfo_done(void); + +#endif /* ZONEINFO_DB_H */
View file
cyrus-imapd-2.5.tar.gz/lib/imapoptions
Changed
@@ -297,8 +297,10 @@ layers of MIME structure. The default of 1000 is much higher than any sane message should have. */ -{ "caldav_allowscheduling", 1, SWITCH } -/* If enabled, the server will perform calendar scheduling operations. */ +{ "caldav_allowscheduling", "on", ENUM("off", "on", "apple") } +/* Enable calendar scheduling operations. If set to "apple", the + server will emulate Apple CalendarServer behavior as closely as + possible. */ { "caldav_realm", NULL, STRING } /* The realm to present for HTTP authentication of CalDAV resources. @@ -503,10 +505,10 @@ /* Don't send event notification for folder with given special-use attributes. Set ALL for any folder */ -{ "event_extra_params", "timestamp", BITFIELD("bodyStructure", "clientAddress", "diskUsed", "flagNames", "messageContent", "messageSize", "messages", "modseq", "service", "timestamp", "uidnext", "vnd.cmu.midset", "vnd.cmu.unseenMessages", "vnd.cmu.envelope", "vnd.fastmail.clientId", "vnd.fastmail.sessionId", "vnd.fastmail.convExists", "vnd.fastmail.convUnseen", "vnd.fastmail.cid", "vnd.cmu.mbtype", "vnd.cmu.davFilename", "vnd.cmu.davUid") } -/* Space-separated list of extra parameters to add to any appropriated event. */ +{ "event_extra_params", "timestamp", BITFIELD("bodyStructure", "clientAddress", "diskUsed", "flagNames", "messageContent", "messageSize", "messages", "modseq", "service", "timestamp", "uidnext", "vnd.cmu.midset", "vnd.cmu.unseenMessages", "vnd.cmu.envelope", "vnd.cmu.mbtype", "vnd.cmu.davFilename", "vnd.cmu.davUid", "vnd.fastmail.clientId", "vnd.fastmail.sessionId", "vnd.fastmail.convExists", "vnd.fastmail.convUnseen", "vnd.fastmail.cid") } +/* Space-separated list of extra parameters to add to any appropriated event. */ -{ "event_groups", "message mailbox", BITFIELD("message", "quota", "flags", "access", "mailbox") } +{ "event_groups", "message mailbox", BITFIELD("message", "quota", "flags", "access", "mailbox", "subscription") } /* Space-separated list of groups of related events to turn on notification */ { "event_notifier", NULL, STRING } @@ -640,7 +642,7 @@ provisional responses every \fIhttpkeepalive\fR seconds until the final response can be sent */ -{ "httpmodules", "", BITFIELD("caldav", "carddav", "domainkey", "ischedule", "rss") } +{ "httpmodules", "", BITFIELD("caldav", "carddav", "domainkey", "ischedule", "rss", "timezone") } /* Space-separated list of HTTP modules that will be enabled in httpd(8). This option has no effect on modules that are disabled at compile time due to missing dependencies (e.g. libical). */ @@ -724,6 +726,21 @@ { "ldap_deref", "never", STRINGLIST("search", "find", "always", "never") } /* Specify how aliases dereferencing is handled during search. */ +{ "ldap_domain_base_dn", "", STRING } +/* Base DN to search for domain name spaces. */ + +{ "ldap_domain_filter", "(&(objectclass=domainrelatedobject)(associateddomain=%s))", STRING } +/* Filter to use searching for domains */ + +{ "ldap_domain_name_attribute", "associateddomain", STRING } +/* The attribute name for domains. */ + +{ "ldap_domain_scope", "sub", STRINGLIST("sub", "one", "base") } +/* Search scope */ + +{ "ldap_domain_result_attribute", "inetdomainbasedn", STRING } +/* Result attribute */ + { "ldap_filter", "(uid=%u)", STRING } /* Specify a filter that searches user identifiers. The following tokens can be used in the filter string: @@ -964,7 +981,7 @@ { "mboxname_lockpath", NULL, STRING } /* Path to mailbox name lock files (default $conf/lock) */ -{ "metapartition_files", "", BITFIELD("header", "index", "cache", "expunge", "squat", "annotations", "lock", "archivecache") } +{ "metapartition_files", "", BITFIELD("header", "index", "cache", "expunge", "squat", "annotations", "lock", "dav", "archivecache") } /* Space-separated list of metadata files to be stored on a \fImetapartition\fR rather than in the mailbox directory on a spool partition. */ @@ -1464,8 +1481,13 @@ { "servername", NULL, STRING } /* This is the hostname visible in the greeting messages of the POP, IMAP and LMTP daemons. If it is unset, then the result returned - from gethostname(2) is used. */ - + from gethostname(2) is used. This is also the value used by murder + clusters to identify the host name. It should be resolvable by + DNS to the correct host, and unique within an active cluster. If + you are using low level replication (e.g. drbd) then it should be + the same on each copy and the DNS name should also be moved to + the new master on failover. */ + { "serverinfo", "on", ENUM("off", "min", "on") } /* The server information to display in the greeting and capability responses. Information is displayed as follows: @@ -1811,6 +1833,13 @@ { "defaultsearchtier", "", STRING } /* Name of the default tier that messages will be indexed to */ +{ "zoneinfo_db", "skiplist", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist")} +/* The cyrusdb backend to use for zoneinfo. */ + +{ "zoneinfo_db_path", NULL, STRING } +/* The absolute path to the zoneinfo db file. If not specified, + will be confdir/zoneinfo.db */ + /* .SH SEE ALSO .PP
View file
cyrus-imapd-2.5.tar.gz/lib/retry.c
Changed
@@ -48,6 +48,7 @@ #include <unistd.h> #endif +#include "exitcodes.h" #include "retry.h" #include "xmalloc.h" @@ -114,7 +115,8 @@ int i; ssize_t n; size_t written = 0; - struct iovec *iov, *baseiov; + size_t len = 0; + struct iovec *iov, *baseiov = NULL; static int iov_max = #ifdef MAXIOV MAXIOV @@ -130,6 +132,17 @@ if (!iovcnt) return 0; + for (i = 0; i < iovcnt; i++) { + len += srciov[i].iov_len; + } + + n = written = writev(fd, srciov, iovcnt > iov_max ? iov_max : iovcnt); + + /* did we get lucky and write it all? */ + if (written == len) + return written; + + /* oh well, welcome to the slow path - we have copies */ baseiov = iov = (struct iovec *)xmalloc(iovcnt * sizeof(struct iovec)); for (i = 0; i < iovcnt; i++) { iov[i].iov_base = srciov[i].iov_base; @@ -137,13 +150,18 @@ } for (;;) { - while (iovcnt && iov[0].iov_len == 0) { + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len > (size_t)n) { + iov[i].iov_base += n; + iov[i].iov_len -= n; + break; + } + n -= iov[i].iov_len; iov++; iovcnt--; + if (!iovcnt) fatal("ran out of iov", EC_SOFTWARE); } - if (!iovcnt) break; - n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt); if (n == -1) { if (errno == EINVAL && iov_max > 10) { @@ -157,17 +175,7 @@ written += n; - for (i = 0; i < iovcnt; i++) { - if (iov[i].iov_len > (size_t)n) { - iov[i].iov_base = (char *)iov[i].iov_base + n; - iov[i].iov_len -= n; - break; - } - n -= iov[i].iov_len; - iov[i].iov_len = 0; - } - - if (i == iovcnt) break; + if (written == len) break; } free(baseiov);
View file
cyrus-imapd-2.5.tar.gz/lib/util.c
Changed
@@ -266,6 +266,17 @@ (b == NULL ? "" : b)); } +/* in which NULL is NOT equal to "" */ +EXPORTED int strcmpnull(const char *a, const char *b) +{ + if (a) { + if (b) return strcmp(a, b); + return 1; + } + if (b) return -1; + return 0; +} + /* do a binary search in a keyvalue array * nelem is the number of keyvalue elements in the kv array @@ -821,24 +832,23 @@ } /* this function has a side-effect of always leaving the buffer writable */ -EXPORTED void buf_ensure(struct buf *buf, size_t n) +EXPORTED void _buf_ensure(struct buf *buf, size_t n) { - size_t newalloc = roundup(buf->len + n); + size_t newlen = buf->len + n; + char *s; - /* can't create a zero byte buffer */ - assert(newalloc); + assert(newlen); /* we never alloc zero bytes */ - /* protect against wrap */ - assert(newalloc >= buf->len); - - if (buf->alloc >= newalloc) + if (buf->alloc >= newlen) return; if (buf->alloc) { - buf->s = xrealloc(buf->s, newalloc); + buf->alloc = roundup(newlen); + buf->s = xrealloc(buf->s, buf->alloc); } else { - char *s = xmalloc(newalloc); + buf->alloc = roundup(newlen); + s = xmalloc(buf->alloc); /* if no allocation, but data exists, it means copy on write. * grab a copy of what's there now */ @@ -847,32 +857,31 @@ memcpy(s, buf->s, buf->len); } - /* can release MMAP now, we've already copied the data out */ + /* can release MMAP now, we've copied the data out */ if (buf->flags & BUF_MMAP) { - const char *base = buf->s; - size_t len = buf->len; - map_free(&base, &len); + size_t len = buf->len; /* don't wipe the length, we still need it */ + map_free((const char **)&buf->s, &len); buf->flags &= ~BUF_MMAP; } buf->s = s; } - - /* either way, our allocated space is now this long */ - buf->alloc = newalloc; } EXPORTED const char *buf_cstring(struct buf *buf) { - if (!(buf->flags & BUF_CSTRING) || buf->s == NULL) { - buf_ensure(buf, 1); - buf->s[buf->len] = '\0'; - buf->flags |= BUF_CSTRING; - } - + buf_ensure(buf, 1); + buf->s[buf->len] = '\0'; return buf->s; } +EXPORTED char *buf_newcstring(struct buf *buf) +{ + char *ret = xstrdup(buf_cstring(buf)); + buf_reset(buf); + return ret; +} + EXPORTED char *buf_release(struct buf *buf) { char *ret = (char *)buf_cstring(buf); @@ -935,8 +944,10 @@ EXPORTED void buf_reset(struct buf *buf) { + if (buf->flags & BUF_MMAP) + map_free((const char **)&buf->s, &buf->len); buf->len = 0; - buf->flags &= ~BUF_CSTRING; + buf->flags = 0; } EXPORTED void buf_truncate(struct buf *buf, size_t len) @@ -948,7 +959,6 @@ memset(buf->s + buf->len, 0, more); } buf->len = len; - buf->flags &= ~BUF_CSTRING; } EXPORTED void buf_setcstr(struct buf *buf, const char *str) @@ -993,7 +1003,6 @@ buf_ensure(buf, len); memcpy(buf->s + buf->len, base, len); buf->len += len; - buf->flags &= ~BUF_CSTRING; } } @@ -1020,13 +1029,6 @@ } } -EXPORTED void buf_putc(struct buf *buf, char c) -{ - buf_ensure(buf, 1); - buf->s[buf->len++] = c; - buf->flags &= ~BUF_CSTRING; -} - EXPORTED void buf_vprintf(struct buf *buf, const char *fmt, va_list args) { va_list ap; @@ -1052,9 +1054,6 @@ va_end(ap); buf->len += n; - /* vsnprintf() gave us a trailing NUL, so we may as well remember - * that for later */ - buf->flags |= BUF_CSTRING; } EXPORTED void buf_printf(struct buf *buf, const char *fmt, ...) @@ -1066,18 +1065,6 @@ va_end(args); } -static void buf_writable_cstring(struct buf *buf) -{ - if ((buf->flags & BUF_CSTRING) && !buf->alloc) { - /* has a \0 terminator but not writable: force - * re-allocation of the data and initialisation - * of the terminator */ - buf->flags &= ~BUF_CSTRING; - } - buf_cstring(buf); - assert(buf->alloc > 0); -} - static void buf_replace_buf(struct buf *buf, size_t offset, size_t length, @@ -1088,7 +1075,7 @@ length = buf->len - offset; /* we need buf to be a writable C string now please */ - buf_writable_cstring(buf); + buf_cstring(buf); if (replace->len > length) { /* string will need to expand */ @@ -1142,7 +1129,7 @@ size_t i; /* we need writable, so may as well cstring it */ - buf_writable_cstring(buf); + buf_cstring(buf); for (i = 0; i < buf->len; i++) { if (buf->s[i] == match) { @@ -1300,7 +1287,7 @@ { buf->alloc = 0; buf->len = (str ? strlen(str) : 0); - buf->flags = (str ? BUF_CSTRING : 0); + buf->flags = 0; buf->s = (char *)str; } @@ -1321,11 +1308,8 @@ { if (buf->alloc) free(buf->s); - else if (buf->flags & BUF_MMAP) { - const char *base = buf->s; - size_t len = buf->len; - map_free(&base, &len); - } + else if (buf->flags & BUF_MMAP) + map_free((const char **)&buf->s, &buf->len); } EXPORTED void buf_free(struct buf *buf)
View file
cyrus-imapd-2.5.tar.gz/lib/util.h
Changed
@@ -141,6 +141,9 @@ /* ditto strncmp */ int strncmpsafe(const char *a, const char *b, size_t n); +/* NULL isn't "" */ +int strcmpnull(const char *a, const char *b); + /* do a binary search in a keyvalue array * nelem is the number of keyvalue elements in the kv array * cmpf is the comparison function (strcmp, stricmp, etc). @@ -220,7 +223,6 @@ extern clock_t sclock(void); -#define BUF_CSTRING (1<<0) #define BUF_MMAP (1<<1) struct buf { @@ -231,11 +233,15 @@ }; #define BUF_INITIALIZER { NULL, 0, 0, 0 } +#define buf_ensure(b, n) do { if ((b)->alloc < (b)->len + (n)) _buf_ensure((b), (n)); } while (0) +#define buf_putc(b, c) do { buf_ensure((b), 1); (b)->s[(b)->len++] = (c); } while (0) + +void _buf_ensure(struct buf *buf, size_t len); const char *buf_cstring(struct buf *buf); const char *buf_cstringnull(struct buf *buf); char *buf_release(struct buf *buf); +char *buf_newcstring(struct buf *buf); char *buf_releasenull(struct buf *buf); -void buf_ensure(struct buf *buf, size_t morebytes); void buf_getmap(struct buf *buf, const char **base, size_t *len); int buf_getline(struct buf *buf, FILE *fp); size_t buf_len(const struct buf *buf); @@ -254,7 +260,6 @@ void buf_insert(struct buf *dst, unsigned int off, const struct buf *src); void buf_insertcstr(struct buf *buf, unsigned int off, const char *str); void buf_insertmap(struct buf *buf, unsigned int off, const char *base, int len); -void buf_putc(struct buf *buf, char c); void buf_vprintf(struct buf *buf, const char *fmt, va_list args); void buf_printf(struct buf *buf, const char *fmt, ...) __attribute__((format(printf,2,3)));
View file
cyrus-imapd-2.5.tar.gz/man/ctl_zoneinfo.8
Added
@@ -0,0 +1,86 @@ +.\" -*- nroff -*- +.TH CTL_ZONEINFO 8 "Project Cyrus" CMU +.\" +.\" Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" 3. The name "Carnegie Mellon University" must not be used to +.\" endorse or promote products derived from this software without +.\" prior written permission. For permission or any legal +.\" details, please contact +.\" Carnegie Mellon University +.\" Center for Technology Transfer and Enterprise Creation +.\" 4615 Forbes Avenue +.\" Suite 302 +.\" Pittsburgh, PA 15213 +.\" (412) 268-7393, fax: (412) 268-7395 +.\" innovation@andrew.cmu.edu + * +.\" 4. Redistributions of any form whatsoever must retain the following +.\" acknowledgment: +.\" "This product includes software developed by Computing Services +.\" at Carnegie Mellon University (http://www.cmu.edu/computing/)." +.\" +.\" CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO +.\" THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE +.\" FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +.\" AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING +.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.SH NAME +ctl_zoneinfo \- perform operations on the zoneinfo database +.SH SYNOPSIS +.B ctl_zoneinfo +[ +.B \-C +.I config-file +] +[ +.B \-v +] +.B \-r +.I version-string +.SH DESCRIPTION +.I Ctl_zoneinfo +is used to perform various administrative operations on the zoneinfo +database. +.PP +.I Ctl_zoneinfo +reads its configuration options out of the +.IR imapd.conf (5) +file unless specified otherwise by \fB-C\fR. +.SH OPTIONS +.TP +.BI \-C " config-file" +Read configuration options from \fIconfig-file\fR. +.TP +.B \-v +Enable verbose output. +.TP +.BI \-r " version-string" +Rebuild the zoneinfo database based on the directory structure of +\fIconfigdirectory\fB/zoneinfo\fR. The database to be rebuilt will be +in the default location of \fIconfigdirectory\fB/zoneinfo.db\fR unless +otherwise specified by the \fIzoneinfo_db_path\fR option in +\fBimapd.conf\fR. The \fIversion-string\fR should describe the source +of the timezone data (e.g. "Olson 2013h") and will be used by the +\fItimezone\fR module of \fBhttpd\fR. +.SH FILES +.TP +.B /etc/imapd.conf +.SH SEE ALSO +.PP +\fBimapd.conf(5)\fR, \fBmaster(8)\fR, \fBhttpd(8)\fR
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_external.c
Changed
@@ -61,10 +61,10 @@ const char *user, const char *mailbox, int nopt __attribute__((unused)), char **options __attribute__((unused)), - const char *message) + const char *message, const char *fname) { const char *notify; - const char *buf[10]; + const char *buf[12]; int fds[2], status; pid_t child_pid; FILE *stream; @@ -89,7 +89,9 @@ buf[6] = user; buf[7] = "-m"; buf[8] = mailbox; - buf[9] = NULL; + buf[9] = "-f"; + buf[10] = fname; + buf[11] = NULL; if (pipe(fds) < 0) { syslog(LOG_ERR,
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_external.h
Changed
@@ -49,7 +49,8 @@ char* notify_external(const char *class, const char *priority, const char *user, const char *mailbox, - int nopt, char **options, const char *message); + int nopt, char **options, + const char *message, const char *fname); #endif /* _NOTIFY_EXTERNAL_H */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_log.c
Changed
@@ -51,7 +51,8 @@ char* notify_log(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message) + const char *message, + const char *fname __attribute__((unused))) { char opt_str[1024] = ""; char *sep = "";
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_log.h
Changed
@@ -48,6 +48,6 @@ char* notify_log(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message); + const char *message, const char *fname); #endif /* _NOTIFY_LOG_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_mailto.c
Changed
@@ -65,7 +65,8 @@ const char *user __attribute__((unused)), const char *mailbox __attribute__((unused)), int nopt, char **options, - const char *message) + const char *message, + const char *fname __attribute__((unused))) { FILE *sm; const char *smbuf[7]; @@ -109,14 +110,14 @@ return strdup("NO mailto could not spawn sendmail process"); t = time(NULL); - snprintf(outmsgid, sizeof(outmsgid), "<cmu-sieve-%d-%lu-%d@%s>", + snprintf(outmsgid, sizeof(outmsgid), "<cmu-sieve-%d-%lu-%d@%s>", (int) sm_pid, t, global_outgoing_count++, config_servername); - + fprintf(sm, "Message-ID: %s\r\n", outmsgid); time_to_rfc822(t, datestr, sizeof(datestr)); fprintf(sm, "Date: %s\r\n", datestr); - + fprintf(sm, "X-Sieve: %s\r\n", SIEVE_VERSION); fprintf(sm, "From: Mail Sieve Subsystem <%s>\r\n", config_getstring(IMAPOPT_POSTMASTER)); fprintf(sm, "To: <%s>\r\n", options[0]); @@ -146,7 +147,7 @@ if (msg) { const unsigned char *s = (const unsigned char *)msg; - + while (*s) { if (0 != (*s & 0x80)) { result = 1;
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_mailto.h
Changed
@@ -46,11 +46,9 @@ #include <config.h> /* the only option should be a mailto URI */ -char* notify_mailto(const char *class __attribute__((unused)), - const char *priority __attribute__((unused)), - const char *user __attribute__((unused)), - const char *mailbox __attribute__((unused)), +char* notify_mailto(const char *class, const char *priority, + const char *user, const char *mailbox, int nopt, char **options, - const char *message); + const char *message, const char *fname); #endif /* _NOTIFY_MAILTO_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_null.c
Changed
@@ -52,7 +52,8 @@ const char *mailbox __attribute__((unused)), int nopt __attribute__((unused)), char **options __attribute__((unused)), - const char *message __attribute__((unused))) + const char *message __attribute__((unused)), + const char *fname __attribute__((unused))) { return strdup("OK null notification successful"); }
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_null.h
Changed
@@ -45,12 +45,9 @@ #include <config.h> -char* notify_null(const char *class __attribute__((unused)), - const char *priority __attribute__((unused)), - const char *user __attribute__((unused)), - const char *mailbox __attribute__((unused)), - int nopt __attribute__((unused)), - char **options __attribute__((unused)), - const char *message __attribute__((unused))); +char* notify_null(const char *class, const char *priority, + const char *user, const char *mailbox, + int nopt, char **options, + const char *message, const char *fname); #endif /* _NOTIFY_NULL_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_zephyr.c
Changed
@@ -73,7 +73,8 @@ char* notify_zephyr(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message) + const char *message, + const char *fname __attribute__((unused))) { ZNotice_t notice; int retval;
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_zephyr.h
Changed
@@ -49,6 +49,6 @@ char* notify_zephyr(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message); + const char *message, const char *fname); #endif /* _NOTIFY_ZEPHYR_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notifyd.c
Changed
@@ -107,6 +107,7 @@ strarray_t options = STRARRAY_INITIALIZER; long nopt; char *reply; + char *fname; notifymethod_t *nmethod; while (1) { @@ -147,6 +148,7 @@ strarray_appendm(&options, cp = fetch_arg(cp, tail)); if (cp) message = (cp = fetch_arg(cp, tail)); + if (cp) fname = (cp = fetch_arg(cp, tail)); if (!message) { syslog(LOG_ERR, "malformed notify request"); @@ -169,7 +171,7 @@ if (nmethod->name) { reply = nmethod->notify(class, priority, user, mailbox, - nopt, options.data, message); + nopt, options.data, message, fname); } #if 0 /* we don't care about responses right now */ else {
View file
cyrus-imapd-2.5.tar.gz/notifyd/notifyd.h
Changed
@@ -54,7 +54,7 @@ char *(*notify)(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message); /* notification function */ + const char *message, const char *fname); /* notification function */ } notifymethod_t; /* array of supported notification methods */
View file
cyrus-imapd-2.5.tar.gz/perl/imap/Cyrus/CacheFile.pm
Added
@@ -0,0 +1,212 @@ +#!/usr/bin/perl -c + +package Cyrus::CacheFile; + +use strict; +use warnings; + +use IO::File; +use IO::File::fcntl; +use IO::Handle; +use File::Temp; +use YAML; + +=pod + +=head1 NAME + +Cyrus::CacheFile - A pure perl interface to the "cyrus.cache" file +format as generated by Cyrus IMAPd. + +=head1 EXAMPLES + +XXX: document. + +See examples/index_uids.pl for some usage + +=cut + +# /* Access assistance macros for memory-mapped cache file data */ +# /* CACHE_ITEM_BIT32: Convert to host byte order */ +# /* CACHE_ITEM_LEN: Get the length out */ +# /* CACHE_ITEM_NEXT: Return a pointer to the next entry. Sizes are +# * 4-byte aligned, so round up to the next 4 byte boundry */ +# #define CACHE_ITEM_BIT32(ptr) (ntohl(*((bit32 *)(ptr)))) +# #define CACHE_ITEM_LEN(ptr) CACHE_ITEM_BIT32(ptr) +# #define CACHE_ITEM_NEXT(ptr) ((ptr)+4+((3+CACHE_ITEM_LEN(ptr))&~3)) + +# #define MAILBOX_CACHE_MINOR_VERSION 2 +# #define NUM_CACHE_FIELDS 10 + +our $NUM_CACHE_FIELDS = 10; +our @NAMES = qw( + ENVELOPE + BODYSTRUCTURE + BODY + SECTION + HEADERS + FROM + TO + CC + BCC + SUBJECT +); + +# PUBLIC API + +sub new { + my $class = shift; + my $handle = shift; + + # read header + my $buf; + # XXX - check for success! + sysread($handle, $buf, 4); + my $version = unpack('N', $buf); + my $Self = bless { version => $version, handle => $handle, offset => 4 }, ref($class) || $class; + return $Self; +} + +sub new_file { + my $class = shift; + my $file = shift; + my $lockopts = shift; + + my $fh; + if ($lockopts) { + $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY'; + $fh = IO::File::fcntl->new($file, '+<', @$lockopts) + || die "Can't open $file for locked read: $!"; + } else { + $fh = IO::File->new("< $file") + || die "Can't open $file for read: $!"; + } + + return $class->new($fh); +} + +sub next_record { + my $Self = shift; + my $buf; + + my @record; + my $size = 0; + for (1..$NUM_CACHE_FIELDS) { + sysread($Self->{handle}, $buf, 4); + return undef unless $buf; + my $num = unpack('N', $buf); + my $bytes = $num; + $bytes += 4 - $num % 4 if $num % 4; # offsets are multiple of 4 bytes + sysread($Self->{handle}, $buf, $bytes); + push @record, [$num, $bytes, $buf]; + $size += $bytes + 4; + } + + my $ret = { + size => $size, + records => \@record, + }; + + $Self->{record} = $ret; + $Self->{offset} += $size; + + return $ret; +} + +sub record { + my $Self = shift; + my $Field = shift; + + return undef unless ($Self->{record}); + + if ($Field) { + return $Self->{record}{$Field}; + } + return $Self->{record}; +} + +sub offset { + my $Self = shift; + + if (@_) { + my $spot = shift; + seek($Self->{handle}, $spot, 0); + $Self->{offset} = $spot; + } + + return $Self->{offset}; +} + +sub dump { + my $Self = shift; + + while (my $rec = $Self->next_record()) { + $Self->dump_record($rec); + } +} + +sub dump_record { + my $Self = shift; + my $rec = shift || $Self->{record}; + return unless $rec; + print Dump($rec->{records}); +} + +sub print_record { + my $Self = shift; + my $rec = shift || $Self->{record}; + return unless $rec; + foreach my $rnum (0..$NUM_CACHE_FIELDS-1) { + my $record = $rec->{records}[$rnum]; + my $str = substr($record->[2], 0, $record->[0]); + if ($rnum == 3) { # section + my @items = unpack('N*', $str); + $str = parse_section(0, \@items); + } + print "$NAMES[$rnum]: $str\n"; + } +} + +sub parse_section { + my $part = shift; + my $items = shift; + my $num_parts = shift @$items; + if ($num_parts == 0) { + return "$part:()"; + } + my $ret = "$part:(" . parse_item($items); + my $n = 1; + while ($n < $num_parts) { + my $subpart = $part ? "$part.$n" : $n; + $ret .= " " . parse_item($items); + $n++; + } + $n = 1; + $ret .= ")"; + while ($n < $num_parts) { + my $subpart = $part ? "$part.$n" : $n; + $ret .= " " . parse_section($subpart, $items); + $n++; + } + return $ret; +} + +sub parse_item { + my $items = shift; + my $header_offset = shift @$items; + my $header_size = shift @$items; + my $content_offset = shift @$items; + my $content_size = shift @$items; + my $encoding = shift @$items; + return "($header_offset:$header_size $content_offset:$content_size $encoding)"; +} + +=head1 AUTHOR AND COPYRIGHT + +Bron Gondwana <brong@fastmail.fm> - Copyright 2008 FastMail + +Licenced under the same terms as Cyrus IMAPd. + +=cut + +1;
View file
cyrus-imapd-2.5.tar.gz/perl/imap/Cyrus/HeaderFile.pm
Added
@@ -0,0 +1,175 @@ +#!/usr/bin/perl -c + +# Package to handle Cyrus Header files + +package Cyrus::HeaderFile; + +use strict; +use warnings; + +use IO::File; +use IO::File::fcntl; +use IO::Handle; +use File::Temp; +use Data::Dumper; + +=pod + +=head1 NAME + +Cyrus::HeaderFile - A pure perl interface to the "cyrus.header" file +format as generated by Cyrus IMAPd. + +=head1 EXAMPLES + +Like Cyrus::IndexFile, uses fcntl locking (default for Cyrus on systems +which support it) + +my $header = Cyrus::HeaderFile->new_file("path/to/cyrus.header"); + +XXX: see index_uids.pl + +=cut + +our $HL1 = qq{\241\002\213\015Cyrus mailbox header}; +our $HL2 = qq{"The best thing about this system was that it had lots of goals."}; +our $HL3 = qq{\t--Jim Morris on Andrew}; + +=head1 PUBLIC API + +=over + +=item Cyrus::HeaderFile->new($fh) + +Read the header file in $fh + +=cut + +sub new { + my $class = shift; + my $handle = shift; + + # read header + local $/ = undef; + my $body = <$handle>; + + my $Self = bless {}, ref($class) || $class; + $Self->{handle} = $handle; # keep for locking + $Self->{rawheader} = $body; + $Self->{header} = $Self->parse_header($body); + + return $Self; +} + +=item Cyrus::HeaderFile->new_file($fname, $lockopts) + +Open the file to read, optionally locking it with IO::File::fcntl. If you +pass a scalar for lockopts then it will be locked with ['lock_ex'], otherwise +you can pass a tuple, e.g. ['lock_ex', 5] for a 5 second timeout. + +This function will die if it can't open or lock the file. On success, it +calls $class->new() with the filehandle. + +=cut + +sub new_file { + my $class = shift; + my $file = shift; + my $lockopts = shift; + + my $fh; + if ($lockopts) { + $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY'; + $fh = IO::File::fcntl->new($file, '+<', @$lockopts) + || die "Can't open $file for locked read: $!"; + } else { + $fh = IO::File->new("< $file") + || die "Can't open $file for read: $!"; + } + + return $class->new($fh); +} + +=item $header->header([$Field]) + +Return the entire header as a hash, or individual named field. + +=cut + +sub header { + my $Self = shift; + my $Field = shift; + + if ($Field) { + return $Self->{header}{$Field}; + } + + return $Self->{header}; +} + +=item $header->write_header($fh, $headerData) + +Write a header file with the data (e.g. returned from ->header()) +to the given filehandle. + +=cut + +sub write_header { + my $Self = shift; + my $fh = shift; + my $header = shift || $Self->header(); + + $fh->print($Self->make_header($header)); +} + +sub make_header { + my $Self = shift; + my $ds = shift || $Self->header(); + + # NOTE: acl and flags should have '' as the last element! + my $flags = join(" ", @{$ds->{Flags}}, ''); + my $acl = join("\t", @{$ds->{ACL}}, ''); + my $buf = <<EOF; +$HL1 +$HL2 +$HL3 +$ds->{QuotaRoot} $ds->{UniqueId} +$flags +$acl +EOF + return $buf; +} + +sub parse_header { + my $Self = shift; + my $body = shift; + + my @lines = split /\n/, $body; + + die "Not a mailbox header file" unless $lines[0] eq $HL1; + die "Not a mailbox header file" unless $lines[1] eq $HL2; + die "Not a mailbox header file" unless $lines[2] eq $HL3; + my ($quotaroot, $uniqueid) = split /\t/, $lines[3]; + my (@flags) = split / /, $lines[4]; + my (@acl) = split /\t/, $lines[5]; + + return { + QuotaRoot => $quotaroot, + UniqueId => $uniqueid, + Flags => \@flags, + ACL => \@acl, + }; +} + +=back + +=head1 AUTHOR AND COPYRIGHT + +Bron Gondwana <brong@fastmail.fm> - Copyright 2008 FastMail + +Licenced under the same terms as Cyrus IMAPd. + +=cut + + +1;
View file
cyrus-imapd-2.5.tar.gz/perl/imap/Cyrus/IndexFile.pm
Added
@@ -0,0 +1,1472 @@ +#!/usr/bin/perl -c + +package Cyrus::IndexFile; + +use strict; +use warnings; + +use IO::File; +use IO::Handle; +use String::CRC32 qw(crc32); + +=pod + +=head1 NAME + +Cyrus::IndexFile - A pure perl interface to the "cyrus.index" file +format as generated by Cyrus IMAPd. + +=head1 EXAMPLES + + use Cyrus::IndexFile; + + # Note: requires IO::File::fcntl module installed for locking support + my $index = Cyrus::IndexFile->new_file("$path/cyrus.index", ['lock_ex', 5]); + + print "EXISTS: " . $index->header('Exists') . "\n"; + while (my $record = $index->next_record_hash()) { + print "$record->{Uid}: $record->{MessageGuid} $record->{Size}\n"; + } + +=head1 SUPPORTED FORMAT VERSIONS + + Definitions: + ============ + + * int32 4 - 32 bit value taking 4 octets on disk. Visible in perl as an integer + * int64 8 - 64 bit value taking 8 octets on disk. Visible in perl as an integer + * time_t 4 - same as int32 + * bitmap N - a bitmap taking up N octets on disk. Visible in perl as a string of 1s and 0s. + * hex N - a big value taking up N octets on disk. Visible in perl as a hexadecimal string (0-9a-f) + + These values can be referenced by name using the hash API, or by index using the array API. + You can also use the 'raw' API to get the record in on-disk format. + + All numbers are in network byte order as per Cyrus standard encoding. Bitmap and hex values are + layed out as octets on disk and encoded directly in order. + + Version 9: + ========== + + Header: + 0: Generation int32 4 + 1: Format int32 4 + 2: MinorVersion int32 4 + 3: StartOffset int32 4 + 4: RecordSize int32 4 + 5: Exists int32 4 + 6: LastAppenddate time_t 4 + 7: LastUid int32 4 + 8: QuotaUsed int64 8 + 9: Pop3LastLogin time_t 4 + 10: UidValidity int32 4 + 11: Deleted int32 4 + 12: Answered int32 4 + 13: Flagged int32 4 + 14: Options bitmap 4 + 15: LeakedCache int32 4 + 16: HighestModseq int64 8 + 17: Spare0 int32 4 + 18: Spare1 int32 4 + 19: Spare2 int32 4 + 20: Spare3 int32 4 + 21: Spare4 int32 4 + + Record: + 0: Uid int32 4 + 1: InternalDate time_t 4 + 2: SentDate time_t 4 + 3: Size int32 4 + 4: HeaderSize int32 4 + 5: ContentOffset int32 4 + 6: CacheOffset int32 4 + 7: LastUpdated time_t 4 + 8: SystemFlags bitmap 4 + 9: UserFlags bitmap 16 + 10: ContentLines int32 4 + 11: CacheVersion int32 4 + 12: MessageUuid hex 12 + 13: Modseq int64 8 + + Version 10: + =========== + + Header: + 0: Generation int32 4 + 1: Format int32 4 + 2: MinorVersion int32 4 + 3: StartOffset int32 4 + 4: RecordSize int32 4 + 5: Exists int32 4 + 6: LastAppenddate time_t 4 + 7: LastUid int32 4 + 8: QuotaUsed int64 8 + 9: Pop3LastLogin time_t 4 + 10: UidValidity int32 4 + 11: Deleted int32 4 + 12: Answered int32 4 + 13: Flagged int32 4 + 14: Options bitmap 4 + 15: LeakedCache int32 4 + 16: HighestModseq int64 8 + 17: Spare0 int32 4 + 18: Spare1 int32 4 + 19: Spare2 int32 4 + 20: Spare3 int32 4 + 21: Spare4 int32 4 + + Record: + 0: Uid int32 4 + 1: InternalDate time_t 4 + 2: SentDate time_t 4 + 3: Size int32 4 + 4: HeaderSize int32 4 + 5: ContentOffset int32 4 + 6: CacheOffset int32 4 + 7: LastUpdated time_t 4 + 8: SystemFlags bitmap 4 + 9: UserFlags bitmap 16 + 10: ContentLines int32 4 + 11: CacheVersion int32 4 + 12: MessageGuid hex 20 + 13: Modseq int64 8 + +SKIPPED VERSION 11 - Fastmail internal only + + Version 12: + =========== + + Header: + 0: Generation int32 4 + 1: Format int32 4 + 2: MinorVersion int32 4 + 3: StartOffset int32 4 + 4: RecordSize int32 4 + 5: Exists int32 4 + 6: LastAppenddate time_t 4 + 7: LastUid int32 4 + 8: QuotaUsed int64 8 + 9: Pop3LastLogin time_t 4 + 10: UidValidity int32 4 + 11: Deleted int32 4 + 12: Answered int32 4 + 13: Flagged int32 4 + 14: Options bitmap 4 + 15: LeakedCache int32 4 + 16: HighestModseq int64 8 + 17: DeletedModseq int64 8 + 18: Exists int32 4 + 19: FirstExpunged time_t 4 + 20: LastCleanup time_t 4 + 21: HeaderFileCRC int32 4 + 22: SyncCRC int32 4 + 23: RecentUid int32 4 + 24: RecentTime time_t 4 + 25: Spare0 int32 4 + 26: Spare1 int32 4 + 27: Spare2 int32 4 + 28: HeaderCRC int32 4 + + Record: + 0: Uid int32 4 + 1: InternalDate time_t 4 + 2: SentDate time_t 4 + 3: Size int32 4 + 4: HeaderSize int32 4 + 5: GmTime time_t 4 + 6: CacheOffset int32 4 + 7: LastUpdated time_t 4 + 8: SystemFlags bitmap 4 + 9: UserFlags bitmap 16 + 10: ContentLines int32 4 + 11: CacheVersion int32 4 + 12: MessageGuid hex 20 + 13: Modseq int64 8 + 14: CacheCRC int32 4 + 15: RecordCRC int32 4 + + Version 13: + =========== + + Header: + 0: Generation int32 4 + 1: Format int32 4 + 2: MinorVersion int32 4 + 3: StartOffset int32 4 + 4: RecordSize int32 4 + 5: Exists int32 4 + 6: LastAppenddate time_t 4 + 7: LastUid int32 4 + 8: QuotaUsed int64 8 + 9: Pop3LastLogin time_t 4 + 10: UidValidity int32 4 + 11: Deleted int32 4 + 12: Answered int32 4 + 13: Flagged int32 4 + 14: Options bitmap 4 + 15: LeakedCache int32 4 + 16: HighestModseq int64 8 + 17: DeletedModseq int64 8 + 18: Exists int32 4 + 19: FirstExpunged time_t 4 + 20: LastCleanup time_t 4 + 21: HeaderFileCRC int32 4 + 22: SyncCRC int32 4 + 23: RecentUid int32 4 + 24: RecentTime time_t 4 + 25: Spare0 int32 4 + 26: Spare1 int32 4 + 27: Spare2 int32 4 + 28: HeaderCRC int32 4 + + Record: + 0: Uid int32 4 + 1: InternalDate time_t 4 + 2: SentDate time_t 4 + 3: Size int32 4 + 4: HeaderSize int32 4 + 5: GmTime time_t 4 + 6: CacheOffset int32 4 + 7: LastUpdated time_t 4 + 8: SystemFlags bitmap 4 + 9: UserFlags bitmap 16 + 10: ContentLines int32 4 + 11: CacheVersion int32 4 + 12: MessageGuid hex 20 + 13: Modseq int64 8 + 14: CID hex 8 + 15: CacheCRC int32 4 + 16: RecordCRC int32 4 + +=cut + +# Set up header and record formatting information {{{ + +my $VersionFormats = { + 9 => { + HeaderSize => 96, + _make_fields('Header',<<EOF), +Generation int32 4 +Format int32 4 +MinorVersion int32 4 +StartOffset int32 4 +RecordSize int32 4 +Exists int32 4 +LastAppenddate time_t 4 +LastUid int32 4 +QuotaUsed int64 8 +Pop3LastLogin time_t 4 +UidValidity int32 4 +Deleted int32 4 +Answered int32 4 +Flagged int32 4 +Options bitmap 4 +LeakedCache int32 4 +HighestModseq int64 8 +HighestModseq int64 8 +Spare0 int32 4 +Spare1 int32 4 +Spare2 int32 4 +Spare3 int32 4 +Spare4 int32 4 +EOF + RecordSize => 80, # defined in file too, check it! + _make_fields('Record', <<EOF), +Uid int32 4 +InternalDate time_t 4 +SentDate time_t 4 +Size int32 4 +HeaderSize int32 4 +ContentOffset int32 4 +CacheOffset int32 4 +LastUpdated time_t 4 +SystemFlags bitmap 4 +UserFlags bitmap 16 +ContentLines int32 4 +CacheVersion int32 4 +MessageUuid hex 12 +Modseq int64 8 +EOF + }, + 10 => { + HeaderSize => 96, + _make_fields('Header',<<EOF), +Generation int32 4 +Format int32 4 +MinorVersion int32 4 +StartOffset int32 4 +RecordSize int32 4 +Exists int32 4 +LastAppenddate time_t 4 +LastUid int32 4 +QuotaUsed int64 8 +Pop3LastLogin time_t 4 +UidValidity int32 4 +Deleted int32 4 +Answered int32 4 +Flagged int32 4 +Options bitmap 4 +LeakedCache int32 4 +HighestModseq int64 8 +Spare0 int32 4 +Spare1 int32 4 +Spare2 int32 4 +Spare3 int32 4 +Spare4 int32 4 +EOF + RecordSize => 88, # defined in file too, check it! + _make_fields('Record', <<EOF), +Uid int32 4 +InternalDate time_t 4 +SentDate time_t 4 +Size int32 4 +HeaderSize int32 4 +ContentOffset int32 4 +CacheOffset int32 4 +LastUpdated time_t 4 +SystemFlags bitmap 4 +UserFlags bitmap 16 +ContentLines int32 4 +CacheVersion int32 4 +MessageGuid hex 20 +Modseq int64 8 +EOF + }, + 11 => { + HeaderSize => 96, + _make_fields('Header',<<EOF), +Generation int32 4 +Format int32 4 +MinorVersion int32 4 +StartOffset int32 4 +RecordSize int32 4 +Exists int32 4 +LastAppenddate time_t 4 +LastUid int32 4 +QuotaUsed int64 8 +Pop3LastLogin time_t 4 +UidValidity int32 4 +Deleted int32 4 +Answered int32 4 +Flagged int32 4 +Options bitmap 4 +LeakedCache int32 4 +HighestModseq int64 8 +Spare0 int32 4 +Spare1 int32 4 +Spare2 int32 4 +Spare3 int32 4 +HeaderCrc int32 4 +EOF + RecordSize => 96, # defined in file too, check it! + _make_fields('Record', <<EOF), +Uid int32 4 +InternalDate time_t 4 +SentDate time_t 4 +Size int32 4 +HeaderSize int32 4 +ContentOffset int32 4 +CacheOffset int32 4 +LastUpdated time_t 4 +SystemFlags bitmap 4 +UserFlags bitmap 16 +ContentLines int32 4 +CacheVersion int32 4 +MessageGuid hex 20 +Modseq int64 8 +CacheCrc int32 4 +RecordCrc int32 4 +EOF + }, + 12 => { + HeaderSize => 128, + _make_fields('Header',<<EOF), +Generation int32 4 +Format int32 4 +MinorVersion int32 4 +StartOffset int32 4 +RecordSize int32 4 +NumRecords int32 4 +LastAppenddate time_t 4 +LastUid int32 4 +QuotaUsed int64 8 +Pop3LastLogin time_t 4 +UidValidity int32 4 +Deleted int32 4 +Answered int32 4 +Flagged int32 4 +Options bitmap 4 +LeakedCache int32 4 +HighestModseq int64 8 +DeletedModseq int64 8 +Exists int32 4 +FirstExpunged time_t 4 +LastCleanup time_t 4 +HeaderFileCRC int32 4 +SyncCRC int32 4 +RecentUid int32 4 +RecentTime time_t 4 +Spare0 int32 4 +Spare1 int32 4 +Spare2 int32 4 +HeaderCrc int32 4 +EOF + RecordSize => 96, # defined in file too, check it! + _make_fields('Record', <<EOF), +Uid int32 4 +InternalDate time_t 4 +SentDate time_t 4 +Size int32 4 +HeaderSize int32 4 +GmTime time_t 4 +CacheOffset int32 4 +LastUpdated time_t 4 +SystemFlags bitmap 4 +UserFlags bitmap 16 +ContentLines int32 4 +CacheVersion int32 4 +MessageGuid hex 20 +Modseq int64 8 +CacheCrc int32 4 +RecordCrc int32 4 +EOF + }, + 13 => { + HeaderSize => 128, + _make_fields('Header',<<EOF), +Generation int32 4 +Format int32 4 +MinorVersion int32 4 +StartOffset int32 4 +RecordSize int32 4 +NumRecords int32 4 +LastAppenddate time_t 4 +LastUid int32 4 +QuotaUsed int64 8 +Pop3LastLogin time_t 4 +UidValidity int32 4 +Deleted int32 4 +Answered int32 4 +Flagged int32 4 +Options bitmap 4 +LeakedCache int32 4 +HighestModseq int64 8 +DeletedModseq int64 8 +Exists int32 4 +FirstExpunged time_t 4 +LastCleanup time_t 4 +HeaderFileCRC int32 4 +SyncCRC int32 4 +RecentUid int32 4 +RecentTime time_t 4 +Pop3ShowAfter int32 4 +QuotaAnnotUsed int32 4 +SyncCRCVersion int32 4 +HeaderCrc int32 4 +EOF + RecordSize => 104, # defined in file too, check it! + _make_fields('Record', <<EOF), +Uid int32 4 +InternalDate time_t 4 +SentDate time_t 4 +Size int32 4 +HeaderSize int32 4 +GmTime time_t 4 +CacheOffset int32 4 +LastUpdated time_t 4 +SystemFlags bitmap 4 +UserFlags bitmap 16 +ContentLines int32 4 +CacheVersion int32 4 +MessageGuid hex 20 +Modseq int64 8 +CID hex 8 +CacheCrc int32 4 +RecordCrc int32 4 +EOF + }, +}; + +my %SystemFlagMap = ( + 0 => "\\Answered", + 1 => "\\Flagged", + 2 => "\\Deleted", + 3 => "\\Draft", + 4 => "\\Seen", + 29 => "[ARCHIVED]", + 30 => "[UNLINKED]", + 31 => "[EXPUNGED]", +); + +# parse our the plaintext field definitions into a useful datastructure +sub _make_fields { + my $prefix = shift; + my $string = shift; + + my @lines = grep { m/\S/ } split /\n/, $string; + + my @names; + my @items; + my @packitems; + my $Pos = 0; + my $Num = 0; + foreach my $line (@lines) { + my ($Name, $Type, $Size) = split /\s+/, $line; + + push @names, $Name; + push @items, [$Name, $Type, $Size, $Num, $Pos]; + push @packitems, _make_pack($Type, $Size); + + $Pos += $Size; + $Num++; + } + + return ( + $prefix . 'Names' => { map { $names[$_] => $_ } 0..$#names }, + $prefix . 'Fields' => \@items, + $prefix . 'Pack' => join("", @packitems), + ); +} + +# build the pack/unpack expression for a single field +sub _make_pack { + my $format = shift; + my $size = shift; + if ($format eq 'int32' or $format eq 'time_t') { + return 'N'; + } + elsif ($format eq 'int64') { # ignore start.. + return 'x[N]N'; + } + elsif ($format eq 'bitmap') { + return 'B' . (8 * $size); + } + elsif ($format eq 'hex') { + return 'H' . (2 * $size); + } +} + +# end format definitions +# }}} + +=head1 PUBLIC API + +=over + +=item Cyrus::IndexFile->new($fh) + +Build a new Cyrus::IndexFile object from a filehandle. The handle is not +required to be seekable, so make sure you have rewound it before use. + + seek($fh, 0, 0); + my $index = Cyrus::IndexFile->new($fh); + +This function reads the header from the file and returns a Cyrus::IndexFile +object. The filehandle will be pointing at the start of the first record. + +If there is a problem, then the position of the filehandle is undefined +(though probably either at 12 bytes or the end of the header) and the +function will "die". + +Causes of death: + + * unable to read a full header's length of data from the file + * version of the file is not one of the supported versions + +=cut + +sub new { + my $class = shift; + my $handle = shift; + + my $buf; + + # read initial header information to determine version + my $read = sysread($handle, $buf, 12); + die "Unable to read header information\n" + unless $read == 12; + + # version is always at this offset! + my $version = unpack('N', substr($buf, 8)); + + # check that it's a supported version + my $frm = $VersionFormats->{$version} + || die "Unknown header format $version\n"; + + # read the rest of the header (length depends on version) + sysread($handle, $buf, $frm->{HeaderSize} - 12, 12); + my $Self = bless { + @_, + version => $version, + handle => $handle, + format => $frm, + rawheader => $buf, + recno => 0, + }, ref($class) || $class; + + $Self->{header} = $Self->_header_b2h($buf); + die "Unable to parse header" unless $Self->{header}; + + return $Self; +} + +=item Cyrus::IndexFile->new_file($filename, $lockopts) + +Open the file to read, optionally locking it with IO::File::fcntl. If you +pass a scalar for lockopts then it will be locked with ['lock_ex'], otherwise +you can pass a tuple, e.g. ['lock_ex', 5] for a 5 second timeout. + +This function will die if it can't open or lock the file. On success, it +calls $class->new() with the filehandle. + +=cut + +sub new_file { + my $class = shift; + my $filename = shift; + my $lockopts = shift; + + my $fh; + if ($lockopts) { + require 'IO/File/fcntl.pm' || die "can't lock without IO::File::fcntl module"; + $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY'; + $fh = IO::File::fcntl->new($filename, '+<', @$lockopts) + || die "Can't open $filename for locked read: $!"; + } else { + $fh = IO::File->new("< $filename") + || die "Can't open $filename for read: $!"; + } + + return $class->new($fh, @_); +} + +=item Cyrus::IndexFile->new_empty($version) + +Create a new empty index file with the specified version. This is +useful when you want to generate a new index file, as you can use +the write_record function and set header fields on the new object. + +=cut + +sub new_empty { + my $class = shift; + my $version = shift; + + # check that the version is supported + my $frm = $VersionFormats->{$version} + || die "unknown version $version"; + + my $Self = bless { + @_, + version => $version, + format => $frm, + }, ref($class) || $class; + + return $Self; +} + +=item $index->stream_copy($outfh, $decider, %Opts) + +Currently broken! Supposed to copy this file into the output filehandle. + +NOTE: outfh must be seekable, as we write an initial header record with +Exists == 0, then update the header at the end with a new Exists and a +new LastUpdated. + +=cut + +sub stream_copy { + my $Self = shift; + my $outfh = shift; + my $decide = shift; + my %Opts = @_; + + my $out = $Self->new_empty($Opts{version} || $Self->{version}); + + my $newheader = $Self->header_copy(); + if ($Opts{headerfields}) { + foreach my $field (keys %{$Opts{headerfields}}) { + $newheader->{$field} = $Opts{headerfields}{$field}; + } + } + + # initially empty + $newheader->{NumRecords} = 0; + # Important! Otherwise you get versions out of skew! + $newheader->{MinorVersion} = $out->{version}; + $newheader->{RecordSize} = $out->{format}{RecordSize}; + $out->write_header($outfh, $newheader); + + $Self->reset(); + while (my $record = $Self->next_record()) { + if ($decide->($newheader, $record)) { + $newheader->{NumRecords}++; + $out->write_record($outfh, $record); + } + } + + # update exists and last updated + $newheader->{LastUpdated} = time(); + sysseek($outfh, 0, 0); + $out->write_header($outfh, $newheader); +} + +=item $index->header() + +=item $index->header_hash() + +Returns a hash reference of the entire header + +=item $index->header($field) + +Returns just the single named field from the header. Dies if there is no +field with that name in the header. + +=item $index->header_array($field) + +Returns an array reference with the values in the order given in the version +information above. + +=item $index->header_raw() + +Returns the raw packed header as it is on disk. + +=cut + +sub header { + my $Self = shift; + my $Field = shift; + + if ($Field) { + die "No such header field $Field\n" unless exists $Self->{header}{$Field}; + return $Self->{header}{$Field}; + } + + return $Self->{header}; +} + +sub header_array { + my $Self = shift; + return $Self->_header_h2a($Self->{header}); +} + +sub header_hash { + my $Self = shift; + return $Self->{header}; +} + +sub header_raw { + my $Self = shift; + return $Self->{rawheader}; +} + +=item $index->header_copy() + +Returns a hashref the same as header_hash, but it's "non live", so you can +make destructive changes without affecting the original. + +=cut + +sub header_copy { + my $Self = shift; + my $orig = $Self->{header}; + return { %$orig }; +} + +=item $index->reset($num) + +Deletes the cached 'current record' and seeks back to the given record +number, or the end of the header (record 0) if no number given. + +Requires the input filehandle to be seekable. + +=cut + +sub reset { + my $Self = shift; + my $num = shift || 0; + + my $NumRecords = $Self->{header}{MinorVersion} < 12 ? + $Self->{header}{Exists} : $Self->{header}{NumRecords}; + + die "Invalid record $num (must be >= 0 and <= $NumRecords" + unless ($num >= 0 and $num <= $NumRecords); + + my $HeaderSize = $Self->{format}{HeaderSize}; + my $RecordSize = $Self->{format}{RecordSize}; + + sysseek($Self->{handle}, $HeaderSize + ($num * $RecordSize), 0) + || die "unable to seek on this filehandle"; + + $Self->{recno} = $num; + + delete $Self->{record}; + delete $Self->{rawrecord}; + delete $Self->{checksum_failure}; +} + +=item $index->next_record() +=item $index->next_record_hash() + +Read the next record from the file and parse it in to a hash reference +per the format of the index file. + +This works even on non-seekable files. + +Returns undef when there are no more records (until you call reset) + +=item $index->next_record_array() + +As above, but returns the array in the format order. + +More efficient, as the hash doesn't need to be created. + +=item $index->next_record_raw() + +Returns the raw bytes of the index file. Most efficient, as no unpacking +is done, but then you have to deal with all the version checking and +offsets yourself. + +=cut + +sub next_record { + my $Self = shift; + $Self->next_record_raw(); + return $Self->record(@_); +} + +sub next_record_hash { + my $Self = shift; + $Self->next_record_raw(); + return $Self->record_hash(@_); +} + +sub next_record_array { + my $Self = shift; + $Self->next_record_raw(); + return $Self->record_array(@_); +} + +sub next_record_raw { + my $Self = shift; + + delete $Self->{record}; + delete $Self->{checksum_failure}; + + # use direct access for speed + my $NumRecords = $Self->{header}{MinorVersion} < 12 ? + $Self->{header}{Exists} : $Self->{header}{NumRecords}; + my $RecordSize = $Self->{header}{RecordSize}; + + return undef unless $RecordSize; + + if ($Self->{recno} < $NumRecords) { + my $res = sysread($Self->{handle}, $Self->{rawrecord}, $RecordSize); + die "Failed to read entire record" unless $RecordSize == $res; + # rewrite if passed so save the allocation cost + $Self->{recno}++; + return $Self->{rawrecord}; + } + else { + delete $Self->{rawrecord}; + return undef; # no more records! + } +} + +=item $index->record() +=item $index->record_hash() + +Returns the "current" record, i.e. the last record returned by +next_record_*() as a hash reference. + +Returns undef if there is no current record (either next_record has never +been called, reset has just been called, or the file is finished) + +=item $index->record_array() + +As above, but return the version-dependant arrayref or undef + +=item $index->record_raw() + +As above, but return just the raw record bytes as a string or undef + +=item $index->record($field) + +If a field name is given, return that field only from the record, or die if +it doesn't exist in this version. + +Returns undef if there is no current record. No legitimate field ever +returns undef, because there's no such concept in the datastructure. + +=cut + +sub record { + my $Self = shift; + my $Field = shift; + + my $record = $Self->record_hash(); + return undef unless $record; + + if ($Field) { + die "No such record field $Field\n" unless exists $record->{$Field}; + return $record->{$Field}; + } + + return $record; +} + +sub record_hash { + my $Self = shift; + unless (exists $Self->{record}{hash}) { + $Self->{record}{hash} = $Self->_record_a2h($Self->record_array(@_)); + } + return $Self->{record}{hash}; +} + +sub record_array { + my $Self = shift; + unless (exists $Self->{record}{array}) { + $Self->{record}{array} = $Self->_record_b2a($Self->{rawrecord}); + } + return $Self->{record}{array}; +} + +sub record_raw { + my $Self = shift; + return $Self->{rawrecord}; +} + +=item $index->system_flags([$Key]) + +Returns a hash of the system flags set on the current record, or just the +named flag if a Key is passed. + +=cut + +sub system_flags { + my $Self = shift; + my $Field = shift; + + my @sfdata = reverse split //, $Self->record('SystemFlags'); + my %hash; + foreach my $key (0..$#sfdata) { + next unless $sfdata[$key]; + $hash{$SystemFlagMap{$key} || $key} = $key; + } + + if ($Field) { + return $hash{$Field}; + } + + return wantarray ? %hash : \%hash; +} + +=item $index->flagslist($Header) + +Given a Cyrus::HeaderFile object to name the UserFlags, return an array of all +flags, both SystemFlags and UserFlags set on the record. + +=cut +sub flagslist { + my $Self = shift; + my $Header = shift; + my @flags; + + # 32 bit sets + my @sfdata = reverse split //, $Self->record('SystemFlags'); + foreach my $i (0..$#sfdata) { + next unless $sfdata[$i]; + push @flags, $SystemFlagMap{$i} || $i; + } + + if ($Header) { + my $userflags = $Header->header('Flags'); + my @ufdata = split //, $Self->record('UserFlags'); + foreach my $base (0, 32, 64, 96) { + foreach my $i (0..31) { + my $f = $userflags->[$base+31-$i]; + push @flags, $f if ($f and $ufdata[$base+$i]); + } + } + } + + return wantarray ? @flags : \@flags; +} + +=item $index->field_number($Field) + +Return the field number in a record array for the named field, or die +if there isn't one. + +=cut + +sub field_number { + my $Self = shift; + my $Field = shift; + my $names = $Self->{format}{RecordNames}; + die "No such record field $Field\n" unless exists $names->{$Field}; + return $names->{$Field}; +} + +=item $index->write_header($fh, $header) + +Writes a header to $fh - you need to make sure it's seeked to the start (can be used on a non-seekable filehandle) + +$header can be in array, hash or buffer format + +=cut + +sub write_header { + my $Self = shift; + my $fh = shift; + my $header = shift; + + my $buf = $Self->_make_header($header); + + syswrite($fh, $buf); +} + +=item $index->append_record($record) + +Appends the record (can be hash, array or buf) to the current file. Needs the filehandle to be seekable. Uses "Exists" from the header to find the position, so don't mess it up! + +Also seeks back to the header and rewrites it with exists incremented by one. + +=cut + +sub append_record { + my $Self = shift; + my $record = shift; + + my $NumRecords = $Self->{header}{MinorVersion} < 12 ? + $Self->{header}{Exists} : $Self->{header}{NumRecords}; + + $Self->reset($NumRecords); + $Self->write_record($Self->{handle}, $record); + + # extend the header: + # XXX - sysflags + my $header = $Self->header(); + $header->{NumRecords}++; + $Self->rewrite_header($header); +} + +sub rewrite_header { + my $Self = shift; + my $header = shift || $Self->header(); + + sysseek($Self->{handle}, 0, 0); + $Self->write_header($Self->{handle}, $header); + + $Self->reset(); # remove any cache and update the seek pointer +} + +=item $index->rewrite_record($record, $num) + +Rewrite the record at position given by $num with the record (hash, array or buf) passed. + +=cut + +sub rewrite_record { + my $Self = shift; + my $record = shift; + my $num = @_ ? shift : ($Self->{recno} - 1); + + $Self->reset($num); + + $Self->write_record($Self->{handle}, $record); + + $Self->{recno}++; +} + +=item $index->write_record($fh, $record, $num) + +Write the record to the new filehandle $fh. If $num is not given then it doesn't need to be seekable. + +XXX - $num support not done yet +=cut + +sub write_record { + my $Self = shift; + my $fh = shift; + my $record = shift; + my $num = shift; # XXX - seek? + + my $buf = $Self->_make_record($record); + + syswrite($fh, $buf); +} + +=item $index->merge_indexes($target, @extras) + +XXX - broken anyway. The purpose of this function is to allow multiple index files to combined into one (say, an expunged file and an index file) + +=cut + +sub merge_indexes { + my $Self = shift; + my $target = shift; + my @extras = shift; + + # copy the current header first + my $targetpos = tell($target); + my $header = $Self->header(); + # reset some stuff + $header->{NumRecords} = 0; + $header->{LastAppenddate} = 0; + $header->{LastUid} = 0; + $header->{QuotaUsed} = 0; + $header->{Deleted} = 0; + $header->{Answered} = 0; + $header->{Flagged} = 0; + $header->{HighestModseq} = 0; + $Self->write_header($target, $header); + + my @all = ($Self, @extras); + + my @records = map { $_->next_record() } @all; + + my $nextuid = -1; + + while ($nextuid) { + my $this; + my $higheruid; + + # read the first record of all lists + foreach my $n (0..$#all) { + next unless $records[$n]; + if ($records[$n]{Uid} == $nextuid) { + # algorithm: keep most recently modified + if (not $this or $this->{LastModified} < $records[$n]{LastModified}) { + $this = $records[$n]{LastModified}; + } + # step forwards + $records[$n] = $all[$n]->next_record(); + } + # find the minimum now + if (not $higheruid or $higheruid > $records[$n]{Uid}) { + $higheruid = $records[$n]{Uid}; + } + } + + # write out the best record found + if ($this) { + $Self->write_record($target, $this); + $header->{NumRecords}++; + # XXX - to make everything else work, we probably need to reconstruct or + # put the entire logic here! + } + + # move along + $nextuid = $higheruid; + } + + # move back to the start of this file and re-write the header + seek($target, $targetpos, 0); + $Self->write_header($target, $header); +} + +=item $index->header_dump() + +=item $index->record_dump() + +=item $index->header_longdump() + +=item $index->record_longdump() + +=item $index->header_undump() + +=item $index->record_undump() + +Dump the headers and records in either space separated fields or named lines with a blank line between for long. + +The "undump" option is able to parse the space separated format, allowing pipe to a standard unix tool to +process the records, and then re-parse them back into a binary index file. + +=cut + +sub header_dump { + my $Self = shift; + my $array = $Self->header_array(); + return join(' ', @$array); +} + +sub header_longdump { + my $Self = shift; + my $array = $Self->header_array(); + my @data; + my $frm = $Self->{format}{HeaderFields}; + foreach my $field (0..$#$frm) { + my $name = $frm->[$field][0]; + my $val = $array->[$field]; + $val = sprintf("%08x", $val) if $name =~ m/Crc$/; + push @data, "$name: $val"; + } + return join("\n", @data, ''); +} + +sub header_undump { + my $Self = shift; + my $string = shift; + my @items = split ' ', $string; + return \@items; +} + +sub record_dump { + my $Self = shift; + my $array = $Self->record_array(); + return join(' ', @$array); +} + +sub record_longdump { + my $Self = shift; + my $array = $Self->record_array(); + my @data; + my $frm = $Self->{format}{RecordFields}; + foreach my $field (0..$#$frm) { + my $name = $frm->[$field][0]; + my $val = $array->[$field]; + $val = sprintf("%08x", $val) if $name =~ m/Crc$/; + push @data, "$name: $val"; + } + return join("\n", @data, ''); +} + +sub record_undump { + my $Self = shift; + my $string = shift; + my @items = split ' ', $string; + return \@items; +} + +# INTERNAL METHODS + +sub _make_header { + my $Self = shift; + my $ds = shift; + + my $ref = ref($ds); + + # check what sort of format it is: + + # scalar - already a buffer + return $ds unless $ref; + + # array + return $Self->_header_a2b($ds) if ref($ds) eq 'ARRAY'; + + # must be hash + return $Self->_header_h2b($ds); +} + +sub _make_record { + my $Self = shift; + my $ds = shift; + + my $ref = ref($ds); + + # check what sort of format it is: + + # scalar - already a buffer + return $ds unless $ref; + + # array + return $Self->_record_a2b($ds) if ref($ds) eq 'ARRAY'; + + # must be hash + return $Self->_record_h2b($ds); +} + +#################### +# Header Conversions + +sub _header_b2h { + my $Self = shift; + my $buf = shift; + return undef unless $buf; + + my $array = $Self->_header_b2a($buf); + my $hash = $Self->_header_a2h($array); + + return $hash; +} + +sub _header_b2a { + my $Self = shift; + my $buf = shift; + return undef unless $buf; + + my @array = unpack($Self->{format}{HeaderPack}, $buf); + + # check checksum match! + if ($Self->{version} >= 11) { + my $Header = $Self->{format}{HeaderFields}[$Self->{format}{HeaderNames}{HeaderCrc}]; + my $crc = crc32(substr($buf, 0, $Header->[4])); + if ($array[$Header->[3]] != $crc) { + $Self->{checksum_failure} = 1; + warn "Header CRC Failure $array[$Header->[3]] != $crc"; + die "Header CRC Failure $array[$Header->[3]] != $crc" + if $Self->{strict_crc}; + } + } + + return \@array; +} + +sub _header_h2b { + my $Self = shift; + my $hash = shift; + return undef unless $hash; + + my $array = $Self->_header_h2a($hash); + my $buf = $Self->_header_a2b($array); + + return $buf; +} + +sub _header_a2b { + my $Self = shift; + my $array = shift; + return undef unless $array; + + my $buf = pack($Self->{format}{HeaderPack}, @$array); + + if ($Self->{version} >= 11) { + my $Header = $Self->{format}{HeaderFields}[$Self->{format}{HeaderNames}{HeaderCrc}]; + my $crc = crc32(substr($buf, 0, $Header->[4])); + substr($buf, $Header->[4]) = pack('N', $crc); + } + + return $buf; +} + +sub _header_a2h { + my $Self = shift; + my $array = shift; + return undef unless $array; + + my %res; + my $frm = $Self->{format}{HeaderFields}; + for (0..$#$frm) { + $res{$frm->[$_][0]} = $array->[$_]; + } + + return \%res; +} + +sub _header_h2a { + my $Self = shift; + my $hash = shift; + return undef unless $hash; + + my @array; + my $frm = $Self->{format}{HeaderFields}; + for (0..$#$frm) { + $array[$_] = $hash->{$frm->[$_][0]}; + } + + return \@array; +} + +#################### +# Record conversions + +sub _record_h2b { + my $Self = shift; + my $hash = shift; + return undef unless $hash; + + my $array = $Self->_record_h2a($hash); + my $buf = $Self->_record_a2b($array); + + return $buf; +} + +sub _record_a2b { + my $Self = shift; + my $array = shift; + return undef unless $array; + + my $buf = pack($Self->{format}{RecordPack}, @$array); + + if ($Self->{version} >= 11) { + my $Record = $Self->{format}{RecordFields}[$Self->{format}{RecordNames}{RecordCrc}]; + my $crc = crc32(substr($buf, 0, $Record->[4])); + substr($buf, $Record->[4]) = pack('N', $crc); + } + + return $buf; +} + +sub _record_b2h { + my $Self = shift; + my $buf = shift; + return undef unless $buf; + + my $array = $Self->_record_b2a($buf); + my $hash = $Self->_record_a2h($array); + + return $hash; +} + +sub _record_b2a { + my $Self = shift; + my $buf = shift; + return undef unless $buf; + + my @array = unpack($Self->{format}{RecordPack}, $buf); + + # check checksum match! + if ($Self->{version} >= 11) { + my $Record = $Self->{format}{RecordFields}[$Self->{format}{RecordNames}{RecordCrc}]; + my $crc = crc32(substr($buf, 0, $Record->[4])); + if ($array[$Record->[3]] != $crc) { + $Self->{checksum_failure} = 1; + warn "Record CRC Failure ($Self->{recno}) $array[$Record->[3]] != $crc"; + die "Record CRC Failure ($Self->{recno}) $array[$Record->[3]] != $crc" + if $Self->{strict_crc}; + } + } + + return \@array; +} + +sub _record_a2h { + my $Self = shift; + my $array = shift; + return undef unless $array; + + my %res; + my $frm = $Self->{format}{RecordFields}; + for (0..$#$frm) { + $res{$frm->[$_][0]} = $array->[$_]; + } + + return \%res; +} + +sub _record_h2a { + my $Self = shift; + my $hash = shift; + return undef unless $hash; + + my @array; + my $frm = $Self->{format}{RecordFields}; + for (0..$#$frm) { + $array[$_] = $hash->{$frm->[$_][0]}; + } + + return \@array; +} + +=back + +=head1 AUTHOR AND COPYRIGHT + +Bron Gondwana <brong@fastmail.fm> - Copyright 2008 FastMail + +Licenced under the same terms as Cyrus IMAPd. + +=cut + +1;
View file
cyrus-imapd-2.5.tar.gz/perl/imap/examples/index_uids.pl
Added
@@ -0,0 +1,76 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Getopt::Std; + +use Cyrus::IndexFile; +use Cyrus::CacheFile; +use Cyrus::HeaderFile; + +my %Opts; +getopts('CHcdDu:', \%Opts); + +my $file = shift || die "Usage: $0 <indexfile>\n"; +unless (-f $file) { + die "File doesn't exist $file\n"; +} + +my $cfile = $Opts{C}; +my $hfile = $Opts{H}; +unless ($cfile) { + $cfile = $file; + $cfile =~ s/index$/cache/; +} +unless ($hfile) { + $hfile = $file; + $hfile =~ s/index$/header/; +} +my $index = Cyrus::IndexFile->new_file($file); +my $cache; +if ($Opts{c}) { + $cache = Cyrus::CacheFile->new_file($cfile); +} +my $headerfile = Cyrus::HeaderFile->new_file($hfile); +my $header = $index->header(); +unless ($Opts{u}) { + if ($Opts{d}) { + print $index->header_dump() . "\n"; + } elsif ($Opts{D}) { + print $index->header_longdump() . "\n"; + } else { + $header->{NumRecords} ||= $header->{Exists}; + print "V:$header->{MinorVersion} E:$header->{Exists} N:$header->{NumRecords} U:$header->{LastUid} M:$header->{HighestModseq}\n"; + } +} +while (my $r = $index->next_record) { + next if ($Opts{u} and $Opts{u} != $r->{Uid}); + if ($Opts{d}) { + print $index->record_dump() . "\n"; + } + elsif ($Opts{D}) { + my $offset = sysseek($index->{handle}, 0, 1); + print "Offset: $offset\n"; + print $index->record_longdump(); + my @flags = $index->flagslist($headerfile); + print "FLAGS: @flags\n"; + print "\n"; + } + elsif ($header->{MinorVersion} == 9) { + print "$r->{Uid} $r->{MessageUuid} $r->{Size}\n"; + } + elsif ($header->{MinorVersion} < 13) { + my @flags = $index->flagslist(); + print "$r->{Uid}\@$r->{Modseq} $r->{MessageGuid} $r->{Size} (@flags)\n"; + } + else { + my @flags = $index->flagslist($headerfile); + printf "$r->{Uid}\@$r->{Modseq} $r->{MessageGuid} $r->{CID} $r->{Size} (@flags)\n"; + } + if ($Opts{c}) { + $cache->offset($r->{CacheOffset}); + my $r = $cache->next_record(); + print $cache->print_record($r); + print "------------------------------------------------\n"; + } +}
View file
cyrus-imapd-2.5.tar.gz/ptclient/ldap.c
Changed
@@ -131,42 +131,50 @@ }; typedef struct _ptsm { - const char *uri; - int version; - struct timeval timeout; - int size_limit; - int time_limit; - int deref; - int referrals; - int restart; - int scope; - const char *base; - int sasl; - const char *id; - const char *bind_dn; - const char *password; - const char *authz; - const char *mech; - const char *realm; - const char *filter; - const char *sasl_secprops; - int start_tls; - int tls_check_peer; - const char *tls_cacert_file; - const char *tls_cacert_dir; - const char *tls_ciphers; - const char *tls_cert; - const char *tls_key; - int member_method; - const char *user_attribute; - const char *member_attribute; - const char *member_filter; - const char *member_base; - int member_scope; - const char *group_filter; - const char *group_base; - int group_scope; - LDAP *ld; + const char *uri; + int version; + struct timeval timeout; + int size_limit; + int time_limit; + int deref; + int referrals; + int restart; + int scope; + const char *base; + int sasl; + const char *id; + const char *bind_dn; + const char *password; + const char *authz; + const char *mech; + const char *realm; + const char *filter; + const char *sasl_secprops; + int start_tls; + int tls_check_peer; + const char *tls_cacert_file; + const char *tls_cacert_dir; + const char *tls_ciphers; + const char *tls_cert; + const char *tls_key; + int member_method; + const char *user_attribute; + const char *member_attribute; + const char *member_filter; + const char *member_base; + int member_scope; + const char *group_filter; + const char *group_base; + int group_scope; + + /* Used for domain name space -> root dn discovery */ + const char *domain_base_dn; + const char *domain_filter; + const char *domain_name_attribute; + int domain_scope; + const char *domain_result_attribute; + + LDAP *ld; } t_ptsm; #define PTSM_OK 0 @@ -448,11 +456,14 @@ ptsm->uri = (config_getstring(IMAPOPT_LDAP_URI) ? config_getstring(IMAPOPT_LDAP_URI) : config_getstring(IMAPOPT_LDAP_SERVERS)); + ptsm->version = (config_getint(IMAPOPT_LDAP_VERSION) == 2 ? LDAP_VERSION2 : LDAP_VERSION3); ptsm->timeout.tv_sec = config_getint(IMAPOPT_LDAP_TIME_LIMIT); ptsm->timeout.tv_usec = 0; ptsm->restart = config_getswitch(IMAPOPT_LDAP_RESTART); + p = config_getstring(IMAPOPT_LDAP_DEREF); + if (!strcasecmp(p, "search")) { ptsm->deref = LDAP_DEREF_SEARCHING; } else if (!strcasecmp(p, "find")) { @@ -462,10 +473,13 @@ } else { ptsm->deref = LDAP_DEREF_NEVER; } + ptsm->referrals = config_getswitch(IMAPOPT_LDAP_REFERRALS); ptsm->size_limit = config_getint(IMAPOPT_LDAP_SIZE_LIMIT); ptsm->time_limit = config_getint(IMAPOPT_LDAP_TIME_LIMIT); + p = config_getstring(IMAPOPT_LDAP_SCOPE); + if (!strcasecmp(p, "one")) { ptsm->scope = LDAP_SCOPE_ONELEVEL; } else if (!strcasecmp(p, "base")) { @@ -473,18 +487,24 @@ } else { ptsm->scope = LDAP_SCOPE_SUBTREE; } + ptsm->bind_dn = config_getstring(IMAPOPT_LDAP_BIND_DN); ptsm->sasl = config_getswitch(IMAPOPT_LDAP_SASL); ptsm->id = (config_getstring(IMAPOPT_LDAP_ID) ? config_getstring(IMAPOPT_LDAP_ID) : config_getstring(IMAPOPT_LDAP_SASL_AUTHC)); + ptsm->authz = (config_getstring(IMAPOPT_LDAP_AUTHZ) ? config_getstring(IMAPOPT_LDAP_AUTHZ) : config_getstring(IMAPOPT_LDAP_SASL_AUTHZ)); + ptsm->mech = (config_getstring(IMAPOPT_LDAP_MECH) ? config_getstring(IMAPOPT_LDAP_MECH) : config_getstring(IMAPOPT_LDAP_SASL_MECH)); + ptsm->realm = (config_getstring(IMAPOPT_LDAP_REALM) ? config_getstring(IMAPOPT_LDAP_REALM) : config_getstring(IMAPOPT_LDAP_SASL_REALM)); + ptsm->password = (config_getstring(IMAPOPT_LDAP_PASSWORD) ? config_getstring(IMAPOPT_LDAP_PASSWORD) : config_getstring(IMAPOPT_LDAP_SASL_PASSWORD)); + ptsm->start_tls = config_getswitch(IMAPOPT_LDAP_START_TLS); ptsm->tls_check_peer = config_getswitch(IMAPOPT_LDAP_TLS_CHECK_PEER); ptsm->tls_cacert_file = config_getstring(IMAPOPT_LDAP_TLS_CACERT_FILE); @@ -492,12 +512,14 @@ ptsm->tls_ciphers = config_getstring(IMAPOPT_LDAP_TLS_CIPHERS); ptsm->tls_cert = config_getstring(IMAPOPT_LDAP_TLS_CERT); ptsm->tls_key = config_getstring(IMAPOPT_LDAP_TLS_KEY); + p = config_getstring(IMAPOPT_LDAP_MEMBER_METHOD); if (!strcasecmp(p, "filter")) { ptsm->member_method = PTSM_MEMBER_METHOD_FILTER; } else { ptsm->member_method = PTSM_MEMBER_METHOD_ATTRIBUTE; } + p = config_getstring(IMAPOPT_LDAP_MEMBER_SCOPE); if (!strcasecmp(p, "one")) { ptsm->member_scope = LDAP_SCOPE_ONELEVEL; @@ -506,12 +528,15 @@ } else { ptsm->member_scope = LDAP_SCOPE_SUBTREE; } + ptsm->member_filter = config_getstring(IMAPOPT_LDAP_MEMBER_FILTER); ptsm->member_base = config_getstring(IMAPOPT_LDAP_MEMBER_BASE); ptsm->member_attribute = (config_getstring(IMAPOPT_LDAP_MEMBER_ATTRIBUTE) ? config_getstring(IMAPOPT_LDAP_MEMBER_ATTRIBUTE) : config_getstring(IMAPOPT_LDAP_MEMBER_ATTRIBUTE)); + ptsm->user_attribute = (config_getstring(IMAPOPT_LDAP_USER_ATTRIBUTE) ? config_getstring(IMAPOPT_LDAP_USER_ATTRIBUTE) : config_getstring(IMAPOPT_LDAP_USER_ATTRIBUTE)); + p = config_getstring(IMAPOPT_LDAP_GROUP_SCOPE); if (!strcasecmp(p, "one")) { ptsm->group_scope = LDAP_SCOPE_ONELEVEL; @@ -520,16 +545,31 @@ } else { ptsm->group_scope = LDAP_SCOPE_SUBTREE; } + ptsm->group_filter = config_getstring(IMAPOPT_LDAP_GROUP_FILTER); ptsm->group_base = config_getstring(IMAPOPT_LDAP_GROUP_BASE); ptsm->filter = config_getstring(IMAPOPT_LDAP_FILTER); ptsm->base = config_getstring(IMAPOPT_LDAP_BASE); - if (ptsm->version != LDAP_VERSION3 && - (ptsm->sasl || - ptsm->start_tls)) + if (ptsm->version != LDAP_VERSION3 && (ptsm->sasl || ptsm->start_tls)) ptsm->version = LDAP_VERSION3; + ptsm->domain_base_dn = config_getstring(IMAPOPT_LDAP_DOMAIN_BASE_DN); + ptsm->domain_filter = config_getstring(IMAPOPT_LDAP_DOMAIN_FILTER); + ptsm->domain_name_attribute = config_getstring(IMAPOPT_LDAP_DOMAIN_NAME_ATTRIBUTE); + + p = config_getstring(IMAPOPT_LDAP_DOMAIN_SCOPE); + + if (!strcasecmp(p, "one")) { + ptsm->domain_scope = LDAP_SCOPE_ONELEVEL; + } else if (!strcasecmp(p, "base")) { + ptsm->domain_scope = LDAP_SCOPE_BASE; + } else { + ptsm->domain_scope = LDAP_SCOPE_SUBTREE; + } + + ptsm->domain_result_attribute = config_getstring(IMAPOPT_LDAP_DOMAIN_RESULT_ATTRIBUTE); + ptsm->ld = NULL; } @@ -588,6 +628,63 @@ return PTSM_OK; } +static int *ptsmodule_standard_root_dn(const char *domain, const char **result) +{ + /* number of dots */ + int dots; + /* the expected length of the result */ + int root_dn_len; + + char *buf; + char *part; + char *ptr; + + syslog(LOG_DEBUG, "ptsmodule_standard_root_dn called for domain %s", domain); + + for (dots = 0, buf=(char *)domain; *buf; buf++) { + if (*buf == '.') { + dots++; + } + } + + /* Each dot is to be replaced with ',dc=' (length 4), so add + * length 3 for each of them. + */ + root_dn_len = strlen(domain) + (dots * 3); + + buf = xmalloc(root_dn_len); + buf[0] = '\0'; // (?) + /* AM: Above: Terminate the string by default, so strlen won't buffer-overflow later */ + + /* AM: No need for another allocation/dup */ + part = strtok_r(domain, ".", &ptr); + + while (part != NULL) { + syslog(LOG_DEBUG, "Root DN now %s", buf); + strcat(buf, ",dc="); + syslog(LOG_DEBUG, "Root DN now %s", buf); + strcat(buf, part); + syslog(LOG_DEBUG, "Root DN now %s", buf); + part = strtok_r(NULL, ".", &ptr); + } + + syslog(LOG_DEBUG, "Root DN now %s", buf); + + if (buf[0] == ',') + memmove(buf, buf+1, strlen(buf)); + + *result = xstrdup(buf); + + free(buf); + free(part); +/* free(ptr); + free(root_dn_len); +*/ + syslog(LOG_DEBUG, "Root DN now %s", *result); + + return PTSM_OK; +} + static int ptsmodule_tokenize_domains( const char *d, int n, @@ -779,7 +876,6 @@ return PTSM_OK; } - static int ptsmodule_get_dn( const char *canon_id, size_t size, @@ -794,11 +890,14 @@ char *authzid; #endif char *base = NULL, *filter = NULL; + char *domain = NULL; + char domain_filter[1024]; char *attrs[] = {LDAP_NO_ATTRS,NULL}; //do not return all attrs! + char *domain_attrs[] = {(char *)ptsm->domain_name_attribute,(char *)ptsm->domain_result_attribute,NULL}; LDAPMessage *res; LDAPMessage *entry; - char *attr, **vals; - BerElement *ber; + char **vals; + /* unused: BerElement *ber; */ *ret = NULL; @@ -847,30 +946,96 @@ if (rc != PTSM_OK) return rc; - rc = ptsmodule_expand_tokens(ptsm->base, canon_id, NULL, &base); - if (rc != PTSM_OK) - return rc; + if (ptsm->domain_base_dn && (strrchr(canon_id, '@') != NULL)) { + syslog(LOG_DEBUG, "Attempting to get domain for %s from %s", canon_id, ptsm->domain_base_dn); + + /* Get the base dn to search from domain_base_dn searched on domain_scope with + domain_filter */ + domain = strrchr(canon_id, '@'); + + /* Strip the first character which is a '@' */ + domain = domain+1; + + snprintf(domain_filter, sizeof(domain_filter), ptsm->domain_filter, domain); + + syslog(LOG_DEBUG, "Domain filter: %s", domain_filter); + + rc = ldap_search_st(ptsm->ld, ptsm->domain_base_dn, ptsm->domain_scope, domain_filter, domain_attrs, 0, &(ptsm->timeout), &res); + + if (rc != LDAP_SUCCESS) { + syslog(LOG_ERR, "LDAP search for domain failed."); + return rc; + } + + if (ldap_count_entries(ptsm->ld, res) < 1) { + syslog(LOG_ERR, "No domain %s found", domain); + return PTSM_FAIL; + } else if (ldap_count_entries(ptsm->ld, res) > 1) { + syslog(LOG_ERR, "Multiple domains %s found", domain); + return PTSM_FAIL; + } else { + if ((entry = ldap_first_entry(ptsm->ld, res)) != NULL) { + if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_result_attribute)) != NULL) { + syslog(LOG_DEBUG, "Root DN for domain %s is %s", domain, vals[0]); + ptsm->base = vals[0]; + rc = PTSM_OK; + } else if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_name_attribute)) != NULL) { + char *new_domain = xstrdup(vals[0]); + syslog(LOG_DEBUG, "Domain %s is an alias domain for parent domain %s", domain, new_domain); + rc = ptsmodule_standard_root_dn(new_domain, &ptsm->base); + free(new_domain); + } else { + rc = ptsmodule_standard_root_dn(domain, &ptsm->base); + } + + if (rc != PTSM_OK) { + return rc; + } else { + base = xstrdup(ptsm->base); + syslog(LOG_DEBUG, "Continuing with ptsm->base: %s", ptsm->base); + } + } + } + + } else { + rc = ptsmodule_expand_tokens(ptsm->base, canon_id, NULL, &base); + if (rc != PTSM_OK) + return rc; + } rc = ldap_search_st(ptsm->ld, base, ptsm->scope, filter, attrs, 0, &(ptsm->timeout), &res); - free(filter); - free(base); + if (rc != LDAP_SUCCESS) { + syslog(LOG_DEBUG, "Searching %s with %s failed", base, base); + free(filter); + free(base); + if (rc == LDAP_SERVER_DOWN) { ldap_unbind(ptsm->ld); ptsm->ld = NULL; return PTSM_RETRY; } + return PTSM_FAIL; } - /* - * We don't want to return the *first* entry found, we want to return - * the *only* entry found. - */ - if ( ldap_count_entries(ptsm->ld, res) == 1 ) { - if ( (entry = ldap_first_entry(ptsm->ld, res)) != NULL ) - *ret = ldap_get_dn(ptsm->ld, entry); - } + free(filter); + free(base); + + /* + * We don't want to return the *first* entry found, we want to return + * the *only* entry found. + */ + if (ldap_count_entries(ptsm->ld, res) < 1) { + syslog(LOG_ERR, "No entries found"); + } else if (ldap_count_entries(ptsm->ld, res) > 1) { + syslog(LOG_ERR, "Multiple entries found: %d", ldap_count_entries(ptsm->ld, res)); + } else { +/* if ( ldap_count_entries(ptsm->ld, res) == 1 ) { */ + if ((entry = ldap_first_entry(ptsm->ld, res)) != NULL) { + *ret = ldap_get_dn(ptsm->ld, entry); + } + } ldap_msgfree(res); res = NULL; @@ -1145,6 +1310,9 @@ struct auth_state **newstate) { char *base = NULL, *filter = NULL; + char *domain = NULL; + char domain_filter[1024]; + char *domain_attrs[] = {(char *)ptsm->domain_name_attribute,(char *)ptsm->domain_result_attribute,NULL}; int rc; int i; int n; LDAPMessage *res = NULL; @@ -1169,6 +1337,71 @@ goto done; } + if (ptsm->domain_base_dn && (strrchr(canon_id, '@') != NULL)) { + syslog(LOG_DEBUG, "(groups) Attempting to get domain for %s from %s", canon_id, ptsm->domain_base_dn); + + /* Get the base dn to search from domain_base_dn searched on domain_scope with + domain_filter */ + domain = strrchr(canon_id, '@'); + + syslog(LOG_DEBUG, "(groups) Input domain would be %s", domain); + + /* Strip the first character which is a '@' AM: assuming ASCII */ + domain = domain+1; + + syslog(LOG_DEBUG, "(groups) Input domain would be %s", domain); + + snprintf(domain_filter, sizeof(domain_filter), ptsm->domain_filter, domain); + + syslog(LOG_DEBUG, "(groups) Domain filter: %s", domain_filter); + + rc = ldap_search_st(ptsm->ld, ptsm->domain_base_dn, ptsm->domain_scope, domain_filter, domain_attrs, 0, &(ptsm->timeout), &res); + + if (rc != LDAP_SUCCESS) { + syslog(LOG_DEBUG, "(groups) Result from domain query not OK"); + return rc; + } else { + syslog(LOG_DEBUG, "(groups) Result from domain query OK"); + } + + if (ldap_count_entries(ptsm->ld, res) < 1) { + syslog(LOG_ERR, "(groups) No domain %s found", domain); + return PTSM_FAIL; + } else if (ldap_count_entries(ptsm->ld, res) > 1) { + syslog(LOG_ERR, "(groups) Multiple domains %s found", domain); + return PTSM_FAIL; + } else { + syslog(LOG_DEBUG, "(groups) Domain %s found", domain); + if ((entry = ldap_first_entry(ptsm->ld, res)) != NULL) { + if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_result_attribute)) != NULL) { + ptsm->group_base = vals[0]; + rc = PTSM_OK; + } else if ((vals = ldap_get_values(ptsm->ld, entry, ptsm->domain_name_attribute)) != NULL) { + char *new_domain = xstrdup(vals[0]); + syslog(LOG_DEBUG, "(groups) Domain %s is now domain %s", domain, new_domain); + rc = ptsmodule_standard_root_dn(new_domain, &ptsm->group_base); + free(new_domain); + } else { + rc = ptsmodule_standard_root_dn(domain, &ptsm->group_base); + } + + if (rc != PTSM_OK) { + return rc; + } else { + base = xstrdup(ptsm->group_base); + syslog(LOG_DEBUG, "Continuing with ptsm->group_base: %s", ptsm->group_base); + } + } + } + } else { + rc = ptsmodule_expand_tokens(ptsm->group_base, canon_id, NULL, &base); + if (rc != PTSM_OK) + return rc; + } + + syslog(LOG_DEBUG, "(groups) about to search %s for %s", base, filter); + + rc = ptsmodule_expand_tokens(ptsm->group_base, canon_id+6, NULL, &base); if (rc != PTSM_OK) { *reply = "ptsmodule_expand_tokens() failed for group search base";
View file
cyrus-imapd-2.5.tar.gz/sieve/interp.c
Changed
@@ -213,6 +213,11 @@ interp->getheader = f; } +EXPORTED void sieve_register_fname(sieve_interp_t *interp, sieve_get_fname *f) +{ + interp->getfname = f; +} + EXPORTED void sieve_register_envelope(sieve_interp_t *interp, sieve_get_envelope *f) { interp->getenvelope = f;
View file
cyrus-imapd-2.5.tar.gz/sieve/interp.h
Changed
@@ -57,6 +57,7 @@ sieve_get_envelope *getenvelope; sieve_get_body *getbody; sieve_get_include *getinclude; + sieve_get_fname *getfname; sieve_parse_error *err;
View file
cyrus-imapd-2.5.tar.gz/sieve/script.c
Changed
@@ -350,8 +350,8 @@ } static int send_notify_callback(sieve_interp_t *interp, - void *message_context, - void * script_context, notify_list_t *notify, + void *message_context, + void *script_context, notify_list_t *notify, char *actions_string __attribute__((unused)), const char **errmsg) { @@ -380,12 +380,15 @@ /* buf_appendcstr(&out, actions_string); */ nc.message = buf_cstring(&out); + nc.fname = NULL; + if (interp->getfname) + interp->getfname(message_context, &nc.fname); ret = interp->notify(&nc, interp->interp_context, script_context, message_context, - errmsg); + errmsg); buf_free(&out);
View file
cyrus-imapd-2.5.tar.gz/sieve/sieve_interface.h
Changed
@@ -66,6 +66,7 @@ typedef int sieve_get_header(void *message_context, const char *header, const char ***contents); +typedef int sieve_get_fname(void *message_context, const char **fname); typedef int sieve_get_envelope(void *message_context, const char *field, const char ***contents); @@ -119,6 +120,7 @@ const char **options; const char *priority; const char *message; + const char *fname; } sieve_notify_context_t; #define SIEVE_HASHLEN 16 @@ -156,6 +158,7 @@ sieve_script_parse */ void sieve_register_size(sieve_interp_t *interp, sieve_get_size *f); void sieve_register_header(sieve_interp_t *interp, sieve_get_header *f); +void sieve_register_fname(sieve_interp_t *interp, sieve_get_fname *f); void sieve_register_envelope(sieve_interp_t *interp, sieve_get_envelope *f); void sieve_register_body(sieve_interp_t *interp, sieve_get_body *f);
View file
cyrus-imapd-2.5.tar.gz/tools/vzic
Added
+(directory)
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/ChangeLog
Added
@@ -0,0 +1,57 @@ +2006-03-18 Damon Chaplin <damon@gnome.org> + + * Released Vzic 1.3 + +2006-03-18 Damon Chaplin <damon@gnome.org> + + * vzic-output.c (expand_tzname): added special case for America/Nome. + (output_rrule): made hacks a bit more general, to handle Asia/Gaza + which now has a day=4 rule. At some point we should check what newer + versions of Outlook can handle so we can be more accurate. + + * vzic-dump.c (dump_time_zone_names): try looking for timezone info + using original and linked name. + + * README, *.c: fixed spelling 'compatable' -> 'compatible'. + + * vzic.c: patch from Jonathan Guthrie to support a --olson-dir option. + +2003-10-25 Damon Chaplin <damon@gnome.org> + + * Released Vzic 1.2 + +2003-10-25 Damon Chaplin <damon@gnome.org> + + * vzic-output.c: + * Makefile: moved the PRODUCT_ID and TZID_PREFIX settings to the + Makefile and changed the default so people don't accidentally use + the same IDs as Evolution. + + * vzic-parse.c (parse_time): substitute 23:59:59 when we read a time + of 24:00:00. This is a bit of a kludge to avoid problems, since + 24:00:00 is not a valid iCalendar time. Since 24:00:00 is only used + for a few timezones in the 1930s it doesn't matter too much. + + To write a correct fix we'd need to review all the code that deals + with times to see if it would be affected, e.g. a time of 24:00 on + one day should be considered equal to 0:00 the next day. + + We'd also need to adjust the output times to use 0:00 the next day + rather than 24:00. If we need to output recurrence rules that would + be a problem, since 'last saturday at 24:00' can't be easily + converted to another rule that uses 0:00 instead. + +2003-10-22 Damon Chaplin <damon@gnome.org> + + * Released Vzic 1.1 + +2003-10-22 Damon Chaplin <damon@gnome.org> + + * vzic-parse.c (parse_time): allow a time of 24:00, as used in + the America/Montreal and America/Toronto rules in the 1930s! + I'm not 100% sure the rest of the code will handle this OK, but + it only affects the 'pure' output. + +2003-09-01 Damon Chaplin <damon@gnome.org> + + * Released Vzic 1.0
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/Makefile
Added
@@ -0,0 +1,90 @@ + +# +# You will need to set this to the directory that the Olson timezone data +# files are in. +# +OLSON_DIR = . + + +# This is used as the PRODID property on the iCalendar files output. +# It identifies the product which created the iCalendar objects. +# So you need to substitute your own organization name and product. +PRODUCT_ID = -//CyrusIMAP.org//Cyrus %s//EN + +# This is what libical-evolution uses. +#PRODUCT_ID = -//Ximian//NONSGML Evolution Olson-VTIMEZONE Converter//EN + + +# This is used to create unique IDs for each VTIMEZONE component. +# The prefix is put before each timezone city name. It should start and end +# with a '/'. The first part, i.e. 'myorganization.org' below, should be +# a unique vendor ID, e.g. use a hostname. The part after that can be +# anything you want. We use a date and version number for libical. The %D +# gets expanded to today's date. There is also a vzic-merge.pl which can be +# used to merge changes into a master set of VTIMEZONEs. If a VTIMEZONE has +# changed, it bumps the version number on the end of this prefix. */ +#TZID_PREFIX = /myorganization.org/%D_1/ +TZID_PREFIX = + +# This is what libical-evolution uses. +#TZID_PREFIX = /softwarestudio.org/Olson_%D_1/ + + +# Set any -I include directories to find the libical header files, and the +# libical library to link with. You only need these if you want to run the +# tests. You may need to change the '#include <ical.h>' line at the top of +# test-vzic.c as well. +LIBICAL_CFLAGS = +LIBICAL_LDADD = -lical + + +# +# You shouldn't need to change the rest of the file. +# + +GLIB_CFLAGS = `pkg-config --cflags glib-2.0` +GLIB_LDADD = `pkg-config --libs glib-2.0` + +CFLAGS = -g -I../.. -DOLSON_DIR=\"$(OLSON_DIR)\" -DPRODUCT_ID='"$(PRODUCT_ID)"' -DTZID_PREFIX='"$(TZID_PREFIX)"' $(GLIB_CFLAGS) $(LIBICAL_CFLAGS) + +OBJECTS = vzic.o vzic-parse.o vzic-dump.o vzic-output.o + +all: vzic + +vzic: $(OBJECTS) + $(CC) $(OBJECTS) $(GLIB_LDADD) -o vzic + +test-vzic: test-vzic.o + $(CC) test-vzic.o $(LIBICAL_LDADD) -o test-vzic + +# Dependencies. +$(OBJECTS): vzic.h +vzic.o vzic-parse.o: vzic-parse.h +vzic.o vzic-dump.o: vzic-dump.h +vzic.o vzic-output.o: vzic-output.h + +test-parse: vzic + ./vzic-dump.pl $(OLSON_DIR) + ./vzic --dump --pure + @echo + @echo "#" + @echo "# If either of these diff commands outputs anything there may be a problem." + @echo "#" + diff -ru zoneinfo/ZonesPerl zoneinfo/ZonesVzic + diff -ru zoneinfo/RulesPerl zoneinfo/RulesVzic + +test-changes: vzic test-vzic + ./test-vzic --dump-changes + ./vzic --dump-changes --pure + @echo + @echo "#" + @echo "# If this diff command outputs anything there may be a problem." + @echo "#" + diff -ru zoneinfo/ChangesVzic test-output + +clean: + -rm -rf vzic $(OBJECTS) *~ ChangesVzic RulesVzic ZonesVzic RulesPerl ZonesPerl test-vzic test-vzic.o + +.PHONY: clean perl-dump test-parse + +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/README
Added
@@ -0,0 +1,202 @@ + + +VZIC README +=========== + +This is 'vzic', a program to convert the Olson timezone database files into +VTIMEZONE files compatible with the iCalendar specification (RFC2445). + +(The name is based on the 'zic' program which converts the Olson files into +time zone information files used by several Unix C libraries, including +glibc. See zic(8) and tzfile(5).) + + + +REQUIREMENTS +============ + +You need the Olson timezone database files, which can be found at: + + ftp://elsie.nci.nih.gov/pub/ + + (Old versions can be found at ftp://munnari.oz.au/pub/oldtz/) + + +Vzic also uses the GLib library (for hash tables, dynamic arrays, and date +calculations). You need version 2.0 or higher. You can get this from: + + http://www.gtk.org + + + +BUILDING +======== + +Edit the Makefile to set the OLSON_DIR, PRODUCT_ID and TZID_PREFIX variables. + +Then run 'make'. + + + +RUNNING +======= + +Run 'vzic'. + +The output is placed in the zoneinfo subdirectory by default, +but you can use the --output-dir options to set another toplevel output +directory. + +By default it outputs VTIMEZONEs that try to be compatible with Outlook +(2000, at least). Outlook can't handle certain iCalendar constructs in +VTIMEZONEs, such as RRULEs using BYMONTHDAY, so it has to adjust the RRULEs +slightly to get Outlook to parse them. Unfortunately this means they are +slightly wrong. If given the --pure option, vzic outputs the exact data, +without worrying about compatability. + +NOTE: We don't convert all the Olson files. We skip 'backward', 'etcetera', +'leapseconds', 'pacificnew', 'solar87', 'solar88' and 'solar89', 'factory' +and 'systemv', since these don't really provide any useful timezones. +See vzic.c. + + + +MERGING CHANGES INTO A MASTER SET OF VTIMEZONES +=============================================== + +The Olson timezone files are updated fairly often, so we need to build new +sets of VTIMEZONE files. Though we have to be careful to ensure that the TZID +of updated timezones is also updated, since it must remain unique. + +We use a version number on the end of the TZID prefix (see the TZIDPrefix +variable in vzic-output.c) to ensure this uniqueness. + +But we don't want to update the version numbers of VTIMEZONEs which have not +changed. So we use the vzic-merge.pl Perl script. This merges in the new set +of VTIMEZONEs with a 'master' set. It compares each new VTIMEZONE file with +the one in the master set (ignoring changes to the TZID). If the new +VTIMEZONE file is different, it copies it to the master set and sets the +version number to the old VTIMEZONE's version number + 1. + +To use vzic-merge.pl you must change the $MASTER_ZONEINFO_DIR and +$NEW_ZONEINFO_DIR variables at the top of the file to point to your 2 sets of +VTIMEZONEs. You then just run the script. (I recommend you keep a backup of +the old master VTIMEZONE files, and use diff to compare the new master set +with the old one, in case anything goes wrong.) + +You must merge in changes to the zones.tab file by hand. + +Note that some timezones are renamed or removed occasionally, so applications +should be able to cope with this. + + + +COMPATABILITY NOTES +=================== + +It seems that Microsoft Outlook is very picky about the iCalendar files it +will accept. (I've been testing with Outlook 2000. I hope the other versions +are no worse.) Here's a few problems we've had with the VTIMEZONEs: + + o Outlook doesn't like any years before 1600. We were using '1st Jan 0001' + in all VTIMEZONEs to specify the first UTC offset known for the timezone. + (The Olson data does not give a start date for this.) + + Now we just skip this first component for most timezones. The UTC offset + can still be found from the TZOFFSETFROM property of the first component. + + Though some timezones only specify one UTC offset that applies forever, + so in these cases we output '1st Jan 1970' (Indian/Cocos, + Pacific/Johnston). + + o Outlook doesn't like the BYMONTHDAY specifier in RRULEs. + + We have changed most of the VTIMEZONEs to use things like 'BYDAY=2SU' + rather than 'BYMONTHDAY=8,9,10,11,12,13,14;BYDAY=SU', though some of + them were impossible to convert correctly so they are not always correct. + + o Outlook doesn't like TZOFFSETFROM/TZOFFSETTO properties which include a + seconds component, e.g. 'TZOFFSETFROM:+110628'. + Quite a lot of the Olson timezones include seconds in their UTC offsets, + though no timezones currently have a UTC offset that uses the seconds + value. + + We've rounded all UTC offsets to the nearest minute. Since all timezone + offsets currently used have '00' as the seconds offset, this doesn't lose + us much. + + o Outlook doesn't like lines being split in certain places, even though + the iCalendar spec says they can be split anywhere. + + o Outlook can only handle one RDATE or a pair of RRULEs. So we had to remove + all historical data. + + +TESTING +======= + +Do a 'make test-vic', then run ./test-vic. + +The test-vzic program compares our libical code and VTIMEZONE data against +the Unix functions like mktime(). It steps over a period of time (1970-2037) +converting from UTC to a given timezone and back again every 15 minutes. +Any differences are output into the test-output directory. + +The output matches for all of the timezones, except in a few places where the +result can't be determined. So I think we can be fairly confident that the +VTIMEZONEs are correct. + +Note that you must use the same Olson data in libical that the OS is using +for mktime() etc. For example, I am using RedHat 9 which uses tzdata2002d, +so I converted this to VTIMEZONE files and installed it into the libical +timezone data directory before testing. (You need to use '--pure' when +creating the VTIMEZONE files as well.) + + +Testing the Parsing Code +------------------------ + +Run 'make test-parse'. + +This runs 'vzic --dump' and 'perl-dump' and compares the output. The diff +commands should not produce any output. + +'vzic --dump' dumps all the parsed data out in the original Olson format, +but without comments. The files are written into the ZonesVzic and RulesVzic +subdirectories of the zoneinfo directory. + +'make perl-dump' runs the vzic-dump.pl perl script which outputs the files +in the same format as 'vzic --dump' in the ZonesPerl and RulesPerl +subdirectories. The perl script doesn't actually parse the fields; it only +strips comments and massages the fields so we have the same output format. + +Currently they both produce exactly the same output so we know the parsing +code is OK. + + +Testing the VTIMEZONE Files +--------------------------- + +Run 'make test-changes'. + +This runs 'vzic --dump-changes' and 'test-vzic --dump-changes' and compares +the output. The diff command should not produce any output. + +Both commands output timezone changes for each zone up to a specific year +(2030) into files for each timezone. It outputs the timezone changes in a +list in this format: + + Timezone Name Date and Time of Change in UTC New Offset from UTC + + America/Dawson 26 Oct 1986 2:00:00 -0800 + +Unfortunately there are some differences here, but they all happen before +1970 so it doesn't matter too much. It looks like the libical code has +problems determining things like 'last Sunday of the month' before 1970. +This is because it uses mktime() etc. which can't really handle dates +before 1970. + + + +Damon Chaplin <damon@gnome.org>, 25 Oct 2003. +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/test-vzic.c
Added
@@ -0,0 +1,422 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * test-vzic.c - test vzic + libical against mktime() and friends. + * + * Note that when we output VCALENDAR data compatible with Outlook the + * results aren't all correct. + * + * We have to modify some RRULEs which makes these timezones incorrect: + * + * Africa/Cairo + * America/Godthab + * America/Santiago + * Antarctica/Palmer + * Asia/Baghdad + * Asia/Damascus + * Asia/Jerusalem + * + * Also, we can only output one RDATE or a pair of RRULEs which may make some + * other timezones incorrect sometimes (e.g. if they change). + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#include <ical.h> +/*#include <evolution/ical.h>*/ + +#define CHANGES_MAX_YEAR 2030 + +/* These are the years between which we test against the Unix timezone + functions, inclusive. When using 'vzic --pure' you can test the full + range from 1970 to 2037 and it should match against mktime() etc. + (assuming you are using the same Olson timezone data for both). + + But when using VTIMEZONE's that are compatible with Outlook, it is only + worth testing times in the future. There will be lots of differences in + the past, since we can't include any historical changes in the files. */ +#if 1 +#define DUMP_START_YEAR 2003 +#define DUMP_END_YEAR 2038 +#else +#define DUMP_START_YEAR 1970 +#define DUMP_END_YEAR 2038 +#endif + +/* The maximum size of any complete pathname. */ +#define PATHNAME_BUFFER_SIZE 1024 + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +int VzicDumpChanges = FALSE; + +/* We output beneath the current directory for now. */ +char *directory = "test-output"; + +static void usage (void); +static int parse_zone_name (char *name, + char **directory, + char **subdirectory, + char **filename); +static void ensure_directory_exists (char *directory); +static void dump_local_times (icaltimezone *zone, + FILE *fp); + + +int main(int argc, char* argv[]) +{ + icalarray *zones; + icaltimezone *zone; + char *zone_directory, *zone_subdirectory, *zone_filename, *location; + char output_directory[PATHNAME_BUFFER_SIZE]; + char filename[PATHNAME_BUFFER_SIZE]; + FILE *fp; + int i; + int skipping = TRUE; + + /* + * Command-Line Option Parsing. + */ + for (i = 1; i < argc; i++) { + /* --dump-changes: Dumps a list of times when each timezone changed, + and the new local time offset from UTC. */ + if (!strcmp (argv[i], "--dump-changes")) + VzicDumpChanges = TRUE; + + else + usage (); + } + + + zones = icaltimezone_get_builtin_timezones (); + + ensure_directory_exists (directory); + + for (i = 0; i < zones->num_elements; i++) { + zone = icalarray_element_at (zones, i); + + location = icaltimezone_get_location (zone); + +#if 0 + /* Use this to start at a certain zone. */ + if (skipping && strcmp (location, "America/Boise")) + continue; +#endif + + skipping = FALSE; + + /* Use this to only output data for certain timezones. */ +#if 0 + if (strcmp (location, "America/Cancun") + && strcmp (location, "Asia/Baku") + && strcmp (location, "Asia/Nicosia") + && strcmp (location, "Asia/Novosibirsk") + && strcmp (location, "Asia/Samarkand") + && strcmp (location, "Asia/Tashkent") + && strcmp (location, "Asia/Tbilisi") + && strcmp (location, "Asia/Yerevan") + && strcmp (location, "Australia/Broken_Hill") + && strcmp (location, "Europe/Simferopol") + && strcmp (location, "Europe/Tallinn") + && strcmp (location, "Europe/Zaporozhye") + ) + continue; +#endif + +#if 0 + printf ("%s\n", location); +#endif + + parse_zone_name (location, &zone_directory, &zone_subdirectory, + &zone_filename); + + sprintf (output_directory, "%s/%s", directory, zone_directory); + ensure_directory_exists (output_directory); + sprintf (filename, "%s/%s", output_directory, zone_filename); + + if (zone_subdirectory) { + sprintf (output_directory, "%s/%s/%s", directory, zone_directory, + zone_subdirectory); + ensure_directory_exists (output_directory); + sprintf (filename, "%s/%s", output_directory, zone_filename); + } + + fp = fopen (filename, "w"); + if (!fp) { + fprintf (stderr, "Couldn't create file: %s\n", filename); + exit (1); + } + + /* We can run 2 different tests - output all changes for each zone, or + test against mktime()/localtime(). Should have a command-line option + or something. */ + if (VzicDumpChanges) + icaltimezone_dump_changes (zone, CHANGES_MAX_YEAR, fp); + else + dump_local_times (zone, fp); + + if (ferror (fp)) { + fprintf (stderr, "Error writing file: %s\n", filename); + exit (1); + } + + fclose (fp); + } + + return 0; +} + + +static void +usage (void) +{ + fprintf (stderr, "Usage: test-vzic [--dump-changes]\n"); + + exit (1); +} + + +/* This checks that the Zone name only uses the characters in [-+_/a-zA-Z0-9], + and outputs a warning if it isn't. */ +static int +parse_zone_name (char *name, + char **directory, + char **subdirectory, + char **filename) +{ + static int invalid_zone_num = 1; + + char *p, ch, *first_slash_pos = NULL, *second_slash_pos = NULL; + int invalid = FALSE; + + for (p = name; (ch = *p) != 0; p++) { + if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') + && (ch < '0' || ch > '9') && ch != '/' && ch != '_' + && ch != '-' && ch != '+') { + fprintf (stderr, "Warning: Unusual Zone name: %s\n", name); + invalid = TRUE; + break; + } + + if (ch == '/') { + if (!first_slash_pos) { + first_slash_pos = p; + } else if (!second_slash_pos) { + second_slash_pos = p; + } else { + fprintf (stderr, "Warning: More than 2 '/' characters in Zone name: %s\n", name); + invalid = TRUE; + break; + } + } + } + + if (!first_slash_pos) { + fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name); + return FALSE; + } + + if (invalid) { + fprintf (stderr, "Invalid zone name: %s\n", name); + exit (0); + } else { + *first_slash_pos = '\0'; + *directory = icalmemory_strdup (name); + *first_slash_pos = '/'; + + if (second_slash_pos) { + *second_slash_pos = '\0'; + *subdirectory = icalmemory_strdup (first_slash_pos + 1); + *second_slash_pos = '/'; + + *filename = icalmemory_strdup (second_slash_pos + 1); + } else { + *subdirectory = NULL; + *filename = icalmemory_strdup (first_slash_pos + 1); + } + } +} + + +static void +ensure_directory_exists (char *directory) +{ + struct stat filestat; + + if (stat (directory, &filestat) != 0) { + /* If the directory doesn't exist, try to create it. */ + if (errno == ENOENT) { + if (mkdir (directory, 0777) != 0) { + fprintf (stderr, "Can't create directory: %s\n", directory); + exit (1); + } + } else { + fprintf (stderr, "Error calling stat() on directory: %s\n", directory); + exit (1); + } + } else if (!S_ISDIR (filestat.st_mode)) { + fprintf (stderr, "Can't create directory, already exists: %s\n", + directory); + exit (1); + } +} + + +static void +dump_local_times (icaltimezone *zone, FILE *fp) +{ + static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + icaltimezone *utc_timezone; + struct icaltimetype tt, tt_copy; + struct tm tm, local_tm; + time_t t; + char tzstring[256], *location; + int last_year_output = 0; + int total_error = 0, total_error2 = 0; + + utc_timezone = icaltimezone_get_utc_timezone (); + + /* This is our UTC time that we will use to iterate over the period. */ + tt.year = DUMP_START_YEAR; + tt.month = 1; + tt.day = 1; + tt.hour = 0; + tt.minute = 0; + tt.second = 0; + tt.is_utc = 0; + tt.is_date = 0; + tt.zone = ""; + + tm.tm_year = tt.year - 1900; + tm.tm_mon = tt.month - 1; + tm.tm_mday = tt.day; + tm.tm_hour = tt.hour; + tm.tm_min = tt.minute; + tm.tm_sec = tt.second; + tm.tm_isdst = -1; + + /* Convert it to a time_t by saying it is in UTC. */ + putenv ("TZ=UTC"); + t = mktime (&tm); + + location = icaltimezone_get_location (zone); + sprintf (tzstring, "TZ=%s", location); + + /*printf ("Zone: %s\n", location);*/ + putenv (tzstring); + + /* Loop around converting the UTC time to local time, outputting it, and + then adding on 15 minutes to the UTC time. */ + while (tt.year <= DUMP_END_YEAR) { + if (tt.year > last_year_output) { + last_year_output = tt.year; +#if 0 + printf (" %i\n", last_year_output); + fprintf (fp, " %i\n", last_year_output); +#endif + } + +#if 1 + /* First use the Unix functions. */ + /* Now convert it to a local time in the given timezone. */ + local_tm = *localtime (&t); +#endif + +#if 1 + /* Now use libical. */ + tt_copy = tt; + icaltimezone_convert_time (&tt_copy, utc_timezone, zone); +#endif + +#if 1 + if (local_tm.tm_year + 1900 != tt_copy.year + || local_tm.tm_mon + 1 != tt_copy.month + || local_tm.tm_mday != tt_copy.day + || local_tm.tm_hour != tt_copy.hour + || local_tm.tm_min != tt_copy.minute + || local_tm.tm_sec != tt_copy.second) { + + /* The error format is: + + ERROR: Original-UTC-Time Local-Time-From-mktime Local-Time-From-Libical + + */ + + total_error++; + + fprintf (fp, "ERROR:%2i %s %04i %2i:%02i:%02i UTC", + tt.day, months[tt.month - 1], tt.year, + tt.hour, tt.minute, tt.second); + fprintf (fp, " ->%2i %s %04i %2i:%02i:%02i", + local_tm.tm_mday, months[local_tm.tm_mon], + local_tm.tm_year + 1900, + local_tm.tm_hour, local_tm.tm_min, local_tm.tm_sec); + fprintf (fp, " Us:%2i %s %04i %2i:%02i:%02i\n", + tt_copy.day, months[tt_copy.month - 1], tt_copy.year, + tt_copy.hour, tt_copy.minute, tt_copy.second); + } +#endif + + /* Now convert it back, and check we get the original time. */ + icaltimezone_convert_time (&tt_copy, zone, utc_timezone); + if (tt.year != tt_copy.year + || tt.month != tt_copy.month + || tt.day != tt_copy.day + || tt.hour != tt_copy.hour + || tt.minute != tt_copy.minute + || tt.second != tt_copy.second) { + + total_error2++; + + fprintf (fp, "ERROR 2: %2i %s %04i %2i:%02i:%02i UTC", + tt.day, months[tt.month - 1], tt.year, + tt.hour, tt.minute, tt.second); + fprintf (fp, " Us:%2i %s %04i %2i:%02i:%02i UTC\n", + tt_copy.day, months[tt_copy.month - 1], tt_copy.year, + tt_copy.hour, tt_copy.minute, tt_copy.second); + } + + + /* Increment the time. */ + icaltime_adjust (&tt, 0, 0, 15, 0); + + /* We assume leap seconds are not included in time_t values, which should + be true on POSIX systems. */ + t += 15 * 60; + } + + printf ("Zone: %40s Errors: %i (%i)\n", icaltimezone_get_location (zone), + total_error, total_error2); +}
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-dump.c
Added
@@ -0,0 +1,409 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * These functions are for dumping all the parsed Zones and Rules to + * files, to be compared with the output of vzic-dump.pl to check our parsing + * code is OK. Some of the functions are also used for producing debugging + * output. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "vzic.h" +#include "vzic-dump.h" + + +static void dump_add_rule (char *name, + GArray *rule_array, + GPtrArray *name_array); +static int dump_compare_strings (const void *arg1, + const void *arg2); + + +void +dump_zone_data (GArray *zone_data, + char *filename) +{ + static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + FILE *fp; + ZoneData *zone; + ZoneLineData *zone_line; + int i, j; + gboolean output_month, output_day, output_time; + + fp = fopen (filename, "w"); + if (!fp) { + fprintf (stderr, "Couldn't create file: %s\n", filename); + exit (1); + } + + for (i = 0; i < zone_data->len; i++) { + zone = &g_array_index (zone_data, ZoneData, i); + + fprintf (fp, "Zone\t%s\t", zone->zone_name); + + for (j = 0; j < zone->zone_line_data->len; j++) { + zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, j); + + if (j != 0) + fprintf (fp, "\t\t\t"); + + fprintf (fp, "%s\t", dump_time (zone_line->stdoff_seconds, TIME_WALL, + FALSE)); + + if (zone_line->rules) + fprintf (fp, "%s\t", zone_line->rules); + else if (zone_line->save_seconds != 0) + fprintf (fp, "%s\t", dump_time (zone_line->save_seconds, TIME_WALL, + FALSE)); + else + fprintf (fp, "-\t"); + + fprintf (fp, "%s\t", zone_line->format ? zone_line->format : "-"); + + if (zone_line->until_set) { + fprintf (fp, "%s\t", dump_year (zone_line->until_year)); + + output_month = output_day = output_time = FALSE; + + if (zone_line->until_time_code != TIME_WALL + || zone_line->until_time_seconds != 0) + output_month = output_day = output_time = TRUE; + else if (zone_line->until_day_code != DAY_SIMPLE + || zone_line->until_day_number != 1) + output_month = output_day = TRUE; + else if (zone_line->until_month != 0) + output_month = TRUE; + + if (output_month) + fprintf (fp, "%s", months[zone_line->until_month]); + + fprintf (fp, "\t"); + + if (output_day) + fprintf (fp, "%s", dump_day_coded (zone_line->until_day_code, + zone_line->until_day_number, + zone_line->until_day_weekday)); + + fprintf (fp, "\t"); + + if (output_time) + fprintf (fp, "%s", dump_time (zone_line->until_time_seconds, + zone_line->until_time_code, FALSE)); + + } else { + fprintf (fp, "\t\t\t"); + } + + fprintf (fp, "\n"); + } + } + + fclose (fp); +} + + +void +dump_rule_data (GHashTable *rule_data, + char *filename) +{ + FILE *fp; + GPtrArray *name_array; + GArray *rule_array; + int i; + char *name; + + fp = fopen (filename, "w"); + if (!fp) { + fprintf (stderr, "Couldn't create file: %s\n", filename); + exit (1); + } + + /* We need to sort the rules by their names, so they are in the same order + as the Perl output. So we place all the names in a temporary GPtrArray, + sort it, then output them. */ + name_array = g_ptr_array_new (); + g_hash_table_foreach (rule_data, (GHFunc) dump_add_rule, name_array); + qsort (name_array->pdata, name_array->len, sizeof (char*), + dump_compare_strings); + + for (i = 0; i < name_array->len; i++) { + name = g_ptr_array_index (name_array, i); + rule_array = g_hash_table_lookup (rule_data, name); + if (!rule_array) { + fprintf (stderr, "Couldn't access rules: %s\n", name); + exit (1); + } + dump_rule_array (name, rule_array, fp); + } + + g_ptr_array_free (name_array, TRUE); + + fclose (fp); +} + + +static void +dump_add_rule (char *name, + GArray *rule_array, + GPtrArray *name_array) +{ + g_ptr_array_add (name_array, name); +} + + +static int +dump_compare_strings (const void *arg1, + const void *arg2) +{ + char **a, **b; + + a = (char**) arg1; + b = (char**) arg2; + + return strcmp (*a, *b); +} + + +void +dump_rule_array (char *name, + GArray *rule_array, + FILE *fp) +{ + static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + RuleData *rule; + int i; + +#if 0 + fprintf (fp, "\n# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S"); +#endif + + for (i = 0; i < rule_array->len; i++) { + rule = &g_array_index (rule_array, RuleData, i); + + fprintf (fp, "Rule\t%s\t%s\t", name, dump_year (rule->from_year)); + + if (rule->to_year == rule->from_year) + fprintf (fp, "only\t"); + else + fprintf (fp, "%s\t", dump_year (rule->to_year)); + + fprintf (fp, "%s\t", rule->type ? rule->type : "-"); + + fprintf (fp, "%s\t", months[rule->in_month]); + + fprintf (fp, "%s\t", + dump_day_coded (rule->on_day_code, rule->on_day_number, + rule->on_day_weekday)); + + fprintf (fp, "%s\t", dump_time (rule->at_time_seconds, rule->at_time_code, + FALSE)); + + fprintf (fp, "%s\t", dump_time (rule->save_seconds, TIME_WALL, TRUE)); + + fprintf (fp, "%s", rule->letter_s ? rule->letter_s : "-"); + + fprintf (fp, "\n"); + } +} + + +char* +dump_time (int seconds, + TimeCode time_code, + gboolean use_zero) +{ + static char buffer[256], *sign; + int hours, minutes; + char *code; + + if (time_code == TIME_STANDARD) + code = "s"; + else if (time_code == TIME_UNIVERSAL) + code = "u"; + else + code = ""; + + if (seconds < 0) { + seconds = -seconds; + sign = "-"; + } else { + sign = ""; + } + + hours = seconds / 3600; + minutes = (seconds % 3600) / 60; + seconds = seconds % 60; + + if (use_zero && hours == 0 && minutes == 0 && seconds == 0) + return "0"; + else if (seconds == 0) + sprintf (buffer, "%s%i:%02i%s", sign, hours, minutes, code); + else + sprintf (buffer, "%s%i:%02i:%02i%s", sign, hours, minutes, seconds, code); + + return buffer; +} + + +char* +dump_day_coded (DayCode day_code, + int day_number, + int day_weekday) +{ + static char buffer[256]; + static char *weekdays[] = { "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" }; + + switch (day_code) { + case DAY_SIMPLE: + sprintf (buffer, "%i", day_number); + break; + case DAY_WEEKDAY_ON_OR_AFTER: + sprintf (buffer, "%s>=%i", weekdays[day_weekday], day_number); + break; + case DAY_WEEKDAY_ON_OR_BEFORE: + sprintf (buffer, "%s<=%i", weekdays[day_weekday], day_number); + break; + case DAY_LAST_WEEKDAY: + sprintf (buffer, "last%s", weekdays[day_weekday]); + break; + default: + fprintf (stderr, "Invalid day code: %i\n", day_code); + exit (1); + } + + return buffer; +} + + +char* +dump_year (int year) +{ + static char buffer[256]; + + if (year == YEAR_MINIMUM) + return "min"; + if (year == YEAR_MAXIMUM) + return "max"; + + sprintf (buffer, "%i", year); + return buffer; +} + + +void +dump_time_zone_names (GList *names, + char *output_dir, + GHashTable *zones_hash) +{ + char filename[PATHNAME_BUFFER_SIZE], *zone_name, *zone_name_in_hash = NULL; + char strings_filename[PATHNAME_BUFFER_SIZE]; + FILE *fp, *strings_fp = NULL; + GList *elem; + ZoneDescription *zone_desc; + + sprintf (filename, "%s/zones.tab", output_dir); + sprintf (strings_filename, "%s/zones.h", output_dir); + + fp = fopen (filename, "w"); + if (!fp) { + fprintf (stderr, "Couldn't create file: %s\n", filename); + exit (1); + } + + if (VzicDumpZoneTranslatableStrings) { + strings_fp = fopen (strings_filename, "w"); + if (!strings_fp) { + fprintf (stderr, "Couldn't create file: %s\n", strings_filename); + exit (1); + } + } + + names = g_list_sort (names, (GCompareFunc) strcmp); + + elem = names; + while (elem) { + zone_name = (char*) elem->data; + + zone_desc = g_hash_table_lookup (zones_hash, zone_name); + + /* SPECIAL CASES: These timezones are links from other zones and are + almost exactly the same - they are basically there so users can find + them a bit easier. But they don't have entries in the zone.tab file, + so we use the entry from the timezone linked from. */ + if (!zone_desc) { + if (!strcmp (zone_name, "America/Indiana/Indianapolis")) + zone_name_in_hash = "America/Indianapolis"; + else if (!strcmp (zone_name, "America/Kentucky/Louisville")) + zone_name_in_hash = "America/Louisville"; + else if (!strcmp (zone_name, "Asia/Istanbul")) + zone_name_in_hash = "Europe/Istanbul"; + else if (!strcmp (zone_name, "Europe/Nicosia")) + zone_name_in_hash = "Asia/Nicosia"; + + if (zone_name_in_hash) + zone_desc = g_hash_table_lookup (zones_hash, zone_name_in_hash); + } + + if (zone_desc) { + fprintf (fp, "%+04i%02i%02i %+04i%02i%02i %s\n", + zone_desc->latitude[0], zone_desc->latitude[1], + zone_desc->latitude[2], + zone_desc->longitude[0], zone_desc->longitude[1], + zone_desc->longitude[2], + zone_name); + } else { + g_print ("Zone description not found for: %s\n", zone_name); + fprintf (fp, "%s\n", zone_name); + } + + + if (VzicDumpZoneTranslatableStrings) { +#if 0 + char zone_name_buffer[1024], *src, *dest; + + for (src = zone_name, dest = zone_name_buffer; *src; src++, dest++) + *dest = (*src == '_') ? ' ' : *src; + *dest = '\0'; +#endif + + fprintf (strings_fp, "N_(\"%s\");\n", zone_name); + } + + elem = elem->next; + } + + fclose (fp); + + if (VzicDumpZoneTranslatableStrings) + fclose (strings_fp); +} +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-dump.h
Added
@@ -0,0 +1,58 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * These functions are for dumping all the parsed Zones and Rules to + * files, to be compared with the output of vzic-dump.pl to check our parsing + * code is OK. Some of the functions are also used for producing debugging + * output. + */ + +#ifndef _VZIC_DUMP_H_ +#define _VZIC_DUMP_H_ + +#include <glib.h> + +void dump_zone_data (GArray *zone_data, + char *filename); +void dump_rule_data (GHashTable *rule_data, + char *filename); + +void dump_rule_array (char *name, + GArray *rule_array, + FILE *fp); + +char* dump_year (int year); +char* dump_day_coded (DayCode day_code, + int day_number, + int day_weekday); +char* dump_time (int seconds, + TimeCode time_code, + gboolean use_zero); + +void dump_time_zone_names (GList *names, + char *output_dir, + GHashTable *zones_hash); + +#endif /* _VZIC_DUMP_H_ */
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-dump.pl
Added
@@ -0,0 +1,222 @@ +#!/usr/bin/perl -w + +# +# Vzic - a program to convert Olson timezone database files into VZTIMEZONE +# files compatible with the iCalendar specification (RFC2445). +# +# Copyright (C) 2000-2001 Ximian, Inc. +# Copyright (C) 2003 Damon Chaplin. +# +# Author: Damon Chaplin <damon@gnome.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# + +# +# This reads the Olson timezone files, strips any comments, and outputs them +# in a very simple format with tab-separated fields. It is used to compare +# with the output of dump_zone_data() and dump_rule_data() to double-check +# that we have parsed the files correctly. +# + +my $zones_fh = "zonesfile"; +my $rules_fh = "rulesfile"; + +my %Rules; + +if ($#ARGV != 0) { + die "Usage: $0 <OlsonDirectory>"; +} + +my $OLSON_DIR = $ARGV[0]; + +# We place output in subdirectories of the current directory. +my $OUTPUT_DIR = "zoneinfo"; + +if (! -d "$OUTPUT_DIR") { + mkdir ("$OUTPUT_DIR", 0777) + || die "Can't create directory: $OUTPUT_DIR"; +} +if (! -d "$OUTPUT_DIR/ZonesPerl") { + mkdir ("$OUTPUT_DIR/ZonesPerl", 0777) + || die "Can't create directory: $OUTPUT_DIR/ZonesPerl"; +} +if (! -d "$OUTPUT_DIR/RulesPerl") { + mkdir ("$OUTPUT_DIR/RulesPerl", 0777) + || die "Can't create directory: $OUTPUT_DIR/RulesPerl"; +} + + +&ReadOlsonFile ("africa"); +&ReadOlsonFile ("antarctica"); +&ReadOlsonFile ("asia"); +&ReadOlsonFile ("australasia"); +&ReadOlsonFile ("europe"); +&ReadOlsonFile ("northamerica"); +&ReadOlsonFile ("southamerica"); + +# These are backwards-compatability and weird stuff. +#&ReadOlsonFile ("backward"); +#&ReadOlsonFile ("etcetera"); +#&ReadOlsonFile ("leapseconds"); +#&ReadOlsonFile ("pacificnew"); +#&ReadOlsonFile ("solar87"); +#&ReadOlsonFile ("solar88"); +#&ReadOlsonFile ("solar89"); + +# We don't do this one since it is not useful and the use of '"' in the Zone +# line messes up our split() command. +#&ReadOlsonFile ("factory"); + +# We don't do this since the vzic program can't do it. +#&ReadOlsonFile ("systemv"); + + + + +1; + + +sub ReadOlsonFile { + my ($file) = @_; + +# print ("Reading olson file: $file\n"); + + open (OLSONFILE, "$OLSON_DIR/$file") + || die "Can't open file: $file"; + + open ($zones_fh, ">$OUTPUT_DIR/ZonesPerl/$file") + || die "Can't open file: $OUTPUT_DIR/ZonesPerl/$file"; + + open ($rules_fh, ">$OUTPUT_DIR/RulesPerl/$file") + || die "Can't open file: $OUTPUT_DIR/RulesPerl/$file"; + + %Rules = (); + + my $zone_continues = 0; + + while (<OLSONFILE>) { + next if (m/^#/); + + # '#' characters can appear in strings, but the Olson files don't use + # that feature at present so we treat all '#' as comments for now. + s/#.*//; + + next if (m/^\s*$/); + + if ($zone_continues) { + $zone_continues = &ReadZoneContinuationLine; + + } elsif (m/^Rule\s/) { + &ReadRuleLine; + + } elsif (m/^Zone\s/) { + $zone_continues = &ReadZoneLine; + + } elsif (m/^Link\s/) { +# print "Link: $link_from, $link_to\n"; + + } elsif (m/^Leap\s/) { +# print "Leap\n"; + + } else { + die "Invalid line: $_"; + } + } + +# print ("Read olson file: $file\n"); + + foreach $key (sort (keys (%Rules))) { + print $rules_fh "$Rules{$key}" + } + + close ($zones_fh); + close ($rules_fh); + close (OLSONFILE); +} + + +sub ReadZoneLine { + my ($zone, $name, $gmtoff, $rules_save, $format, + $until_year, $until_month, $until_day, $until_time, $remainder) + = split ' ', $_, 10; + + return &ReadZoneLineCommon ($zone, $name, $gmtoff, $rules_save, $format, + $until_year, $until_month, $until_day, + $until_time); +} + + +sub ReadZoneContinuationLine { + my ($gmtoff, $rules_save, $format, + $until_year, $until_month, $until_day, $until_time, $remainder) + = split ' ', $_, 8; + + return &ReadZoneLineCommon ("", "", $gmtoff, $rules_save, $format, + $until_year, $until_month, $until_day, + $until_time); +} + + +sub ReadZoneLineCommon { + my ($zone, $name, $gmtoff, $rules_save, $format, + $until_year, $until_month, $until_day, $until_time) = @_; + + if (!defined ($until_year)) { $until_year = ""; } + if (!defined ($until_month)) { $until_month = ""; } + if (!defined ($until_day)) { $until_day = ""; } + if (!defined ($until_time)) { $until_time = ""; } + + # A few of the gmtoffsets have an unnecessary :00 seconds. + $gmtoff =~ s/(\d+):(\d+):00/$1:$2/; + + # Make sure the gmtoff does have minutes. + $gmtoff =~ s/^(-?\d+)$/$1:00/; + + # Fix a few other bits so they all use the same format. + if ($gmtoff eq "0") { $gmtoff = "0:00"; } + $until_time =~ s/^0(\d):/$1:/; + if ($until_time eq "0:00") { $until_time = ""; } + if ($until_day eq "1" && $until_time eq "") { $until_day = ""; } + if ($until_month eq "Jan" && $until_day eq "" && $until_time eq "") { + $until_month = ""; + } + + # For Zone continuation lines we need to insert an extra TAB. + if (!$zone) { $zone = "\t" }; + + print $zones_fh "$zone\t$name\t$gmtoff\t$rules_save\t$format\t$until_year\t$until_month\t$until_day\t$until_time\n"; + + if (defined ($until_year) && $until_year) { + return 1; + } else { + return 0; + } +} + + +sub ReadRuleLine { + my ($rule, $name, $from, $to, $type, $in, $on, $at, $save, $letter_s, + $remainder) = split; + + $at =~ s/(\d+:\d+):00/$1/; + $save =~ s/(\d+:\d+):00/$1/; + if ($save eq "0:00") { $save = "0"; } + + $Rules{$name} .= "$rule\t$name\t$from\t$to\t$type\t$in\t$on\t$at\t$save\t$letter_s\n"; + +# print $rules_fh "$rule\t$name\t$from\t$to\t$type\t$in\t$on\t$at\t$save\t$letter_s\n"; +} +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-merge.pl
Added
@@ -0,0 +1,177 @@ +#!/usr/bin/perl -w + +# +# Vzic - a program to convert Olson timezone database files into VZTIMEZONE +# files compatible with the iCalendar specification (RFC2445). +# +# Copyright (C) 2001 Ximian, Inc. +# Copyright (C) 2003 Damon Chaplin. +# +# Author: Damon Chaplin <damon@gnome.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# + +# +# This merges in a new set of VTIMEZONE files with the 'master' set. It only +# updates the files in the master set if the VTIMEZONE component has really +# been changes. Note that the TZID normally includes the date the VTIMEZONE +# file was generated on, so we have to ignore this when comparing the files. +# + +# Set these to the toplevel directories of the 2 sets of VTIMEZONE files. +$NEW_ZONEINFO_DIR = "zoneinfo"; + +# Set this to 1 if you have version numbers in the TZID like libical. +$LIBICAL_VERSIONING = 0; + +# Set this to 0 for dry-runs, and 1 to actually update. +$DO_UPDATES = 1; + +# Save this so we can restore it later. +$input_record_separator = $/; + +sub read_conf { + my $file = shift; + + open CONF, $file or die "can't open $file"; + while (<CONF>) { + if (/^#/) { + next; + } + if (/\@include:\s+(.*)$/) { + push @configs, $1; + } + if (/^configdirectory:\s+(.*)$/) { + $confdir = $1; + } + } + close CONF; +} + +$imapdconf = shift || "/etc/imapd.conf"; + +push @configs, $imapdconf; + +while ($conf = shift @configs) { + read_conf($conf); +} + +if (! $confdir) { $confdir = "/var/imap"; } + +$MASTER_ZONEINFO_DIR = $confdir . "/zoneinfo"; + + +chdir $NEW_ZONEINFO_DIR + || die "Can't cd to $NEW_ZONEINFO_DIR"; + +foreach $new_file (`find -name "*.ics"`) { + # Get rid of './' at start and whitespace at end. + $new_file =~ s/^\.\///; + $new_file =~ s/\s+$//; + +# print "File: $new_file\n"; + + open (NEWZONEFILE, "$new_file") + || die "Can't open file: $NEW_ZONEINFO_DIR/$new_file"; + undef $/; + $new_contents = <NEWZONEFILE>; + $/ = $input_record_separator; + close (NEWZONEFILE); + + $master_file = $MASTER_ZONEINFO_DIR . "/$new_file"; + +# print "Master File: $master_file\n"; + + $copy_to_master = 0; + + # If the ics file exists in the master copy we have to compare them, + # otherwise we can just copy the new file into the master directory. + if (-l $new_file) { + $link_to = readlink($new_file); + + if (! -e $master_file || ! -l $master_file || + (readlink($master_file) ne $link_to)) { + + print "Linking $new_file to $link_to...\n"; + + if ($DO_UPDATES) { + unlink($master_file); + symlink($link_to, $master_file); + } + } + } elsif (-e $master_file) { + open (MASTERZONEFILE, "$master_file") + || die "Can't open file: $master_file"; + undef $/; + $master_contents = <MASTERZONEFILE>; + $/ = $input_record_separator; + close (MASTERZONEFILE); + + $new_contents_copy = $new_contents; + + # Strip the TZID from both contents. +# $new_contents_copy =~ s/^TZID:\S+\r$//m; +# $new_tzid = $&; +# $master_contents =~ s/^TZID:\S+\r$//m; +# $master_tzid = $&; + + # Strip the PRODID from both contents. + $new_contents_copy =~ s/^PRODID:.*$//m; + $master_contents =~ s/^PRODID:.*$//m; + + # Strip the LAST-MODIFIED from both contents. + $new_contents_copy =~ s/^LAST-MODIFIED:(\S+)\r$//m; + $master_contents =~ s/^LAST-MODIFIED:(\S+)\r$//m; + +# print "Matched: $master_tzid\n"; + + + if ($new_contents_copy ne $master_contents) { + print "$new_file has changed. Updating...\n"; + $copy_to_master = 1; + + if ($LIBICAL_VERSIONING) { + # We bump the version number in the new file. +# $master_tzid =~ m%_(\d+)/%; + $version_num = $1; +# print "Version: $version_num\n"; + + $version_num++; + $new_tzid =~ s%_(\d+)/%_$version_num/%; + +# print "New TZID: $new_tzid\n"; + $new_contents =~ s/^TZID:\S+$/$new_tzid/m; + } + } + + } else { + print "$new_file doesn't exist in master directory. Copying...\n"; + $copy_to_master = 1; + } + + if ($copy_to_master) { +# print "Updating: $new_file\n"; + + if ($DO_UPDATES) { + open (MASTERZONEFILE, ">$master_file") + || die "Can't create file: $master_file"; + print MASTERZONEFILE $new_contents; + close (MASTERZONEFILE); + } + } + +} +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-output.c
Added
@@ -0,0 +1,2367 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +/* ALGORITHM: + * + * First we expand all the Rule arrays, so that each element only represents 1 + * year. If a Rule extends to infinity we expand it up to a few years past the + * maximum UNTIL year used in any of the timezones. We do this to make sure + * that the last of the expanded Rules (which may be infinite) is only used + * in the last of the time periods (i.e. the last Zone line). + * + * The Rule arrays are also sorted by the start time (FROM + IN + ON + AT). + * Doing all this makes it much easier to find which rules apply to which + * periods. + * + * For each timezone (i.e. ZoneData element), we step through each of the + * time periods, the ZoneLineData elements (which represent each Zone line + * from the Olson file.) + * + * We calculate the start & end time of the period. + * - For the first line the start time is -infinity. + * - For the last line the end time is +infinity. + * - The end time of each line is also the start time of the next. + * + * We create an array of time changes which occur in this period, including + * the one implied by the Zone line itself (though this is later taken out + * if it is found to be at exactly the same time as the first Rule). + * + * Now we iterate over the time changes, outputting them as STANDARD or + * DAYLIGHT components. We also try to merge them together into RRULEs or + * use RDATEs. + */ + + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "vzic.h" +#include "vzic-output.h" + +#include "vzic-dump.h" + +#include "xversion.h" + + +/* These come from the Makefile. See the comments there. */ +char *ProductID = PRODUCT_ID; +char *TZIDPrefix = TZID_PREFIX; + +/* We expand the TZIDPrefix, replacing %D with the date, in here. */ +char TZIDPrefixExpanded[1024]; + + +/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences, + since otherwise RDATEs are more efficient. Actually, I've set this high + so we only use RRULEs for infinite recurrences. Since expanding RRULEs is + very time-consuming, this seems sensible. */ +#define MIN_RRULE_OCCURRENCES 1 + + +/* The year we go up to when dumping the list of timezone changes (used + for testing & debugging). */ +#define MAX_CHANGES_YEAR 2030 + +/* This is the maximum year that time_t value can typically hold on 32-bit + systems. */ +#define MAX_TIME_T_YEAR 2038 + + +/* The year we use to start RRULEs. */ +#define RRULE_START_YEAR 1970 + +/* The year we use for RDATEs. */ +#define RDATE_YEAR 1970 + + +static char *WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" }; +static int DaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +char *CurrentZoneName; + + +typedef struct _VzicTime VzicTime; +struct _VzicTime +{ + /* Normal years, e.g. 2001. */ + int year; + + /* 0 (Jan) to 11 (Dec). */ + int month; + + /* The day, either a simple month day number, 1-31, or a rule such as + the last Sunday, or the first Monday on or after the 8th. */ + DayCode day_code; + int day_number; /* 1 to 31. */ + int day_weekday; /* 0 (Sun) to 6 (Sat). */ + + /* The time, in seconds from midnight. The code specifies whether the + time is a wall clock time, local standard time, or universal time. */ + int time_seconds; + TimeCode time_code; + + /* The offset from UTC for local standard time. */ + int stdoff; + + /* The offset from UTC for local wall clock time. If this is different to + stdoff then this is a DAYLIGHT component. This is TZOFFSETTO. */ + int walloff; + + /* TRUE if the time change recurs every year to infinity. */ + gboolean is_infinite; + + /* TRUE if the change has already been output. */ + gboolean output; + + /* These are the offsets of the previous VzicTime, and are used when + calculating the time of the change. We place them here in + output_zone_components() to simplify the output code. */ + int prev_stdoff; + int prev_walloff; + + /* The abbreviated form of the timezone name. Note that this may not be + unique. */ + char *tzname; +}; + + +static void expand_and_sort_rule_array (gpointer key, + gpointer value, + gpointer data); +static int rule_sort_func (const void *arg1, + const void *arg2); +static void output_zone (char *directory, + ZoneData *zone, + char *zone_name, + GHashTable *rule_data); +static gboolean parse_zone_name (char *name, + char **directory, + char **subdirectory, + char **filename); +static void output_zone_to_files (ZoneData *zone, + char *zone_name, + GHashTable *rule_data, + FILE *fp, + FILE *changes_fp); +static gboolean add_rule_changes (ZoneLineData *zone_line, + char *zone_name, + GArray *changes, + GHashTable *rule_data, + VzicTime *start, + VzicTime *end, + char **start_letter_s, + int *save_seconds); +static char* expand_tzname (char *zone_name, + char *format, + gboolean have_letter_s, + char *letter_s, + gboolean is_daylight); +static int compare_times (VzicTime *time1, + int stdoff1, + int walloff1, + VzicTime *time2, + int stdoff2, + int walloff2); +static gboolean times_match (VzicTime *time1, + int stdoff1, + int walloff1, + VzicTime *time2, + int stdoff2, + int walloff2); +static void output_zone_components (FILE *fp, + char *name, + GArray *changes); +static void set_previous_offsets (GArray *changes); +static gboolean check_for_recurrence (FILE *fp, + GArray *changes, + int idx); +static void check_for_rdates (FILE *fp, + GArray *changes, + int idx); +static gboolean timezones_match (char *tzname1, + char *tzname2); +static int output_component_start (char *buffer, + VzicTime *vzictime, + gboolean output_rdate, + gboolean use_same_tz_offset); +static void output_component_end (FILE *fp, + VzicTime *vzictime); + +static void vzictime_init (VzicTime *vzictime); +static int calculate_actual_time (VzicTime *vzictime, + TimeCode time_code, + int stdoff, + int walloff); +static int calculate_wall_time (int time, + TimeCode time_code, + int stdoff, + int walloff, + int *day_offset); +static int calculate_until_time (int time, + TimeCode time_code, + int stdoff, + int walloff, + int *year, + int *month, + int *day); +static void fix_time_overflow (int *year, + int *month, + int *day, + int day_offset); + +static char* format_time (int year, + int month, + int day, + int time); +static char* format_tz_offset (int tz_offset, + gboolean round_seconds); +static gboolean output_rrule (char *rrule_buffer, + int month, + DayCode day_code, + int day_number, + int day_weekday, + int day_offset, + char *until); +static gboolean output_rrule_2 (char *buffer, + int month, + int day_number, + int day_weekday); + +static char* format_vzictime (VzicTime *vzictime); + +static void dump_changes (FILE *fp, + char *zone_name, + GArray *changes); +static void dump_change (FILE *fp, + char *zone_name, + VzicTime *vzictime, + int year); + +static void expand_tzid_prefix (void); + + +void +output_vtimezone_files (char *directory, + GArray *zone_data, + GHashTable *rule_data, + GHashTable *link_data, + int max_until_year) +{ + ZoneData *zone; + GList *links; + char *link_to; + int i; + + /* Insert today's date into the TZIDs we output. */ + expand_tzid_prefix (); + + /* Expand the rule data so that each entry specifies only one year, and + sort it so we can easily find the rules applicable to each Zone span. */ + g_hash_table_foreach (rule_data, expand_and_sort_rule_array, + GINT_TO_POINTER (max_until_year)); + + /* Output each timezone. */ + for (i = 0; i < zone_data->len; i++) { + zone = &g_array_index (zone_data, ZoneData, i); + output_zone (directory, zone, zone->zone_name, rule_data); + + /* Look for any links from this zone. */ + links = g_hash_table_lookup (link_data, zone->zone_name); + + while (links) { + link_to = links->data; + + /* We ignore Links that don't have a '/' in them (things like 'EST5EDT'). + */ + if (strchr (link_to, '/')) { + output_zone (directory, zone, link_to, rule_data); + } + + links = links->next; + } + } +} + + +static void +expand_and_sort_rule_array (gpointer key, + gpointer value, + gpointer data) +{ + char *name = key; + GArray *rule_array = value; + RuleData *rule, tmp_rule; + int len, max_year, i, from, to, year; + gboolean is_infinite; + + /* We expand the rule data to a year greater than any year used in a Zone + UNTIL value. This is so that we can easily get parts of the array to + use for each Zone line. */ + max_year = GPOINTER_TO_INT (data) + 2; + + /* If any of the rules apply to several years, we turn it into a single rule + for each year. If the Rule is infinite we go up to max_year. + We change the FROM field in the copies of the Rule, setting it to each + of the years, and set TO to FROM, except if TO was YEAR_MAXIMUM we set + the last TO to YEAR_MAXIMUM, so we still know the Rule is infinite. */ + len = rule_array->len; + for (i = 0; i < len; i++) { + rule = &g_array_index (rule_array, RuleData, i); + + /* None of the Rules currently use the TYPE field, but we'd better check. + */ + if (rule->type) { + fprintf (stderr, "Rules %s has a TYPE: %s\n", name, rule->type); + exit (1); + } + + if (rule->from_year != rule->to_year) { + from = rule->from_year; + to = rule->to_year; + + tmp_rule = *rule; + + /* Flag that this is a shallow copy so we don't free anything twice. */ + tmp_rule.is_shallow_copy = TRUE; + + /* See if it is an infinite Rule. */ + if (to == YEAR_MAXIMUM) { + is_infinite = TRUE; + to = max_year; + if (from < to) + rule->to_year = rule->from_year; + } else { + is_infinite = FALSE; + } + + /* Create a copy of the Rule for each year. */ + for (year = from + 1; year <= to; year++) { + tmp_rule.from_year = year; + + /* If the Rule is infinite, mark the last copy as infinite. */ + if (year == to && is_infinite) + tmp_rule.to_year = YEAR_MAXIMUM; + else + tmp_rule.to_year = year; + + g_array_append_val (rule_array, tmp_rule); + } + } + } + + /* Now sort the rules. */ + qsort (rule_array->data, rule_array->len, sizeof (RuleData), rule_sort_func); + +#if 0 + dump_rule_array (name, rule_array, stdout); +#endif +} + + +/* This is used to sort the rules, after the rules have all been expanded so + that each one is only for one year. */ +static int +rule_sort_func (const void *arg1, + const void *arg2) +{ + RuleData *rule1, *rule2; + int time1_year, time1_month, time1_day; + int time2_year, time2_month, time2_day; + int month_diff, result; + VzicTime t1, t2; + + rule1 = (RuleData*) arg1; + rule2 = (RuleData*) arg2; + + time1_year = rule1->from_year; + time1_month = rule1->in_month; + time2_year = rule2->from_year; + time2_month = rule2->in_month; + + /* If there is more that one month difference we don't need to calculate + the day or time. */ + month_diff = (time1_year - time2_year) * 12 + time1_month - time2_month; + + if (month_diff > 1) + return 1; + if (month_diff < -1) + return -1; + + /* Now we have to calculate the day and time of the Rule start and the + VzicTime, using the given offsets. */ + t1.year = time1_year; + t1.month = time1_month; + t1.day_code = rule1->on_day_code; + t1.day_number = rule1->on_day_number; + t1.day_weekday = rule1->on_day_weekday; + t1.time_code = rule1->at_time_code; + t1.time_seconds = rule1->at_time_seconds; + + t2.year = time2_year; + t2.month = time2_month; + t2.day_code = rule2->on_day_code; + t2.day_number = rule2->on_day_number; + t2.day_weekday = rule2->on_day_weekday; + t2.time_code = rule2->at_time_code; + t2.time_seconds = rule2->at_time_seconds; + + /* FIXME: We don't know the offsets yet, but I don't think any Rules are + close enough together that the offsets can make a difference. Should + check this. */ + calculate_actual_time (&t1, TIME_WALL, 0, 0); + calculate_actual_time (&t2, TIME_WALL, 0, 0); + + /* Now we can compare the entire time. */ + if (t1.year > t2.year) + result = 1; + else if (t1.year < t2.year) + result = -1; + + else if (t1.month > t2.month) + result = 1; + else if (t1.month < t2.month) + result = -1; + + else if (t1.day_number > t2.day_number) + result = 1; + else if (t1.day_number < t2.day_number) + result = -1; + + else if (t1.time_seconds > t2.time_seconds) + result = 1; + else if (t1.time_seconds < t2.time_seconds) + result = -1; + + else { + printf ("WARNING: Rule dates matched.\n"); + result = 0; + } + + return result; +} + + +static void +output_zone (char *directory, + ZoneData *zone, + char *zone_name, + GHashTable *rule_data) +{ + FILE *fp, *changes_fp = NULL; + char output_directory[PATHNAME_BUFFER_SIZE]; + char filename[PATHNAME_BUFFER_SIZE]; + char changes_filename[PATHNAME_BUFFER_SIZE]; + char *zone_directory, *zone_subdirectory, *zone_filename; + + /* Set a global for the zone_name, to be used only for debug messages. */ + CurrentZoneName = zone_name; + + /* Use this to only output a particular zone. */ +#if 0 + if (strcmp (zone_name, "Atlantic/Azores")) + return; +#endif + +#if 0 + printf ("Outputting Zone: %s\n", zone_name); +#endif + + if (!parse_zone_name (zone_name, &zone_directory, &zone_subdirectory, + &zone_filename)) + return; + + if (VzicDumpZoneNamesAndCoords) { + VzicTimeZoneNames = g_list_prepend (VzicTimeZoneNames, + g_strdup (zone_name)); + } + + sprintf (output_directory, "%s/%s", directory, zone_directory); + ensure_directory_exists (output_directory); + sprintf (filename, "%s/%s.ics", output_directory, zone_filename); + + if (VzicDumpChanges) { + sprintf (output_directory, "%s/ChangesVzic/%s", directory, zone_directory); + ensure_directory_exists (output_directory); + sprintf (changes_filename, "%s/%s", output_directory, zone_filename); + } + + if (zone_subdirectory) { + sprintf (output_directory, "%s/%s/%s", directory, zone_directory, + zone_subdirectory); + ensure_directory_exists (output_directory); + sprintf (filename, "%s/%s.ics", output_directory, zone_filename); + + if (VzicDumpChanges) { + sprintf (output_directory, "%s/ChangesVzic/%s/%s", directory, + zone_directory, zone_subdirectory); + ensure_directory_exists (output_directory); + sprintf (changes_filename, "%s/%s", output_directory, zone_filename); + } + } + + /* Create the files. */ + fp = fopen (filename, "w"); + if (!fp) { + fprintf (stderr, "Couldn't create file: %s\n", filename); + exit (1); + } + + if (VzicDumpChanges) { + changes_fp = fopen (changes_filename, "w"); + if (!changes_fp) { + fprintf (stderr, "Couldn't create file: %s\n", changes_filename); + exit (1); + } + } + + fprintf (fp, "BEGIN:VCALENDAR\r\nPRODID:"); + fprintf (fp, ProductID, _CYRUS_VERSION); + fprintf (fp, "\r\nVERSION:2.0\r\n"); + + + output_zone_to_files (zone, zone_name, rule_data, fp, changes_fp); + + if (ferror (fp)) { + fprintf (stderr, "Error writing file: %s\n", filename); + exit (1); + } + + fprintf (fp, "END:VCALENDAR\r\n"); + + fclose (fp); + + g_free (zone_directory); + g_free (zone_subdirectory); + g_free (zone_filename); +} + + +/* This checks that the Zone name only uses the characters in [-+_/a-zA-Z0-9], + and outputs a warning if it isn't. */ +static gboolean +parse_zone_name (char *name, + char **directory, + char **subdirectory, + char **filename) +{ + static int invalid_zone_num = 1; + + char *p, ch, *first_slash_pos = NULL, *second_slash_pos = NULL; + gboolean invalid = FALSE; + + for (p = name; (ch = *p) != 0; p++) { + if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') + && (ch < '0' || ch > '9') && ch != '/' && ch != '_' + && ch != '-' && ch != '+') { + fprintf (stderr, "WARNING: Unusual Zone name: %s\n", name); + invalid = TRUE; + break; + } + + if (ch == '/') { + if (!first_slash_pos) { + first_slash_pos = p; + } else if (!second_slash_pos) { + second_slash_pos = p; + } else { + fprintf (stderr, "WARNING: More than 2 '/' characters in Zone name: %s\n", name); + invalid = TRUE; + break; + } + } + } + + if (!first_slash_pos) { +#if 0 + fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name); +#endif + return FALSE; + } + + if (invalid) { + *directory = g_strdup ("Invalid"); + *filename = g_strdup_printf ("Zone%i", invalid_zone_num++); + } else { + *first_slash_pos = '\0'; + *directory = g_strdup (name); + *first_slash_pos = '/'; + + if (second_slash_pos) { + *second_slash_pos = '\0'; + *subdirectory = g_strdup (first_slash_pos + 1); + *second_slash_pos = '/'; + + *filename = g_strdup (second_slash_pos + 1); + } else { + *subdirectory = NULL; + *filename = g_strdup (first_slash_pos + 1); + } + } + + return invalid ? FALSE : TRUE; +} + + +static void +output_zone_to_files (ZoneData *zone, + char *zone_name, + GHashTable *rule_data, + FILE *fp, + FILE *changes_fp) +{ + ZoneLineData *zone_line; + GArray *changes; + int i, stdoff, walloff, start_index, save_seconds; + VzicTime start, end, *vzictime_start, *vzictime, *vzictime_first_rule_change; + gboolean is_daylight, found_letter_s; + char *start_letter_s; + + changes = g_array_new (FALSE, FALSE, sizeof (VzicTime)); + + vzictime_init (&start); + vzictime_init (&end); + + /* The first period starts at -infinity. */ + start.year = YEAR_MINIMUM; + + for (i = 0; i < zone->zone_line_data->len; i++) { + zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, i); + + /* This is the local standard time offset from GMT for this period. */ + start.stdoff = stdoff = zone_line->stdoff_seconds; + start.walloff = walloff = stdoff + zone_line->save_seconds; + + if (zone_line->until_set) { + end.year = zone_line->until_year; + end.month = zone_line->until_month; + end.day_code = zone_line->until_day_code; + end.day_number = zone_line->until_day_number; + end.day_weekday = zone_line->until_day_weekday; + end.time_seconds = zone_line->until_time_seconds; + end.time_code = zone_line->until_time_code; + } else { + /* The last period ends at +infinity. */ + end.year = YEAR_MAXIMUM; + } + + /* Add a time change for the start of the period. This may be removed + later if one of the rules expands to exactly the same time. */ + start_index = changes->len; + g_array_append_val (changes, start); + + /* If there are Rules associated with this period, add all the relevant + time changes. */ + save_seconds = 0; + if (zone_line->rules) + found_letter_s = add_rule_changes (zone_line, zone_name, changes, + rule_data, &start, &end, + &start_letter_s, &save_seconds); + else + found_letter_s = FALSE; + + /* FIXME: I'm not really sure what to do about finding a LETTER_S for the + first part of the period (i.e. before the first Rule comes into effect). + Currently we try to use the same LETTER_S as the first Rule of the + period which is in local standard time. */ + if (zone_line->save_seconds) + save_seconds = zone_line->save_seconds; + is_daylight = save_seconds ? TRUE : FALSE; + vzictime_start = &g_array_index (changes, VzicTime, start_index); + walloff = vzictime_start->walloff = stdoff + save_seconds; + + /* TEST: See if the first Rule time is exactly the same as the change from + the Zone line. In which case we can remove the Zone line change. */ + if (changes->len > start_index + 1) { + int prev_stdoff, prev_walloff; + + if (start_index > 0) { + VzicTime *v = &g_array_index (changes, VzicTime, start_index - 1); + prev_stdoff = v->stdoff; + prev_walloff = v->walloff; + } else { + prev_stdoff = 0; + prev_walloff = 0; + } + vzictime_first_rule_change = &g_array_index (changes, VzicTime, + start_index + 1); + if (times_match (vzictime_start, prev_stdoff, prev_walloff, + vzictime_first_rule_change, stdoff, walloff)) { +#if 0 + printf ("Removing zone-line change (using new offsets)\n"); +#endif + g_array_remove_index (changes, start_index); + vzictime_start = NULL; + } else if (times_match (vzictime_start, prev_stdoff, prev_walloff, + vzictime_first_rule_change, prev_stdoff, prev_walloff)) { +#if 0 + printf ("Removing zone-line change (using previous offsets)\n"); +#endif + g_array_remove_index (changes, start_index); + vzictime_start = NULL; + } + } + + + if (vzictime_start) { + vzictime_start->tzname = expand_tzname (zone_name, zone_line->format, + found_letter_s, + start_letter_s, is_daylight); + } + + /* The start of the next Zone line is the end time of this one. */ + start = end; + } + + set_previous_offsets (changes); + + output_zone_components (fp, zone_name, changes); + + if (VzicDumpChanges) + dump_changes (changes_fp, zone_name, changes); + + /* Free all the TZNAME fields. */ + for (i = 0; i < changes->len; i++) { + vzictime = &g_array_index (changes, VzicTime, i); + g_free (vzictime->tzname); + } + + g_array_free (changes, TRUE); +} + + +/* This appends any timezone changes specified by the rules associated with + the timezone, that happen between the start and end times. + It returns the letter_s field of the first STANDARD rule found in the + search. We need this to fill in any %s in the FORMAT field of the first + component of the time period (the Zone line). */ +static gboolean +add_rule_changes (ZoneLineData *zone_line, + char *zone_name, + GArray *changes, + GHashTable *rule_data, + VzicTime *start, + VzicTime *end, + char **start_letter_s, + int *save_seconds) +{ + GArray *rule_array; + RuleData *rule, *prev_rule = NULL; + int stdoff, walloff, i, prev_stdoff, prev_walloff; + VzicTime vzictime; + gboolean is_daylight, found_start_letter_s = FALSE; + gboolean checked_for_previous = FALSE; + + *save_seconds = 0; + + rule_array = g_hash_table_lookup (rule_data, zone_line->rules); + if (!rule_array) { + fprintf (stderr, "Couldn't access rules: %s\n", zone_line->rules); + exit (1); + } + + /* The stdoff is the same for all the rules. */ + stdoff = start->stdoff; + + /* The walloff changes as we go through the rules. */ + walloff = start->walloff; + + /* Get the stdoff & walloff from the last change before this period. */ + if (changes->len >= 2) { + VzicTime *change = &g_array_index (changes, VzicTime, changes->len - 2); + prev_stdoff = change->stdoff; + prev_walloff = change->walloff; + } else { + prev_stdoff = prev_walloff = 0; + } + + + for (i = 0; i < rule_array->len; i++) { + rule = &g_array_index (rule_array, RuleData, i); + + is_daylight = rule->save_seconds != 0 ? TRUE : FALSE; + + vzictime_init (&vzictime); + vzictime.year = rule->from_year; + vzictime.month = rule->in_month; + vzictime.day_code = rule->on_day_code; + vzictime.day_number = rule->on_day_number; + vzictime.day_weekday = rule->on_day_weekday; + vzictime.time_seconds = rule->at_time_seconds; + vzictime.time_code = rule->at_time_code; + vzictime.stdoff = stdoff; + vzictime.walloff = stdoff + rule->save_seconds; + vzictime.is_infinite = (rule->to_year == YEAR_MAXIMUM) ? TRUE : FALSE; + + /* If the rule time is before the given start time, skip it. */ + if (compare_times (&vzictime, stdoff, walloff, + start, prev_stdoff, prev_walloff) < 0) + continue; + + /* If the previous Rule was a daylight Rule, then we may want to use the + walloff from that. */ + if (!checked_for_previous) { + checked_for_previous = TRUE; + if (i > 0) { + prev_rule = &g_array_index (rule_array, RuleData, i - 1); + if (prev_rule->save_seconds) { + walloff = start->walloff = stdoff + prev_rule->save_seconds; + *save_seconds = prev_rule->save_seconds; + found_start_letter_s = TRUE; + *start_letter_s = prev_rule->letter_s; +#if 0 + printf ("Could use save_seconds from previous Rule: %s\n", + zone_name); +#endif + } + } + } + + /* If an end time has been given, then if the rule time is on or after it + break out of the loop. */ + if (end->year != YEAR_MAXIMUM + && compare_times (&vzictime, stdoff, walloff, + end, stdoff, walloff) >= 0) + break; + + vzictime.tzname = expand_tzname (zone_name, zone_line->format, TRUE, + rule->letter_s, is_daylight); + + g_array_append_val (changes, vzictime); + + /* When we find the first STANDARD time we set letter_s. */ + if (!found_start_letter_s && !is_daylight) { + found_start_letter_s = TRUE; + *start_letter_s = rule->letter_s; + } + + /* Now that we have added the Rule, the new walloff comes into effect + for any following Rules. */ + walloff = vzictime.walloff; + } + + return found_start_letter_s; +} + + +/* This expands the Zone line FORMAT field, using the given LETTER_S from a + Rule line. There are 3 types of FORMAT field: + 1. a string with an %s in, e.g. "WE%sT". The %s is replaced with LETTER_S. + 2. a string with an '/' in, e.g. "CAT/CAWT". The first part is used for + standard time and the second part for when daylight-saving is in effect. + 3. a plain string, e.g. "LMT", which we leave as-is. + Note that (1) is the only type in which letter_s is required. +*/ +static char* +expand_tzname (char *zone_name, + char *format, + gboolean have_letter_s, + char *letter_s, + gboolean is_daylight) +{ + char *p, buffer[256], *guess = NULL; + int len; + +#if 0 + printf ("Expanding %s with %s\n", format, letter_s); +#endif + + if (!format || !format[0]) { + fprintf (stderr, "Missing FORMAT\n"); + exit (1); + } + + /* 1. Look for a "%s". */ + p = strchr (format, '%'); + if (p && *(p + 1) == 's') { + if (!have_letter_s) { + + /* NOTE: These are a few hard-coded TZNAMEs that I've looked up myself. + These are needed in a few places where a Zone line comes into effect + but no Rule has been found, so we have no LETTER_S to use. + I've tried to use whatever is the normal LETTER_S in the Rules for + the particular zone, in local standard time. */ + if (!strcmp (zone_name, "Asia/Macao") + && !strcmp (format, "C%sT")) + guess = "CST"; + else if (!strcmp (zone_name, "Asia/Macau") + && !strcmp (format, "C%sT")) + guess = "CST"; + else if (!strcmp (zone_name, "Asia/Ashgabat") + && !strcmp (format, "ASH%sT")) + guess = "ASHT"; + else if (!strcmp (zone_name, "Asia/Ashgabat") + && !strcmp (format, "TM%sT")) + guess = "TMT"; + else if (!strcmp (zone_name, "Asia/Samarkand") + && !strcmp (format, "TAS%sT")) + guess = "TAST"; + else if (!strcmp (zone_name, "Atlantic/Azores") + && !strcmp (format, "WE%sT")) + guess = "WET"; + else if (!strcmp (zone_name, "Europe/Paris") + && !strcmp (format, "WE%sT")) + guess = "WET"; + else if (!strcmp (zone_name, "Europe/Warsaw") + && !strcmp (format, "CE%sT")) + guess = "CET"; + else if (!strcmp (zone_name, "America/Phoenix") + && !strcmp (format, "M%sT")) + guess = "MST"; + else if (!strcmp (zone_name, "America/Nome") + && !strcmp (format, "Y%sT")) + guess = "YST"; + + if (guess) { +#if 0 + fprintf (stderr, + "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Guessing: %s\n", + format, zone_name, guess); +#endif + return g_strdup (guess); + } + +#if 1 + fprintf (stderr, + "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Leaving TZNAME empty\n", + format, zone_name); +#endif + +#if 0 + /* This is useful to spot exactly which component had a problem. */ + sprintf (buffer, "FIXME: %s", format); + return g_strdup (buffer); +#else + /* We give up and don't output a TZNAME. */ + return NULL; +#endif + } + + sprintf (buffer, format, letter_s ? letter_s : ""); + return g_strdup (buffer); + } + + /* 2. Look for a "/". */ + p = strchr (format, '/'); + if (p) { + if (is_daylight) { + return g_strdup (p + 1); + } else { + len = p - format; + strncpy (buffer, format, len); + buffer[len] = '\0'; + return g_strdup (buffer); + } + } + + /* 3. Just use format as it is. */ + return g_strdup (format); +} + + +/* Compares 2 VzicTimes, returning strcmp()-like values, i.e. 0 if equal, + 1 if the 1st is after the 2nd and -1 if the 1st is before the 2nd. */ +static int +compare_times (VzicTime *time1, + int stdoff1, + int walloff1, + VzicTime *time2, + int stdoff2, + int walloff2) +{ + VzicTime t1, t2; + int result; + + t1 = *time1; + t2 = *time2; + + calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1); + calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2); + + /* Now we can compare the entire time. */ + if (t1.year > t2.year) + result = 1; + else if (t1.year < t2.year) + result = -1; + + else if (t1.month > t2.month) + result = 1; + else if (t1.month < t2.month) + result = -1; + + else if (t1.day_number > t2.day_number) + result = 1; + else if (t1.day_number < t2.day_number) + result = -1; + + else if (t1.time_seconds > t2.time_seconds) + result = 1; + else if (t1.time_seconds < t2.time_seconds) + result = -1; + + else + result = 0; + +#if 0 + printf ("%i/%i/%i %i <=> %i/%i/%i %i -> %i\n", + t1.day_number, t1.month + 1, t1.year, t1.time_seconds, + t2.day_number, t2.month + 1, t2.year, t2.time_seconds, + result); +#endif + + return result; +} + + +/* Returns TRUE if the 2 times are exactly the same. It will calculate the + actual day, but doesn't convert times. */ +static gboolean +times_match (VzicTime *time1, + int stdoff1, + int walloff1, + VzicTime *time2, + int stdoff2, + int walloff2) +{ + VzicTime t1, t2; + + t1 = *time1; + t2 = *time2; + + calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1); + calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2); + + if (t1.year == t2.year + && t1.month == t2.month + && t1.day_number == t2.day_number + && t1.time_seconds == t2.time_seconds) + return TRUE; + + return FALSE; +} + + +static void +output_zone_components (FILE *fp, + char *name, + GArray *changes) +{ + VzicTime *vzictime; + int i, start_index = 0; + gboolean only_one_change = FALSE; + char start_buffer[1024]; + time_t now = time(0); + struct tm *tm = gmtime(&now); + + fprintf (fp, "BEGIN:VTIMEZONE\r\nTZID:%s%s\r\n", TZIDPrefixExpanded, name); + + /* Use current time as LAST-MODIFIED */ + fprintf (fp, "LAST-MODIFIED:%04i%02i%02iT%02i%02i%02iZ\r\n", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + if (VzicUrlPrefix != NULL) + fprintf (fp, "TZURL:%s/%s\r\n", VzicUrlPrefix, name); + +#if 0 + /* We use an 'X-' property to place the city name in. */ + fprintf (fp, "X-LIC-LOCATION:%s\r\n", name); +#endif + + /* We try to find any recurring components first, or they may get output + as lots of RDATES instead. */ + if (!VzicNoRRules) { + int num_rrules_output = 0; + + for (i = 1; i < changes->len; i++) { + if (check_for_recurrence (fp, changes, i)) { + num_rrules_output++; + } + } + +#if 0 + printf ("Zone: %s had %i infinite RRULEs\n", CurrentZoneName, + num_rrules_output); +#endif + + if (!VzicPureOutput && num_rrules_output == 2) { +#if 0 + printf ("Zone: %s using 2 RRULEs\n", CurrentZoneName); +#endif + fprintf (fp, "END:VTIMEZONE\r\n"); + return; + } + } + + /* We skip the first change, which starts at -infinity, unless it is the only + change for the timezone. */ + if (changes->len > 1) + start_index = 1; + else + only_one_change = TRUE; + + /* For pure output, we start at the start of the array and step through it + outputting RDATEs. For Outlook-compatible output we start at the end + and step backwards to find the first STANDARD time to output. */ + if (VzicPureOutput) + i = start_index - 1; + else + i = changes->len; + + for (;;) { + if (VzicPureOutput) + i++; + else + i--; + + if (VzicPureOutput) { + if (i >= changes->len) + break; + } else { + if (i < start_index) + break; + } + + vzictime = &g_array_index (changes, VzicTime, i); + + /* If we have already output this component as part of an RRULE or RDATE, + then we skip it. */ + if (vzictime->output) + continue; + + /* For Outlook-compatible output we only want to output the last STANDARD + time as a DTSTART, so skip any DAYLIGHT changes. */ + if (!VzicPureOutput && vzictime->stdoff != vzictime->walloff) { + printf ("Skipping DAYLIGHT change\n"); + continue; + } + +#if 0 + printf ("Zone: %s using DTSTART Year: %i\n", CurrentZoneName, + vzictime->year); +#endif + + if (VzicPureOutput) { + output_component_start (start_buffer, vzictime, TRUE, only_one_change); + } else { + /* For Outlook compatability we don't output the RDATE and use the same + TZOFFSET for TZOFFSETFROM and TZOFFSETTO. */ + vzictime->year = RDATE_YEAR; + vzictime->month = 0; + vzictime->day_code = DAY_SIMPLE; + vzictime->day_number = 1; + vzictime->time_code = TIME_WALL; + vzictime->time_seconds = 0; + + output_component_start (start_buffer, vzictime, FALSE, TRUE); + } + + fprintf (fp, "%s", start_buffer); + + /* This will look for matching components and output them as RDATEs + instead of separate components. */ + if (VzicPureOutput && !VzicNoRDates) + check_for_rdates (fp, changes, i); + + output_component_end (fp, vzictime); + + vzictime->output = TRUE; + + if (!VzicPureOutput) + break; + } + + fprintf (fp, "END:VTIMEZONE\r\n"); +} + + +/* This sets the prev_stdoff and prev_walloff (i.e. the TZOFFSETFROM) of each + VzicTime, using the stdoff and walloff of the previous VzicTime. It makes + the rest of the code much simpler. */ +static void +set_previous_offsets (GArray *changes) +{ + VzicTime *vzictime, *prev_vzictime; + int i; + + prev_vzictime = &g_array_index (changes, VzicTime, 0); + prev_vzictime->prev_stdoff = 0; + prev_vzictime->prev_walloff = 0; + + for (i = 1; i < changes->len; i++) { + vzictime = &g_array_index (changes, VzicTime, i); + + vzictime->prev_stdoff = prev_vzictime->stdoff; + vzictime->prev_walloff = prev_vzictime->walloff; + + prev_vzictime = vzictime; + } +} + + +/* Returns TRUE if we output an infinite recurrence. */ +static gboolean +check_for_recurrence (FILE *fp, + GArray *changes, + int idx) +{ + VzicTime *vzictime_start, *vzictime, vzictime_start_copy; + gboolean is_daylight_start, is_daylight; + int last_match, i, next_year, day_offset; + char until[256], rrule_buffer[2048], start_buffer[1024]; + GList *matching_elements = NULL, *elem; + + vzictime_start = &g_array_index (changes, VzicTime, idx); + + /* If this change has already been output, skip it. */ + if (vzictime_start->output) + return FALSE; + + /* There can't possibly be an RRULE starting from YEAR_MINIMUM. */ + if (vzictime_start->year == YEAR_MINIMUM) + return FALSE; + + is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff) + ? TRUE : FALSE; + +#if 0 + printf ("\nChecking: %s OFFSETFROM: %i %s\n", + format_vzictime (vzictime_start), vzictime_start->prev_walloff, + is_daylight_start ? "DAYLIGHT" : ""); +#endif + + /* If this is an infinitely recurring change, output the RRULE and return. + There won't be any changes after it that we could merge. */ + if (vzictime_start->is_infinite) { + + /* Change the year to our minimum start year. */ + vzictime_start_copy = *vzictime_start; + if (!VzicPureOutput) + vzictime_start_copy.year = RRULE_START_YEAR; + + day_offset = output_component_start (start_buffer, &vzictime_start_copy, + FALSE, FALSE); + + if (!output_rrule (rrule_buffer, vzictime_start_copy.month, + vzictime_start_copy.day_code, + vzictime_start_copy.day_number, + vzictime_start_copy.day_weekday, day_offset, "")) { + if (vzictime_start->year != MAX_TIME_T_YEAR) { + fprintf (stderr, "WARNING: Failed to output infinite recurrence with start year: %i\n", vzictime_start->year); + } + return TRUE; + } + + fprintf (fp, "%s%s", start_buffer, rrule_buffer); + output_component_end (fp, vzictime_start); + vzictime_start->output = TRUE; + return TRUE; + } + + last_match = idx; + next_year = vzictime_start->year + 1; + for (i = idx + 1; i < changes->len; i++) { + vzictime = &g_array_index (changes, VzicTime, i); + + is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; + + if (vzictime->output) + continue; + +#if 0 + printf (" %s OFFSETFROM: %i %s\n", + format_vzictime (vzictime), vzictime->prev_walloff, + is_daylight ? "DAYLIGHT" : ""); +#endif + + /* If it is more than one year ahead, we are finished, since we want + consecutive years. */ + if (vzictime->year > next_year) { + break; + } + + /* It must be the same type of component - STANDARD or DAYLIGHT. */ + if (is_daylight != is_daylight_start) { + continue; + } + + /* It must be the following year, with the same month, day & time. + It is possible that the time has a different code but does in fact + match when normalized, but we don't care (for now at least). */ + if (vzictime->year != next_year + || vzictime->month != vzictime_start->month + || vzictime->day_code != vzictime_start->day_code + || vzictime->day_number != vzictime_start->day_number + || vzictime->day_weekday != vzictime_start->day_weekday + || vzictime->time_seconds != vzictime_start->time_seconds + || vzictime->time_code != vzictime_start->time_code) { + continue; + } + + /* The TZOFFSETFROM and TZOFFSETTO must match. */ + if (vzictime->prev_walloff != vzictime_start->prev_walloff) { + continue; + } + + if (vzictime->walloff != vzictime_start->walloff) { + continue; + } + + /* TZNAME must match. */ + if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) { + continue; + } + + /* We have a match. */ + last_match = i; + next_year = vzictime->year + 1; + + matching_elements = g_list_prepend (matching_elements, vzictime); + } + + if (last_match == idx) + return FALSE; + +#if 0 + printf ("Found recurrence %i - %i!!!\n", vzictime_start->year, + next_year - 1); +#endif + + vzictime = &g_array_index (changes, VzicTime, last_match); + +/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences, + since otherwise RDATEs are more efficient. */ + if (!vzictime->is_infinite) { + int years = vzictime->year - vzictime_start->year + 1; +#if 0 + printf ("RRULE Years: %i\n", years); +#endif + if (years < MIN_RRULE_OCCURRENCES) + return FALSE; + } + + if (vzictime->is_infinite) { + until[0] = '\0'; + } else { + VzicTime t1 = *vzictime; + +#if 0 + printf ("RRULE with UNTIL - aborting\n"); + abort (); +#endif + + calculate_actual_time (&t1, TIME_UNIVERSAL, vzictime->prev_stdoff, + vzictime->prev_walloff); + + /* Output UNTIL, in UTC. */ + sprintf (until, ";UNTIL=%sZ", format_time (t1.year, t1.month, + t1.day_number, + t1.time_seconds)); + } + + /* Change the year to our minimum start year. */ + vzictime_start_copy = *vzictime_start; + if (!VzicPureOutput) + vzictime_start_copy.year = RRULE_START_YEAR; + + day_offset = output_component_start (start_buffer, &vzictime_start_copy, + FALSE, FALSE); + if (output_rrule (rrule_buffer, vzictime_start_copy.month, + vzictime_start_copy.day_code, + vzictime_start_copy.day_number, + vzictime_start_copy.day_weekday, day_offset, until)) { + fprintf (fp, "%s%s", start_buffer, rrule_buffer); + output_component_end (fp, vzictime_start); + + /* Mark all the changes as output. */ + vzictime_start->output = TRUE; + for (elem = matching_elements; elem; elem = elem->next) { + vzictime = elem->data; + vzictime->output = TRUE; + } + } + + g_list_free (matching_elements); + + return TRUE; +} + + +static void +check_for_rdates (FILE *fp, + GArray *changes, + int idx) +{ + VzicTime *vzictime_start, *vzictime, tmp_vzictime; + gboolean is_daylight_start, is_daylight; + int i, year, month, day, time; + + vzictime_start = &g_array_index (changes, VzicTime, idx); + + is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff) + ? TRUE : FALSE; + +#if 0 + printf ("\nChecking: %s OFFSETFROM: %i %s\n", + format_vzictime (vzictime_start), vzictime_start->prev_walloff, + is_daylight_start ? "DAYLIGHT" : ""); +#endif + + /* We want to go backwards through the array now, for Outlook compatability. + (It only looks at the first DTSTART/RDATE.) */ + for (i = idx + 1; i < changes->len; i++) { + vzictime = &g_array_index (changes, VzicTime, i); + + is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; + + if (vzictime->output) + continue; + +#if 0 + printf (" %s OFFSETFROM: %i %s\n", format_vzictime (vzictime), + vzictime->prev_walloff, is_daylight ? "DAYLIGHT" : ""); +#endif + + /* It must be the same type of component - STANDARD or DAYLIGHT. */ + if (is_daylight != is_daylight_start) { + continue; + } + + /* The TZOFFSETFROM and TZOFFSETTO must match. */ + if (vzictime->prev_walloff != vzictime_start->prev_walloff) { + continue; + } + + if (vzictime->walloff != vzictime_start->walloff) { + continue; + } + + /* TZNAME must match. */ + if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) { + continue; + } + + /* We have a match. */ + + tmp_vzictime = *vzictime; + calculate_actual_time (&tmp_vzictime, TIME_WALL, vzictime->prev_stdoff, + vzictime->prev_walloff); + + fprintf (fp, "RDATE:%s\r\n", format_time (tmp_vzictime.year, + tmp_vzictime.month, + tmp_vzictime.day_number, + tmp_vzictime.time_seconds)); + + vzictime->output = TRUE; + } +} + + +static gboolean +timezones_match (char *tzname1, + char *tzname2) +{ + if (tzname1 && tzname2 && !strcmp (tzname1, tzname2)) + return TRUE; + + if (!tzname1 && !tzname2) + return TRUE; + + return FALSE; +} + + +/* Outputs the start of a VTIMEZONE component, with the BEGIN line, + the DTSTART, TZOFFSETFROM, TZOFFSETTO & TZNAME properties. */ +static int +output_component_start (char *buffer, + VzicTime *vzictime, + gboolean output_rdate, + gboolean use_same_tz_offset) +{ + gboolean is_daylight, skip_day_offset = FALSE; + gint year, month, day, time, day_offset = 0; + GDate old_date, new_date; + char *formatted_time; + char line1[1024], line2[1024], line3[1024]; + char line4[1024], line5[1024], line6[1024]; + VzicTime tmp_vzictime; + int prev_walloff; + + is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; + + tmp_vzictime = *vzictime; + day_offset = calculate_actual_time (&tmp_vzictime, TIME_WALL, + vzictime->prev_stdoff, + vzictime->prev_walloff); + + sprintf (line1, "BEGIN:%s\r\n", is_daylight ? "DAYLIGHT" : "STANDARD"); + + /* If the timezone only has one change, that means it uses the same offset + forever, so we use the same TZOFFSETFROM as the TZOFFSETTO. (If the zone + has more than one change, we don't output the first one.) */ + if (use_same_tz_offset) + prev_walloff = vzictime->walloff; + else + prev_walloff = vzictime->prev_walloff; + + sprintf (line2, "TZOFFSETFROM:%s\r\n", + format_tz_offset (prev_walloff, !VzicPureOutput)); + + sprintf (line3, "TZOFFSETTO:%s\r\n", + format_tz_offset (vzictime->walloff, !VzicPureOutput)); + + if (vzictime->tzname) + sprintf (line4, "TZNAME:%s\r\n", vzictime->tzname); + else + line4[0] = '\0'; + + formatted_time = format_time (tmp_vzictime.year, tmp_vzictime.month, + tmp_vzictime.day_number, + tmp_vzictime.time_seconds); + sprintf (line5, "DTSTART:%s\r\n", formatted_time); +#if 0 /* The RDATE matching DTSTART is unnecessary */ + if (output_rdate) + sprintf (line6, "RDATE:%s\r\n", formatted_time); + else +#endif + line6[0] = '\0'; + + sprintf (buffer, "%s%s%s%s%s%s", line1, line2, line3, line4, line5, line6); + + return day_offset; +} + + +/* Outputs the END line of the VTIMEZONE component. */ +static void +output_component_end (FILE *fp, + VzicTime *vzictime) +{ + gboolean is_daylight; + + is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; + + fprintf (fp, "END:%s\r\n", is_daylight ? "DAYLIGHT" : "STANDARD"); +} + + +/* Initializes a VzicTime to 1st Jan in YEAR_MINIMUM at midnight, with all + offsets set to 0. */ +static void +vzictime_init (VzicTime *vzictime) +{ + vzictime->year = YEAR_MINIMUM; + vzictime->month = 0; + vzictime->day_code = DAY_SIMPLE; + vzictime->day_number = 1; + vzictime->day_weekday = 0; + vzictime->time_seconds = 0; + vzictime->time_code = TIME_UNIVERSAL; + vzictime->stdoff = 0; + vzictime->walloff = 0; + vzictime->is_infinite = FALSE; + vzictime->output = FALSE; + vzictime->prev_stdoff = 0; + vzictime->prev_walloff = 0; + vzictime->tzname = NULL; +} + + +/* This calculates the actual local time that a change will occur, given + the offsets from standard and wall-clock time. It returns -1 or 1 if it + had to move backwards or forwards one day while converting to local time. + If it does this then we need to change the RRULEs we output. */ +static int +calculate_actual_time (VzicTime *vzictime, + TimeCode time_code, + int stdoff, + int walloff) +{ + GDate date; + gint day_offset, days_in_month, weekday, offset, result; + + vzictime->time_seconds = calculate_wall_time (vzictime->time_seconds, + vzictime->time_code, + stdoff, walloff, &day_offset); + + if (vzictime->day_code != DAY_SIMPLE) { + if (vzictime->year == YEAR_MINIMUM || vzictime->year == YEAR_MAXIMUM) { + fprintf (stderr, "In calculate_actual_time: invalid year\n"); + exit (0); + } + + g_date_clear (&date, 1); + days_in_month = g_date_days_in_month (vzictime->month + 1, vzictime->year); + + /* Note that the day_code refers to the date before we convert it to + a wall-clock date and time. So we find the day it was referring to, + then make any adjustments needed due to converting the time. */ + if (vzictime->day_code == DAY_LAST_WEEKDAY) { + /* Find out what day the last day of the month is. */ + g_date_set_dmy (&date, days_in_month, vzictime->month + 1, + vzictime->year); + weekday = g_date_weekday (&date) % 7; + + /* Calculate how many days we have to go back to get to day_weekday. */ + offset = (weekday + 7 - vzictime->day_weekday) % 7; + + vzictime->day_number = days_in_month - offset; + } else { + /* Find out what day day_number actually is. */ + g_date_set_dmy (&date, vzictime->day_number, vzictime->month + 1, + vzictime->year); + weekday = g_date_weekday (&date) % 7; + + if (vzictime->day_code == DAY_WEEKDAY_ON_OR_AFTER) + offset = (vzictime->day_weekday + 7 - weekday) % 7; + else + offset = - ((weekday + 7 - vzictime->day_weekday) % 7); + + vzictime->day_number = vzictime->day_number + offset; + } + + vzictime->day_code = DAY_SIMPLE; + + if (vzictime->day_number <= 0 || vzictime->day_number > days_in_month) { + fprintf (stderr, "Day overflow: %i\n", vzictime->day_number); + exit (1); + } + } + +#if 0 + fprintf (stderr, "%s -> %i/%i/%i\n", + dump_day_coded (vzictime->day_code, vzictime->day_number, + vzictime->day_weekday), + vzictime->day_number, vzictime->month + 1, vzictime->year); +#endif + + fix_time_overflow (&vzictime->year, &vzictime->month, + &vzictime->day_number, day_offset); + + /* If we want UTC time, we have to convert it now. */ + if (time_code == TIME_UNIVERSAL) { + vzictime->time_seconds = calculate_until_time (vzictime->time_seconds, + TIME_WALL, stdoff, walloff, + &vzictime->year, + &vzictime->month, + &vzictime->day_number); + } + + return day_offset; +} + + +/* This converts the given time into universal time (UTC), to be used in + the UNTIL property. */ +static int +calculate_until_time (int time, + TimeCode time_code, + int stdoff, + int walloff, + int *year, + int *month, + int *day) +{ + int result, day_offset; + + day_offset = 0; + + switch (time_code) { + case TIME_WALL: + result = time - walloff; + break; + case TIME_STANDARD: + result = time - stdoff; + break; + case TIME_UNIVERSAL: + return time; + default: + fprintf (stderr, "Invalid time code\n"); + exit (1); + } + + if (result < 0) { + result += 24 * 60 * 60; + day_offset = -1; + } else if (result >= 24 * 60 * 60) { + result -= 24 * 60 * 60; + day_offset = 1; + } + + /* Sanity check - we shouldn't have an overflow any more. */ + if (result < 0 || result >= 24 * 60 * 60) { + fprintf (stderr, "Time overflow: %i\n", result); + abort (); + } + + fix_time_overflow (year, month, day, day_offset); + + return result; +} + + +/* This converts the given time into wall clock time (the local standard time + with any adjustment for daylight-saving). */ +static int +calculate_wall_time (int time, + TimeCode time_code, + int stdoff, + int walloff, + int *day_offset) +{ + int result; + + *day_offset = 0; + + switch (time_code) { + case TIME_WALL: + return time; + case TIME_STANDARD: + /* We have a local standard time, so we have to subtract stdoff to get + back to UTC, then add walloff to get wall time. */ + result = time - stdoff + walloff; + break; + case TIME_UNIVERSAL: + result = time + walloff; + break; + default: + fprintf (stderr, "Invalid time code\n"); + exit (1); + } + + if (result < 0) { + result += 24 * 60 * 60; + *day_offset = -1; + } else if (result >= 24 * 60 * 60) { + result -= 24 * 60 * 60; + *day_offset = 1; + } + + /* Sanity check - we shouldn't have an overflow any more. */ + if (result < 0 || result >= 24 * 60 * 60) { + fprintf (stderr, "Time overflow: %i\n", result); + exit (1); + } + +#if 0 + printf ("%s -> ", dump_time (time, time_code, TRUE)); + printf ("%s (%i)\n", dump_time (result, TIME_WALL, TRUE), *day_offset); +#endif + + return result; +} + + +static void +fix_time_overflow (int *year, + int *month, + int *day, + int day_offset) +{ + if (day_offset == -1) { + *day = *day - 1; + + if (*day == 0) { + *month = *month - 1; + if (*month == -1) { + *month = 11; + *year = *year - 1; + } + *day = g_date_days_in_month (*month + 1, *year); + } + } else if (day_offset == 1) { + *day = *day + 1; + + if (*day > g_date_days_in_month (*month + 1, *year)) { + *month = *month + 1; + if (*month == 12) { + *month = 0; + *year = *year + 1; + } + *day = 1; + } + } +} + + +static char* +format_time (int year, + int month, + int day, + int time) +{ + static char buffer[128]; + int hour, minute, second; + + /* When we are outputting the first component year will be YEAR_MINIMUM. + We used to use 1 when outputting this, but Outlook doesn't like any years + less that 1600, so we use 1600 instead. We don't output the first change + for most zones now, so it doesn't matter too much. */ + if (year == YEAR_MINIMUM) + year = 1601; + + /* We just use 9999 here, so we keep to 4 characters. But this should only + be needed when debugging - it shouldn't be needed in the VTIMEZONEs. */ + if (year == YEAR_MAXIMUM) { + fprintf (stderr, "format_time: YEAR_MAXIMUM used\n"); + year = 9999; + } + + hour = time / 3600; + minute = (time % 3600) / 60; + second = time % 60; + + sprintf (buffer, "%04i%02i%02iT%02i%02i%02i", + year, month + 1, day, hour, minute, second); + + return buffer; +} + + +/* Outlook doesn't support 6-digit values, i.e. including the seconds, so + we round to the nearest minute. No current offsets use the seconds value, + so we aren't losing much. */ +static char* +format_tz_offset (int tz_offset, + gboolean round_seconds) +{ + static char buffer[128]; + char *sign = "+"; + int hours, minutes, seconds; + + if (tz_offset < 0) { + tz_offset = -tz_offset; + sign = "-"; + } + + if (round_seconds) + tz_offset += 30; + + hours = tz_offset / 3600; + minutes = (tz_offset % 3600) / 60; + seconds = tz_offset % 60; + + if (round_seconds) + seconds = 0; + + /* Sanity check. Standard timezone offsets shouldn't be much more than 12 + hours, and daylight saving shouldn't change it by more than a few hours. + (The maximum offset is 15 hours 56 minutes at present.) */ + if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 + || seconds < 0 || seconds >= 60) { + fprintf (stderr, "WARNING: Strange timezone offset: H:%i M:%i S:%i\n", + hours, minutes, seconds); + } + + if (seconds == 0) + sprintf (buffer, "%s%02i%02i", sign, hours, minutes); + else + sprintf (buffer, "%s%02i%02i%02i", sign, hours, minutes, seconds); + + return buffer; +} + + +static gboolean +output_rrule (char *rrule_buffer, + int month, + DayCode day_code, + int day_number, + int day_weekday, + int day_offset, + char *until) +{ + char buffer[1024], buffer2[1024]; + + buffer[0] = '\0'; + + if (day_offset > 1 || day_offset < -1) { + fprintf (stderr, "Invalid day_offset: %i\n", day_offset); + exit (0); + } + + /* If the DTSTART time was moved to another day when converting to local + time, we need to adjust the RRULE accordingly. e.g. If the original RRULE + was on the 19th of the month, but DTSTART was moved 1 day forward, then + we output the 20th of the month instead. */ + if (day_offset == 1) { + if (day_code != DAY_LAST_WEEKDAY) + day_number++; + day_weekday = (day_weekday + 1) % 7; + + /* Check we don't use February 29th. */ + if (month == 1 && day_number > 28) { + fprintf (stderr, "Can't format RRULE - out of bounds. Month: %i Day number: %i\n", month + 1, day_number); + exit (0); + } + + /* If we go past the end of the month, move to the next month. */ + if (day_code != DAY_LAST_WEEKDAY && day_number > DaysInMonth[month]) { + month++; + day_number = 1; + } + + } else if (day_offset == -1) { + if (day_code != DAY_LAST_WEEKDAY) + day_number--; + day_weekday = (day_weekday + 6) % 7; + + if (day_code != DAY_LAST_WEEKDAY && day_number < 1) + fprintf (stderr, "Month: %i Day number: %i\n", month + 1, day_number); + } + + switch (day_code) { + case DAY_SIMPLE: + /* Outlook (2000) will not parse the simple YEARLY RRULEs in VTIMEZONEs, + or BYMONTHDAY, or BYYEARDAY, which makes this option difficult! + Currently we use something like BYDAY=1SU, which will be incorrect + at times. This only affects Asia/Baghdad, Asia/Gaza, Asia/Jerusalem & + Asia/Damascus at present (and Jerusalem doesn't have specific rules + at the moment anyway, so that isn't a big loss). */ + if (!VzicPureOutput) { + if (day_number < 8) { + printf ("WARNING: %s: Outputting BYDAY=1SU instead of BYMONTHDAY=1-7 for Outlook compatability\n", CurrentZoneName); + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1SU", + month + 1); + } else if (day_number < 15) { + printf ("WARNING: %s: Outputting BYDAY=2SU instead of BYMONTHDAY=8-14 for Outlook compatability\n", CurrentZoneName); + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2SU", + month + 1); + } else if (day_number < 22) { + printf ("WARNING: %s: Outputting BYDAY=3SU instead of BYMONTHDAY=15-21 for Outlook compatability\n", CurrentZoneName); + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3SU", + month + 1); + } else { + printf ("ERROR: %s: Couldn't output RRULE (day=%i) compatible with Outlook\n", CurrentZoneName, day_number); + exit (1); + } + } else { + sprintf (buffer, "RRULE:FREQ=YEARLY"); + } + break; + + case DAY_WEEKDAY_ON_OR_AFTER: + if (day_number > DaysInMonth[month] - 6) { + /* This isn't actually needed at present. */ +#if 0 + fprintf (stderr, "DAY_WEEKDAY_ON_OR_AFTER: %i %i\n", day_number, + month + 1); +#endif + + if (!VzicPureOutput) { + printf ("ERROR: %s: Couldn't output RRULE (day>=x) compatible with Outlook\n", CurrentZoneName); + exit (1); + } else { + /* We do 6 days at the end of this month, and 1 at the start of the + next. We can't do this if we want Outlook compatability, as it + needs BYMONTHDAY, which Outlook doesn't support. */ +/* + sprintf (buffer, + "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s", + month + 1, + day_number, day_number + 1, day_number + 2, day_number + 3, + day_number + 4, day_number + 5, + WeekDays[day_weekday]); + + sprintf (buffer2, + "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s", + (month + 1) % 12 + 1, + WeekDays[day_weekday]); + + sprintf (rrule_buffer, "%s%s\n%s%s\r\n", + buffer, until, buffer2, until); +*/ + /* Multiple RRULEs within the component are illegal according to new iCal RFC 5545, + so combine the above RRULEs (commented) into a single RRULE using BYYEARDAY */ + day_number = 0; + int i; + for (i = month+1; i < 12; i++) { + day_number += DaysInMonth[i]; + } + sprintf (rrule_buffer, "RRULE:FREQ=YEARLY;BYYEARDAY=-%i,-%i,-%i,-%i,-%i,-%i,-%i;BYDAY=%s%s\r\n", + day_number, day_number+1, day_number+2, day_number+3, + day_number+4, day_number+5, day_number+6, WeekDays[day_weekday], until); + + return TRUE; + } + } + + if (!output_rrule_2 (buffer, month, day_number, day_weekday)) + return FALSE; + + break; + + case DAY_WEEKDAY_ON_OR_BEFORE: + if (day_number < 7) { + /* FIXME: This is unimplemented, but it isn't needed at present anway. */ + fprintf (stderr, "DAY_WEEKDAY_ON_OR_BEFORE: %i. Unimplemented. Exiting...\n", day_number); + exit (0); + } + + if (!output_rrule_2 (buffer, month, day_number - 6, day_weekday)) + return FALSE; + + break; + + case DAY_LAST_WEEKDAY: + if (day_offset == 1) { + if (month == 1) { + fprintf (stderr, "DAY_LAST_WEEKDAY - day moved, in February - can't fix\n"); + exit (0); + } + + /* This is only used once at present, for Africa/Cairo. */ +#if 0 + fprintf (stderr, "DAY_LAST_WEEKDAY - day moved\n"); +#endif + + if (!VzicPureOutput) { + printf ("WARNING: %s: Modifying RRULE (last weekday) for Outlook compatability\n", CurrentZoneName); + sprintf (buffer, + "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", + month + 1, WeekDays[day_weekday]); + printf (" Outputting: %s\n", buffer); + } else { + /* We do 6 days at the end of this month, and 1 at the start of the + next. We can't do this if we want Outlook compatability, as it needs + BYMONTHDAY, which Outlook doesn't support. */ +/* + day_number = DaysInMonth[month]; + sprintf (buffer, + "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s", + month + 1, + day_number - 5, day_number - 4, day_number - 3, + day_number - 2, day_number - 1, day_number, + WeekDays[day_weekday]); + + sprintf (buffer2, + "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s", + (month + 1) % 12 + 1, + WeekDays[day_weekday]); + + sprintf (rrule_buffer, "%s%s\r\n%s%s\r\n", + buffer, until, buffer2, until); +*/ + /* Multiple RRULEs within the component are illegal according to new iCal RFC 5545, + so combine the above RRULEs (commented) into a single RRULE using BYYEARDAY */ + day_number = 0; + int i; + for (i = month+1; i < 12; i++) { + day_number += DaysInMonth[i]; + } + sprintf (rrule_buffer, "RRULE:FREQ=YEARLY;BYYEARDAY=-%i,-%i,-%i,-%i,-%i,-%i,-%i;BYDAY=%s%s\r\n", + day_number, day_number+1, day_number+2, day_number+3, + day_number+4, day_number+5, day_number+6, WeekDays[day_weekday], until); + + return TRUE; + } + + } else if (day_offset == -1) { + /* We do 7 days 1 day before the end of this month. */ + day_number = DaysInMonth[month]; + + if (!output_rrule_2 (buffer, month, day_number - 7, day_weekday)) + return FALSE; + + sprintf (rrule_buffer, "%s%s\r\n", buffer, until); + return TRUE; + } + + sprintf (buffer, + "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", + month + 1, WeekDays[day_weekday]); + break; + + default: + fprintf (stderr, "Invalid day code\n"); + exit (1); + } + + sprintf (rrule_buffer, "%s%s\r\n", buffer, until); + return TRUE; +} + + +/* This tries to convert a RRULE like 'BYMONTHDAY=8,9,10,11,12,13,14;BYDAY=FR' + into 'BYDAY=2FR'. We need this since Outlook doesn't accept BYMONTHDAY. + It returns FALSE if conversion is not possible. */ +static gboolean +output_rrule_2 (char *buffer, + int month, + int day_number, + int day_weekday) +{ + + if (day_number == 1) { + /* Convert it to a BYDAY=1SU type of RRULE. */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s", + month + 1, WeekDays[day_weekday]); + + } else if (day_number == 8) { + /* Convert it to a BYDAY=2SU type of RRULE. */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s", + month + 1, WeekDays[day_weekday]); + + } else if (day_number == 15) { + /* Convert it to a BYDAY=3SU type of RRULE. */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3%s", + month + 1, WeekDays[day_weekday]); + + } else if (day_number == 22) { + /* Convert it to a BYDAY=4SU type of RRULE. (Currently not used.) */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=4%s", + month + 1, WeekDays[day_weekday]); + + } else if (month != 1 && day_number == DaysInMonth[month] - 6) { + /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.) */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", + month + 1, WeekDays[day_weekday]); + + } else { + /* Can't convert to a correct RRULE. If we want Outlook compatability we + have to use a slightly incorrect RRULE, so the time change will be 1 + week out every 7 or so years. Alternatively we could possibly move the + change by an hour or so so we would always be 1 or 2 hours out, but + never 1 week out. Yes, that sounds a better idea. */ + if (!VzicPureOutput) { + printf ("WARNING: %s: Modifying RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1); + + if (day_number == 2) { + /* Convert it to a BYDAY=1SU type of RRULE. + This is needed for Asia/Karachi. */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s", + month + 1, WeekDays[day_weekday]); + } else if (day_number == 9) { + /* Convert it to a BYDAY=2SU type of RRULE. + This is needed for Antarctica/Palmer & America/Santiago. */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s", + month + 1, WeekDays[day_weekday]); + } else if (month != 1 && day_number <= DaysInMonth[month] - 7 && day_number > DaysInMonth[month] - 14) { + /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.) + This is needed for America/Godthab. */ + sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", + month + 1, WeekDays[day_weekday]); + } else { + printf ("ERROR: %s: Couldn't modify RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1); + exit (1); + } + + } else { + sprintf (buffer, + "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i,%i;BYDAY=%s", + month + 1, + day_number, day_number + 1, day_number + 2, day_number + 3, + day_number + 4, day_number + 5, day_number + 6, + WeekDays[day_weekday]); + } + } + + return TRUE; +} + + +static char* +format_vzictime (VzicTime *vzictime) +{ + static char buffer[1024]; + + sprintf (buffer, "%s %2i %s %s %i %i %s", + dump_year (vzictime->year), vzictime->month + 1, + dump_day_coded (vzictime->day_code, vzictime->day_number, + vzictime->day_weekday), + dump_time (vzictime->time_seconds, vzictime->time_code, TRUE), + vzictime->stdoff, vzictime->walloff, + vzictime->is_infinite ? "INFINITE" : ""); + + return buffer; +} + + +static void +dump_changes (FILE *fp, + char *zone_name, + GArray *changes) +{ + VzicTime *vzictime, *vzictime2 = NULL; + int i, year_offset, year; + + for (i = 0; i < changes->len; i++) { + vzictime = &g_array_index (changes, VzicTime, i); + + if (vzictime->year > MAX_CHANGES_YEAR) + return; + + dump_change (fp, zone_name, vzictime, vzictime->year); + } + + if (changes->len < 2) + return; + + /* Now see if the changes array ends with a pair of recurring changes. */ + vzictime = &g_array_index (changes, VzicTime, changes->len - 2); + vzictime2 = &g_array_index (changes, VzicTime, changes->len - 1); + if (!vzictime->is_infinite || !vzictime2->is_infinite) + return; + + year_offset = 1; + for (;;) { + year = vzictime->year + year_offset; + if (year > MAX_CHANGES_YEAR) + break; + dump_change (fp, zone_name, vzictime, year); + + year = vzictime2->year + year_offset; + if (year > MAX_CHANGES_YEAR) + break; + dump_change (fp, zone_name, vzictime2, year); + + year_offset++; + } +} + + +static void +dump_change (FILE *fp, + char *zone_name, + VzicTime *vzictime, + int year) +{ + int hour, minute, second; + VzicTime tmp_vzictime; + static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + /* Output format is: + + Zone-Name [tab] Date [tab] Time [tab] UTC-Offset + + The Date and Time fields specify the time change in UTC. + + The UTC Offset is for local (wall-clock) time. It is the amount of time + to add to UTC to get local time. + */ + + fprintf (fp, "%s\t", zone_name); + + if (year == YEAR_MINIMUM) { + fprintf (fp, " 1 Jan 0001\t 0:00:00", zone_name); + } else if (year == YEAR_MAXIMUM) { + fprintf (stderr, "Maximum year found in change time\n"); + exit (1); + } else { + tmp_vzictime = *vzictime; + tmp_vzictime.year = year; + calculate_actual_time (&tmp_vzictime, TIME_UNIVERSAL, + vzictime->prev_stdoff, vzictime->prev_walloff); + + hour = tmp_vzictime.time_seconds / 3600; + minute = (tmp_vzictime.time_seconds % 3600) / 60; + second = tmp_vzictime.time_seconds % 60; + + fprintf (fp, "%2i %s %04i\t%2i:%02i:%02i", + tmp_vzictime.day_number, months[tmp_vzictime.month], + tmp_vzictime.year, hour, minute, second); + } + + fprintf (fp, "\t%s", format_tz_offset (vzictime->walloff, FALSE)); + + fprintf (fp, "\r\n"); +} + + +void +ensure_directory_exists (char *directory) +{ + struct stat filestat; + + if (stat (directory, &filestat) != 0) { + /* If the directory doesn't exist, try to create it. */ + if (errno == ENOENT) { + if (mkdir (directory, 0777) != 0) { + fprintf (stderr, "Can't create directory: %s\n", directory); + exit (1); + } + } else { + fprintf (stderr, "Error calling stat() on directory: %s\n", directory); + exit (1); + } + } else if (!S_ISDIR (filestat.st_mode)) { + fprintf (stderr, "Can't create directory, already exists: %s\n", + directory); + exit (1); + } +} + + +static void +expand_tzid_prefix (void) +{ + char *src, *dest; + char date_buf[16]; + char ch1, ch2; + time_t t; + struct tm *tm; + + /* Get today's date as a string in the format "YYYYMMDD". */ + t = time (NULL); + tm = localtime (&t); + sprintf (date_buf, "%4i%02i%02i", tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday); + + src = TZIDPrefix; + dest = TZIDPrefixExpanded; + + while (ch1 = *src++) { + + /* Look for a '%'. */ + if (ch1 == '%') { + ch2 = *src++; + + if (ch2 == 'D') { + /* '%D' gets expanded into the date string. */ + strcpy (dest, date_buf); + dest += strlen (dest); + } else if (ch2 == '%') { + /* '%%' gets converted into one '%'. */ + *dest++ = '%'; + } else { + /* Anything else is output as is. */ + *dest++ = '%'; + *dest++ = ch2; + } + } else { + *dest++ = ch1; + } + } + +#if 0 + printf ("TZID : %s\n", TZIDPrefix); + printf ("Expanded: %s\n", TZIDPrefixExpanded); +#endif +}
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-output.h
Added
@@ -0,0 +1,38 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _VZIC_OUTPUT_H_ +#define _VZIC_OUTPUT_H_ + +#include <glib.h> + +void output_vtimezone_files (char *directory, + GArray *zone_data, + GHashTable *rule_data, + GHashTable *link_data, + int max_until_year); + +void ensure_directory_exists (char *directory); + +#endif /* _VZIC_OUTPUT_H_ */
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-parse.c
Added
@@ -0,0 +1,938 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libgen.h> + +#include "vzic.h" +#include "vzic-parse.h" + +/* This is the maximum line length we allow. */ +#define MAX_LINE_LEN 1024 + +/* The maximum number of fields on a line. */ +#define MAX_FIELDS 12 + +#define CREATE_SYMLINK 1 + +typedef enum +{ + ZONE_ID = 0, /* The 'Zone' at the start of the line. */ + ZONE_NAME = 1, + ZONE_GMTOFF = 2, + ZONE_RULES_SAVE = 3, + ZONE_FORMAT = 4, + ZONE_UNTIL_YEAR = 5, + ZONE_UNTIL_MONTH = 6, + ZONE_UNTIL_DAY = 7, + ZONE_UNTIL_TIME = 8 +} ZoneFieldNumber; + + +typedef enum +{ + RULE_ID = 0, /* The 'Rule' at the start of the line. */ + RULE_NAME = 1, + RULE_FROM = 2, + RULE_TO = 3, + RULE_TYPE = 4, + RULE_IN = 5, + RULE_ON = 6, + RULE_AT = 7, + RULE_SAVE = 8, + RULE_LETTER_S = 9 +} RuleFieldNumber; + + +typedef enum +{ + LINK_ID = 0, /* The 'Link' at the start of the line. */ + LINK_FROM = 1, + LINK_TO = 2 +} LinkFieldNumber; + + +/* This struct contains information used while parsing the files, and is + passed to most parsing functions. */ +typedef struct _ParsingData ParsingData; +struct _ParsingData +{ + /* This is the line being parsed. buffer is a copy that we break into fields + and sub-fields as it is parsed. */ + char line[MAX_LINE_LEN]; + char buffer[MAX_LINE_LEN]; + + /* These are pointers to the start of each field in buffer. */ + char *fields[MAX_FIELDS]; + int num_fields; + + /* These are just for producing error messages. */ + char *filename; + int line_number; + + + /* This is an array of ZoneData structs, 1 for each timezone read. */ + GArray *zone_data; + + /* This is a hash table of arrays of RuleData structs. As each Rule line is + read in, a new RuleData struct is filled in and appended to the + appropriate GArray in the hash table. */ + GHashTable *rule_data; + + /* A hash containing data on the Link lines. The keys are the timezones + where the link is from (i.e. the timezone we will be outputting anyway) + and the data is a GList of timezones to link to (where we will copy the + timezone data to). */ + GHashTable *link_data; + + int max_until_year; +}; + + +/* + * Parsing functions, used when reading the Olson timezone data file. + */ +static void parse_fields (ParsingData *data); +static gboolean parse_zone_line (ParsingData *data); +static gboolean parse_zone_continuation_line (ParsingData *data); +static gboolean parse_zone_common (ParsingData *data, + int offset); +static void parse_rule_line (ParsingData *data); +static void parse_link_line (ParsingData *data); + +static int parse_year (ParsingData *data, + char *field, + gboolean accept_only, + int only_value); +static int parse_month (ParsingData *data, + char *field); +static DayCode parse_day (ParsingData *data, + char *field, + int *day, + int *weekday); +static int parse_weekday (ParsingData *data, + char *field); +static int parse_time (ParsingData *data, + char *field, + TimeCode *time_code); +static int parse_number (ParsingData *data, + char **num); +static int parse_rules_save (ParsingData *data, + char *field, + char **rules); + +static void parse_coord (char *coord, + int len, + int *result); + +void +parse_olson_file (char *filename, + GArray **zone_data, + GHashTable **rule_data, + GHashTable **link_data, + int *max_until_year) +{ + ParsingData data; + FILE *fp; + int zone_continues = 0; + + *zone_data = g_array_new (FALSE, FALSE, sizeof (ZoneData)); + *rule_data = g_hash_table_new (g_str_hash, g_str_equal); + *link_data = g_hash_table_new (g_str_hash, g_str_equal); + + fp = fopen (filename, "r"); + if (!fp) { + fprintf (stderr, "Couldn't open file: %s\n", filename); + exit (1); + } + + data.filename = filename; + data.zone_data = *zone_data; + data.rule_data = *rule_data; + data.link_data = *link_data; + data.max_until_year = 0; + + for (data.line_number = 0; ; data.line_number++) { + if (fgets (data.line, sizeof (data.line), fp) != data.line) + break; + + strcpy (data.buffer, data.line); + + parse_fields (&data); + if (data.num_fields == 0) + continue; + + if (zone_continues) { + zone_continues = parse_zone_continuation_line (&data); + } else if (!strcmp (data.fields[0], "Zone")) { + zone_continues = parse_zone_line (&data); + } else if (!strcmp (data.fields[0], "Rule")) { + parse_rule_line (&data); + } else if (!strcmp (data.fields[0], "Link")) { + parse_link_line (&data); + } else if (!strcmp (data.fields[0], "Leap")) { + /* We don't care about Leap lines. */ + } else { + fprintf (stderr, "%s:%i: Invalid line.\n%s\n", filename, + data.line_number, data.line); + exit (1); + } + } + + if (ferror (fp)) { + fprintf (stderr, "Error reading file: %s\n", filename); + exit (1); + } + + if (zone_continues) { + fprintf (stderr, "%s:%i: Zone continuation line expected.\n%s\n", + filename, data.line_number, data.line); + exit (1); + } + + fclose (fp); + +#if 0 + printf ("Max UNTIL year: %i\n", data.max_until_year); +#endif + *max_until_year = data.max_until_year; +} + + +/* Converts the line into fields. */ +static void +parse_fields (ParsingData *data) +{ + int i; + char *p, *s, ch; + + /* Reset all fields to NULL. */ + for (i = 0; i < MAX_FIELDS; i++) + data->fields[i] = 0; + + data->num_fields = 0; + p = data->buffer; + + for (;;) { + /* Skip whitespace. */ + while (isspace (*p)) + p++; + + /* See if we have reached the end of the line or a comment. */ + if (*p == '\0' || *p == '#') + break; + + /* We must have another field, so save the start position. */ + data->fields[data->num_fields++] = p; + + /* Now find the end of the field. If the field contains '"' characters + they are removed and we have to move the rest of the chars back. */ + s = p; + for (;;) { + ch = *p; + if (ch == '\0' || ch == '#') { + /* Don't move p on since this is the end of the line. */ + *s = '\0'; + break; + } else if (isspace (ch)) { + *s = '\0'; + p++; + break; + } else if (ch == '"') { + p++; + for (;;) { + ch = *p; + if (ch == '\0') { + fprintf (stderr, + "%s:%i: Closing quote character ('\"') missing.\n%s\n", + data->filename, data->line_number, data->line); + exit (1); + } else if (ch == '"') { + p++; + break; + } else { + *s++ = ch; + } + p++; + } + } else { + *s++ = ch; + } + p++; + } + } + +#if 0 + printf ("%i fields: ", data->num_fields); + for (i = 0; i < data->num_fields; i++) + printf ("'%s' ", data->fields[i]); + printf ("\n"); +#endif +} + + +static gboolean +parse_zone_line (ParsingData *data) +{ + ZoneData zone; + + /* All 5 fields up to FORMAT must be present. */ + if (data->num_fields < 5 || data->num_fields > 9) { + fprintf (stderr, "%s:%i: Invalid Zone line - %i fields.\n%s\n", + data->filename, data->line_number, data->num_fields, + data->line); + exit (1); + } + + zone.zone_name = g_strdup (data->fields[ZONE_NAME]); + zone.zone_line_data = g_array_new (FALSE, FALSE, sizeof (ZoneLineData)); + + g_array_append_val (data->zone_data, zone); + + return parse_zone_common (data, 0); +} + + +static gboolean +parse_zone_continuation_line (ParsingData *data) +{ + /* All 3 fields up to FORMAT must be present. */ + if (data->num_fields < 3 || data->num_fields > 7) { + fprintf (stderr, + "%s:%i: Invalid Zone continuation line - %i fields.\n%s\n", + data->filename, data->line_number, data->num_fields, + data->line); + exit (1); + } + + return parse_zone_common (data, -2); +} + + +static gboolean +parse_zone_common (ParsingData *data, + int offset) +{ + ZoneData *zone; + ZoneLineData zone_line; + TimeCode time_code; + + zone_line.stdoff_seconds = parse_time (data, + data->fields[ZONE_GMTOFF + offset], + &time_code); + zone_line.save_seconds = parse_rules_save (data, + data->fields[ZONE_RULES_SAVE + offset], + &zone_line.rules); + + if (!VzicPureOutput) { + /* We round the UTC offsets to the nearest minute, to be compatible with + Outlook. This also works with -ve numbers, I think. + -56 % 60 = -59. -61 % 60 = -1. */ + if (zone_line.stdoff_seconds >= 0) + zone_line.stdoff_seconds += 30; + else + zone_line.stdoff_seconds -= 29; + zone_line.stdoff_seconds -= zone_line.stdoff_seconds % 60; + + if (zone_line.save_seconds >= 0) + zone_line.save_seconds += 30; + else + zone_line.save_seconds -= 29; + zone_line.save_seconds -= zone_line.save_seconds % 60; + } + + zone_line.format = g_strdup (data->fields[ZONE_FORMAT + offset]); + + if (data->num_fields - offset >= 6) { + zone_line.until_set = TRUE; + zone_line.until_year = parse_year (data, + data->fields[ZONE_UNTIL_YEAR + offset], + FALSE, 0); + zone_line.until_month = parse_month (data, + data->fields[ZONE_UNTIL_MONTH + offset]); + zone_line.until_day_code = parse_day (data, + data->fields[ZONE_UNTIL_DAY + offset], + &zone_line.until_day_number, + &zone_line.until_day_weekday); + zone_line.until_time_seconds = parse_time (data, + data->fields[ZONE_UNTIL_TIME + offset], + &zone_line.until_time_code); + + /* We also want to know the maximum year used in any UNTIL value, so we + know where to expand all the infinite Rule data to. */ + if (zone_line.until_year != YEAR_MAXIMUM + && zone_line.until_year != YEAR_MINIMUM) + data->max_until_year = MAX (data->max_until_year, zone_line.until_year); + + } else { + zone_line.until_set = FALSE; + } + + /* Append it to the last Zone, since that is the one we are currently + reading. */ + zone = &g_array_index (data->zone_data, ZoneData, data->zone_data->len - 1); + g_array_append_val (zone->zone_line_data, zone_line); + + return zone_line.until_set; +} + + +static void +parse_rule_line (ParsingData *data) +{ + GArray *rule_array; + RuleData rule; + char *name; + TimeCode time_code; + + /* All 10 fields must be present. */ + if (data->num_fields != 10) { + fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n", + data->filename, data->line_number, data->num_fields, + data->line); + exit (1); + } + + name = data->fields[RULE_NAME]; + + /* Create the GArray and add it to the hash table if it doesn't already + exist. */ + rule_array = g_hash_table_lookup (data->rule_data, name); + if (!rule_array) { + rule_array = g_array_new (FALSE, FALSE, sizeof (RuleData)); + g_hash_table_insert (data->rule_data, g_strdup (name), rule_array); + } + + rule.from_year = parse_year (data, data->fields[RULE_FROM], FALSE, 0); + if (rule.from_year == YEAR_MAXIMUM) { + fprintf (stderr, "%s:%i: Invalid Rule FROM value: '%s'\n", + data->filename, data->line_number, data->fields[RULE_FROM]); + exit (1); + } + + rule.to_year = parse_year (data, data->fields[RULE_TO], TRUE, + rule.from_year); + if (rule.to_year == YEAR_MINIMUM) { + fprintf (stderr, "%s:%i: Invalid Rule TO value: %s\n", + data->filename, data->line_number, data->fields[RULE_TO]); + exit (1); + } + + if (!strcmp (data->fields[RULE_TYPE], "-")) + rule.type = NULL; + else { + printf ("Type: %s\n", data->fields[RULE_TYPE]); + rule.type = g_strdup (data->fields[RULE_TYPE]); + } + + rule.in_month = parse_month (data, data->fields[RULE_IN]); + rule.on_day_code = parse_day (data, data->fields[RULE_ON], + &rule.on_day_number, &rule.on_day_weekday); + rule.at_time_seconds = parse_time (data, data->fields[RULE_AT], + &rule.at_time_code); + rule.save_seconds = parse_time (data, data->fields[RULE_SAVE], &time_code); + + if (!strcmp (data->fields[RULE_LETTER_S], "-")) { + rule.letter_s = NULL; + } else { + rule.letter_s = g_strdup (data->fields[RULE_LETTER_S]); + } + + rule.is_shallow_copy = FALSE; + + g_array_append_val (rule_array, rule); +} + + +static void +parse_link_line (ParsingData *data) +{ + char *from, *to, *old_from; + GList *zone_list; + + /* We must have 3 fields for a Link. */ + if (data->num_fields != 3) { + fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n", + data->filename, data->line_number, data->num_fields, + data->line); + exit (1); + } + + from = data->fields[LINK_FROM]; + to = data->fields[LINK_TO]; + +#if 0 + printf ("LINK FROM: %s\tTO: %s\n", from, to); +#endif + +#if CREATE_SYMLINK + { + int len = strnlen(to,254); + int dirs = 0; + int i; + for (i = 0; i < len; i++) { + dirs += to[i] == '/' ? 1 : 0; + } + if (dirs) { + char rel_from[255]; + char to_dir[255]; + char to_path[255]; + if (dirs == 1) { + sprintf(rel_from, "../%s.ics", from); + } else if (dirs == 2) { + sprintf(rel_from, "../../%s.ics", from); + } else { + return; + } + sprintf(to_path, "%s/%s.ics", VzicOutputDir, to); + strncpy(to_dir, to_path, 254); + ensure_directory_exists(dirname(to_dir)); + //printf("Creating symlink from %s to %s\n", rel_from, to_path); + symlink(rel_from, to_path); + } + } +#else + if (g_hash_table_lookup_extended (data->link_data, from, + (gpointer) &old_from, + (gpointer) &zone_list)) { + from = old_from; + } else { + from = g_strdup (from); + zone_list = NULL; + } + + zone_list = g_list_prepend (zone_list, g_strdup (to)); + + g_hash_table_insert (data->link_data, from, zone_list); +#endif +} + + +static int +parse_year (ParsingData *data, + char *field, + gboolean accept_only, + int only_value) +{ + int len, year = 0; + char *p; + + if (!field) { + fprintf (stderr, "%s:%i: Missing year.\n%s\n", data->filename, + data->line_number, data->line); + exit (1); + } + + len = strlen (field); + if (accept_only && !strncmp (field, "only", len)) + return only_value; + if (len >= 2) { + if (!strncmp (field, "maximum", len)) + return YEAR_MAXIMUM; + else if (!strncmp (field, "minimum", len)) + return YEAR_MINIMUM; + } + + for (p = field; *p; p++) { + if (*p < '0' || *p > '9') { + fprintf (stderr, "%s:%i: Invalid year: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); + } + + year = year * 10 + *p - '0'; + } + + if (year < 1000 || year > 2038) { + fprintf (stderr, "%s:%i: Strange year: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); + } + + return year; +} + + +/* Parses a month name, returning 0 (Jan) to 11 (Dec). */ +static int +parse_month (ParsingData *data, + char *field) +{ + static char* months[] = { "january", "february", "march", "april", "may", + "june", "july", "august", "september", "october", + "november", "december" }; + char *p; + int len, i; + + /* If the field is missing, it must be the optional UNTIL month, so we return + 0 for January. */ + if (!field) + return 0; + + for (p = field, len = 0; *p; p++, len++) { + *p = tolower (*p); + } + + for (i = 0; i < 12; i++) { + if (!strncmp (field, months[i], len)) + return i; + } + + fprintf (stderr, "%s:%i: Invalid month: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); +} + + +/* Parses a day specifier, returning a code representing the type of match + together with a day of the month and a weekday number (0=Sun). */ +static DayCode +parse_day (ParsingData *data, + char *field, + int *day, + int *weekday) +{ + char *day_part, *p; + DayCode day_code; + + if (!field) { + *day = 1; + return DAY_SIMPLE; + } + + *day = *weekday = 0; + + if (!strncmp (field, "last", 4)) { + *weekday = parse_weekday (data, field + 4); + /* We set the day to the end of the month to make sorting Rules easy. */ + *day = 31; + return DAY_LAST_WEEKDAY; + } + + day_part = field; + day_code = DAY_SIMPLE; + + for (p = field; *p; p++) { + if (*p == '<' || *p == '>') { + if (*(p + 1) == '=') { + day_code = (*p == '<') ? DAY_WEEKDAY_ON_OR_BEFORE + : DAY_WEEKDAY_ON_OR_AFTER; + *p = '\0'; + *weekday = parse_weekday (data, field); + day_part = p + 2; + break; + } + + fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); + } + } + + for (p = day_part; *p; p++) { + if (*p < '0' || *p > '9') { + fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); + } + + *day = *day * 10 + *p - '0'; + } + + if (*day < 1 || *day > 31) { + fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); + } + + return day_code; +} + + +/* Parses a weekday name, returning 0 (Sun) to 6 (Sat). */ +static int +parse_weekday (ParsingData *data, + char *field) +{ + static char* weekdays[] = { "sunday", "monday", "tuesday", "wednesday", + "thursday", "friday", "saturday" }; + char *p; + int len, i; + + for (p = field, len = 0; *p; p++, len++) { + *p = tolower (*p); + } + + for (i = 0; i < 7; i++) { + if (!strncmp (field, weekdays[i], len)) + return i; + } + + fprintf (stderr, "%s:%i: Invalid weekday: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); +} + + +/* Parses a time (hour + minute + second) and returns the result in seconds, + together with a time code specifying whether it is Wall clock time, + local standard time, or universal time. + The time can start with a '-' in which case it will be negative. */ +static int +parse_time (ParsingData *data, + char *field, + TimeCode *time_code) +{ + char *p; + int hours = 0, minutes = 0, seconds = 0, result, negative = 0; + + if (!field) { + *time_code = TIME_WALL; + return 0; + } + + p = field; + if (*p == '-') { + p++; + negative = 1; + } + + hours = parse_number (data, &p); + + if (*p == ':') { + p++; + minutes = parse_number (data, &p); + + if (*p == ':') { + p++; + seconds = parse_number (data, &p); + } + } + + if (hours < 0 || hours > 24 + || minutes < 0 || minutes > 59 + || seconds < 0 || seconds > 59 + || (hours == 24 && (minutes != 0 || seconds != 0))) { + fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); + } + + if (hours == 24) { + hours = 23; + minutes = 59; + seconds = 59; + } + +#if 0 + printf ("Time: %s -> %i:%02i:%02i\n", field, hours, minutes, seconds); +#endif + + result = hours * 3600 + minutes * 60 + seconds; + if (negative) + result = -result; + + if (*p == '\0') { + *time_code = TIME_WALL; + return result; + } + + if (*(p + 1) == '\0') { + if (*p == 'w') { + *time_code = TIME_WALL; + return result; + } else if (*p == 's') { + *time_code = TIME_STANDARD; + return result; + } else if (*p == 'u' || *p == 'g' || *p == 'z') { + *time_code = TIME_UNIVERSAL; + return result; + } + } + + fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename, + data->line_number, field, data->line); + exit (1); +} + + +/* Parses a simple number and returns the result. The pointer argument + is moved to the first character after the number. */ +static int +parse_number (ParsingData *data, + char **num) +{ + char *p; + int result; + + p = *num; + +#if 0 + printf ("In parse_number p:%s\n", p); +#endif + + // potential null value where '-' is specified. assume zero. + if (*p == NULL) { + return 0; + } + + if (*p < '0' || *p > '9') { + fprintf (stderr, "%s:%i: Invalid number: %s\n%s\n", data->filename, + data->line_number, *num, data->line); + exit (1); + } + + result = *p++ - '0'; + + while (*p >= '0' && *p <= '9') + result = result * 10 + *p++ - '0'; + + *num = p; + return result; +} + + +static int +parse_rules_save (ParsingData *data, + char *field, + char **rules) +{ + TimeCode time_code; + + *rules = NULL; + + /* Check for just "-". */ + if (field[0] == '-' && field[1] == '\0') + return 0; + + /* Check for a time to add to local standard time. We don't care about a + time code here, since it is just an offset. */ + if (*field == '-' || (*field >= '0' && *field <= '9')) + return parse_time (data, field, &time_code); + + /* It must be a rules name. */ + *rules = g_strdup (field); + return 0; +} + + + + + +GHashTable* +parse_zone_tab (char *filename) +{ + GHashTable *zones_hash; + ZoneDescription *zone_desc; + FILE *fp; + char buf[4096]; + gchar **fields, *zone_name, *latitude, *longitude, *p; + + + fp = fopen (filename, "r"); + if (!fp) { + fprintf (stderr, "Couldn't open file: %s\n", filename); + exit (1); + } + + zones_hash = g_hash_table_new (g_str_hash, g_str_equal); + + while (fgets (buf, sizeof(buf), fp)) { + if (*buf == '#') continue; + + g_strchomp (buf); + fields = g_strsplit (buf,"\t", 4); + + if (strlen (fields[0]) != 2) { + fprintf (stderr, "Invalid zone description line: %s\n", buf); + exit (1); + } + + zone_name = g_strdup (fields[2]); + + zone_desc = g_new (ZoneDescription, 1); + zone_desc->country_code[0] = fields[0][0]; + zone_desc->country_code[1] = fields[0][1]; + zone_desc->comment = (fields[3] && fields[3][0]) ? g_strdup (fields[3]) + : NULL; + + /* Now parse the latitude and longitude. */ + latitude = fields[1]; + longitude = latitude + 1; + while (*longitude != '+' && *longitude != '-') + longitude++; + + parse_coord (latitude, longitude - latitude, zone_desc->latitude); + parse_coord (longitude, strlen (longitude), zone_desc->longitude); + + g_hash_table_insert (zones_hash, zone_name, zone_desc); + +#if 0 + g_print ("Found zone: %s %i %02i %02i,%i %02i %02i\n", zone_name, + zone_desc->latitude[0], zone_desc->latitude[1], + zone_desc->latitude[2], + zone_desc->longitude[0], zone_desc->longitude[1], + zone_desc->longitude[2]); +#endif + } + + fclose (fp); + + return zones_hash; +} + + +static void +parse_coord (char *coord, + int len, + int *result) +{ + int degrees = 0, minutes = 0, seconds = 0; + + if (len == 5) + sscanf (coord + 1, "%2d%2d", °rees, &minutes); + else if (len == 6) + sscanf (coord + 1, "%3d%2d", °rees, &minutes); + else if (len == 7) + sscanf (coord + 1, "%2d%2d%2d", °rees, &minutes, &seconds); + else if (len == 8) + sscanf (coord + 1, "%3d%2d%2d", °rees, &minutes, &seconds); + else { + fprintf (stderr, "Invalid coordinate: %s\n", coord); + exit (1); + } + + if (coord[0] == '-') + degrees = -degrees; + + result[0] = degrees; + result[1] = minutes; + result[2] = seconds; +} +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-parse.h
Added
@@ -0,0 +1,38 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _VZIC_PARSE_H_ +#define _VZIC_PARSE_H_ + +#include <glib.h> + +void parse_olson_file (char *filename, + GArray **zone_data, + GHashTable **rule_data, + GHashTable **link_data, + int *max_until_year); + +GHashTable* parse_zone_tab (char *filename); + +#endif /* _VZIC_PARSE_H_ */
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-test.pl
Added
@@ -0,0 +1,164 @@ +#!/usr/bin/perl -w + +# +# Vzic - a program to convert Olson timezone database files into VZTIMEZONE +# files compatible with the iCalendar specification (RFC2445). +# +# Copyright (C) 2001 Ximian, Inc. +# Copyright (C) 2003 Damon Chaplin. +# +# Author: Damon Chaplin <damon@gnome.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +# + +# +# This outputs an iCalendar file containing one event in each timezone, +# as well as all the VTIMEZONEs. We use it for testing compatability with +# other iCalendar apps like Outlook, by trying to import it there. +# +# Currently we have 377 timezones (with tzdata2001d). +# + +# Set this to the toplevel directory of the VTIMEZONE files. +$ZONEINFO_DIR = "/home/damon/src/zoneinfo"; + +$output_file = "calendar.ics"; + + +# Save this so we can restore it later. +$input_record_separator = $/; + +chdir $ZONEINFO_DIR + || die "Can't cd to $ZONEINFO_DIR"; + +# Create the output file, to contain all the VEVENTs & VTIMEZONEs. +open (OUTPUTFILE, ">$output_file") + || die "Can't create file: $output_file"; + +# Output the standard header. + print OUTPUTFILE <<EOF; +BEGIN:VCALENDAR +PRODID:-//Ximian//NONSGML Vzic Test//EN +VERSION:2.0 +METHOD:PUBLISH +EOF + +$zone_num = 0; + +# 365 days in a non-leap year. +@days_in_month = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); + +foreach $file (`find -name "*.ics"`) { + # Get rid of './' at start and whitespace at end. + $file =~ s/^\.\///; + $file =~ s/\s+$//; + + if ($file eq $output_file) { + next; + } + +# print "File: $file\n"; + + # Get the VTIMEZONE data. + open (ZONEFILE, "$file") + || die "Can't open file: $ZONEINFO_DIR/$file"; + undef $/; + $vtimezone = <ZONEFILE>; + $/ = $input_record_separator; + close (ZONEFILE); + + # Strip the stuff before and after the VTIMEZONE component + $vtimezone =~ s/^.*BEGIN:VTIMEZONE/BEGIN:VTIMEZONE/s; + $vtimezone =~ s/END:VTIMEZONE.*$/END:VTIMEZONE\n/s; + + print OUTPUTFILE $vtimezone; + + # Find the TZID. + $vtimezone =~ m/TZID:(.*)/; + $tzid = $1; +# print "TZID: $tzid\n"; + + # Find the location. + $file =~ m/(.*)\.ics/; + $location = $1; +# print "LOCATION: $location\n"; + + # Try to find the current UTC offset that Outlook will use. + # If there is an RRULE, we look for the first 2 TZOFFSETTO properties, + # else we just get the first one. + if ($vtimezone =~ m/RRULE/) { + $vtimezone =~ m/TZOFFSETTO:([+-]?\d+)/; + $tzoffsetto = $1; + $vtimezone =~ m/TZOFFSETFROM:([+-]?\d+)/; + $tzoffsetfrom = $1; + $tzoffset = "$tzoffsetfrom/$tzoffsetto"; + } else { + $vtimezone =~ m/TZOFFSETTO:([+-]?\d+)/s; + $tzoffset = $1; + } +# print "TZOFFSET: $tzoffset\n"; + + # We put each event on a separate day in 2001 and Jan 2002. + $day_num = $zone_num; + if ($day_num >= 365) { + $year = 2002; + $day_num -= 365; + } else { + $year = 2001; + } + $month = -1; + for ($i = 0; $i < 12; $i++) { + if ($day_num < $days_in_month[$i]) { + $month = $i; + last; + } + $day_num -= $days_in_month[$i] + } + if ($month == -1) { + die "month = -1"; + } + + $month++; + $day_num++; + $date = sprintf ("%i%02i%02i", $year, $month, $day_num); +# print "Date: $date\n"; + + # Output a VEVENT using the timezone. + print OUTPUTFILE <<EOF; +BEGIN:VEVENT +UID:vzic-test-${zone_num} +DTSTAMP:20010101T000000Z +DTSTART;TZID=${tzid}:${date}T120000 +DTEND;TZID=${tzid}:${date}T130000 +RRULE:FREQ=MONTHLY;BYMONTHDAY=${day_num} +SUMMARY:($tzoffset) ${location} 12:00-13:00 UTC +SEQUENCE:1 +END:VEVENT +EOF + + $zone_num++; + + # Use this to stop after a certain number. +# last if ($zone_num == 100); +} + +# Output the standard footer. + print OUTPUTFILE <<EOF; +END:VCALENDAR +EOF + +close (OUTPUTFILE); +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic.c
Added
@@ -0,0 +1,321 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "vzic.h" +#include "vzic-parse.h" +#include "vzic-dump.h" +#include "vzic-output.h" + + +/* + * Global command-line options. + */ + +/* By default we output Outlook-compatible output. If --pure is used we + output pure output, with no changes to be compatible with Outlook. */ +gboolean VzicPureOutput = FALSE; + +gboolean VzicDumpOutput = FALSE; +gboolean VzicDumpChanges = FALSE; +gboolean VzicDumpZoneNamesAndCoords = FALSE; +gboolean VzicDumpZoneTranslatableStrings= TRUE; +gboolean VzicNoRRules = FALSE; +gboolean VzicNoRDates = FALSE; +char* VzicOutputDir = "zoneinfo"; +char* VzicUrlPrefix = NULL; +char* VzicOlsonDir = OLSON_DIR; + +GList* VzicTimeZoneNames = NULL; + +static void convert_olson_file (char *olson_file); + +static void usage (void); + +static void free_zone_data (GArray *zone_data); +static void free_rule_array (gpointer key, + gpointer value, + gpointer data); +static void free_link_data (gpointer key, + gpointer value, + gpointer data); + + +int +main (int argc, + char *argv[]) +{ + int i; + char directory[PATHNAME_BUFFER_SIZE]; + char filename[PATHNAME_BUFFER_SIZE]; + GHashTable *zones_hash; + + /* + * Command-Line Option Parsing. + */ + for (i = 1; i < argc; i++) { + /* + * User Options. + */ + + /* --pure: Output the perfect VCALENDAR data, which Outlook won't parse + as it has problems with certain iCalendar constructs. */ + if (!strcmp (argv[i], "--pure")) + VzicPureOutput = TRUE; + + /* --output-dir: specify where to output all the files beneath. The + default is the current directory. */ + else if (argc > i + 1 && !strcmp (argv[i], "--output-dir")) + VzicOutputDir = argv[++i]; + + /* --url-prefix: Used as the base for the TZURL property in each + VTIMEZONE. The default is to not output TZURL properties. */ + else if (argc > i + 1 && !strcmp (argv[i], "--url-prefix")) { + int length; + VzicUrlPrefix = argv[++i]; + /* remove the trailing '/' if there is one */ + length = strlen (VzicUrlPrefix); + if (VzicUrlPrefix[length - 1] == '/') + VzicUrlPrefix[length - 1] = '\0'; + } + + else if (argc > i + 1 && !strcmp (argv[i], "--olson-dir")) { + VzicOlsonDir = argv[++i]; + } + + /* + * Debugging Options. + */ + + /* --dump: Dump the Rule and Zone data that we parsed from the Olson + timezone files. This is used to test the parsing code. */ + else if (!strcmp (argv[i], "--dump")) + VzicDumpOutput = TRUE; + + /* --dump-changes: Dumps a list of times when each timezone changed, + and the new local time offset from UTC. */ + else if (!strcmp (argv[i], "--dump-changes")) + VzicDumpChanges = TRUE; + + /* --no-rrules: Don't output RRULE properties in the VTIMEZONEs. Instead + it will just output RDATEs for each year up to a certain year. */ + else if (!strcmp (argv[i], "--no-rrules")) + VzicNoRRules = TRUE; + + /* --no-rdates: Don't output multiple RDATEs in a single VTIMEZONE + component. Instead they will be output separately. */ + else if (!strcmp (argv[i], "--no-rdates")) + VzicNoRDates = TRUE; + + else + usage (); + } + + /* + * Create any necessary directories. + */ + ensure_directory_exists (VzicOutputDir); + + if (VzicDumpOutput) { + /* Create the directories for the dump output, if they don't exist. */ + sprintf (directory, "%s/ZonesVzic", VzicOutputDir); + ensure_directory_exists (directory); + sprintf (directory, "%s/RulesVzic", VzicOutputDir); + ensure_directory_exists (directory); + } + + if (VzicDumpChanges) { + /* Create the directory for the changes output, if it doesn't exist. */ + sprintf (directory, "%s/ChangesVzic", VzicOutputDir); + ensure_directory_exists (directory); + } + + /* + * Convert the Olson timezone files. + */ + convert_olson_file ("africa"); + convert_olson_file ("antarctica"); + convert_olson_file ("asia"); + convert_olson_file ("australasia"); + convert_olson_file ("europe"); + convert_olson_file ("northamerica"); + convert_olson_file ("southamerica"); + + /* These are backwards-compatability and weird stuff. */ + convert_olson_file ("backward"); +#if 0 + convert_olson_file ("etcetera"); + convert_olson_file ("leapseconds"); + convert_olson_file ("pacificnew"); + convert_olson_file ("solar87"); + convert_olson_file ("solar88"); + convert_olson_file ("solar89"); +#endif + + /* This doesn't really do anything and it messes up vzic-dump.pl so we + don't bother. */ +#if 0 + convert_olson_file ("factory"); +#endif + + /* This is old System V stuff, which we don't currently support since it + uses 'min' as a Rule FROM value which messes up our algorithm, making + it too slow and use too much memory. */ +#if 0 + convert_olson_file ("systemv"); +#endif + + /* Output the timezone names and coordinates in a zone.tab file, and + the translatable strings to feed to gettext. */ + if (VzicDumpZoneNamesAndCoords) { + sprintf (filename, "%s/zone.tab", VzicOlsonDir); + zones_hash = parse_zone_tab (filename); + + dump_time_zone_names (VzicTimeZoneNames, VzicOutputDir, zones_hash); + } + + return 0; +} + + +static void +convert_olson_file (char *olson_file) +{ + char input_filename[PATHNAME_BUFFER_SIZE]; + GArray *zone_data; + GHashTable *rule_data, *link_data; + char dump_filename[PATHNAME_BUFFER_SIZE]; + ZoneData *zone; + int i, max_until_year; + + sprintf (input_filename, "%s/%s", VzicOlsonDir, olson_file); + + parse_olson_file (input_filename, &zone_data, &rule_data, &link_data, + &max_until_year); + + if (VzicDumpOutput) { + sprintf (dump_filename, "%s/ZonesVzic/%s", VzicOutputDir, olson_file); + dump_zone_data (zone_data, dump_filename); + + sprintf (dump_filename, "%s/RulesVzic/%s", VzicOutputDir, olson_file); + dump_rule_data (rule_data, dump_filename); + } + + output_vtimezone_files (VzicOutputDir, zone_data, rule_data, link_data, + max_until_year); + + free_zone_data (zone_data); + g_hash_table_foreach (rule_data, free_rule_array, NULL); + g_hash_table_destroy (rule_data); + g_hash_table_foreach (link_data, free_link_data, NULL); + g_hash_table_destroy (link_data); +} + + +static void +usage (void) +{ + fprintf (stderr, "Usage: vzic [--dump] [--dump-changes] [--no-rrules] [--no-rdates] [--pure] [--output-dir <directory>] [--url-prefix <url>] [--olson-dir <directory>]\n"); + + exit (1); +} + + + + +/* + * Functions to free the data structures. + */ + +static void +free_zone_data (GArray *zone_data) +{ + ZoneData *zone; + ZoneLineData *zone_line; + int i, j; + + for (i = 0; i < zone_data->len; i++) { + zone = &g_array_index (zone_data, ZoneData, i); + + g_free (zone->zone_name); + + for (j = 0; j < zone->zone_line_data->len; j++) { + zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, j); + + g_free (zone_line->rules); + g_free (zone_line->format); + } + + g_array_free (zone->zone_line_data, TRUE); + } + + g_array_free (zone_data, TRUE); +} + + +static void +free_rule_array (gpointer key, + gpointer value, + gpointer data) +{ + char *name = key; + GArray *rule_array = value; + RuleData *rule; + int i; + + for (i = 0; i < rule_array->len; i++) { + rule = &g_array_index (rule_array, RuleData, i); + + if (!rule->is_shallow_copy) { + g_free (rule->type); + g_free (rule->letter_s); + } + } + + g_array_free (rule_array, TRUE); + + g_free (name); +} + + +static void +free_link_data (gpointer key, + gpointer value, + gpointer data) +{ + GList *link = data; + + g_free (key); + + while (link) { + g_free (link->data); + link = link->next; + } + + g_list_free (data); +} +
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic.h
Added
@@ -0,0 +1,197 @@ +/* + * Vzic - a program to convert Olson timezone database files into VZTIMEZONE + * files compatible with the iCalendar specification (RFC2445). + * + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2003 Damon Chaplin. + * + * Author: Damon Chaplin <damon@gnome.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _VZIC_H_ +#define _VZIC_H_ + +#include <glib.h> + + +/* + * Global command-line options. + */ + +/* By default we output Outlook-compatible output. If --pure is used we output + pure output, with no changes to be compatible with Outlook. */ +extern gboolean VzicPureOutput; + +extern gboolean VzicDumpOutput; +extern gboolean VzicDumpChanges; +extern gboolean VzicDumpZoneNamesAndCoords; +extern gboolean VzicDumpZoneTranslatableStrings; +extern gboolean VzicNoRRules; +extern gboolean VzicNoRDates; +extern char* VzicUrlPrefix; +extern char* VzicOutputDir; + +extern GList* VzicTimeZoneNames; + +/* The minimum & maximum years we can use. */ +#define YEAR_MINIMUM G_MININT +#define YEAR_MAXIMUM G_MAXINT + +/* The maximum size of any complete pathname. */ +#define PATHNAME_BUFFER_SIZE 1024 + +/* Days can be expressed either as a simple month day number, 1-31, or a rule + such as the last Sunday, or the first Monday on or after the 8th. */ +typedef enum +{ + DAY_SIMPLE, + DAY_WEEKDAY_ON_OR_AFTER, + DAY_WEEKDAY_ON_OR_BEFORE, + DAY_LAST_WEEKDAY +} DayCode; + + +/* Times can be given either as universal time (UTC), local standard time + (without daylight-saving adjustments) or wall clock time (local standard + time plus daylight-saving adjustments, i.e. what you would see on a clock + on the wall!). */ +typedef enum +{ + TIME_WALL, + TIME_STANDARD, + TIME_UNIVERSAL +} TimeCode; + + +/* This represents one timezone, e.g. "Africa/Algiers". + It contains the timezone name, and an array of ZoneLineData structs which + hold data from each Zone line, including the continuation lines. */ +typedef struct _ZoneData ZoneData; +struct _ZoneData +{ + char *zone_name; + + /* An array of ZoneLineData, one for each Zone & Zone continuation line + read in. */ + GArray *zone_line_data; +}; + + +typedef struct _ZoneLineData ZoneLineData; +struct _ZoneLineData +{ + /* The amount of time to add to UTC to get local standard time for the + current time range, in seconds. */ + int stdoff_seconds; + + /* Either rules is set to the name of a set of rules, or rules is NULL and + save is set to the time to add to local standard time to get wall time, in + seconds. If save is 0 as well, then standard time always applies. */ + char *rules; + int save_seconds; + + /* The format to use for the abbreviated timezone name, e.g. WE%sT. + The %s is replaced by variable part of the name. (See the letter_s field + in the RuleData struct below). */ + char *format; + + /* TRUE if an UNTIL time is given. */ + gboolean until_set; + + /* The UNTIL year, e.g. 2000. */ + int until_year; + + /* The UNTIL month 0 (Jan) to 11 (Dec). */ + int until_month; + + /* The UNTIL day, either a simple month day number, 1-31, or a rule such as + the last Sunday, or the first Monday on or after the 8th. */ + DayCode until_day_code; + int until_day_number; /* 1 to 31. */ + int until_day_weekday; /* 0 (Sun) to 6 (Sat). */ + + /* The UNTIL time, in seconds from midnight. The code specifies whether the + time is a wall clock time, local standard time, or universal time. */ + int until_time_seconds; + TimeCode until_time_code; +}; + + +typedef struct _RuleData RuleData; +struct _RuleData +{ + /* The first year that the rule applies to, e.g. 1996. + Can also be YEAR_MINIMUM. */ + int from_year; + + /* The last year that the rule applies to, e.g. 1996. + Can also be YEAR_MAXIMUM. */ + int to_year; + + /* A string used to only match certain years between from and to. + The rule only applies to the years which match. If type is NULL the rule + applies to all years betweeen from and to. + zic uses an external program called yearistype to check the string. + Currently it is not used in the Olson database. */ + char *type; + + /* The month of the rule 0 (Jan) to 11 (Dec). */ + int in_month; + + /* The day, either a simple month day number, 1-31, or a rule such as + the last Sunday, or the first Monday on or after the 8th. */ + DayCode on_day_code; + int on_day_number; + int on_day_weekday; /* 0 (Sun) to 6 (Sat). */ + + /* The time, in seconds from midnight. The code specifies whether the + time is a wall clock time, local standard time, or universal time. */ + int at_time_seconds; + TimeCode at_time_code; + + /* The amount of time to add to local standard time when the rule is in + effect, in seconds. If this is not 0 then it must be a daylight-saving + time. */ + int save_seconds; + + /* The letter(s) to use as the variable part in the abbreviated timezone + name. If this is NULL then no variable part is used. (See the format field + in the ZoneLineData struct above.) */ + char *letter_s; + + + /* This is set to TRUE if this element is a shallow copy of another one, + in which case we don't free any of the fields. */ + gboolean is_shallow_copy; +}; + + +typedef struct _ZoneDescription ZoneDescription; +struct _ZoneDescription +{ + /* 2-letter ISO 3166 country code. */ + char country_code[2]; + + /* latitude and longitude in degrees, minutes & seconds. The degrees value + holds the sign of the entire latitude/longitude. */ + int latitude[3]; + int longitude[3]; + + char *comment; +}; + +#endif /* _VZIC_H_ */
View file
cyrus-imapd.cvt_cyrusdb_all
Changed
@@ -124,12 +124,18 @@ # # v1.3.4, Dec 22 2009 Simon Matter <simon.matter@invoca.ch> # - add support for user_deny.db +# # v1.3.5, Mar 6 2014 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> # - use default database configuration values if not specified in # configuration VERSION=1.3.5 +[ -f /etc/sysconfig/cyrus-imapd${INSTANCE} ] && \ + . /etc/sysconfig/cyrus-imapd${INSTANCE} + +[ "${QUICK}" == "1" ] && exit 0 + PIDFILE=/var/run/cyrus-master${INSTANCE}.pid # instance config
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.