diff --git a/cyrus-imapd-2.3.3-autosieve-0.6.0.diff b/cyrus-imapd-2.3.3-autosieve-0.6.0.diff new file mode 100644 index 0000000..63eb9fa --- /dev/null +++ b/cyrus-imapd-2.3.3-autosieve-0.6.0.diff @@ -0,0 +1,181 @@ +diff -Naur cyrus-imapd-2.3.3/README.autosievefolder cyrus-imapd-2.3.3-autosieve.uncompiled/README.autosievefolder +--- cyrus-imapd-2.3.3/README.autosievefolder 1970-01-01 02:00:00.000000000 +0200 ++++ cyrus-imapd-2.3.3-autosieve.uncompiled/README.autosievefolder 2006-03-01 16:57:26.000000000 +0200 +@@ -0,0 +1,42 @@ ++Cyrus IMAP autosievefolder patch ++---------------------------------- ++ ++NOTE : This patch has been created at the University of Athens. For more info, as well ++as more patches on Cyrus IMAPD server, please visit http://email.uoa.gr ++ ++ ++ When the lmtpd daemon receives an email message prior to delivering it to the ++INBOX folder of the user, checks if the user has specified sieve filters. If the ++user has specified sieve filters the filters are evaluated. If the message matches ++any of the filters the action that is specified in the filter is executed. If the action ++is FileInto it is stored in the subfolder specified in the filter. If the ++subfolder doesn't exist then the message is sent to the INBOX folder of the user. ++ ++ With this patch if the folder doesn't exist AND the name of the subfolder is ++specified in the autosievefolders option, OR the anysievefolder is set to ++yes in the cyrus-imap configuration file then the subfolder is created and the mail ++is stored there. ++ ++ ++Check the following options of the imapd.conf file ++================================================== ++ ++* anysievefolder : It must be "yes" in order to permit the autocreation of any ++INBOX subfolder requested by a sieve filter, through the "fileinto" action. (default = no) ++* autosievefolders : It is a "|" separated list of subfolders of INBOX that will be ++automatically created, if requested by a sieve filter, through the "fileinto" ++action. (default = null) ++ i.e. autosievefolders: Junk | Spam ++ ++WARNING: anysievefolder, takes precedence over autosievefolders . Which means that if ++anysievefolder is set to "yes", cyrus will create any INBOX subfolder requested, no-matter what the value of autosievefolders is. ++ ++ ++Things to be done ++================= ++ ++1. Support cyrus wildcards in the autosievefolders option. ++ ++ ++For more information and updates please visit http://email.uoa.gr/projects/cyrus/autosievefolder ++ +diff -Naur cyrus-imapd-2.3.3/imap/lmtp_sieve.c cyrus-imapd-2.3.3-autosieve.uncompiled/imap/lmtp_sieve.c +--- cyrus-imapd-2.3.3/imap/lmtp_sieve.c 2005-11-21 18:26:54.000000000 +0200 ++++ cyrus-imapd-2.3.3-autosieve.uncompiled/imap/lmtp_sieve.c 2006-03-01 16:57:26.000000000 +0200 +@@ -86,6 +86,9 @@ + struct auth_state *authstate; + } script_data_t; + ++static int autosieve_subfolder(char *userid, struct auth_state *auth_state, ++ char *subfolder, struct namespace *namespace); ++ + static char *make_sieve_db(const char *user) + { + static char buf[MAX_MAILBOX_PATH+1]; +@@ -487,7 +490,20 @@ + sd->username, mdata->notifyheader, + namebuf, quotaoverride, 0); + } +- ++ ++ if (ret == IMAP_MAILBOX_NONEXISTENT) { ++ /* if "plus" folder under INBOX, then try to create it */ ++ ret = autosieve_subfolder((char *) sd->username, sd->authstate, namebuf, mdata->namespace); ++ ++ /* Try to deliver the mail again. */ ++ if (!ret) ++ ret = deliver_mailbox(md->f, mdata->content, mdata->stage, md->size, ++ fc->imapflags->flag, fc->imapflags->nflags, ++ (char *) sd->username, sd->authstate, md->id, ++ sd->username, mdata->notifyheader, ++ namebuf, quotaoverride, 0); ++ } ++ + if (!ret) { + snmp_increment(SIEVE_FILEINTO, 1); + return SIEVE_OK; +@@ -939,3 +955,80 @@ + we'll do normal delivery */ + return r; + } ++ ++ ++#define SEP '|' ++ ++static int autosieve_subfolder(char *userid, struct auth_state *auth_state, ++ char *subfolder, struct namespace *namespace) ++{ ++ char option_name_external[MAX_MAILBOX_NAME + 1]; ++ char option_name_internal[MAX_MAILBOX_NAME + 1]; ++ const char *subf ; ++ char *p, *q, *next_subf; ++ int len, r = 0; ++ int createsievefolder = 0; ++ ++ /* Check if subfolder or userid are NULL */ ++ if(userid == NULL || subfolder == NULL) ++ return IMAP_MAILBOX_NONEXISTENT; ++ ++ syslog(LOG_DEBUG, "autosievefolder: autosieve_subfolder() was called for user %s, folder %s", ++ userid, subfolder); ++ ++ if (config_getswitch(IMAPOPT_ANYSIEVEFOLDER)) { ++ createsievefolder = 1; ++ } else if ((subf = config_getstring(IMAPOPT_AUTOSIEVEFOLDERS)) != NULL) { ++ /* Roll through subf */ ++ next_subf = (char *) subf; ++ while (*next_subf) { ++ for (p = next_subf ; isspace((int) *p) || *p == SEP ; p++); ++ for (next_subf = p ; *next_subf && *next_subf != SEP ; next_subf++); ++ for (q = next_subf ; q > p && (isspace((int) *q) || *q == SEP || !*q); q--); ++ ++ if (!*p) continue; ++ ++ len = q - p + 1; ++ /* ++ * This is a preliminary length check based on the assumption ++ * that the *final* internal format will be something ++ * like user.userid.subfolder(s). ++ */ ++ if (len > sizeof(option_name_external) - strlen(userid) - 5) ++ return IMAP_MAILBOX_BADNAME; ++ ++ strlcpy(option_name_external, namespace->prefix[NAMESPACE_INBOX], sizeof(option_name_external)); ++ strncat(option_name_external, p, len); ++ ++ /* ++ * Transform the option folder name to internal namespace and compare it ++ * with what must be created. ++ */ ++ r = namespace->mboxname_tointernal(namespace, option_name_external, userid, option_name_internal); ++ if (r) continue; ++ ++ if (!strcmp(option_name_internal, subfolder)) { ++ createsievefolder = 1; ++ break; ++ } ++ } ++ } ++ ++ if (createsievefolder) { ++ /* Folder is already in internal namespace format */ ++ r = mboxlist_createmailbox(subfolder, MAILBOX_FORMAT_NORMAL, NULL, ++ 1, userid, auth_state, 0, 0, 0); ++ if (!r) { ++ mboxlist_changesub(subfolder, userid, auth_state, 1, 1); ++ syslog(LOG_DEBUG, "autosievefolder: User %s, folder %s creation succeeded", ++ userid, subfolder); ++ return 0; ++ } else { ++ syslog(LOG_ERR, "autosievefolder: User %s, folder %s creation failed. %s", ++ userid, subfolder,error_message(r)); ++ return r; ++ } ++ } else ++ return IMAP_MAILBOX_NONEXISTENT; ++} ++ +diff -Naur cyrus-imapd-2.3.3/lib/imapoptions cyrus-imapd-2.3.3-autosieve.uncompiled/lib/imapoptions +--- cyrus-imapd-2.3.3/lib/imapoptions 2006-02-01 21:44:06.000000000 +0200 ++++ cyrus-imapd-2.3.3-autosieve.uncompiled/lib/imapoptions 2006-03-01 16:57:26.000000000 +0200 +@@ -863,6 +863,15 @@ + /* If enabled, lmtpd will look for Sieve scripts in user's home + directories: ~user/.sieve. */ + ++{ "anysievefolder", 0, SWITCH } ++/* It must be "yes" in order to permit the autocreation of any INBOX subfolder ++ requested by a sieve filter, through the "fileinto" action. (default = no) */ ++ ++{ "autosievefolders", NULL, STRING } ++/* It is a "|" separated list of subfolders of INBOX that will be automatically created, ++ if requested by a sieve filter, through the "fileinto" action. (default = null) ++ i.e. autosievefolders: Junk | Spam */ ++ + { "singleinstancestore", 1, SWITCH } + /* If enabled, imapd, lmtpd and nntpd attempt to only write one copy + of a message per partition and create hard links, resulting in a diff --git a/cyrus-imapd-2.3.7-autocreate-0.10-0.diff b/cyrus-imapd-2.3.7-autocreate-0.10-0.diff new file mode 100644 index 0000000..eae1b42 --- /dev/null +++ b/cyrus-imapd-2.3.7-autocreate-0.10-0.diff @@ -0,0 +1,12929 @@ +--- cyrus-imapd-2.3.7/imap/Makefile.in.autocreate0 2006-03-15 19:56:29.000000000 +0100 ++++ cyrus-imapd-2.3.7/imap/Makefile.in 2006-07-23 12:37:31.000000000 +0200 +@@ -101,7 +101,7 @@ + convert_code.o duplicate.o saslclient.o saslserver.o signals.o \ + annotate.o search_engines.o squat.o squat_internal.o mbdump.o \ + imapparse.o telemetry.o user.o notify.o protocol.o idle.o quota_db.o \ +- sync_log.o $(SEEN) mboxkey.o backend.o tls.o ++ sync_log.o $(SEEN) autosieve.o mboxkey.o backend.o tls.o + + IMAPDOBJS=pushstats.o imapd.o proxy.o imap_proxy.o index.o version.o + +@@ -117,7 +117,7 @@ + fud smmapd reconstruct quota mbpath ipurge \ + cyrdump chk_cyrus cvt_cyrusdb deliver ctl_mboxlist \ + ctl_deliver ctl_cyrusdb squatter mbexamine cyr_expire arbitron \ +- unexpunge @IMAP_PROGS@ ++ unexpunge compile_sieve @IMAP_PROGS@ + + BUILTSOURCES = imap_err.c imap_err.h pushstats.c pushstats.h \ + lmtpstats.c lmtpstats.h xversion.h mupdate_err.c mupdate_err.h \ +@@ -185,7 +185,7 @@ + ### Services + idled: idled.o mutex_fake.o libimap.a $(DEPLIBS) + $(CC) $(LDFLAGS) -o idled \ +- idled.o mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) ++ idled.o mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + + lmtpd: lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) mutex_fake.o \ + libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(SERVICE) +@@ -199,10 +199,10 @@ + $(SERVICE) lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \ + mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +-imapd: xversion $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) ++imapd: xversion $(IMAPDOBJS) mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o imapd \ + $(SERVICE) $(IMAPDOBJS) mutex_fake.o \ +- libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + + imapd.pure: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) + $(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o imapd.pure \ +@@ -219,7 +219,7 @@ + $(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 ++ $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) -lpthread + + mupdate.pure: mupdate.o mupdate-slave.o mupdate-client.o mutex_pthread.o \ + libimap.a $(DEPLIBS) +@@ -228,118 +228,122 @@ + 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) ++ $(SIEVE_LIBS) $(DEPLIBS) $(SERVICE) + $(CC) $(LDFLAGS) -o pop3d pop3d.o proxy.o backend.o tls.o $(SERVICE) \ +- mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) ++ mutex_fake.o libimap.a $(SIEVE_LIBS) $(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) ++ mutex_fake.o nntp_err.o libimap.a $(SIEVE_LIBS) $(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) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +-fud: fud.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) ++fud: fud.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o fud $(SERVICE) fud.o mutex_fake.o libimap.a \ +- $(DEPLIBS) $(LIBS) $(LIB_WRAP) ++ $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + +-smmapd: smmapd.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) ++smmapd: smmapd.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o smmapd $(SERVICE) smmapd.o mutex_fake.o libimap.a \ +- $(DEPLIBS) $(LIBS) $(LIB_WRAP) ++ $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + + sync_server: sync_server.o sync_support.o sync_commit.o \ +- imapparse.o tls.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) ++ imapparse.o tls.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ + sync_server sync_server.o sync_support.o sync_commit.o \ + imapparse.o tls.o $(SERVICE) libimap.a mutex_fake.o \ +- $(DEPLIBS) $(LIBS) $(LIB_WRAP) ++ $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) + + ### Command Line Utilities +-arbitron: arbitron.o $(CLIOBJS) libimap.a $(DEPLIBS) ++arbitron: arbitron.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o arbitron arbitron.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-cvt_cyrusdb: cvt_cyrusdb.o mutex_fake.o libimap.a $(DEPLIBS) ++cvt_cyrusdb: cvt_cyrusdb.o mutex_fake.o libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o cvt_cyrusdb cvt_cyrusdb.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-chk_cyrus: chk_cyrus.o mutex_fake.o libimap.a $(DEPLIBS) ++chk_cyrus: chk_cyrus.o mutex_fake.o libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o chk_cyrus chk_cyrus.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-deliver: deliver.o $(LMTPOBJS) proxy.o mutex_fake.o libimap.a $(DEPLIBS) ++deliver: deliver.o $(LMTPOBJS) proxy.o mutex_fake.o libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o deliver deliver.o $(LMTPOBJS) proxy.o \ +- mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) ++ mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-ctl_deliver: ctl_deliver.o $(CLIOBJS) libimap.a $(DEPLIBS) ++ctl_deliver: ctl_deliver.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ +- $@ ctl_deliver.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) ++ $@ ctl_deliver.o $(CLIOBJS) libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-ctl_mboxlist: ctl_mboxlist.o mupdate-client.o $(CLIOBJS) libimap.a $(DEPLIBS) ++ctl_mboxlist: ctl_mboxlist.o mupdate-client.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o $@ ctl_mboxlist.o mupdate-client.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-ctl_cyrusdb: ctl_cyrusdb.o $(CLIOBJS) libimap.a $(DEPLIBS) ++ctl_cyrusdb: ctl_cyrusdb.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ +- $@ ctl_cyrusdb.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) ++ $@ ctl_cyrusdb.o $(CLIOBJS) libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-cyr_expire: cyr_expire.o $(CLIOBJS) libimap.a $(DEPLIBS) ++cyr_expire: cyr_expire.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o $@ cyr_expire.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-fetchnews: fetchnews.o $(CLIOBJS) libimap.a $(DEPLIBS) ++fetchnews: fetchnews.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ +- $@ fetchnews.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) ++ $@ fetchnews.o $(CLIOBJS) libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-squatter: squatter.o index.o squat_build.o $(CLIOBJS) libimap.a $(DEPLIBS) ++squatter: squatter.o index.o squat_build.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o squatter squatter.o index.o squat_build.o \ +- $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) ++ $(CLIOBJS) libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-mbpath: mbpath.o $(CLIOBJS) libimap.a $(DEPLIBS) ++mbpath: mbpath.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o mbpath mbpath.o $(CLIOBJS) libimap.a \ +- $(DEPLIBS) $(LIBS) +- +-ipurge: ipurge.o $(CLIOBJS) libimap.a $(DEPLIBS) ++ $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) ++ ++ipurge: ipurge.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o ipurge ipurge.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-cyrdump: cyrdump.o index.o $(CLIOBJS) libimap.a $(DEPLIBS) ++cyrdump: cyrdump.o index.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o cyrdump cyrdump.o index.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-mbexamine: mbexamine.o $(CLIOBJS) libimap.a $(DEPLIBS) ++mbexamine: mbexamine.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ +- mbexamine mbexamine.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) ++ mbexamine mbexamine.o $(CLIOBJS) libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-reconstruct: reconstruct.o $(CLIOBJS) libimap.a $(DEPLIBS) ++reconstruct: reconstruct.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ +- reconstruct reconstruct.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) ++ reconstruct reconstruct.o $(CLIOBJS) libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-quota: quota.o $(CLIOBJS) libimap.a $(DEPLIBS) ++quota: quota.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o quota quota.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-tls_prune: tls_prune.o tls.o $(CLIOBJS) libimap.a $(DEPLIBS) ++tls_prune: tls_prune.o tls.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ +- $@ tls_prune.o tls.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) ++ $@ tls_prune.o tls.o $(CLIOBJS) libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-unexpunge: unexpunge.o $(CLIOBJS) libimap.a $(DEPLIBS) ++unexpunge: unexpunge.o $(CLIOBJS) libimap.a $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o $@ unexpunge.o $(CLIOBJS) \ +- libimap.a $(DEPLIBS) $(LIBS) ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + +-make_md5: make_md5.o libimap.a mutex_fake.o $(DEPLIBS) +- $(CC) $(LDFLAGS) -o make_md5 make_md5.o libimap.a mutex_fake.o $(DEPLIBS) $(LIBS) ++make_md5: make_md5.o libimap.a mutex_fake.o $(DEPLIBS) $(SIEVE_LIBS) ++ $(CC) $(LDFLAGS) -o make_md5 make_md5.o libimap.a mutex_fake.o $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + + sync_client: sync_client.o sync_support.o \ +- backend.o tls.o imapparse.o libimap.a mutex_fake.o $(DEPLIBS) ++ backend.o tls.o imapparse.o libimap.a mutex_fake.o $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ + sync_client sync_client.o sync_support.o \ +- backend.o tls.o imapparse.o libimap.a mutex_fake.o $(DEPLIBS) $(LIBS) ++ backend.o tls.o imapparse.o libimap.a mutex_fake.o $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + + sync_reset: sync_reset.o sync_support.o sync_commit.o \ +- libimap.a mutex_fake.o $(DEPLIBS) ++ libimap.a mutex_fake.o $(DEPLIBS) $(SIEVE_LIBS) + $(CC) $(LDFLAGS) -o \ + sync_reset sync_reset.o sync_support.o sync_commit.o \ +- libimap.a mutex_fake.o $(DEPLIBS) $(LIBS) ++ libimap.a mutex_fake.o $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) ++ ++compile_sieve: compile_sieve.o libimap.a $(DEPLIBS) $(SIEVE_LIBS) ++ $(CC) $(LDFLAGS) -o compile_sieve compile_sieve.o $(CLIOBJS) \ ++ libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) + + ### Other Misc Targets + +--- /dev/null 2006-07-21 18:50:55.248316500 +0200 ++++ cyrus-imapd-2.3.7/imap/autosieve.c 2006-07-23 12:35:41.000000000 +0200 +@@ -0,0 +1,587 @@ ++#include ++#include ++#include ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "global.h" ++#include "util.h" ++#include "mailbox.h" ++#include "imap_err.h" ++#include "sieve_interface.h" ++#include "script.h" ++ ++#define TIMSIEVE_FAIL -1 ++#define TIMSIEVE_OK 0 ++#define MAX_FILENAME 1024 ++ ++static int get_script_name(char *sievename, size_t buflen, const char *filename); ++static int get_script_dir(char *sieve_script_dir, size_t buflen, char *userid, const char *sieve_dir); ++int autoadd_sieve(char *userid, const char *source_script); ++ ++static void fatal(const char *s, int code); ++static void foo(void); ++static int sieve_notify(void *ac __attribute__((unused)), ++ void *interp_context __attribute__((unused)), ++ void *script_context __attribute__((unused)), ++ void *message_context __attribute__((unused)), ++ const char **errmsg __attribute__((unused))); ++static int mysieve_error(int lineno, const char *msg, ++ void *i __attribute__((unused)), void *s); ++static int is_script_parsable(FILE *stream, char **errstr, sieve_script_t **ret); ++ ++ ++sieve_vacation_t vacation2 = { ++ 0, /* min response */ ++ 0, /* max response */ ++ (sieve_callback *) &foo, /* autorespond() */ ++ (sieve_callback *) &foo /* send_response() */ ++}; ++ ++ ++/* ++ * Find the name of the sieve script ++ * given the source script and compiled script names ++ */ ++static int get_script_name(char *sievename, size_t buflen, const char *filename) ++{ ++ char *p; ++ int r; ++ ++ p = strrchr(filename, '/'); ++ if (p == NULL) ++ p = (char *) filename; ++ else ++ p++; ++ ++ r = strlcpy(sievename, p, buflen) - buflen; ++ return (r >= 0 || r == -buflen ? 1 : 0); ++} ++ ++ ++/* ++ * Find the directory where the sieve scripts of the user ++ * reside ++ */ ++static int get_script_dir(char *sieve_script_dir, size_t buflen, char *userid, const char *sieve_dir) ++{ ++ char *user = NULL, *domain = NULL; ++ ++ /* Setup the user and the domain */ ++ if(config_virtdomains && (domain = strchr(userid, '@'))) { ++ user = (char *) xmalloc((domain - userid +1) * sizeof(char)); ++ strlcpy(user, userid, domain - userid + 1); ++ domain++; ++ } else ++ user = userid; ++ ++ /* Find the dir path where the sieve scripts of the user will reside */ ++ if (config_virtdomains && domain) { ++ if(snprintf(sieve_script_dir, buflen, "%s%s%c/%s/%c/%s/", ++ sieve_dir, FNAME_DOMAINDIR, dir_hash_c(domain), domain, dir_hash_c(user), user) >= buflen) { ++ free(user); ++ return 1; ++ } ++ } else { ++ if(snprintf(sieve_script_dir, buflen, "%s/%c/%s/", ++ sieve_dir, dir_hash_c(user), user) >= buflen) ++ return 1; ++ } ++ ++ /* Free the xmalloced user memory, reserved above */ ++ if(user != userid) ++ free(user); ++ ++ return 0; ++} ++ ++int autoadd_sieve(char *userid, const char *source_script) ++{ ++ sieve_script_t *s = NULL; ++ bytecode_info_t *bc = NULL; ++ char *err = NULL; ++ FILE *in_stream, *out_fp; ++ int out_fd, in_fd, r, k; ++ int do_compile = 0; ++ const char *sieve_dir = NULL; ++ const char *compiled_source_script = NULL; ++ char sievename[MAX_FILENAME]; ++ char sieve_script_name[MAX_FILENAME]; ++ char sieve_script_dir[MAX_FILENAME]; ++ char sieve_bcscript_name[MAX_FILENAME]; ++ char sieve_default[MAX_FILENAME]; ++ char sieve_tmpname[MAX_FILENAME]; ++ char sieve_bctmpname[MAX_FILENAME]; ++ char sieve_bclink_name[MAX_FILENAME]; ++ char buf[4096]; ++ mode_t oldmask; ++ struct stat statbuf; ++ ++ /* We don't support using the homedirectory, like timsieved */ ++ if (config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR)) { ++ syslog(LOG_WARNING,"autocreate_sieve: autocreate_sieve does not work with sieveusehomedir option in imapd.conf"); ++ return 1; ++ } ++ ++ /* Check if sievedir is defined in imapd.conf */ ++ if(!(sieve_dir = config_getstring(IMAPOPT_SIEVEDIR))) { ++ syslog(LOG_WARNING, "autocreate_sieve: sievedir option is not defined. Check imapd.conf"); ++ return 1; ++ } ++ ++ /* Check if autocreate_sieve_compiledscript is defined in imapd.conf */ ++ if(!(compiled_source_script = config_getstring(IMAPOPT_AUTOCREATE_SIEVE_COMPILEDSCRIPT))) { ++ syslog(LOG_WARNING, "autocreate_sieve: autocreate_sieve_compiledscript option is not defined. Compiling it"); ++ do_compile = 1; ++ } ++ ++ if(get_script_dir(sieve_script_dir, sizeof(sieve_script_dir), userid, sieve_dir)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Cannot find sieve scripts directory"); ++ return 1; ++ } ++ ++ if (get_script_name(sievename, sizeof(sievename), source_script)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve script %s", source_script); ++ return 1; ++ } ++ ++ if(snprintf(sieve_tmpname, sizeof(sieve_tmpname), "%s%s.script.NEW",sieve_script_dir, sievename) >= sizeof(sieve_tmpname)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_dir, sievename, userid); ++ return 1; ++ } ++ if(snprintf(sieve_bctmpname, sizeof(sieve_bctmpname), "%s%s.bc.NEW",sieve_script_dir, sievename) >= sizeof(sieve_bctmpname)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_dir, sievename, userid); ++ return 1; ++ } ++ if(snprintf(sieve_script_name, sizeof(sieve_script_name), "%s%s.script",sieve_script_dir, sievename) >= sizeof(sieve_script_name)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_dir, sievename, userid); ++ return 1; ++ } ++ if(snprintf(sieve_bcscript_name, sizeof(sieve_bcscript_name), "%s%s.bc",sieve_script_dir, sievename) >= sizeof(sieve_bcscript_name)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_dir, sievename, userid); ++ return 1; ++ } ++ if(snprintf(sieve_default, sizeof(sieve_default), "%s%s",sieve_script_dir,"defaultbc") >= sizeof(sieve_default)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_dir, sievename, userid); ++ return 1; ++ } ++ if(snprintf(sieve_bclink_name, sizeof(sieve_bclink_name), "%s.bc", sievename) >= sizeof(sieve_bclink_name)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_dir, sievename, userid); ++ return 1; ++ } ++ ++ /* Check if a default sieve filter alrady exists */ ++ if(!stat(sieve_default,&statbuf)) { ++ syslog(LOG_WARNING,"autocreate_sieve: Default sieve script already exists"); ++ fclose(in_stream); ++ return 1; ++ } ++ ++ /* Open the source script. if there is a problem with that exit */ ++ in_stream = fopen(source_script, "r"); ++ if(!in_stream) { ++ syslog(LOG_WARNING,"autocreate_sieve: Unable to open sieve script %s. Check permissions",source_script); ++ return 1; ++ } ++ ++ ++ /* ++ * At this point we start the modifications of the filesystem ++ */ ++ ++ /* Create the directory where the sieve scripts will reside */ ++ r = cyrus_mkdir(sieve_script_dir, 0755); ++ if(r == -1) { ++ /* If this fails we just leave */ ++ syslog(LOG_WARNING,"autocreate_sieve: Unable to create directory %s. Check permissions",sieve_script_name); ++ return 1; ++ } ++ ++ /* ++ * We open the file that will be used as the bc file. If this file exists, overwrite it ++ * since something bad has happened. We open the file here so that this error checking is ++ * done before we try to open the rest of the files to start copying etc. ++ */ ++ out_fd = open(sieve_bctmpname, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); ++ if(out_fd < 0) { ++ if(errno == EEXIST) { ++ syslog(LOG_WARNING,"autocreate_sieve: File %s already exists. Probaly left over. Ignoring",sieve_bctmpname); ++ } else if (errno == EACCES) { ++ syslog(LOG_WARNING,"autocreate_sieve: No access to create file %s. Check permissions",sieve_bctmpname); ++ fclose(in_stream); ++ return 1; ++ } else { ++ syslog(LOG_WARNING,"autocreate_sieve: Unable to create %s. Unknown error",sieve_bctmpname); ++ fclose(in_stream); ++ return 1; ++ } ++ } ++ ++ if(!do_compile && compiled_source_script && (in_fd = open(compiled_source_script, O_RDONLY)) != -1) { ++ while((r = read(in_fd, buf, sizeof(buf))) > 0) { ++ if((k=write(out_fd, buf,r)) < 0) { ++ syslog(LOG_WARNING, "autocreate_sieve: Error writing to file: %s, error: %d", sieve_bctmpname, errno); ++ close(out_fd); ++ close(in_fd); ++ fclose(in_stream); ++ unlink(sieve_bctmpname); ++ return 1; ++ } ++ } ++ ++ if(r == 0) { /* EOF */ ++ close(out_fd); ++ close(in_fd); ++ } else if (r < 0) { ++ syslog(LOG_WARNING, "autocreate_sieve: Error reading compiled script file: %s. Will try to compile it", ++ compiled_source_script); ++ close(in_fd); ++ do_compile = 1; ++ if(lseek(out_fd, 0, SEEK_SET)) { ++ syslog(LOG_WARNING, "autocreate_sieve: Major IO problem. Aborting"); ++ return 1; ++ } ++ } ++ close(in_fd); ++ } else { ++ if(compiled_source_script) ++ syslog(LOG_WARNING,"autocreate_sieve: Problem opening compiled script file: %s. Compiling it", compiled_source_script); ++ do_compile = 1; ++ } ++ ++ ++ /* Because we failed to open a precompiled bc sieve script, we compile one */ ++ if(do_compile) { ++ if(is_script_parsable(in_stream,&err, &s) == TIMSIEVE_FAIL) { ++ if(err && *err) { ++ syslog(LOG_WARNING,"autocreate_sieve: Error while parsing script %s.",err); ++ free(err); ++ } else ++ syslog(LOG_WARNING,"autocreate_sieve: Error while parsing script"); ++ ++ unlink(sieve_bctmpname); ++ fclose(in_stream); ++ close(out_fd); ++ return 1; ++ } ++ ++ /* generate the bytecode */ ++ if(sieve_generate_bytecode(&bc, s) == TIMSIEVE_FAIL) { ++ syslog(LOG_WARNING,"autocreate_sieve: problem compiling sieve script"); ++ /* removing the copied script and cleaning up memory */ ++ unlink(sieve_bctmpname); ++ sieve_script_free(&s); ++ fclose(in_stream); ++ close(out_fd); ++ return 1; ++ } ++ ++ if(sieve_emit_bytecode(out_fd, bc) == TIMSIEVE_FAIL) { ++ syslog(LOG_WARNING,"autocreate_sieve: problem emiting sieve script"); ++ /* removing the copied script and cleaning up memory */ ++ unlink(sieve_bctmpname); ++ sieve_free_bytecode(&bc); ++ sieve_script_free(&s); ++ fclose(in_stream); ++ close(out_fd); ++ return 1; ++ } ++ ++ /* clean up the memory */ ++ sieve_free_bytecode(&bc); ++ sieve_script_free(&s); ++ } ++ ++ close(out_fd); ++ rewind(in_stream); ++ ++ /* Copy the initial script */ ++ oldmask = umask(077); ++ if((out_fp = fopen(sieve_tmpname, "w")) == NULL) { ++ syslog(LOG_WARNING,"autocreate_sieve: Unable to open %s destination sieve script", sieve_tmpname); ++ unlink(sieve_bctmpname); ++ umask(oldmask); ++ fclose(in_stream); ++ return 1; ++ } ++ umask(oldmask); ++ ++ while((r = fread(buf,sizeof(char), sizeof(buf), in_stream))) { ++ if( fwrite(buf,sizeof(char), r, out_fp) != r) { ++ syslog(LOG_WARNING,"autocreate_sieve: Problem writing to sieve script file: %s",sieve_tmpname); ++ fclose(out_fp); ++ unlink(sieve_tmpname); ++ unlink(sieve_bctmpname); ++ fclose(in_stream); ++ return 1; ++ } ++ } ++ ++ if(feof(in_stream)) { ++ fclose(out_fp); ++ } else { /* ferror */ ++ fclose(out_fp); ++ unlink(sieve_tmpname); ++ unlink(sieve_bctmpname); ++ fclose(in_stream); ++ return 1; ++ } ++ ++ /* Renaming the necessary stuff */ ++ if(rename(sieve_tmpname, sieve_script_name)) { ++ unlink(sieve_tmpname); ++ unlink(sieve_bctmpname); ++ return 1; ++ } ++ ++ if(rename(sieve_bctmpname, sieve_bcscript_name)) { ++ unlink(sieve_bctmpname); ++ unlink(sieve_bcscript_name); ++ return 1; ++ } ++ ++ /* end now with the symlink */ ++ if(symlink(sieve_bclink_name, sieve_default)) { ++ if(errno != EEXIST) { ++ syslog(LOG_WARNING, "autocreate_sieve: problem making the default link."); ++ /* Lets delete the files */ ++ unlink(sieve_script_name); ++ unlink(sieve_bcscript_name); ++ } ++ } ++ ++ /* ++ * If everything has succeeded AND we have compiled the script AND we have requested ++ * to generate the global script so that it is not compiled each time then we create it. ++ */ ++ if(do_compile && ++ config_getswitch(IMAPOPT_GENERATE_COMPILED_SIEVE_SCRIPT)) { ++ ++ if(!compiled_source_script) { ++ syslog(LOG_WARNING, "autocreate_sieve: To save a compiled sieve script, autocreate_sieve_compiledscript must have been defined in imapd.conf"); ++ return 0; ++ } ++ ++ if(snprintf(sieve_tmpname, sizeof(sieve_tmpname), "%s.NEW", compiled_source_script) >= sizeof(sieve_tmpname)) ++ return 0; ++ ++ /* ++ * Copy everything from the newly created bc sieve sieve script. ++ */ ++ if((in_fd = open(sieve_bcscript_name, O_RDONLY))<0) { ++ return 0; ++ } ++ ++ if((out_fd = open(sieve_tmpname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) { ++ if(errno == EEXIST) { ++ /* Someone is already doing this so just bail out. */ ++ syslog(LOG_WARNING, "autocreate_sieve: %s already exists. Some other instance processing it, or it is left over", sieve_tmpname); ++ close(in_fd); ++ return 0; ++ } else if (errno == EACCES) { ++ syslog(LOG_WARNING,"autocreate_sieve: No access to create file %s. Check permissions",sieve_tmpname); ++ close(in_fd); ++ return 0; ++ } else { ++ syslog(LOG_WARNING,"autocreate_sieve: Unable to create %s",sieve_tmpname); ++ close(in_fd); ++ return 0; ++ } ++ } ++ ++ while((r = read(in_fd, buf, sizeof(buf))) > 0) { ++ if((k = write(out_fd,buf,r)) < 0) { ++ syslog(LOG_WARNING, "autocreate_sieve: Error writing to file: %s, error: %d", sieve_tmpname, errno); ++ close(out_fd); ++ close(in_fd); ++ unlink(sieve_tmpname); ++ return 0; ++ } ++ } ++ ++ if(r == 0 ) { /*EOF */ ++ close(out_fd); ++ close(in_fd); ++ } else if (r < 0) { ++ syslog(LOG_WARNING, "autocreate_sieve: Error writing to file: %s, error: %d", sieve_tmpname, errno); ++ close(out_fd); ++ close(in_fd); ++ unlink(sieve_tmpname); ++ return 0; ++ } ++ ++ /* Rename the temporary created sieve script to its final name. */ ++ if(rename(sieve_tmpname, compiled_source_script)) { ++ if(errno != EEXIST) { ++ unlink(sieve_tmpname); ++ unlink(compiled_source_script); ++ } ++ return 0; ++ } ++ ++ syslog(LOG_NOTICE, "autocreate_sieve: Compiled sieve script was successfully saved in %s", compiled_source_script); ++ } ++ ++ return 0; ++} ++ ++static void fatal(const char *s, int code) ++{ ++ printf("Fatal error: %s (%d)\r\n", s, code); ++ exit(1); ++} ++ ++/* to make larry's stupid functions happy :) */ ++static void foo(void) ++{ ++ fatal("stub function called", 0); ++} ++ ++static int sieve_notify(void *ac __attribute__((unused)), ++ void *interp_context __attribute__((unused)), ++ void *script_context __attribute__((unused)), ++ void *message_context __attribute__((unused)), ++ const char **errmsg __attribute__((unused))) ++{ ++ fatal("stub function called", 0); ++ return SIEVE_FAIL; ++} ++ ++static int mysieve_error(int lineno, const char *msg, ++ void *i __attribute__((unused)), void *s) ++{ ++ char buf[1024]; ++ char **errstr = (char **) s; ++ ++ snprintf(buf, 80, "line %d: %s\r\n", lineno, msg); ++ *errstr = (char *) xrealloc(*errstr, strlen(*errstr) + strlen(buf) + 30); ++ syslog(LOG_DEBUG, "%s", buf); ++ strcat(*errstr, buf); ++ ++ return SIEVE_OK; ++} ++ ++/* end the boilerplate */ ++ ++/* returns TRUE or FALSE */ ++int is_script_parsable(FILE *stream, char **errstr, sieve_script_t **ret) ++{ ++ sieve_interp_t *i; ++ sieve_script_t *s; ++ int res; ++ ++ res = sieve_interp_alloc(&i, NULL); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_interp_alloc() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_redirect(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_redirect() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_discard(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_discard() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_reject(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_reject() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_fileinto(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_fileinto() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_keep(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_keep() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_imapflags(i, NULL); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_imapflags() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_size(i, (sieve_get_size *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_size() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_header(i, (sieve_get_header *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_header() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_envelope(i, (sieve_get_envelope *) &foo); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_envelope() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_vacation(i, &vacation2); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_vacation() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_notify(i, &sieve_notify); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_notify() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_parse_error(i, &mysieve_error); ++ if (res != SIEVE_OK) { ++ syslog(LOG_WARNING, "sieve_register_parse_error() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ rewind(stream); ++ ++ *errstr = (char *) xmalloc(20 * sizeof(char)); ++ strcpy(*errstr, "script errors:\r\n"); ++ ++ res = sieve_script_parse(i, stream, errstr, &s); ++ ++ if (res == SIEVE_OK) { ++ if(ret) { ++ *ret = s; ++ } else { ++ sieve_script_free(&s); ++ } ++ free(*errstr); ++ *errstr = NULL; ++ } ++ ++ /* free interpreter */ ++ sieve_interp_free(&i); ++ ++ return (res == SIEVE_OK) ? TIMSIEVE_OK : TIMSIEVE_FAIL; ++} ++ ++/* ++ * Btw the initial date of this patch is Sep, 02 2004 which is the birthday of ++ * Pavlos. Author of cyrusmaster. So consider this patch as his birthday present ++ */ ++ +--- /dev/null 2006-07-21 18:50:55.248316500 +0200 ++++ cyrus-imapd-2.3.7/imap/compile_sieve.c 2006-07-23 12:35:41.000000000 +0200 +@@ -0,0 +1,364 @@ ++/* This tool compiles the sieve script from a command ++line so that it can be used wby the autoadd patch */ ++#include ++#include ++ ++#include ++#include ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "global.h" ++ ++#include "util.h" ++#include "mailbox.h" ++#include "imap_err.h" ++#include "sieve_interface.h" ++#include "script.h" ++ ++#include ++ ++#define TIMSIEVE_FAIL -1 ++#define TIMSIEVE_OK 0 ++#define MAX_FILENAME_SIZE 100 ++ ++/* Needed by libconfig */ ++const int config_need_data = 0; ++ ++static int is_script_parsable(FILE *stream, char **errstr, sieve_script_t **ret); ++ ++static void fatal(const char *s, int code) ++{ ++ printf("Fatal error: %s (%d)\r\n", s, code); ++ ++ exit(1); ++} ++ ++void usage(void) ++{ ++ fprintf(stderr, ++ "Usage:\n\tcompile_sieve [-C ] [-i -o ]\n"); ++ exit(-1); ++} ++ ++ ++int main (int argc, char **argv) ++{ ++ ++ sieve_script_t *s = NULL; ++ bytecode_info_t *bc = NULL; ++ char *err = NULL; ++ FILE *in_stream; ++ int out_fd,r, k, opt; ++ char *source_script = NULL; ++ char *compiled_source_script = NULL; ++ mode_t oldmask; ++ struct stat statbuf; ++ char *alt_config = NULL; ++ extern char *optarg; ++ char sieve_tmpname[MAX_MAILBOX_NAME+1]; ++ ++ if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); ++ ++ while((opt = getopt(argc, argv, "C:i:o:")) != EOF) { ++ switch (opt) { ++ case 'C': /* alt config file */ ++ alt_config = optarg; ++ break; ++ case 'i': /* input script file */ ++ source_script = optarg; ++ break; ++ case 'o': /* output script file */ ++ compiled_source_script = optarg; ++ break; ++ default: ++ usage(); ++ break; ++ } ++ } ++ ++ if(source_script && !compiled_source_script) { ++ fprintf(stderr, "No output file was defined\n"); ++ usage(); ++ } else if (!source_script && compiled_source_script) { ++ fprintf(stderr, "No input file was defined\n"); ++ usage(); ++ } ++ ++ /* ++ * If no has been defined, then read them from ++ * the configuration file. ++ */ ++ if (!source_script && !compiled_source_script) { ++ cyrus_init(alt_config, "compile_sieve", 0); ++ ++ /* Initially check if we want to have the sieve script created */ ++ if(!(source_script = (char *) config_getstring(IMAPOPT_AUTOCREATE_SIEVE_SCRIPT))) { ++ fprintf(stderr,"autocreate_sieve_script option not defined. Check imapd.conf\n"); ++ return 1; ++ } ++ ++ /* Check if we have an already compiled sieve script*/ ++ if(!(compiled_source_script = (char *) config_getstring(IMAPOPT_AUTOCREATE_SIEVE_COMPILEDSCRIPT))) { ++ fprintf(stderr, "autocreate_sieve_compiledscript option not defined. Check imapd.conf\n"); ++ return 1; ++ } ++ ++ if(!strrchr(source_script,'/') || !strrchr(compiled_source_script,'/')) { ++ /* ++ * At this point the only think that is inconsistent is the directory ++ * that was created. But if the user will have any sieve scripts then ++ * they will eventually go there, so no big deal ++ */ ++ fprintf(stderr, ++ "In imapd.conf the full path of the filenames must be defined\n"); ++ return 1; ++ } ++ } ++ ++ printf("input file : %s, output file : %s\n", source_script, compiled_source_script); ++ ++ ++ if(strlen(compiled_source_script) + sizeof(".NEW") + 1 > sizeof(sieve_tmpname)) { ++ fprintf(stderr, "Filename %s is too big\n", compiled_source_script); ++ return 1; ++ } ++ ++ snprintf(sieve_tmpname, sizeof(sieve_tmpname), "%s.NEW", compiled_source_script); ++ ++ in_stream = fopen(source_script,"r"); ++ ++ if(!in_stream) { ++ fprintf(stderr,"Unable to open %s source sieve script\n",source_script); ++ return; ++ } ++ ++ /* ++ * We open the file that will be used as the bc file. If this file exists, overwrite it ++ * since something bad has happened. We open the file here so that this error checking is ++ * done before we try to open the rest of the files to start copying etc. ++ */ ++ out_fd = open(sieve_tmpname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); ++ if(out_fd < 0) { ++ if(errno == EEXIST) { ++ fprintf(stderr, "File %s already exists\n", sieve_tmpname); ++ } else if (errno == EACCES) { ++ fprintf(stderr,"No access to create file %s. Please check that you have the correct permissions\n", ++ sieve_tmpname); ++ } else { ++ fprintf(stderr,"Unable to create %s. Please check that you have the correct permissions\n", ++ sieve_tmpname); ++ } ++ ++ fclose(in_stream); ++ return 1; ++ } ++ ++ if(is_script_parsable(in_stream,&err, &s) == TIMSIEVE_FAIL) { ++ if(err && *err) { ++ fprintf(stderr, "Error while parsing script %s\n",err); ++ free(err); ++ } ++ else ++ fprintf(stderr,"Error while parsing script\n"); ++ unlink(sieve_tmpname); ++ fclose(in_stream); ++ close(out_fd); ++ return; ++ } ++ ++ ++ /* generate the bytecode */ ++ if(sieve_generate_bytecode(&bc,s) == TIMSIEVE_FAIL) { ++ fprintf(stderr,"Error occured while compiling sieve script\n"); ++ /* removing the copied script and cleaning up memory */ ++ unlink(sieve_tmpname); ++ sieve_script_free(&s); ++ fclose(in_stream); ++ close(out_fd); ++ return; ++ } ++ if(sieve_emit_bytecode(out_fd,bc) == TIMSIEVE_FAIL) { ++ fprintf(stderr, "Error occured while emitting sieve script\n"); ++ unlink(sieve_tmpname); ++ sieve_free_bytecode(&bc); ++ sieve_script_free(&s); ++ fclose(in_stream); ++ close(out_fd); ++ return; ++ } ++ ++ /* clean up the memory */ ++ sieve_free_bytecode(&bc); ++ sieve_script_free(&s); ++ ++ close(out_fd); ++ ++ if(rename(sieve_tmpname, compiled_source_script)) { ++ if(errno != EEXIST) { ++ unlink(sieve_tmpname); ++ unlink(compiled_source_script); ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++ ++/* to make larry's stupid functions happy :) */ ++static void foo(void) ++{ ++ fatal("stub function called", 0); ++} ++ ++extern sieve_vacation_t vacation2;/* = { ++ 0, / min response / ++ 0, / max response / ++ (sieve_callback *) &foo, / autorespond() / ++ (sieve_callback *) &foo / send_response() / ++}; */ ++ ++static int sieve_notify(void *ac __attribute__((unused)), ++ void *interp_context __attribute__((unused)), ++ void *script_context __attribute__((unused)), ++ void *message_context __attribute__((unused)), ++ const char **errmsg __attribute__((unused))) ++{ ++ fatal("stub function called", 0); ++ return SIEVE_FAIL; ++} ++ ++static int mysieve_error(int lineno, const char *msg, ++ void *i __attribute__((unused)), void *s) ++{ ++ char buf[1024]; ++ char **errstr = (char **) s; ++ ++ snprintf(buf, 80, "line %d: %s\r\n", lineno, msg); ++ *errstr = (char *) xrealloc(*errstr, strlen(*errstr) + strlen(buf) + 30); ++ fprintf(stderr, "%s\n", buf); ++ strcat(*errstr, buf); ++ ++ return SIEVE_OK; ++} ++ ++/* end the boilerplate */ ++ ++/* returns TRUE or FALSE */ ++int is_script_parsable(FILE *stream, char **errstr, sieve_script_t **ret) ++{ ++ sieve_interp_t *i; ++ sieve_script_t *s; ++ int res; ++ ++ res = sieve_interp_alloc(&i, NULL); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_interp_alloc() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_redirect(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_redirect() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_discard(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_discard() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_reject(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_reject() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_fileinto(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_fileinto() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ res = sieve_register_keep(i, (sieve_callback *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_keep() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_imapflags(i, NULL); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_imapflags() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_size(i, (sieve_get_size *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_size() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_header(i, (sieve_get_header *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_header() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_envelope(i, (sieve_get_envelope *) &foo); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_envelope() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_vacation(i, &vacation2); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_vacation() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_notify(i, &sieve_notify); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_notify() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ res = sieve_register_parse_error(i, &mysieve_error); ++ if (res != SIEVE_OK) { ++ fprintf(stderr, "sieve_register_parse_error() returns %d\n", res); ++ return TIMSIEVE_FAIL; ++ } ++ ++ rewind(stream); ++ ++ *errstr = (char *) xmalloc(20 * sizeof(char)); ++ strcpy(*errstr, "script errors:\r\n"); ++ ++ res = sieve_script_parse(i, stream, errstr, &s); ++ ++ if (res == SIEVE_OK) { ++ if(ret) { ++ *ret = s; ++ } else { ++ sieve_script_free(&s); ++ } ++ free(*errstr); ++ *errstr = NULL; ++ } ++ ++ /* free interpreter */ ++ sieve_interp_free(&i); ++ ++ return (res == SIEVE_OK) ? TIMSIEVE_OK : TIMSIEVE_FAIL; ++} ++ ++ ++ ++ ++ ++ +--- cyrus-imapd-2.3.7/imap/imapd.c.autocreate0 2006-07-03 15:22:41.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/imapd.c 2006-07-23 12:35:41.000000000 +0200 +@@ -197,6 +197,7 @@ + void motd_file(int fd); + void shut_down(int code); + void fatal(const char *s, int code); ++void autocreate_inbox(void); + + void cmdloop(void); + void cmd_login(char *tag, char *user); +@@ -1904,6 +1905,43 @@ + } + + /* ++ * Autocreate Inbox and subfolders upon login ++ */ ++void autocreate_inbox() ++{ ++ char inboxname[MAX_MAILBOX_NAME+1]; ++ int autocreatequota; ++ int r; ++ ++ /* ++ * Exlude admin's accounts ++ */ ++ if (imapd_userisadmin || imapd_userisproxyadmin) ++ return; ++ ++ /* ++ * Exclude anonymous ++ */ ++ if (!strcmp(imapd_userid, "anonymous")) ++ return; ++ ++ if ((autocreatequota = config_getint(IMAPOPT_AUTOCREATEQUOTA))) { ++ /* This is actyally not required ++ as long as the lenght of userid is ok */ ++ r = (*imapd_namespace.mboxname_tointernal) (&imapd_namespace, ++ "INBOX", imapd_userid, inboxname); ++ if (!r) ++ r = mboxlist_lookup(inboxname, NULL, NULL); ++ ++ if (r == IMAP_MAILBOX_NONEXISTENT) { ++ mboxlist_autocreateinbox(&imapd_namespace, imapd_userid, ++ imapd_authstate, inboxname, autocreatequota); ++ } ++ } ++} ++ ++ ++/* + * Perform a LOGIN command + */ + void cmd_login(char *tag, char *user) +@@ -2071,6 +2109,9 @@ + strcspn(imapd_userid, "@") : 0); + + freebuf(&passwdbuf); ++ ++ autocreate_inbox(); ++ + return; + } + +@@ -2227,6 +2268,8 @@ + config_virtdomains ? + strcspn(imapd_userid, "@") : 0); + ++ autocreate_inbox(); ++ + return; + } + +--- /dev/null 2006-07-21 18:50:55.248316500 +0200 ++++ cyrus-imapd-2.3.7/imap/imapd.c.orig 2006-07-23 12:35:41.000000000 +0200 +@@ -0,0 +1,9588 @@ ++/* ++ * Copyright (c) 1998-2003 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 other legal ++ * details, please contact ++ * Office of Technology Transfer ++ * Carnegie Mellon University ++ * 5000 Forbes Avenue ++ * Pittsburgh, PA 15213-3890 ++ * (412) 268-4387, fax: (412) 268-7395 ++ * tech-transfer@andrew.cmu.edu ++ * ++ * 4. Redistributions of any form whatsoever must retain the following ++ * "This product includes software developed by Computing Services ++ * acknowledgment: ++ * 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. ++ */ ++ ++/* $Id$ */ ++ ++#include ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "acl.h" ++#include "annotate.h" ++#include "append.h" ++#include "auth.h" ++#include "backend.h" ++#include "charset.h" ++#include "exitcodes.h" ++#include "idle.h" ++#include "global.h" ++#include "imap_err.h" ++#include "proxy.h" ++#include "imap_proxy.h" ++#include "imapd.h" ++#include "imapurl.h" ++#include "imparse.h" ++#include "index.h" ++#include "iptostring.h" ++#include "mailbox.h" ++#include "message.h" ++#include "mboxkey.h" ++#include "mboxlist.h" ++#include "mboxname.h" ++#include "mbdump.h" ++#include "mkgmtime.h" ++#include "mupdate-client.h" ++#include "quota.h" ++#include "sync_log.h" ++#include "telemetry.h" ++#include "tls.h" ++#include "user.h" ++#include "util.h" ++#include "version.h" ++#include "xmalloc.h" ++ ++#include "pushstats.h" /* SNMP interface */ ++ ++extern void seen_done(void); ++ ++extern int optind; ++extern char *optarg; ++ ++/* global state */ ++const int config_need_data = CONFIG_NEED_PARTITION_DATA; ++ ++static char shutdownfilename[1024]; ++static int imaps = 0; ++static sasl_ssf_t extprops_ssf = 0; ++ ++/* PROXY STUFF */ ++/* we want a list of our outgoing connections here and which one we're ++ currently piping */ ++ ++static const int ultraparanoid = 1; /* should we kick after every operation? */ ++unsigned int proxy_cmdcnt; ++ ++static int referral_kick = 0; /* kick after next command recieved, for ++ referrals that are likely to change the ++ mailbox list */ ++ ++/* all subscription commands go to the backend server containing the ++ user's inbox */ ++struct backend *backend_inbox = NULL; ++ ++/* the current server most commands go to */ ++struct backend *backend_current = NULL; ++ ++/* our cached connections */ ++struct backend **backend_cached = NULL; ++ ++/* are we doing virtdomains with multiple IPs? */ ++static int disable_referrals; ++ ++/* has the client issued an RLIST or RLSUB? */ ++static int supports_referrals; ++ ++/* end PROXY STUFF */ ++ ++/* per-user/session state */ ++struct protstream *imapd_out = NULL; ++struct protstream *imapd_in = NULL; ++struct protgroup *protin = NULL; ++static char imapd_clienthost[NI_MAXHOST*2+1] = "[local]"; ++static int imapd_logfd = -1; ++char *imapd_userid = NULL, *proxy_userid = NULL; ++static char *imapd_magicplus = NULL; ++struct auth_state *imapd_authstate = 0; ++static int imapd_userisadmin = 0; ++static int imapd_userisproxyadmin = 0; ++static sasl_conn_t *imapd_saslconn; /* the sasl connection context */ ++static int imapd_starttls_done = 0; /* have we done a successful starttls? */ ++const char *plaintextloginalert = NULL; ++#ifdef HAVE_SSL ++/* our tls connection, if any */ ++static SSL *tls_conn = NULL; ++#endif /* HAVE_SSL */ ++ ++/* stage(s) for APPEND */ ++struct appendstage { ++ struct stagemsg *stage; ++ char **flag; ++ int nflags, flagalloc; ++ time_t internaldate; ++} **stage = NULL; ++unsigned long numstage = 0; ++ ++/* the sasl proxy policy context */ ++static struct proxy_context imapd_proxyctx = { ++ 1, 1, &imapd_authstate, &imapd_userisadmin, &imapd_userisproxyadmin ++}; ++ ++/* current sub-user state */ ++static struct mailbox mboxstruct; ++static struct mailbox *imapd_mailbox; ++int imapd_exists = -1; ++ ++/* current namespace */ ++struct namespace imapd_namespace; ++ ++static const char *monthname[] = { ++ "jan", "feb", "mar", "apr", "may", "jun", ++ "jul", "aug", "sep", "oct", "nov", "dec" ++}; ++ ++static const int max_monthdays[] = { ++ 31, 29, 31, 30, 31, 30, ++ 31, 31, 30, 31, 30, 31 ++}; ++ ++void motd_file(int fd); ++void shut_down(int code); ++void fatal(const char *s, int code); ++ ++void cmdloop(void); ++void cmd_login(char *tag, char *user); ++void cmd_authenticate(char *tag, char *authtype, char *resp); ++void cmd_noop(char *tag, char *cmd); ++void cmd_capability(char *tag); ++void cmd_append(char *tag, char *name, const char *cur_name); ++void cmd_select(char *tag, char *cmd, char *name); ++void cmd_close(char *tag); ++void cmd_fetch(char *tag, char *sequence, int usinguid); ++void cmd_partial(const char *tag, const char *msgno, char *data, ++ const char *start, const char *count); ++void cmd_store(char *tag, char *sequence, char *operation, int usinguid); ++void cmd_search(char *tag, int usinguid); ++void cmd_sort(char *tag, int usinguid); ++void cmd_thread(char *tag, int usinguid); ++void cmd_copy(char *tag, char *sequence, char *name, int usinguid); ++void cmd_expunge(char *tag, char *sequence); ++void cmd_create(char *tag, char *name, char *partition, int localonly); ++void cmd_delete(char *tag, char *name, int localonly, int force); ++void cmd_dump(char *tag, char *name, int uid_start); ++void cmd_undump(char *tag, char *name); ++void cmd_xfer(char *tag, char *name, char *toserver, char *topart); ++void cmd_rename(char *tag, char *oldname, char *newname, char *partition); ++void cmd_reconstruct(const char *tag, const char *name, int recursive); ++void cmd_find(char *tag, char *namespace, char *pattern); ++void cmd_list(char *tag, int listopts, char *reference, char *pattern); ++void cmd_changesub(char *tag, char *namespace, char *name, int add); ++void cmd_getacl(const char *tag, const char *name); ++void cmd_listrights(char *tag, char *name, char *identifier); ++void cmd_myrights(const char *tag, const char *name); ++void cmd_setacl(char *tag, const char *name, ++ const char *identifier, const char *rights); ++void cmd_getquota(const char *tag, const char *name); ++void cmd_getquotaroot(const char *tag, const char *name); ++void cmd_setquota(const char *tag, const char *quotaroot); ++void cmd_status(char *tag, char *name); ++void cmd_unselect(char* tag); ++void cmd_namespace(char* tag); ++void cmd_mupdatepush(char *tag, char *name); ++void cmd_id(char* tag); ++extern void id_getcmdline(int argc, char **argv); ++extern void id_response(struct protstream *pout); ++ ++void cmd_idle(char* tag); ++void idle_update(idle_flags_t flags); ++ ++void cmd_starttls(char *tag, int imaps); ++ ++#ifdef HAVE_SSL ++void cmd_urlfetch(char *tag); ++void cmd_genurlauth(char *tag); ++void cmd_resetkey(char *tag, char *mailbox, char *mechanism); ++#endif ++ ++#ifdef ENABLE_X_NETSCAPE_HACK ++void cmd_netscrape(char* tag); ++#endif ++ ++void cmd_getannotation(char* tag, char *mboxpat); ++void cmd_setannotation(char* tag, char *mboxpat); ++ ++int getannotatefetchdata(char *tag, ++ struct strlist **entries, struct strlist **attribs); ++int getannotatestoredata(char *tag, struct entryattlist **entryatts); ++ ++void annotate_response(struct entryattlist *l); ++ ++#ifdef ENABLE_LISTEXT ++int getlistopts(char *tag, int *listopts); ++#endif ++ ++int getsearchprogram(char *tag, struct searchargs *searchargs, ++ int *charset, int parsecharset); ++int getsearchcriteria(char *tag, struct searchargs *searchargs, ++ int *charset, int parsecharset); ++int getsearchdate(time_t *start, time_t *end); ++int getsortcriteria(char *tag, struct sortcrit **sortcrit); ++int getdatetime(time_t *date); ++ ++void printstring(const char *s); ++void printastring(const char *s); ++ ++void appendfieldlist(struct fieldlist **l, char *section, ++ struct strlist *fields, char *trail, ++ void *d, size_t size); ++void freefieldlist(struct fieldlist *l); ++void freestrlist(struct strlist *l); ++void appendsearchargs(struct searchargs *s, struct searchargs *s1, ++ struct searchargs *s2); ++void freesearchargs(struct searchargs *s); ++static void freesortcrit(struct sortcrit *s); ++ ++static int mailboxdata(char *name, int matchlen, int maycreate, void *rock); ++static int listdata(char *name, int matchlen, int maycreate, void *rock); ++static void mstringdata(char *cmd, char *name, int matchlen, int maycreate, ++ int listopts); ++ ++extern void setproctitle_init(int argc, char **argv, char **envp); ++extern int proc_register(const char *progname, const char *clienthost, ++ const char *userid, const char *mailbox); ++extern void proc_cleanup(void); ++ ++extern int saslserver(sasl_conn_t *conn, const char *mech, ++ const char *init_resp, const char *resp_prefix, ++ const char *continuation, const char *empty_resp, ++ struct protstream *pin, struct protstream *pout, ++ int *sasl_result, char **success_data); ++ ++/* Enable the resetting of a sasl_conn_t */ ++static int reset_saslconn(sasl_conn_t **conn); ++ ++static struct ++{ ++ char *ipremoteport; ++ char *iplocalport; ++ sasl_ssf_t ssf; ++ char *authid; ++} saslprops = {NULL,NULL,0,NULL}; ++ ++static int imapd_canon_user(sasl_conn_t *conn, void *context, ++ const char *user, unsigned ulen, ++ unsigned flags, const char *user_realm, ++ char *out, unsigned out_max, unsigned *out_ulen) ++{ ++ char userbuf[MAX_MAILBOX_NAME+1], *p; ++ size_t n; ++ int r; ++ ++ if (!ulen) ulen = strlen(user); ++ ++ if (config_getswitch(IMAPOPT_IMAPMAGICPLUS)) { ++ /* make a working copy of the auth[z]id */ ++ if (ulen > MAX_MAILBOX_NAME) { ++ sasl_seterror(conn, 0, "buffer overflow while canonicalizing"); ++ return SASL_BUFOVER; ++ } ++ memcpy(userbuf, user, ulen); ++ userbuf[ulen] = '\0'; ++ user = userbuf; ++ ++ /* See if we're using the magic plus ++ (currently we don't support anything after '+') */ ++ if ((p = strchr(userbuf, '+')) && ++ (n = config_virtdomains ? strcspn(p, "@") : strlen(p)) == 1) { ++ ++ if (flags & SASL_CU_AUTHZID) { ++ /* make a copy of the magic plus */ ++ if (imapd_magicplus) free(imapd_magicplus); ++ imapd_magicplus = xstrndup(p, n); ++ } ++ ++ /* strip the magic plus from the auth[z]id */ ++ memmove(p, p+n, strlen(p+n)+1); ++ ulen -= n; ++ } ++ } ++ ++ r = mysasl_canon_user(conn, context, user, ulen, flags, user_realm, ++ out, out_max, out_ulen); ++ ++ if (!r && imapd_magicplus && flags == SASL_CU_AUTHZID) { ++ /* If we're only doing the authzid, put back the magic plus ++ in case its used in the challenge/response calculation */ ++ n = strlen(imapd_magicplus); ++ if (*out_ulen + n > out_max) { ++ sasl_seterror(conn, 0, "buffer overflow while canonicalizing"); ++ r = SASL_BUFOVER; ++ } ++ else { ++ p = (config_virtdomains && (p = strchr(out, '@'))) ? ++ p : out + *out_ulen; ++ memmove(p+n, p, strlen(p)+1); ++ memcpy(p, imapd_magicplus, n); ++ *out_ulen += n; ++ } ++ } ++ ++ return r; ++} ++ ++static int imapd_proxy_policy(sasl_conn_t *conn, ++ void *context, ++ const char *requested_user, unsigned rlen, ++ const char *auth_identity, unsigned alen, ++ const char *def_realm, ++ unsigned urlen, ++ struct propctx *propctx) ++{ ++ if (config_getswitch(IMAPOPT_IMAPMAGICPLUS)) { ++ char userbuf[MAX_MAILBOX_NAME+1], *p; ++ size_t n; ++ ++ /* make a working copy of the authzid */ ++ if (!rlen) rlen = strlen(requested_user); ++ if (rlen > MAX_MAILBOX_NAME) { ++ sasl_seterror(conn, 0, "buffer overflow while proxying"); ++ return SASL_BUFOVER; ++ } ++ memcpy(userbuf, requested_user, rlen); ++ userbuf[rlen] = '\0'; ++ requested_user = userbuf; ++ ++ /* See if we're using the magic plus */ ++ if ((p = strchr(userbuf, '+'))) { ++ n = config_virtdomains ? strcspn(p, "@") : strlen(p); ++ ++ /* strip the magic plus from the authzid */ ++ memmove(p, p+n, strlen(p+n)+1); ++ rlen -= n; ++ } ++ } ++ ++ return mysasl_proxy_policy(conn, context, requested_user, rlen, ++ auth_identity, alen, def_realm, urlen, propctx); ++} ++ ++static const struct sasl_callback mysasl_cb[] = { ++ { SASL_CB_GETOPT, &mysasl_config, NULL }, ++ { SASL_CB_PROXY_POLICY, &imapd_proxy_policy, (void*) &imapd_proxyctx }, ++ { SASL_CB_CANON_USER, &imapd_canon_user, (void*) &disable_referrals }, ++ { SASL_CB_LIST_END, NULL, NULL } ++}; ++ ++/* imapd_refer() issues a referral to the client. */ ++static void imapd_refer(const char *tag, ++ const char *server, ++ const char *mailbox) ++{ ++ struct imapurl imapurl; ++ char url[MAX_MAILBOX_PATH+1]; ++ ++ memset(&imapurl, 0, sizeof(struct imapurl)); ++ imapurl.server = server; ++ imapurl.mailbox = mailbox; ++ imapurl.auth = !strcmp(imapd_userid, "anonymous") ? "anonymous" : "*"; ++ ++ imapurl_toURL(url, &imapurl); ++ ++ prot_printf(imapd_out, "%s NO [REFERRAL %s] Remote mailbox.\r\n", ++ tag, url); ++} ++ ++/* wrapper for mboxlist_lookup that will force a referral if we are remote ++ * returns IMAP_SERVER_UNAVAILABLE if we don't have a place to send the client ++ * (that'd be a bug). ++ * returns IMAP_MAILBOX_MOVED if we referred the client */ ++/* ext_name is the external name of the mailbox */ ++/* you can avoid referring the client by setting tag or ext_name to NULL. */ ++int mlookup(const char *tag, const char *ext_name, ++ const char *name, int *flags, char **pathp, char **mpathp, ++ char **partp, char **aclp, struct txn **tid) ++{ ++ int r, mbtype; ++ char *remote, *acl; ++ ++ r = mboxlist_detail(name, &mbtype, pathp, mpathp, &remote, &acl, tid); ++ if ((r == IMAP_MAILBOX_NONEXISTENT || (mbtype & MBTYPE_RESERVE)) && ++ config_mupdate_server) { ++ /* It is not currently active, make sure we have the most recent ++ * copy of the database */ ++ kick_mupdate(); ++ r = mboxlist_detail(name, &mbtype, pathp, mpathp, &remote, &acl, tid); ++ } ++ ++ if(partp) *partp = remote; ++ if(aclp) *aclp = acl; ++ if(flags) *flags = mbtype; ++ if(r) return r; ++ ++ if(mbtype & MBTYPE_RESERVE) return IMAP_MAILBOX_RESERVED; ++ ++ if(mbtype & MBTYPE_MOVING) { ++ /* do we have rights on the mailbox? */ ++ if(!imapd_userisadmin && ++ (!acl || !(cyrus_acl_myrights(imapd_authstate,acl) & ACL_LOOKUP))) { ++ r = IMAP_MAILBOX_NONEXISTENT; ++ } else if(tag && ext_name && remote && *remote) { ++ char *c = NULL; ++ ++ c = strchr(remote, '!'); ++ if(c) *c = '\0'; ++ imapd_refer(tag, remote, ext_name); ++ r = IMAP_MAILBOX_MOVED; ++ } else if(config_mupdate_server) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ } else { ++ r = IMAP_MAILBOX_NOTSUPPORTED; ++ } ++ } ++ else if (mbtype & MBTYPE_REMOTE) { ++ /* xxx hide the fact that we are storing partitions */ ++ if(remote && *remote) { ++ char *c; ++ c = strchr(remote, '!'); ++ if(c) *c = '\0'; ++ } ++ } ++ ++ return r; ++} ++ ++static void imapd_reset(void) ++{ ++ int i; ++ ++ proc_cleanup(); ++ ++ /* close backend connections */ ++ i = 0; ++ while (backend_cached && backend_cached[i]) { ++ proxy_downserver(backend_cached[i]); ++ if (backend_cached[i]->last_result.s) { ++ free(backend_cached[i]->last_result.s); ++ } ++ free(backend_cached[i]); ++ i++; ++ } ++ if (backend_cached) free(backend_cached); ++ backend_cached = NULL; ++ backend_inbox = backend_current = NULL; ++ proxy_cmdcnt = 0; ++ disable_referrals = 0; ++ supports_referrals = 0; ++ ++ if (imapd_mailbox) { ++ index_closemailbox(imapd_mailbox); ++ mailbox_close(imapd_mailbox); ++ imapd_mailbox = 0; ++ } ++ ++ if (imapd_in) { ++ /* Flush the incoming buffer */ ++ prot_NONBLOCK(imapd_in); ++ prot_fill(imapd_in); ++ ++ prot_free(imapd_in); ++ } ++ ++ if (imapd_out) { ++ /* Flush the outgoing buffer */ ++ prot_flush(imapd_out); ++ ++ prot_free(imapd_out); ++ } ++ ++ imapd_in = imapd_out = NULL; ++ ++ if (protin) protgroup_reset(protin); ++ ++#ifdef HAVE_SSL ++ if (tls_conn) { ++ if (tls_reset_servertls(&tls_conn) == -1) { ++ fatal("tls_reset() failed", EC_TEMPFAIL); ++ } ++ tls_conn = NULL; ++ } ++#endif ++ ++ cyrus_reset_stdio(); ++ ++ strcpy(imapd_clienthost, "[local]"); ++ if (imapd_logfd != -1) { ++ close(imapd_logfd); ++ imapd_logfd = -1; ++ } ++ if (imapd_userid != NULL) { ++ free(imapd_userid); ++ imapd_userid = NULL; ++ } ++ if (proxy_userid != NULL) { ++ free(proxy_userid); ++ proxy_userid = NULL; ++ } ++ if (imapd_magicplus != NULL) { ++ free(imapd_magicplus); ++ imapd_magicplus = NULL; ++ } ++ if (imapd_authstate) { ++ auth_freestate(imapd_authstate); ++ imapd_authstate = NULL; ++ } ++ imapd_userisadmin = 0; ++ imapd_userisproxyadmin = 0; ++ if (imapd_saslconn) { ++ sasl_dispose(&imapd_saslconn); ++ imapd_saslconn = NULL; ++ } ++ imapd_starttls_done = 0; ++ plaintextloginalert = NULL; ++ ++ if(saslprops.iplocalport) { ++ free(saslprops.iplocalport); ++ saslprops.iplocalport = NULL; ++ } ++ if(saslprops.ipremoteport) { ++ free(saslprops.ipremoteport); ++ saslprops.ipremoteport = NULL; ++ } ++ if(saslprops.authid) { ++ free(saslprops.authid); ++ saslprops.authid = NULL; ++ } ++ saslprops.ssf = 0; ++ ++ imapd_exists = -1; ++} ++ ++/* ++ * run once when process is forked; ++ * MUST NOT exit directly; must return with non-zero error code ++ */ ++int service_init(int argc, char **argv, char **envp) ++{ ++ int ret; ++ int opt; ++ ++ if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); ++ setproctitle_init(argc, argv, envp); ++ ++ /* set signal handlers */ ++ signals_set_shutdown(&shut_down); ++ signal(SIGPIPE, SIG_IGN); ++ ++ /* load the SASL plugins */ ++ global_sasl_init(1, 1, mysasl_cb); ++ ++ ret = snprintf(shutdownfilename, sizeof(shutdownfilename), ++ "%s/msg/shutdown", config_dir); ++ ++ if(ret < 0 || ret >= sizeof(shutdownfilename)) { ++ fatal("shutdownfilename buffer too small (configdirectory too long)", ++ EC_CONFIG); ++ } ++ ++ /* open the mboxlist, we'll need it for real work */ ++ mboxlist_init(0); ++ mboxlist_open(NULL); ++ mailbox_initialize(); ++ ++ /* open the quota db, we'll need it for real work */ ++ quotadb_init(0); ++ quotadb_open(NULL); ++ ++ /* setup for sending IMAP IDLE notifications */ ++ idle_enabled(); ++ ++ /* create connection to the SNMP listener, if available. */ ++ snmp_connect(); /* ignore return code */ ++ snmp_set_str(SERVER_NAME_VERSION,CYRUS_VERSION); ++ ++ while ((opt = getopt(argc, argv, "sp:")) != EOF) { ++ switch (opt) { ++ case 's': /* imaps (do starttls right away) */ ++ imaps = 1; ++ if (!tls_enabled()) { ++ syslog(LOG_ERR, "imaps: required OpenSSL options not present"); ++ fatal("imaps: required OpenSSL options not present", ++ EC_CONFIG); ++ } ++ break; ++ case 'p': /* external protection */ ++ extprops_ssf = atoi(optarg); ++ break; ++ default: ++ break; ++ } ++ } ++ ++ /* Initialize the annotatemore extention */ ++ if (config_mupdate_server) ++ annotatemore_init(0, annotate_fetch_proxy, annotate_store_proxy); ++ else ++ annotatemore_init(0, NULL, NULL); ++ annotatemore_open(NULL); ++ ++ /* Create a protgroup for input from the client and selected backend */ ++ protin = protgroup_new(2); ++ ++ /* YYY Sanity checks possible here? */ ++ message_uuid_client_init(getenv("CYRUS_UUID_PREFIX")); ++ ++ return 0; ++} ++ ++/* ++ * run for each accepted connection ++ */ ++#ifdef ID_SAVE_CMDLINE ++int service_main(int argc, char **argv, char **envp __attribute__((unused))) ++#else ++int service_main(int argc __attribute__((unused)), ++ char **argv __attribute__((unused)), ++ char **envp __attribute__((unused))) ++#endif ++{ ++ socklen_t salen; ++ int timeout; ++ sasl_security_properties_t *secprops = NULL; ++ struct sockaddr_storage imapd_localaddr, imapd_remoteaddr; ++ char localip[60], remoteip[60]; ++ char hbuf[NI_MAXHOST]; ++ int niflags; ++ int imapd_haveaddr = 0; ++ ++ signals_poll(); ++ ++#ifdef ID_SAVE_CMDLINE ++ /* get command line args for use in ID before getopt mangles them */ ++ id_getcmdline(argc, argv); ++#endif ++ ++ sync_log_init(); ++ ++ imapd_in = prot_new(0, 0); ++ imapd_out = prot_new(1, 1); ++ protgroup_insert(protin, imapd_in); ++ ++ /* Find out name of client host */ ++ salen = sizeof(imapd_remoteaddr); ++ if (getpeername(0, (struct sockaddr *)&imapd_remoteaddr, &salen) == 0 && ++ (imapd_remoteaddr.ss_family == AF_INET || ++ imapd_remoteaddr.ss_family == AF_INET6)) { ++ if (getnameinfo((struct sockaddr *)&imapd_remoteaddr, salen, ++ hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) { ++ strncpy(imapd_clienthost, hbuf, sizeof(hbuf)); ++ strlcat(imapd_clienthost, " ", sizeof(imapd_clienthost)); ++ imapd_clienthost[sizeof(imapd_clienthost)-30] = '\0'; ++ } else { ++ imapd_clienthost[0] = '\0'; ++ } ++ niflags = NI_NUMERICHOST; ++#ifdef NI_WITHSCOPEID ++ if (((struct sockaddr *)&imapd_remoteaddr)->sa_family == AF_INET6) ++ niflags |= NI_WITHSCOPEID; ++#endif ++ if (getnameinfo((struct sockaddr *)&imapd_remoteaddr, salen, hbuf, ++ sizeof(hbuf), NULL, 0, niflags) != 0) ++ strlcpy(hbuf, "unknown", sizeof(hbuf)); ++ strlcat(imapd_clienthost, "[", sizeof(imapd_clienthost)); ++ strlcat(imapd_clienthost, hbuf, sizeof(imapd_clienthost)); ++ strlcat(imapd_clienthost, "]", sizeof(imapd_clienthost)); ++ salen = sizeof(imapd_localaddr); ++ if (getsockname(0, (struct sockaddr *)&imapd_localaddr, &salen) == 0) { ++ if(iptostring((struct sockaddr *)&imapd_remoteaddr, salen, ++ remoteip, sizeof(remoteip)) == 0 ++ && iptostring((struct sockaddr *)&imapd_localaddr, salen, ++ localip, sizeof(localip)) == 0) { ++ imapd_haveaddr = 1; ++ } ++ } ++ } ++ ++ /* create the SASL connection */ ++ if (sasl_server_new("imap", config_servername, ++ NULL, NULL, NULL, NULL, 0, ++ &imapd_saslconn) != SASL_OK) { ++ fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL); ++ } ++ ++ /* never allow plaintext, since IMAP has the LOGIN command */ ++ secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT); ++ sasl_setprop(imapd_saslconn, SASL_SEC_PROPS, secprops); ++ sasl_setprop(imapd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf); ++ ++ if (imapd_haveaddr) { ++ sasl_setprop(imapd_saslconn, SASL_IPREMOTEPORT, remoteip); ++ saslprops.ipremoteport = xstrdup(remoteip); ++ sasl_setprop(imapd_saslconn, SASL_IPLOCALPORT, localip); ++ saslprops.iplocalport = xstrdup(localip); ++ } ++ ++ proc_register("imapd", imapd_clienthost, NULL, NULL); ++ ++ /* Set inactivity timer */ ++ timeout = config_getint(IMAPOPT_TIMEOUT); ++ if (timeout < 30) timeout = 30; ++ prot_settimeout(imapd_in, timeout*60); ++ prot_setflushonread(imapd_in, imapd_out); ++ ++ /* we were connected on imaps port so we should do ++ TLS negotiation immediately */ ++ if (imaps == 1) cmd_starttls(NULL, 1); ++ ++ snmp_increment(TOTAL_CONNECTIONS, 1); ++ snmp_increment(ACTIVE_CONNECTIONS, 1); ++ ++ cmdloop(); ++ ++ /* LOGOUT executed */ ++ prot_flush(imapd_out); ++ snmp_increment(ACTIVE_CONNECTIONS, -1); ++ ++ /* cleanup */ ++ imapd_reset(); ++ ++ return 0; ++} ++ ++/* Called by service API to shut down the service */ ++void service_abort(int error) ++{ ++ shut_down(error); ++} ++ ++/* ++ * found a motd file; spit out message and return ++ */ ++void motd_file(fd) ++int fd; ++{ ++ struct protstream *motd_in; ++ char buf[1024]; ++ char *p; ++ ++ motd_in = prot_new(fd, 0); ++ ++ prot_fgets(buf, sizeof(buf), motd_in); ++ if ((p = strchr(buf, '\r'))!=NULL) *p = 0; ++ if ((p = strchr(buf, '\n'))!=NULL) *p = 0; ++ ++ for(p = buf; *p == '['; p++); /* can't have [ be first char, sigh */ ++ prot_printf(imapd_out, "* OK [ALERT] %s\r\n", p); ++} ++ ++/* ++ * Cleanly shut down and exit ++ */ ++void shut_down(int code) __attribute__((noreturn)); ++void shut_down(int code) ++{ ++ int i; ++ ++ proc_cleanup(); ++ ++ i = 0; ++ while (backend_cached && backend_cached[i]) { ++ proxy_downserver(backend_cached[i]); ++ if (backend_cached[i]->last_result.s) { ++ free(backend_cached[i]->last_result.s); ++ } ++ free(backend_cached[i]); ++ i++; ++ } ++ if (backend_cached) free(backend_cached); ++ ++ if (imapd_mailbox) { ++ index_closemailbox(imapd_mailbox); ++ mailbox_close(imapd_mailbox); ++ } ++ seen_done(); ++ mboxkey_done(); ++ mboxlist_close(); ++ mboxlist_done(); ++ ++ quotadb_close(); ++ quotadb_done(); ++ ++ annotatemore_close(); ++ annotatemore_done(); ++ ++ if (imapd_in) { ++ /* Flush the incoming buffer */ ++ prot_NONBLOCK(imapd_in); ++ prot_fill(imapd_in); ++ ++ prot_free(imapd_in); ++ } ++ ++ if (imapd_out) { ++ /* Flush the outgoing buffer */ ++ prot_flush(imapd_out); ++ prot_free(imapd_out); ++ ++ /* one less active connection */ ++ snmp_increment(ACTIVE_CONNECTIONS, -1); ++ } ++ ++ if (protin) protgroup_free(protin); ++ ++#ifdef HAVE_SSL ++ tls_shutdown_serverengine(); ++#endif ++ ++ cyrus_done(); ++ ++ exit(code); ++} ++ ++void fatal(const char *s, int code) ++{ ++ static int recurse_code = 0; ++ ++ if (recurse_code) { ++ /* We were called recursively. Just give up */ ++ proc_cleanup(); ++ snmp_increment(ACTIVE_CONNECTIONS, -1); ++ exit(recurse_code); ++ } ++ recurse_code = code; ++ if (imapd_out) { ++ prot_printf(imapd_out, "* BYE Fatal error: %s\r\n", s); ++ prot_flush(imapd_out); ++ } ++ if (stage) { ++ /* Cleanup the stage(s) */ ++ while (numstage) { ++ struct appendstage *curstage = stage[--numstage]; ++ ++ append_removestage(curstage->stage); ++ while (curstage->nflags--) { ++ free(curstage->flag[curstage->nflags]); ++ } ++ if (curstage->flag) free((char *) curstage->flag); ++ free(curstage); ++ } ++ free(stage); ++ } ++ ++ syslog(LOG_ERR, "Fatal error: %s", s); ++ shut_down(code); ++} ++ ++/* ++ * Check the currently selected mailbox for updates. ++ * ++ * 'be' is the backend (if any) that we just proxied a command to. ++ */ ++static void imapd_check(struct backend *be, int usinguid, int checkseen) ++{ ++ if (backend_current && backend_current != be) { ++ /* remote mailbox */ ++ char mytag[128]; ++ ++ proxy_gentag(mytag, sizeof(mytag)); ++ ++ prot_printf(backend_current->out, "%s Noop\r\n", mytag); ++ pipe_until_tag(backend_current, mytag, 0); ++ } ++ else if (imapd_mailbox) { ++ /* local mailbox */ ++ index_check(imapd_mailbox, usinguid, checkseen); ++ } ++} ++ ++/* ++ * Top-level command loop parsing ++ */ ++void cmdloop() ++{ ++ int fd; ++ char motdfilename[1024]; ++ int c; ++ int ret; ++ int usinguid, havepartition, havenamespace, recursive; ++ static struct buf tag, cmd, arg1, arg2, arg3, arg4; ++ char *p, shut[1024]; ++ const char *err; ++ ++ prot_printf(imapd_out, ++ "* OK %s Cyrus IMAP4 %s%s server ready\r\n", config_servername, ++ config_mupdate_server ? "(Murder) " : "", CYRUS_VERSION); ++ ++ ret = snprintf(motdfilename, sizeof(motdfilename), "%s/msg/motd", ++ config_dir); ++ ++ if(ret < 0 || ret >= sizeof(motdfilename)) { ++ fatal("motdfilename buffer too small (configdirectory too long)", ++ EC_CONFIG); ++ } ++ ++ if ((fd = open(motdfilename, O_RDONLY, 0)) != -1) { ++ motd_file(fd); ++ close(fd); ++ } ++ ++ for (;;) { ++ /* Flush any buffered output */ ++ prot_flush(imapd_out); ++ if (backend_current) prot_flush(backend_current->out); ++ ++ /* Check for shutdown file */ ++ if ( !imapd_userisadmin && imapd_userid ++ && shutdown_file(shut, sizeof(shut))) { ++ for (p = shut; *p == '['; p++); /* can't have [ be first char */ ++ prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p); ++ shut_down(0); ++ } ++ ++ signals_poll(); ++ ++ if (!proxy_check_input(protin, imapd_in, imapd_out, ++ backend_current ? backend_current->in : NULL, ++ NULL, 0)) { ++ /* No input from client */ ++ continue; ++ } ++ ++ /* Parse tag */ ++ c = getword(imapd_in, &tag); ++ if (c == EOF) { ++ if ((err = prot_error(imapd_in))!=NULL ++ && strcmp(err, PROT_EOF_STRING)) { ++ syslog(LOG_WARNING, "%s, closing connection", err); ++ prot_printf(imapd_out, "* BYE %s\r\n", err); ++ } ++ return; ++ } ++ if (c != ' ' || !imparse_isatom(tag.s) || (tag.s[0] == '*' && !tag.s[1])) { ++ prot_printf(imapd_out, "* BAD Invalid tag\r\n"); ++ eatline(imapd_in, c); ++ continue; ++ } ++ ++ /* Parse command name */ ++ c = getword(imapd_in, &cmd); ++ if (!cmd.s[0]) { ++ prot_printf(imapd_out, "%s BAD Null command\r\n", tag.s); ++ eatline(imapd_in, c); ++ continue; ++ } ++ if (islower((unsigned char) cmd.s[0])) ++ cmd.s[0] = toupper((unsigned char) cmd.s[0]); ++ for (p = &cmd.s[1]; *p; p++) { ++ if (isupper((unsigned char) *p)) *p = tolower((unsigned char) *p); ++ } ++ ++ /* if we need to force a kick, do so */ ++ if (referral_kick) { ++ kick_mupdate(); ++ referral_kick = 0; ++ } ++ ++ if (plaintextloginalert) { ++ prot_printf(imapd_out, "* OK [ALERT] %s\r\n", ++ plaintextloginalert); ++ plaintextloginalert = NULL; ++ } ++ ++ /* Only Authenticate/Login/Logout/Noop/Capability/Id/Starttls ++ allowed when not logged in */ ++ if (!imapd_userid && !strchr("ALNCIS", cmd.s[0])) goto nologin; ++ ++ /* note that about half the commands (the common ones that don't ++ hit the mailboxes file) now close the mailboxes file just in ++ case it was open. */ ++ switch (cmd.s[0]) { ++ case 'A': ++ if (!strcmp(cmd.s, "Authenticate")) { ++ int haveinitresp = 0; ++ ++ if (c != ' ') goto missingargs; ++ c = getword(imapd_in, &arg1); ++ if (!imparse_isatom(arg1.s)) { ++ prot_printf(imapd_out, "%s BAD Invalid authenticate mechanism\r\n", tag.s); ++ eatline(imapd_in, c); ++ continue; ++ } ++ if (c == ' ') { ++ haveinitresp = 1; ++ c = getword(imapd_in, &arg2); ++ if (c == EOF) goto missingargs; ++ } ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ if (imapd_userid) { ++ prot_printf(imapd_out, "%s BAD Already authenticated\r\n", tag.s); ++ continue; ++ } ++ cmd_authenticate(tag.s, arg1.s, haveinitresp ? arg2.s : NULL); ++ ++ snmp_increment(AUTHENTICATE_COUNT, 1); ++ } ++ else if (!imapd_userid) goto nologin; ++ else if (!strcmp(cmd.s, "Append")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ ++ cmd_append(tag.s, arg1.s, NULL); ++ ++ snmp_increment(APPEND_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'B': ++ if (!strcmp(cmd.s, "Bboard")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_select(tag.s, cmd.s, arg1.s); ++ ++ snmp_increment(BBOARD_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'C': ++ if (!strcmp(cmd.s, "Capability")) { ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_capability(tag.s); ++ ++ snmp_increment(CAPABILITY_COUNT, 1); ++ } ++ else if (!imapd_userid) goto nologin; ++ else if (!strcmp(cmd.s, "Check")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_noop(tag.s, cmd.s); ++ ++ snmp_increment(CHECK_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Copy")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ usinguid = 0; ++ if (c != ' ') goto missingargs; ++ copy: ++ c = getword(imapd_in, &arg1); ++ if (c == '\r') goto missingargs; ++ if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_copy(tag.s, arg1.s, arg2.s, usinguid); ++ ++ snmp_increment(COPY_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Create")) { ++ havepartition = 0; ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == ' ') { ++ havepartition = 1; ++ c = getword(imapd_in, &arg2); ++ if (!imparse_isatom(arg2.s)) goto badpartition; ++ } ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_create(tag.s, arg1.s, havepartition ? arg2.s : 0, 0); ++ ++ snmp_increment(CREATE_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Close")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_close(tag.s); ++ ++ snmp_increment(CLOSE_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'D': ++ if (!strcmp(cmd.s, "Delete")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_delete(tag.s, arg1.s, 0, 0); ++ ++ snmp_increment(DELETE_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Deleteacl")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_setacl(tag.s, arg1.s, arg2.s, NULL); ++ ++ snmp_increment(DELETEACL_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Dump")) { ++ int uid_start = 0; ++ ++ if(c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if(c == ' ') { ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if(!imparse_isnumber(arg2.s)) goto extraargs; ++ uid_start = atoi(arg2.s); ++ } ++ ++ if(c == '\r') c = prot_getc(imapd_in); ++ if(c != '\n') goto extraargs; ++ ++ cmd_dump(tag.s, arg1.s, uid_start); ++ /* snmp_increment(DUMP_COUNT, 1);*/ ++ } ++ else goto badcmd; ++ break; ++ ++ case 'E': ++ if (!strcmp(cmd.s, "Expunge")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_expunge(tag.s, 0); ++ ++ snmp_increment(EXPUNGE_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Examine")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_select(tag.s, cmd.s, arg1.s); ++ ++ snmp_increment(EXAMINE_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'F': ++ if (!strcmp(cmd.s, "Fetch")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ usinguid = 0; ++ if (c != ' ') goto missingargs; ++ fetch: ++ c = getword(imapd_in, &arg1); ++ if (c == '\r') goto missingargs; ++ if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence; ++ ++ cmd_fetch(tag.s, arg1.s, usinguid); ++ ++ snmp_increment(FETCH_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Find")) { ++ c = getword(imapd_in, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_find(tag.s, arg1.s, arg2.s); ++ ++ snmp_increment(FIND_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'G': ++ if (!strcmp(cmd.s, "Getacl")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_getacl(tag.s, arg1.s); ++ ++ snmp_increment(GETACL_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Getannotation")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ ++ cmd_getannotation(tag.s, arg1.s); ++ ++ snmp_increment(GETANNOTATION_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Getquota")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_getquota(tag.s, arg1.s); ++ ++ snmp_increment(GETQUOTA_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Getquotaroot")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_getquotaroot(tag.s, arg1.s); ++ ++ snmp_increment(GETQUOTAROOT_COUNT, 1); ++ } ++#ifdef HAVE_SSL ++ else if (!strcmp(cmd.s, "Genurlauth")) { ++ if (c != ' ') goto missingargs; ++ ++ cmd_genurlauth(tag.s); ++ /* snmp_increment(GENURLAUTH_COUNT, 1);*/ ++ } ++#endif ++ else goto badcmd; ++ break; ++ ++ case 'I': ++ if (!strcmp(cmd.s, "Id")) { ++ if (c != ' ') goto missingargs; ++ cmd_id(tag.s); ++ ++ snmp_increment(ID_COUNT, 1); ++ } ++ else if (!imapd_userid) goto nologin; ++ else if (!strcmp(cmd.s, "Idle") && idle_enabled()) { ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_idle(tag.s); ++ ++ snmp_increment(IDLE_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'L': ++ if (!strcmp(cmd.s, "Login")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if(c != ' ') goto missingargs; ++ ++ cmd_login(tag.s, arg1.s); ++ ++ snmp_increment(LOGIN_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Logout")) { ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ snmp_increment(LOGOUT_COUNT, 1); ++ ++ /* force any responses from our selected backend */ ++ if (backend_current) imapd_check(NULL, 0, 0); ++ ++ prot_printf(imapd_out, "* BYE %s\r\n", ++ error_message(IMAP_BYE_LOGOUT)); ++ prot_printf(imapd_out, "%s OK %s\r\n", tag.s, ++ error_message(IMAP_OK_COMPLETED)); ++ return; ++ } ++ else if (!imapd_userid) goto nologin; ++ else if (!strcmp(cmd.s, "List")) { ++ int listopts = LIST_CHILDREN; ++#ifdef ENABLE_LISTEXT ++ /* Check for and parse LISTEXT options */ ++ c = prot_getc(imapd_in); ++ if (c == '(') { ++ c = getlistopts(tag.s, &listopts); ++ if (c == EOF) { ++ eatline(imapd_in, c); ++ continue; ++ } ++ } ++ else ++ prot_ungetc(c, imapd_in); ++#endif /* ENABLE_LISTEXT */ ++ if (imapd_magicplus) listopts += LIST_SUBSCRIBED; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_list(tag.s, listopts, arg1.s, arg2.s); ++ ++ snmp_increment(LIST_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Lsub")) { ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_list(tag.s, LIST_LSUB | LIST_CHILDREN, arg1.s, arg2.s); ++ ++ snmp_increment(LSUB_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Listrights")) { ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_listrights(tag.s, arg1.s, arg2.s); ++ ++ snmp_increment(LISTRIGHTS_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Localappend")) { ++ /* create a local-only mailbox */ ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c != ' ') goto missingargs; ++ ++ cmd_append(tag.s, arg1.s, *arg2.s ? arg2.s : NULL); ++ ++ snmp_increment(APPEND_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Localcreate")) { ++ /* create a local-only mailbox */ ++ havepartition = 0; ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == ' ') { ++ havepartition = 1; ++ c = getword(imapd_in, &arg2); ++ if (!imparse_isatom(arg2.s)) goto badpartition; ++ } ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_create(tag.s, arg1.s, havepartition ? arg2.s : NULL, 1); ++ ++ /* xxxx snmp_increment(CREATE_COUNT, 1); */ ++ } ++ else if (!strcmp(cmd.s, "Localdelete")) { ++ /* delete a mailbox locally only */ ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_delete(tag.s, arg1.s, 1, 1); ++ ++ /* xxxx snmp_increment(DELETE_COUNT, 1); */ ++ } ++ else goto badcmd; ++ break; ++ ++ case 'M': ++ if (!strcmp(cmd.s, "Myrights")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_myrights(tag.s, arg1.s); ++ ++ /* xxxx snmp_increment(MYRIGHTS_COUNT, 1); */ ++ } ++ else if (!strcmp(cmd.s, "Mupdatepush")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if(c == EOF) goto missingargs; ++ if(c == '\r') c = prot_getc(imapd_in); ++ if(c != '\n') goto extraargs; ++ cmd_mupdatepush(tag.s, arg1.s); ++ ++ /* xxxx snmp_increment(MUPDATEPUSH_COUNT, 1); */ ++ } else goto badcmd; ++ break; ++ ++ case 'N': ++ if (!strcmp(cmd.s, "Noop")) { ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_noop(tag.s, cmd.s); ++ ++ /* xxxx snmp_increment(NOOP_COUNT, 1); */ ++ } ++#ifdef ENABLE_X_NETSCAPE_HACK ++ else if (!strcmp(cmd.s, "Netscape")) { ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_netscrape(tag.s); ++ } ++#endif ++ else if (!imapd_userid) goto nologin; ++ else if (!strcmp(cmd.s, "Namespace")) { ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_namespace(tag.s); ++ ++ /* xxxx snmp_increment(NAMESPACE_COUNT, 1); */ ++ } ++ else goto badcmd; ++ break; ++ ++ case 'P': ++ if (!strcmp(cmd.s, "Partial")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ if (c != ' ') goto missingargs; ++ c = getword(imapd_in, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getword(imapd_in, &arg2); ++ if (c != ' ') goto missingargs; ++ c = getword(imapd_in, &arg3); ++ if (c != ' ') goto missingargs; ++ c = getword(imapd_in, &arg4); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_partial(tag.s, arg1.s, arg2.s, arg3.s, arg4.s); ++ ++ /* xxxx snmp_increment(PARTIAL_COUNT, 1); */ ++ } ++ else goto badcmd; ++ break; ++ ++ case 'R': ++ if (!strcmp(cmd.s, "Rename")) { ++ havepartition = 0; ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == EOF) goto missingargs; ++ if (c == ' ') { ++ havepartition = 1; ++ c = getword(imapd_in, &arg3); ++ if (!imparse_isatom(arg3.s)) goto badpartition; ++ } ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_rename(tag.s, arg1.s, arg2.s, havepartition ? arg3.s : 0); ++ ++ /* xxxx snmp_increment(RENAME_COUNT, 1); */ ++ } else if(!strcmp(cmd.s, "Reconstruct")) { ++ recursive = 0; ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if(c == ' ') { ++ /* Optional RECURSEIVE argument */ ++ c = getword(imapd_in, &arg2); ++ if(!imparse_isatom(arg2.s)) ++ goto extraargs; ++ else if(!strcasecmp(arg2.s, "RECURSIVE")) ++ recursive = 1; ++ else ++ goto extraargs; ++ } ++ if(c == '\r') c = prot_getc(imapd_in); ++ if(c != '\n') goto extraargs; ++ cmd_reconstruct(tag.s, arg1.s, recursive); ++ ++ /* snmp_increment(RECONSTRUCT_COUNT, 1); */ ++ } ++ else if (!strcmp(cmd.s, "Rlist")) { ++ supports_referrals = !disable_referrals; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_list(tag.s, LIST_CHILDREN | LIST_REMOTE, arg1.s, arg2.s); ++ ++/* snmp_increment(LIST_COUNT, 1); */ ++ } ++ else if (!strcmp(cmd.s, "Rlsub")) { ++ supports_referrals = !disable_referrals; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_list(tag.s, LIST_LSUB | LIST_CHILDREN | LIST_REMOTE, ++ arg1.s, arg2.s); ++/* snmp_increment(LSUB_COUNT, 1); */ ++ } ++#ifdef HAVE_SSL ++ else if (!strcmp(cmd.s, "Resetkey")) { ++ int have_mbox = 0, have_mech = 0; ++ ++ if (c == ' ') { ++ have_mbox = 1; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == ' ') { ++ have_mech = 1; ++ c = getword(imapd_in, &arg2); ++ } ++ } ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_resetkey(tag.s, have_mbox ? arg1.s : 0, ++ have_mech ? arg2.s : 0); ++ /* snmp_increment(RESETKEY_COUNT, 1);*/ ++ } ++#endif ++ else goto badcmd; ++ break; ++ ++ case 'S': ++ if (!strcmp(cmd.s, "Starttls")) { ++ if (!tls_enabled()) { ++ /* we don't support starttls */ ++ goto badcmd; ++ } ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ /* if we've already done SASL fail */ ++ if (imapd_userid != NULL) { ++ prot_printf(imapd_out, ++ "%s BAD Can't Starttls after authentication\r\n", tag.s); ++ continue; ++ } ++ ++ /* check if already did a successful tls */ ++ if (imapd_starttls_done == 1) { ++ prot_printf(imapd_out, ++ "%s BAD Already did a successful Starttls\r\n", ++ tag.s); ++ continue; ++ } ++ cmd_starttls(tag.s, 0); ++ ++ snmp_increment(STARTTLS_COUNT, 1); ++ continue; ++ } ++ if (!imapd_userid) { ++ goto nologin; ++ } else if (!strcmp(cmd.s, "Store")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ usinguid = 0; ++ if (c != ' ') goto missingargs; ++ store: ++ c = getword(imapd_in, &arg1); ++ if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence; ++ c = getword(imapd_in, &arg2); ++ if (c != ' ') goto missingargs; ++ ++ cmd_store(tag.s, arg1.s, arg2.s, usinguid); ++ ++ snmp_increment(STORE_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Select")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_select(tag.s, cmd.s, arg1.s); ++ ++ snmp_increment(SELECT_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Search")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ usinguid = 0; ++ if (c != ' ') goto missingargs; ++ search: ++ ++ cmd_search(tag.s, usinguid); ++ ++ snmp_increment(SEARCH_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Subscribe")) { ++ if (c != ' ') goto missingargs; ++ havenamespace = 0; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == ' ') { ++ havenamespace = 1; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ } ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ if (havenamespace) { ++ cmd_changesub(tag.s, arg1.s, arg2.s, 1); ++ } ++ else { ++ cmd_changesub(tag.s, (char *)0, arg1.s, 1); ++ } ++ snmp_increment(SUBSCRIBE_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Setacl")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg3); ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_setacl(tag.s, arg1.s, arg2.s, arg3.s); ++ ++ snmp_increment(SETACL_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Setannotation")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ ++ cmd_setannotation(tag.s, arg1.s); ++ ++ snmp_increment(SETANNOTATION_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Setquota")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ cmd_setquota(tag.s, arg1.s); ++ ++ snmp_increment(SETQUOTA_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Sort")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ usinguid = 0; ++ if (c != ' ') goto missingargs; ++ sort: ++ cmd_sort(tag.s, usinguid); ++ ++ snmp_increment(SORT_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Status")) { ++ if (c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') goto missingargs; ++ cmd_status(tag.s, arg1.s); ++ ++ snmp_increment(STATUS_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'T': ++ if (!strcmp(cmd.s, "Thread")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ usinguid = 0; ++ if (c != ' ') goto missingargs; ++ thread: ++ cmd_thread(tag.s, usinguid); ++ ++ snmp_increment(THREAD_COUNT, 1); ++ } ++ else goto badcmd; ++ break; ++ ++ case 'U': ++ if (!strcmp(cmd.s, "Uid")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ usinguid = 1; ++ if (c != ' ') goto missingargs; ++ c = getword(imapd_in, &arg1); ++ if (c != ' ') goto missingargs; ++ lcase(arg1.s); ++ if (!strcmp(arg1.s, "fetch")) { ++ goto fetch; ++ } ++ else if (!strcmp(arg1.s, "store")) { ++ goto store; ++ } ++ else if (!strcmp(arg1.s, "search")) { ++ goto search; ++ } ++ else if (!strcmp(arg1.s, "sort")) { ++ goto sort; ++ } ++ else if (!strcmp(arg1.s, "thread")) { ++ goto thread; ++ } ++ else if (!strcmp(arg1.s, "copy")) { ++ goto copy; ++ } ++ else if (!strcmp(arg1.s, "expunge")) { ++ c = getword(imapd_in, &arg1); ++ if (!imparse_issequence(arg1.s)) goto badsequence; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_expunge(tag.s, arg1.s); ++ ++ snmp_increment(EXPUNGE_COUNT, 1); ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Unrecognized UID subcommand\r\n", tag.s); ++ eatline(imapd_in, c); ++ } ++ } ++ else if (!strcmp(cmd.s, "Unsubscribe")) { ++ if (c != ' ') goto missingargs; ++ havenamespace = 0; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c == ' ') { ++ havenamespace = 1; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ } ++ if (c == EOF) goto missingargs; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ if (havenamespace) { ++ cmd_changesub(tag.s, arg1.s, arg2.s, 0); ++ } ++ else { ++ cmd_changesub(tag.s, (char *)0, arg1.s, 0); ++ } ++ ++ snmp_increment(UNSUBSCRIBE_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Unselect")) { ++ if (!imapd_mailbox && !backend_current) goto nomailbox; ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ cmd_unselect(tag.s); ++ ++ snmp_increment(UNSELECT_COUNT, 1); ++ } ++ else if (!strcmp(cmd.s, "Undump")) { ++ if(c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ ++ /* we want to get a list at this point */ ++ if(c != ' ') goto missingargs; ++ ++ cmd_undump(tag.s, arg1.s); ++ /* snmp_increment(UNDUMP_COUNT, 1);*/ ++ } ++#ifdef HAVE_SSL ++ else if (!strcmp(cmd.s, "Urlfetch")) { ++ if (c != ' ') goto missingargs; ++ ++ cmd_urlfetch(tag.s); ++ /* snmp_increment(URLFETCH_COUNT, 1);*/ ++ } ++#endif ++ else goto badcmd; ++ break; ++ ++ case 'X': ++ if (!strcmp(cmd.s, "Xfer")) { ++ int havepartition = 0; ++ ++ /* Mailbox */ ++ if(c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg1); ++ ++ /* Dest Server */ ++ if(c != ' ') goto missingargs; ++ c = getastring(imapd_in, imapd_out, &arg2); ++ ++ if(c == ' ') { ++ /* Dest Partition */ ++ c = getastring(imapd_in, imapd_out, &arg3); ++ if (!imparse_isatom(arg3.s)) goto badpartition; ++ havepartition = 1; ++ } ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') goto extraargs; ++ ++ cmd_xfer(tag.s, arg1.s, arg2.s, ++ (havepartition ? arg3.s : NULL)); ++ /* snmp_increment(XFER_COUNT, 1);*/ ++ } ++ else goto badcmd; ++ break; ++ ++ default: ++ badcmd: ++ prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag.s); ++ eatline(imapd_in, c); ++ } ++ ++ continue; ++ ++ nologin: ++ prot_printf(imapd_out, "%s BAD Please login first\r\n", tag.s); ++ eatline(imapd_in, c); ++ continue; ++ ++ nomailbox: ++ prot_printf(imapd_out, "%s BAD Please select a mailbox first\r\n", tag.s); ++ eatline(imapd_in, c); ++ continue; ++ ++ missingargs: ++ prot_printf(imapd_out, "%s BAD Missing required argument to %s\r\n", tag.s, cmd.s); ++ eatline(imapd_in, c); ++ continue; ++ ++ extraargs: ++ prot_printf(imapd_out, "%s BAD Unexpected extra arguments to %s\r\n", tag.s, cmd.s); ++ eatline(imapd_in, c); ++ continue; ++ ++ badsequence: ++ prot_printf(imapd_out, "%s BAD Invalid sequence in %s\r\n", tag.s, cmd.s); ++ eatline(imapd_in, c); ++ continue; ++ ++ badpartition: ++ prot_printf(imapd_out, "%s BAD Invalid partition name in %s\r\n", ++ tag.s, cmd.s); ++ eatline(imapd_in, c); ++ continue; ++ } ++} ++ ++/* ++ * Perform a LOGIN command ++ */ ++void cmd_login(char *tag, char *user) ++{ ++ char userbuf[MAX_MAILBOX_NAME+1]; ++ unsigned userlen; ++ const char *canon_user = userbuf; ++ char c; ++ struct buf passwdbuf; ++ char *passwd; ++ const char *reply = NULL; ++ int r; ++ ++ if (imapd_userid) { ++ eatline(imapd_in, ' '); ++ prot_printf(imapd_out, "%s BAD Already logged in\r\n", tag); ++ return; ++ } ++ ++ r = imapd_canon_user(imapd_saslconn, NULL, user, 0, ++ SASL_CU_AUTHID | SASL_CU_AUTHZID, NULL, ++ userbuf, sizeof(userbuf), &userlen); ++ ++ if (r) { ++ syslog(LOG_NOTICE, "badlogin: %s plaintext %s invalid user", ++ imapd_clienthost, beautify_string(user)); ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, ++ error_message(IMAP_INVALID_USER)); ++ return; ++ } ++ ++ /* possibly disallow login */ ++ if ((imapd_starttls_done == 0) && ++ (config_getswitch(IMAPOPT_ALLOWPLAINTEXT) == 0) && ++ !is_userid_anonymous(canon_user)) { ++ eatline(imapd_in, ' '); ++ prot_printf(imapd_out, "%s NO Login only available under a layer\r\n", ++ tag); ++ return; ++ } ++ ++ memset(&passwdbuf,0,sizeof(struct buf)); ++ c = getastring(imapd_in, imapd_out, &passwdbuf); ++ ++ if(c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ freebuf(&passwdbuf); ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to LOGIN\r\n", ++ tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ passwd = passwdbuf.s; ++ ++ if (is_userid_anonymous(canon_user)) { ++ if (config_getswitch(IMAPOPT_ALLOWANONYMOUSLOGIN)) { ++ passwd = beautify_string(passwd); ++ if (strlen(passwd) > 500) passwd[500] = '\0'; ++ syslog(LOG_NOTICE, "login: %s anonymous %s", ++ imapd_clienthost, passwd); ++ reply = "Anonymous access granted"; ++ imapd_userid = xstrdup("anonymous"); ++ } ++ else { ++ syslog(LOG_NOTICE, "badlogin: %s anonymous login refused", ++ imapd_clienthost); ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, ++ error_message(IMAP_ANONYMOUS_NOT_PERMITTED)); ++ freebuf(&passwdbuf); ++ return; ++ } ++ } ++ else if ((r = sasl_checkpass(imapd_saslconn, ++ canon_user, ++ strlen(canon_user), ++ passwd, ++ strlen(passwd))) != SASL_OK) { ++ syslog(LOG_NOTICE, "badlogin: %s plaintext %s %s", ++ imapd_clienthost, canon_user, sasl_errdetail(imapd_saslconn)); ++ ++ sleep(3); ++ ++ if ((reply = sasl_errstring(r, NULL, NULL)) != NULL) { ++ prot_printf(imapd_out, "%s NO Login failed: %s\r\n", tag, reply); ++ } else { ++ prot_printf(imapd_out, "%s NO Login failed: %d\r\n", tag, r); ++ } ++ ++ snmp_increment_args(AUTHENTICATION_NO, 1, ++ VARIABLE_AUTH, 0 /* hash_simple("LOGIN") */, ++ VARIABLE_LISTEND); ++ freebuf(&passwdbuf); ++ return; ++ } ++ else { ++ r = sasl_getprop(imapd_saslconn, SASL_USERNAME, ++ (const void **) &canon_user); ++ ++ if(r != SASL_OK) { ++ if ((reply = sasl_errstring(r, NULL, NULL)) != NULL) { ++ prot_printf(imapd_out, "%s NO Login failed: %s\r\n", ++ tag, reply); ++ } else { ++ prot_printf(imapd_out, "%s NO Login failed: %d\r\n", tag, r); ++ } ++ ++ snmp_increment_args(AUTHENTICATION_NO, 1, ++ VARIABLE_AUTH, 0 /* hash_simple("LOGIN") */, ++ VARIABLE_LISTEND); ++ freebuf(&passwdbuf); ++ return; ++ } ++ ++ reply = "User logged in"; ++ imapd_userid = xstrdup(canon_user); ++ snmp_increment_args(AUTHENTICATION_YES, 1, ++ VARIABLE_AUTH, 0 /*hash_simple("LOGIN") */, ++ VARIABLE_LISTEND); ++ syslog(LOG_NOTICE, "login: %s %s%s plaintext%s %s", imapd_clienthost, ++ imapd_userid, imapd_magicplus ? imapd_magicplus : "", ++ imapd_starttls_done ? "+TLS" : "", ++ reply ? reply : ""); ++ ++ /* Apply penalty only if not under layer */ ++ if (!imapd_starttls_done) { ++ int plaintextloginpause = config_getint(IMAPOPT_PLAINTEXTLOGINPAUSE); ++ if (plaintextloginpause) { ++ sleep(plaintextloginpause); ++ } ++ ++ /* Fetch plaintext login nag message */ ++ plaintextloginalert = config_getstring(IMAPOPT_PLAINTEXTLOGINALERT); ++ } ++ } ++ ++ imapd_authstate = auth_newstate(imapd_userid); ++ ++ imapd_userisadmin = global_authisa(imapd_authstate, IMAPOPT_ADMINS); ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, reply); ++ ++ /* Create telemetry log */ ++ imapd_logfd = telemetry_log(imapd_userid, imapd_in, imapd_out, 0); ++ ++ /* Set namespace */ ++ if ((r = mboxname_init_namespace(&imapd_namespace, ++ imapd_userisadmin || imapd_userisproxyadmin)) != 0) { ++ syslog(LOG_ERR, error_message(r)); ++ fatal(error_message(r), EC_CONFIG); ++ } ++ ++ /* Make a copy of the external userid for use in proxying */ ++ proxy_userid = xstrdup(imapd_userid); ++ ++ /* Translate any separators in userid */ ++ mboxname_hiersep_tointernal(&imapd_namespace, imapd_userid, ++ config_virtdomains ? ++ strcspn(imapd_userid, "@") : 0); ++ ++ freebuf(&passwdbuf); ++ return; ++} ++ ++/* ++ * Perform an AUTHENTICATE command ++ */ ++void ++cmd_authenticate(char *tag, char *authtype, char *resp) ++{ ++ int sasl_result; ++ ++ const int *ssfp; ++ char *ssfmsg=NULL; ++ ++ const char *canon_user; ++ ++ int r; ++ ++ r = saslserver(imapd_saslconn, authtype, resp, "", "+ ", "", ++ imapd_in, imapd_out, &sasl_result, NULL); ++ ++ if (r) { ++ const char *errorstring = NULL; ++ ++ switch (r) { ++ case IMAP_SASL_CANCEL: ++ prot_printf(imapd_out, ++ "%s BAD Client canceled authentication\r\n", tag); ++ break; ++ case IMAP_SASL_PROTERR: ++ errorstring = prot_error(imapd_in); ++ ++ prot_printf(imapd_out, ++ "%s NO Error reading client response: %s\r\n", ++ tag, errorstring ? errorstring : ""); ++ break; ++ default: ++ /* failed authentication */ ++ errorstring = sasl_errstring(sasl_result, NULL, NULL); ++ ++ syslog(LOG_NOTICE, "badlogin: %s %s [%s]", ++ imapd_clienthost, authtype, sasl_errdetail(imapd_saslconn)); ++ ++ snmp_increment_args(AUTHENTICATION_NO, 1, ++ VARIABLE_AUTH, 0, /* hash_simple(authtype) */ ++ VARIABLE_LISTEND); ++ sleep(3); ++ ++ if (errorstring) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, errorstring); ++ } else { ++ prot_printf(imapd_out, "%s NO Error authenticating\r\n", tag); ++ } ++ } ++ ++ reset_saslconn(&imapd_saslconn); ++ return; ++ } ++ ++ /* successful authentication */ ++ ++ /* get the userid from SASL --- already canonicalized from ++ * mysasl_proxy_policy() ++ */ ++ sasl_result = sasl_getprop(imapd_saslconn, SASL_USERNAME, ++ (const void **) &canon_user); ++ if (sasl_result != SASL_OK) { ++ prot_printf(imapd_out, "%s NO weird SASL error %d SASL_USERNAME\r\n", ++ tag, sasl_result); ++ syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME", ++ sasl_result); ++ reset_saslconn(&imapd_saslconn); ++ return; ++ } ++ ++ /* If we're proxying, the authzid may contain a magic plus, ++ so re-canonify it */ ++ if (config_getswitch(IMAPOPT_IMAPMAGICPLUS) && strchr(canon_user, '+')) { ++ char userbuf[MAX_MAILBOX_NAME+1]; ++ unsigned userlen; ++ ++ sasl_result = imapd_canon_user(imapd_saslconn, NULL, canon_user, 0, ++ SASL_CU_AUTHID | SASL_CU_AUTHZID, ++ NULL, userbuf, sizeof(userbuf), &userlen); ++ if (sasl_result != SASL_OK) { ++ prot_printf(imapd_out, ++ "%s NO SASL canonification error %d\r\n", ++ tag, sasl_result); ++ reset_saslconn(&imapd_saslconn); ++ return; ++ } ++ ++ imapd_userid = xstrdup(userbuf); ++ } else { ++ imapd_userid = xstrdup(canon_user); ++ } ++ ++ proc_register("imapd", imapd_clienthost, imapd_userid, (char *)0); ++ ++ syslog(LOG_NOTICE, "login: %s %s%s %s%s %s", imapd_clienthost, ++ imapd_userid, imapd_magicplus ? imapd_magicplus : "", ++ authtype, imapd_starttls_done ? "+TLS" : "", "User logged in"); ++ ++ sasl_getprop(imapd_saslconn, SASL_SSF, (const void **) &ssfp); ++ ++ /* really, we should be doing a sasl_getprop on SASL_SSF_EXTERNAL, ++ but the current libsasl doesn't allow that. */ ++ if (imapd_starttls_done) { ++ switch(*ssfp) { ++ case 0: ssfmsg = "tls protection"; break; ++ case 1: ssfmsg = "tls plus integrity protection"; break; ++ default: ssfmsg = "tls plus privacy protection"; break; ++ } ++ } else { ++ switch(*ssfp) { ++ case 0: ssfmsg = "no protection"; break; ++ case 1: ssfmsg = "integrity protection"; break; ++ default: ssfmsg = "privacy protection"; break; ++ } ++ } ++ ++ snmp_increment_args(AUTHENTICATION_YES, 1, ++ VARIABLE_AUTH, 0, /* hash_simple(authtype) */ ++ VARIABLE_LISTEND); ++ ++ prot_printf(imapd_out, "%s OK Success (%s)\r\n", tag, ssfmsg); ++ ++ prot_setsasl(imapd_in, imapd_saslconn); ++ prot_setsasl(imapd_out, imapd_saslconn); ++ ++ /* Create telemetry log */ ++ imapd_logfd = telemetry_log(imapd_userid, imapd_in, imapd_out, 0); ++ ++ /* Set namespace */ ++ if ((r = mboxname_init_namespace(&imapd_namespace, ++ imapd_userisadmin || imapd_userisproxyadmin)) != 0) { ++ syslog(LOG_ERR, error_message(r)); ++ fatal(error_message(r), EC_CONFIG); ++ } ++ ++ /* Make a copy of the external userid for use in proxying */ ++ proxy_userid = xstrdup(imapd_userid); ++ ++ /* Translate any separators in userid */ ++ mboxname_hiersep_tointernal(&imapd_namespace, imapd_userid, ++ config_virtdomains ? ++ strcspn(imapd_userid, "@") : 0); ++ ++ return; ++} ++ ++/* ++ * Perform a NOOP command ++ */ ++void cmd_noop(char *tag, char *cmd) ++{ ++ if (backend_current) { ++ /* remote mailbox */ ++ prot_printf(backend_current->out, "%s %s\r\n", tag, cmd); ++ ++ return; ++ } ++ ++ if (imapd_mailbox) { ++ index_check(imapd_mailbox, 0, 1); ++ } ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Parse and perform an ID command. ++ * ++ * the command has been parsed up to the parameter list. ++ * ++ * we only allow one ID in non-authenticated state from a given client. ++ * we only allow MAXIDFAILED consecutive failed IDs from a given client. ++ * we only record MAXIDLOG ID responses from a given client. ++ */ ++void cmd_id(char *tag) ++{ ++ static int did_id = 0; ++ static int failed_id = 0; ++ static int logged_id = 0; ++ int error = 0; ++ int c = EOF, npair = 0; ++ static struct buf arg, field; ++ struct attvaluelist *params = 0; ++ ++ /* check if we've already had an ID in non-authenticated state */ ++ if (!imapd_userid && did_id) { ++ prot_printf(imapd_out, ++ "%s NO Only one Id allowed in non-authenticated state\r\n", ++ tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ /* check if we've had too many failed IDs in a row */ ++ if (failed_id >= MAXIDFAILED) { ++ prot_printf(imapd_out, "%s NO Too many (%u) invalid Id commands\r\n", ++ tag, failed_id); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ /* ok, accept parameter list */ ++ c = getword(imapd_in, &arg); ++ /* check for "NIL" or start of parameter list */ ++ if (strcasecmp(arg.s, "NIL") && c != '(') { ++ prot_printf(imapd_out, "%s BAD Invalid parameter list in Id\r\n", tag); ++ eatline(imapd_in, c); ++ failed_id++; ++ return; ++ } ++ ++ /* parse parameter list */ ++ if (c == '(') { ++ for (;;) { ++ if (c == ')') { ++ /* end of string/value pairs */ ++ break; ++ } ++ ++ /* get field name */ ++ c = getstring(imapd_in, imapd_out, &field); ++ if (c != ' ') { ++ prot_printf(imapd_out, ++ "%s BAD Invalid/missing field name in Id\r\n", ++ tag); ++ error = 1; ++ break; ++ } ++ ++ /* get field value */ ++ c = getnstring(imapd_in, imapd_out, &arg); ++ if (c != ' ' && c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Invalid/missing value in Id\r\n", ++ tag); ++ error = 1; ++ break; ++ } ++ ++ /* ok, we're anal, but we'll still process the ID command */ ++ if (strlen(field.s) > MAXIDFIELDLEN) { ++ prot_printf(imapd_out, ++ "%s BAD field longer than %u octets in Id\r\n", ++ tag, MAXIDFIELDLEN); ++ error = 1; ++ break; ++ } ++ if (strlen(arg.s) > MAXIDVALUELEN) { ++ prot_printf(imapd_out, ++ "%s BAD value longer than %u octets in Id\r\n", ++ tag, MAXIDVALUELEN); ++ error = 1; ++ break; ++ } ++ if (++npair > MAXIDPAIRS) { ++ prot_printf(imapd_out, ++ "%s BAD too many (%u) field-value pairs in ID\r\n", ++ tag, MAXIDPAIRS); ++ error = 1; ++ break; ++ } ++ ++ /* ok, we're happy enough */ ++ appendattvalue(¶ms, field.s, arg.s); ++ } ++ ++ if (error || c != ')') { ++ /* erp! */ ++ eatline(imapd_in, c); ++ freeattvalues(params); ++ failed_id++; ++ return; ++ } ++ c = prot_getc(imapd_in); ++ } ++ ++ /* check for CRLF */ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to Id\r\n", tag); ++ eatline(imapd_in, c); ++ freeattvalues(params); ++ failed_id++; ++ return; ++ } ++ ++ /* log the client's ID string. ++ eventually this should be a callback or something. */ ++ if (npair && logged_id < MAXIDLOG) { ++ char logbuf[MAXIDLOGLEN + 1] = ""; ++ struct attvaluelist *pptr; ++ ++ for (pptr = params; pptr; pptr = pptr->next) { ++ /* should we check for and format literals here ??? */ ++ snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf), ++ " \"%s\" ", pptr->attrib); ++ if (!strcmp(pptr->value, "NIL")) ++ snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf), ++ "NIL"); ++ else ++ snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf), ++ "\"%s\"", pptr->value); ++ } ++ ++ syslog(LOG_INFO, "client id:%s", logbuf); ++ ++ logged_id++; ++ } ++ ++ freeattvalues(params); ++ ++ /* spit out our ID string. ++ eventually this might be configurable. */ ++ if (config_getswitch(IMAPOPT_IMAPIDRESPONSE)) { ++ id_response(imapd_out); ++ prot_printf(imapd_out, ")\r\n"); ++ } ++ else ++ prot_printf(imapd_out, "* ID NIL\r\n"); ++ ++ imapd_check(NULL, 0, 0); ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ ++ failed_id = 0; ++ did_id = 1; ++} ++ ++/* ++ * Perform an IDLE command ++ */ ++void cmd_idle(char *tag) ++{ ++ int c = EOF; ++ static struct buf arg; ++ static int idle_period = -1; ++ ++ if (!backend_current) { /* Local mailbox */ ++ /* Setup for doing mailbox updates */ ++ if (!idle_init(idle_update)) { ++ prot_printf(imapd_out, ++ "%s NO cannot start idling\r\n", tag); ++ return; ++ } ++ ++ /* Tell client we are idling and waiting for end of command */ ++ prot_printf(imapd_out, "+ idling\r\n"); ++ prot_flush(imapd_out); ++ ++ /* Start doing mailbox updates */ ++ if (imapd_mailbox) index_check(imapd_mailbox, 0, 1); ++ idle_start(imapd_mailbox); ++ ++ /* Get continuation data */ ++ c = getword(imapd_in, &arg); ++ ++ /* Stop updates and do any necessary cleanup */ ++ idle_done(imapd_mailbox); ++ } ++ else { /* Remote mailbox */ ++ int done = 0, shutdown = 0; ++ char buf[2048]; ++ ++ /* get polling period */ ++ if (idle_period == -1) { ++ idle_period = config_getint(IMAPOPT_IMAPIDLEPOLL); ++ } ++ ++ if (CAPA(backend_current, CAPA_IDLE)) { ++ /* Start IDLE on backend */ ++ prot_printf(backend_current->out, "%s IDLE\r\n", tag); ++ if (!prot_fgets(buf, sizeof(buf), backend_current->in)) { ++ ++ /* If we received nothing from the backend, fail */ ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, ++ error_message(IMAP_SERVER_UNAVAILABLE)); ++ return; ++ } ++ if (buf[0] != '+') { ++ /* If we received anything but a continuation response, ++ spit out what we received and quit */ ++ prot_write(imapd_out, buf, strlen(buf)); ++ return; ++ } ++ } ++ ++ /* Tell client we are idling and waiting for end of command */ ++ prot_printf(imapd_out, "+ idling\r\n"); ++ prot_flush(imapd_out); ++ ++ /* Pipe updates to client while waiting for end of command */ ++ while (!done) { ++ /* Flush any buffered output */ ++ prot_flush(imapd_out); ++ ++ /* Check for shutdown file */ ++ if (!imapd_userisadmin && shutdown_file(buf, sizeof(buf))) { ++ shutdown = done = 1; ++ goto done; ++ } ++ ++ done = proxy_check_input(protin, imapd_in, imapd_out, ++ backend_current->in, NULL, idle_period); ++ ++ /* If not running IDLE on backend, poll the mailbox for updates */ ++ if (!CAPA(backend_current, CAPA_IDLE)) { ++ imapd_check(NULL, 0, 1); ++ } ++ } ++ ++ /* Get continuation data */ ++ c = getword(imapd_in, &arg); ++ ++ done: ++ if (CAPA(backend_current, CAPA_IDLE)) { ++ /* Either the client timed out, or ended the command. ++ In either case we're done, so terminate IDLE on backend */ ++ prot_printf(backend_current->out, "Done\r\n"); ++ pipe_until_tag(backend_current, tag, 0); ++ } ++ ++ if (shutdown) { ++ char *p; ++ ++ for (p = buf; *p == '['; p++); /* can't have [ be first char */ ++ prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p); ++ shut_down(0); ++ } ++ } ++ ++ imapd_check(NULL, 0, 1); ++ ++ if (c != EOF) { ++ if (!strcasecmp(arg.s, "Done") && ++ (c = (c == '\r') ? prot_getc(imapd_in) : c) == '\n') { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++ else { ++ prot_printf(imapd_out, ++ "%s BAD Invalid Idle continuation\r\n", tag); ++ eatline(imapd_in, c); ++ } ++ } ++} ++ ++/* Send unsolicited untagged responses to the client */ ++void idle_update(idle_flags_t flags) ++{ ++ if ((flags & IDLE_MAILBOX) && imapd_mailbox) ++ index_check(imapd_mailbox, 0, 1); ++ ++ if (flags & IDLE_ALERT) { ++ char shut[1024]; ++ if (! imapd_userisadmin && shutdown_file(shut, sizeof(shut))) { ++ char *p; ++ for (p = shut; *p == '['; p++); /* can't have [ be first char */ ++ prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p); ++ shut_down(0); ++ } ++ } ++ ++ prot_flush(imapd_out); ++} ++ ++/* ++ * Perform a CAPABILITY command ++ */ ++void cmd_capability(char *tag) ++{ ++ const char *sasllist; /* the list of SASL mechanisms */ ++ int mechcount; ++ ++ imapd_check(NULL, 0, 0); ++ ++ prot_printf(imapd_out, "* CAPABILITY " CAPABILITY_STRING); ++ ++ if (idle_enabled()) { ++ prot_printf(imapd_out, " IDLE"); ++ } ++ ++ if (tls_enabled() && !imapd_starttls_done && !imapd_authstate) { ++ prot_printf(imapd_out, " STARTTLS"); ++ } ++ if (imapd_authstate || ++ (!imapd_starttls_done && !config_getswitch(IMAPOPT_ALLOWPLAINTEXT))) { ++ prot_printf(imapd_out, " LOGINDISABLED"); ++ } ++ ++ if(config_mupdate_server) { ++ prot_printf(imapd_out, " MUPDATE=mupdate://%s/", config_mupdate_server); ++ } ++ ++ /* add the SASL mechs */ ++ if (!imapd_authstate && ++ sasl_listmech(imapd_saslconn, NULL, ++ "AUTH=", " AUTH=", " SASL-IR", ++ &sasllist, ++ NULL, &mechcount) == SASL_OK && mechcount > 0) { ++ prot_printf(imapd_out, " %s", sasllist); ++ } else { ++ /* else don't show anything */ ++ } ++ ++#ifdef ENABLE_LISTEXT ++ prot_printf(imapd_out, " LISTEXT LIST-SUBSCRIBED"); ++#endif /* ENABLE_LISTEXT */ ++ ++#ifdef ENABLE_X_NETSCAPE_HACK ++ prot_printf(imapd_out, " X-NETSCAPE"); ++#endif ++ ++#ifdef HAVE_SSL ++ prot_printf(imapd_out, " URLAUTH"); ++#endif ++ prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Parse and perform an APPEND command. ++ * The command has been parsed up to and including ++ * the mailbox name. ++ */ ++static int isokflag(char *s, int *isseen) ++{ ++ if (s[0] == '\\') { ++ lcase(s); ++ if (!strcmp(s, "\\seen")) { ++ *isseen = 1; ++ return 1; ++ } ++ if (!strcmp(s, "\\answered")) return 1; ++ if (!strcmp(s, "\\flagged")) return 1; ++ if (!strcmp(s, "\\draft")) return 1; ++ if (!strcmp(s, "\\deleted")) return 1; ++ ++ /* uh oh, system flag i don't recognize */ ++ return 0; ++ } else { ++ /* valid user flag? */ ++ return imparse_isatom(s); ++ } ++} ++ ++static int getliteralsize(char *p, int c, ++ unsigned *size, const char **parseerr) ++ ++{ ++ int sawdigit = 0; ++ int isnowait = 0; ++ ++ /* Check for literal8 */ ++ if (*p == '~') { ++ p++; ++ /* We don't support binary append yet */ ++ return IMAP_NO_UNKNOWN_CTE; ++ } ++ if (*p != '{') { ++ *parseerr = "Missing required argument to Append command"; ++ return IMAP_PROTOCOL_ERROR; ++ } ++ ++ /* Read size from literal */ ++ isnowait = 0; ++ *size = 0; ++ for (++p; *p && isdigit((int) *p); p++) { ++ sawdigit++; ++ if (*size > (UINT_MAX - (*p - '0')) / 10) ++ return IMAP_MESSAGE_TOO_LARGE; ++ *size = (*size)*10 + *p - '0'; ++#if 0 ++ if (*size < 0) { ++ lose(); ++ } ++#endif ++ } ++ if (*p == '+') { ++ isnowait++; ++ p++; ++ } ++ ++ if (c == '\r') { ++ c = prot_getc(imapd_in); ++ } ++ else { ++ prot_ungetc(c, imapd_in); ++ c = ' '; /* Force a syntax error */ ++ } ++ ++ if (*p != '}' || p[1] || c != '\n' || !sawdigit) { ++ *parseerr = "Invalid literal in Append command"; ++ return IMAP_PROTOCOL_ERROR; ++ } ++ ++ if (!isnowait) { ++ /* Tell client to send the message */ ++ prot_printf(imapd_out, "+ go ahead\r\n"); ++ prot_flush(imapd_out); ++ } ++ ++ return 0; ++} ++ ++static int catenate_text(FILE *f, unsigned *totalsize, const char **parseerr) ++{ ++ int c; ++ static struct buf arg; ++ unsigned size = 0; ++ char buf[4096+1]; ++ int n; ++ int r; ++ ++ c = getword(imapd_in, &arg); ++ ++ /* Read size from literal */ ++ r = getliteralsize(arg.s, c, &size, parseerr); ++ if (r) return r; ++ ++ if (*totalsize > UINT_MAX - size) r = IMAP_MESSAGE_TOO_LARGE; ++ ++ /* Catenate message part to stage */ ++ while (size) { ++ n = prot_read(imapd_in, buf, size > 4096 ? 4096 : size); ++ if (!n) { ++ syslog(LOG_ERR, ++ "IOERROR: reading message: unexpected end of file"); ++ return IMAP_IOERROR; ++ } ++ ++ buf[n] = '\0'; ++ if (n != strlen(buf)) r = IMAP_MESSAGE_CONTAINSNULL; ++ ++ size -= n; ++ if (r) continue; ++ ++ /* XXX do we want to try and validate the message like ++ we do in message_copy_strict()? */ ++ ++ if (f) fwrite(buf, n, 1, f); ++ } ++ ++ *totalsize += size; ++ ++ return r; ++} ++ ++static int catenate_url(const char *s, const char *cur_name, FILE *f, ++ unsigned *totalsize, const char **parseerr) ++{ ++ struct imapurl url; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ struct mailbox mboxstruct, *mailbox; ++ unsigned msgno; ++ int r = 0, doclose = 0; ++ unsigned long size = 0; ++ ++ imapurl_fromURL(&url, s); ++ ++ if (url.server) { ++ *parseerr = "Only relative URLs are supported"; ++ r = IMAP_BADURL; ++#if 0 ++ } else if (url.server && strcmp(url.server, config_servername)) { ++ *parseerr = "Can not catenate messages from another server"; ++ r = IMAP_BADURL; ++#endif ++ } else if (!url.mailbox && !imapd_mailbox && !cur_name) { ++ *parseerr = "No mailbox is selected or specified"; ++ r = IMAP_BADURL; ++ } else if (url.mailbox || (url.mailbox = cur_name)) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, ++ url.mailbox, ++ imapd_userid, mailboxname); ++ if (!r) { ++ if (!imapd_mailbox || strcmp(imapd_mailbox->name, mailboxname)) { ++ /* not the currently selected mailbox, so try to open it */ ++ int mbtype; ++ char *newserver; ++ ++ /* lookup the location of the mailbox */ ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &newserver, NULL, NULL); ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ struct backend *be; ++ ++ be = proxy_findserver(newserver, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ } else { ++ r = proxy_catenate_url(be, &url, f, &size, parseerr); ++ if (*totalsize > UINT_MAX - size) ++ r = IMAP_MESSAGE_TOO_LARGE; ++ else ++ *totalsize += size; ++ } ++ ++ free(url.freeme); ++ ++ return r; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ r = mailbox_open_header(mailboxname, imapd_authstate, ++ &mboxstruct); ++ } ++ ++ if (!r) { ++ doclose = 1; ++ r = mailbox_open_index(&mboxstruct); ++ } ++ ++ if (!r && !(mboxstruct.myrights & ACL_READ)) { ++ r = (imapd_userisadmin || (mboxstruct.myrights & ACL_LOOKUP)) ? ++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++ if (!r) { ++ mailbox = &mboxstruct; ++ index_operatemailbox(mailbox); ++ } ++ } else { ++ mailbox = imapd_mailbox; ++ } ++ } ++ ++ if (r) { ++ *parseerr = error_message(r); ++ r = IMAP_BADURL; ++ } ++ } else { ++ mailbox = imapd_mailbox; ++ } ++ ++ if (r) { ++ /* nothing to do, handled up top */ ++ } else if (url.uidvalidity && ++ (mailbox->uidvalidity != url.uidvalidity)) { ++ *parseerr = "Uidvalidity of mailbox has changed"; ++ r = IMAP_BADURL; ++ } else if (!url.uid || !(msgno = index_finduid(url.uid)) || ++ (index_getuid(msgno) != url.uid)) { ++ *parseerr = "No such message in mailbox"; ++ r = IMAP_BADURL; ++ } else { ++ /* Catenate message part to stage */ ++ struct protstream *s = prot_new(fileno(f), 1); ++ ++ r = index_urlfetch(mailbox, msgno, url.section, ++ url.start_octet, url.octet_count, s, &size); ++ if (r == IMAP_BADURL) ++ *parseerr = "No such message part"; ++ else if (!r) { ++ if (*totalsize > UINT_MAX - size) ++ r = IMAP_MESSAGE_TOO_LARGE; ++ else ++ *totalsize += size; ++ } ++ ++ prot_flush(s); ++ prot_free(s); ++ ++ /* XXX do we want to try and validate the message like ++ we do in message_copy_strict()? */ ++ } ++ ++ free(url.freeme); ++ ++ if (doclose) { ++ mailbox_close(&mboxstruct); ++ if (imapd_mailbox) index_operatemailbox(imapd_mailbox); ++ } ++ ++ return r; ++} ++ ++static int append_catenate(FILE *f, const char *cur_name, unsigned *totalsize, ++ const char **parseerr, const char **url) ++{ ++ int c, r = 0; ++ static struct buf arg; ++ ++ do { ++ c = getword(imapd_in, &arg); ++ if (c != ' ') { ++ *parseerr = "Missing message part data in Append command"; ++ return IMAP_PROTOCOL_ERROR; ++ } ++ ++ if (!strcasecmp(arg.s, "TEXT")) { ++ int r1 = catenate_text(!r ? f : NULL, totalsize, parseerr); ++ if (r1) return r1; ++ ++ /* if we see a SP, we're trying to catenate more than one part */ ++ ++ /* Parse newline terminating command */ ++ c = prot_getc(imapd_in); ++ } ++ else if (!strcasecmp(arg.s, "URL")) { ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c != ' ' && c != ')') { ++ *parseerr = "Missing URL in Append command"; ++ return IMAP_PROTOCOL_ERROR; ++ } ++ ++ if (!r) { ++ r = catenate_url(arg.s, cur_name, f, totalsize, parseerr); ++ if (r) *url = arg.s; ++ } ++ } ++ else { ++ *parseerr = "Invalid message part type in Append command"; ++ return IMAP_PROTOCOL_ERROR; ++ } ++ ++ fflush(f); ++ } while (c == ' '); ++ ++ if (c != ')') { ++ *parseerr = "Missing space or ) after catenate list in Append command"; ++ return IMAP_PROTOCOL_ERROR; ++ } ++ ++ if (ferror(f) || fsync(fileno(f))) { ++ syslog(LOG_ERR, "IOERROR: writing message: %m"); ++ return IMAP_IOERROR; ++ } ++ ++ return r; ++} ++ ++/* If an APPEND is proxied from another server, ++ * 'cur_name' is the name of the currently selected mailbox (if any) ++ * in case we have to resolve relative URLs ++ */ ++#define FLAGGROW 10 ++void cmd_append(char *tag, char *name, const char *cur_name) ++{ ++ int c; ++ static struct buf arg; ++ char *p; ++ time_t now = time(NULL); ++ unsigned size, totalsize = 0; ++ int sync_seen = 0; ++ int r, i; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ struct appendstate mailbox; ++ unsigned long uidvalidity; ++ unsigned long firstuid, num; ++ long doappenduid = 0; ++ const char *parseerr = NULL, *url = NULL; ++ int mbtype; ++ char *newserver; ++ FILE *f; ++ int numalloc = 5; ++ struct appendstage *curstage; ++ ++ /* See if we can append */ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL, ++ &newserver, NULL, NULL); ++ } ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ struct backend *s = NULL; ++ ++ if (supports_referrals) { ++ imapd_refer(tag, newserver, name); ++ /* Eat the argument */ ++ eatline(imapd_in, prot_getc(imapd_in)); ++ return; ++ } ++ ++ s = proxy_findserver(newserver, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ ++ imapd_check(s, 0, 0); ++ ++ if (!r) { ++ int is_active = 1; ++ s->context = (void*) &is_active; ++ if (imapd_mailbox) { ++ prot_printf(s->out, "%s Localappend {%d+}\r\n%s {%d+}\r\n%s ", ++ tag, strlen(name), name, ++ strlen(imapd_mailbox->name), imapd_mailbox->name); ++ } else { ++ prot_printf(s->out, "%s Localappend {%d+}\r\n%s {%d+}\r\n%s ", ++ tag, strlen(name), name, 0, ""); ++ } ++ if (!(r = pipe_command(s, 16384))) { ++ if (s != backend_current) pipe_including_tag(s, tag, 0); ++ } ++ s->context = NULL; ++ } else { ++ eatline(imapd_in, prot_getc(imapd_in)); ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, ++ prot_error(imapd_in) ? prot_error(imapd_in) : ++ error_message(r)); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ r = append_check(mailboxname, MAILBOX_FORMAT_NORMAL, ++ imapd_authstate, ACL_INSERT, totalsize); ++ } ++ if (r) { ++ eatline(imapd_in, ' '); ++ prot_printf(imapd_out, "%s NO %s%s\r\n", ++ tag, ++ (r == IMAP_MAILBOX_NONEXISTENT && ++ mboxlist_createmailboxcheck(mailboxname, 0, 0, ++ imapd_userisadmin, ++ imapd_userid, imapd_authstate, ++ (char **)0, (char **)0) == 0) ++ ? "[TRYCREATE] " : "", error_message(r)); ++ return; ++ } ++ ++ stage = xmalloc(numalloc * sizeof(struct appendstage *)); ++ ++ c = ' '; /* just parsed a space */ ++ /* we loop, to support MULTIAPPEND */ ++ while (!r && c == ' ') { ++ /* Grow the stage array, if necessary */ ++ if (numstage == numalloc) { ++ /* Avoid integer wrap as arg to xrealloc */ ++ if (numalloc > INT_MAX/(2*sizeof(struct appendstage *))) ++ goto done; ++ numalloc *= 2; ++ stage = xrealloc(stage, numalloc * sizeof(struct appendstage *)); ++ } ++ curstage = stage[numstage] = xzmalloc(sizeof(struct appendstage)); ++ numstage++; ++ /* Parse flags */ ++ c = getword(imapd_in, &arg); ++ if (c == '(' && !arg.s[0]) { ++ curstage->nflags = 0; ++ do { ++ c = getword(imapd_in, &arg); ++ if (!curstage->nflags && !arg.s[0] && c == ')') break; /* empty list */ ++ if (!isokflag(arg.s, &sync_seen)) { ++ parseerr = "Invalid flag in Append command"; ++ r = IMAP_PROTOCOL_ERROR; ++ goto done; ++ } ++ if (curstage->nflags == curstage->flagalloc) { ++ curstage->flagalloc += FLAGGROW; ++ curstage->flag = ++ (char **) xrealloc((char *) curstage->flag, ++ curstage->flagalloc * sizeof(char *)); ++ } ++ curstage->flag[curstage->nflags] = xstrdup(arg.s); ++ curstage->nflags++; ++ } while (c == ' '); ++ if (c != ')') { ++ parseerr = ++ "Missing space or ) after flag name in Append command"; ++ r = IMAP_PROTOCOL_ERROR; ++ goto done; ++ } ++ c = prot_getc(imapd_in); ++ if (c != ' ') { ++ parseerr = "Missing space after flag list in Append command"; ++ r = IMAP_PROTOCOL_ERROR; ++ goto done; ++ } ++ c = getword(imapd_in, &arg); ++ } ++ ++ /* Parse internaldate */ ++ if (c == '\"' && !arg.s[0]) { ++ prot_ungetc(c, imapd_in); ++ c = getdatetime(&(curstage->internaldate)); ++ if (c != ' ') { ++ parseerr = "Invalid date-time in Append command"; ++ r = IMAP_PROTOCOL_ERROR; ++ goto done; ++ } ++ c = getword(imapd_in, &arg); ++ } else { ++ curstage->internaldate = now; ++ } ++ ++ /* Stage the message */ ++ f = append_newstage(mailboxname, now, numstage, &(curstage->stage)); ++ if (!f) { ++ r = IMAP_IOERROR; ++ goto done; ++ } ++ ++ if (!strcasecmp(arg.s, "CATENATE")) { ++ if (c != ' ' || (c = prot_getc(imapd_in) != '(')) { ++ parseerr = "Missing message part(s) in Append command"; ++ r = IMAP_PROTOCOL_ERROR; ++ goto done; ++ } ++ ++ /* Catenate the message part(s) to stage */ ++ size = 0; ++ r = append_catenate(f, cur_name, &size, &parseerr, &url); ++ if (r) goto done; ++ } ++ else { ++ /* Read size from literal */ ++ r = getliteralsize(arg.s, c, &size, &parseerr); ++ if (r) goto done; ++ ++ /* Copy message to stage */ ++ r = message_copy_strict(imapd_in, f, size); ++ } ++ totalsize += size; ++ fclose(f); ++ ++ /* if we see a SP, we're trying to append more than one message */ ++ ++ /* Parse newline terminating command */ ++ c = prot_getc(imapd_in); ++ } ++ ++ done: ++ if (r) { ++ eatline(imapd_in, c); ++ } else { ++ /* we should be looking at the end of the line */ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ parseerr = "junk after literal"; ++ r = IMAP_PROTOCOL_ERROR; ++ eatline(imapd_in, c); ++ } ++ } ++ ++ /* Append from the stage(s) */ ++ if (!r) { ++ r = append_setup(&mailbox, mailboxname, MAILBOX_FORMAT_NORMAL, ++ imapd_userid, imapd_authstate, ACL_INSERT, totalsize); ++ } ++ if (!r) { ++ struct body *body = NULL; ++ ++ doappenduid = (mailbox.m.myrights & ACL_READ); ++ ++ for (i = 0; !r && i < numstage; i++) { ++ r = append_fromstage(&mailbox, &body, stage[i]->stage, stage[i]->internaldate, ++ (const char **) stage[i]->flag, stage[i]->nflags, 0); ++ if (body) message_free_body(body); ++ } ++ if (body) free(body); ++ ++ if (!r) { ++ r = append_commit(&mailbox, totalsize, &uidvalidity, &firstuid, &num); ++ if (!r) { ++ sync_log_append(mailboxname); ++ if (sync_seen) sync_log_seen(imapd_userid, mailboxname); ++ } ++ } else { ++ append_abort(&mailbox); ++ } ++ } ++ ++ /* Cleanup the stage(s) */ ++ while (numstage) { ++ curstage = stage[--numstage]; ++ ++ append_removestage(curstage->stage); ++ while (curstage->nflags--) { ++ free(curstage->flag[curstage->nflags]); ++ } ++ if (curstage->flag) free((char *) curstage->flag); ++ free(curstage); ++ } ++ if (stage) free(stage); ++ stage = NULL; ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r == IMAP_PROTOCOL_ERROR && parseerr) { ++ prot_printf(imapd_out, "%s BAD %s\r\n", tag, parseerr); ++ } else if (r == IMAP_BADURL) { ++ prot_printf(imapd_out, "%s NO [BADURL \"%s\"] %s\r\n", ++ tag, url, parseerr); ++ } else if (r) { ++ prot_printf(imapd_out, "%s NO %s%s\r\n", ++ tag, ++ (r == IMAP_MAILBOX_NONEXISTENT && ++ mboxlist_createmailboxcheck(mailboxname, 0, 0, ++ imapd_userisadmin, ++ imapd_userid, imapd_authstate, ++ (char **)0, (char **)0) == 0) ++ ? "[TRYCREATE] " : r == IMAP_MESSAGE_TOO_LARGE ++ ? "[TOOBIG]" : "", error_message(r)); ++ } else if (doappenduid) { ++ /* is this a space seperated list or sequence list? */ ++ prot_printf(imapd_out, "%s OK [APPENDUID %lu", tag, uidvalidity); ++ if (num == 1) { ++ prot_printf(imapd_out, " %lu", firstuid); ++ } else { ++ prot_printf(imapd_out, " %lu:%lu", firstuid, firstuid + num - 1); ++ } ++ prot_printf(imapd_out, "] %s\r\n", error_message(IMAP_OK_COMPLETED)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++} ++ ++/* ++ * Perform a SELECT/EXAMINE/BBOARD command ++ */ ++void cmd_select(char *tag, char *cmd, char *name) ++{ ++ struct mailbox mailbox; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int r = 0; ++ double usage; ++ int doclose = 0; ++ int mbtype; ++ char *newserver; ++ struct backend *backend_next = NULL; ++ static char lastqr[MAX_MAILBOX_PATH+1] = ""; ++ static time_t nextalert = 0; ++ ++ if (imapd_mailbox) { ++ index_closemailbox(imapd_mailbox); ++ mailbox_close(imapd_mailbox); ++ imapd_mailbox = 0; ++ } ++ ++ if (cmd[0] == 'B') { ++ /* BBoard namespace is empty */ ++ r = IMAP_MAILBOX_NONEXISTENT; ++ } ++ else { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL, ++ &newserver, NULL, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ if (supports_referrals) { ++ imapd_refer(tag, newserver, name); ++ return; ++ } ++ ++ backend_next = proxy_findserver(newserver, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, ++ imapd_in); ++ if (!backend_next) r = IMAP_SERVER_UNAVAILABLE; ++ ++ if (backend_current && backend_current != backend_next) { ++ char mytag[128]; ++ ++ /* remove backend_current from the protgroup */ ++ protgroup_delete(protin, backend_current->in); ++ ++ /* switching servers; flush old server output */ ++ proxy_gentag(mytag, sizeof(mytag)); ++ prot_printf(backend_current->out, "%s Unselect\r\n", mytag); ++ /* do not fatal() here, because we don't really care about this ++ * server anymore anyway */ ++ pipe_until_tag(backend_current, mytag, 1); ++ } ++ backend_current = backend_next; ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ return; ++ } ++ ++ prot_printf(backend_current->out, "%s %s {%d+}\r\n%s\r\n", tag, cmd, ++ strlen(name), name); ++ switch (pipe_including_tag(backend_current, tag, 0)) { ++ case PROXY_OK: ++ proc_register("imapd", imapd_clienthost, imapd_userid, mailboxname); ++ syslog(LOG_DEBUG, "open: user %s opened %s on %s", ++ imapd_userid, name, newserver); ++ ++ /* add backend_current to the protgroup */ ++ protgroup_insert(protin, backend_current->in); ++ break; ++ default: ++ syslog(LOG_DEBUG, "open: user %s failed to open %s", imapd_userid, ++ name); ++ /* not successfully selected */ ++ backend_current = NULL; ++ break; ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ if (backend_current) { ++ char mytag[128]; ++ ++ /* remove backend_current from the protgroup */ ++ protgroup_delete(protin, backend_current->in); ++ ++ /* switching servers; flush old server output */ ++ proxy_gentag(mytag, sizeof(mytag)); ++ prot_printf(backend_current->out, "%s Unselect\r\n", mytag); ++ /* do not fatal() here, because we don't really care about this ++ * server anymore anyway */ ++ pipe_until_tag(backend_current, mytag, 1); ++ } ++ backend_current = NULL; ++ ++ if (!r) { ++ r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox); ++ } ++ ++ if (!r) { ++ doclose = 1; ++ r = mailbox_open_index(&mailbox); ++ } ++ if (!r && !(mailbox.myrights & ACL_READ)) { ++ r = (imapd_userisadmin || (mailbox.myrights & ACL_LOOKUP)) ? ++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ if (doclose) mailbox_close(&mailbox); ++ return; ++ } ++ ++ mboxstruct = mailbox; ++ imapd_mailbox = &mboxstruct; ++ ++ index_newmailbox(imapd_mailbox, cmd[0] == 'E'); ++ ++ /* Examine command puts mailbox in read-only mode */ ++ if (cmd[0] == 'E') { ++ imapd_mailbox->myrights &= ~(ACL_SEEN|ACL_WRITE|ACL_DELETEMSG|ACL_EXPUNGE); ++ } ++ ++ if (imapd_mailbox->myrights & ACL_EXPUNGE) { ++ time_t now = time(NULL); ++ ++ /* Warn if mailbox is close to or over quota */ ++ r = quota_read(&imapd_mailbox->quota, NULL, 0); ++ if (!r && imapd_mailbox->quota.limit > 0 && ++ (strcmp(imapd_mailbox->quota.root, lastqr) || now > nextalert)) { ++ /* Warn if the following possibilities occur: ++ * - quotawarnkb not set + quotawarn hit ++ * - quotawarnkb set larger than mailbox + quotawarn hit ++ * - quotawarnkb set + hit + quotawarn hit ++ */ ++ int warnsize = config_getint(IMAPOPT_QUOTAWARNKB); ++ if (warnsize <= 0 || warnsize >= imapd_mailbox->quota.limit || ++ ((uquota_t) (imapd_mailbox->quota.limit - warnsize)) * QUOTA_UNITS < ++ imapd_mailbox->quota.used) { ++ usage = ((double) imapd_mailbox->quota.used * 100.0) / (double) ++ ((uquota_t) imapd_mailbox->quota.limit * QUOTA_UNITS); ++ if (usage >= 100.0) { ++ prot_printf(imapd_out, "* NO [ALERT] %s\r\n", ++ error_message(IMAP_NO_OVERQUOTA)); ++ } ++ else if (usage > config_getint(IMAPOPT_QUOTAWARN)) { ++ int usageint = (int) usage; ++ prot_printf(imapd_out, "* NO [ALERT] "); ++ prot_printf(imapd_out, error_message(IMAP_NO_CLOSEQUOTA), ++ usageint); ++ prot_printf(imapd_out, "\r\n"); ++ } ++ } ++ strlcpy(lastqr, imapd_mailbox->quota.root, sizeof(lastqr)); ++ nextalert = now + 600; /* ALERT every 10 min regardless */ ++ } ++ } ++ ++ prot_printf(imapd_out, "%s OK [READ-%s] %s\r\n", tag, ++ (imapd_mailbox->myrights & (ACL_INSERT|ACL_EXPUNGE|ACL_WRITE|ACL_DELETEMSG)) ? ++ "WRITE" : "ONLY", error_message(IMAP_OK_COMPLETED)); ++ ++ proc_register("imapd", imapd_clienthost, imapd_userid, mailboxname); ++ syslog(LOG_DEBUG, "open: user %s opened %s", imapd_userid, name); ++} ++ ++/* ++ * Perform a CLOSE command ++ */ ++void cmd_close(char *tag) ++{ ++ int r; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ prot_printf(backend_current->out, "%s Close\r\n", tag); ++ /* xxx do we want this to say OK if the connection is gone? ++ * saying NO is clearly wrong, hense the fatal request. */ ++ pipe_including_tag(backend_current, tag, 0); ++ ++ /* remove backend_current from the protgroup */ ++ protgroup_delete(protin, backend_current->in); ++ ++ backend_current = NULL; ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!(imapd_mailbox->myrights & ACL_EXPUNGE)) r = 0; ++ else { ++ r = mailbox_expunge(imapd_mailbox, (int (*)())0, (char *)0, 0); ++ if (!r) sync_log_mailbox(imapd_mailbox->name); ++ } ++ ++ index_closemailbox(imapd_mailbox); ++ mailbox_close(imapd_mailbox); ++ imapd_mailbox = 0; ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++} ++ ++/* ++ * Perform an UNSELECT command -- for some support of IMAP proxy. ++ * Just like close except no expunge. ++ */ ++void cmd_unselect(char *tag) ++{ ++ if (backend_current) { ++ /* remote mailbox */ ++ prot_printf(backend_current->out, "%s Unselect\r\n", tag); ++ /* xxx do we want this to say OK if the connection is gone? ++ * saying NO is clearly wrong, hense the fatal request. */ ++ pipe_including_tag(backend_current, tag, 0); ++ backend_current = NULL; ++ ++ /* remove backend_current from the protgroup */ ++ protgroup_delete(protin, backend_current->in); ++ return; ++ } ++ ++ /* local mailbox */ ++ index_closemailbox(imapd_mailbox); ++ mailbox_close(imapd_mailbox); ++ imapd_mailbox = 0; ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Parse the syntax for a partial fetch: ++ * "<" number "." nz-number ">" ++ */ ++#define PARSE_PARTIAL(start_octet, octet_count) \ ++ (start_octet) = (octet_count) = 0; \ ++ if (*p == '<' && isdigit((int) p[1])) { \ ++ (start_octet) = p[1] - '0'; \ ++ p += 2; \ ++ while (isdigit((int) *p)) { \ ++ (start_octet) = \ ++ (start_octet) * 10 + *p++ - '0'; \ ++ } \ ++ \ ++ if (*p == '.' && p[1] >= '1' && p[1] <= '9') { \ ++ (octet_count) = p[1] - '0'; \ ++ p[0] = '>'; p[1] = '\0'; /* clip off the octet count \ ++ (its not used in the reply) */ \ ++ p += 2; \ ++ while (isdigit((int) *p)) { \ ++ (octet_count) = \ ++ (octet_count) * 10 + *p++ - '0'; \ ++ } \ ++ } \ ++ else p--; \ ++ \ ++ if (*p != '>') { \ ++ prot_printf(imapd_out, \ ++ "%s BAD Invalid body partial\r\n", tag); \ ++ eatline(imapd_in, c); \ ++ goto freeargs; \ ++ } \ ++ p++; \ ++ } ++ ++/* ++ * Parse and perform a FETCH/UID FETCH command ++ * The command has been parsed up to and including ++ * the sequence ++ */ ++void cmd_fetch(char *tag, char *sequence, int usinguid) ++{ ++ const char *cmd = usinguid ? "UID Fetch" : "Fetch"; ++ static struct buf fetchatt, fieldname; ++ int c; ++ int inlist = 0; ++ int fetchitems = 0; ++ struct fetchargs fetchargs; ++ struct octetinfo oi; ++ struct strlist *newfields = 0; ++ char *p, *section; ++ int fetchedsomething, r; ++ clock_t start = clock(); ++ char mytime[100]; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ prot_printf(backend_current->out, "%s %s %s ", tag, cmd, sequence); ++ pipe_command(backend_current, 65536); ++ return; ++ } ++ ++ /* local mailbox */ ++ memset(&fetchargs, 0, sizeof(struct fetchargs)); ++ ++ c = getword(imapd_in, &fetchatt); ++ if (c == '(' && !fetchatt.s[0]) { ++ inlist = 1; ++ c = getword(imapd_in, &fetchatt); ++ } ++ for (;;) { ++ ucase(fetchatt.s); ++ switch (fetchatt.s[0]) { ++ case 'A': ++ if (!inlist && !strcmp(fetchatt.s, "ALL")) { ++ fetchitems |= FETCH_ALL; ++ } ++ else goto badatt; ++ break; ++ ++ case 'B': ++ if (!strncmp(fetchatt.s, "BINARY[", 7) || ++ !strncmp(fetchatt.s, "BINARY.PEEK[", 12) || ++ !strncmp(fetchatt.s, "BINARY.SIZE[", 12)) { ++ int binsize = 0; ++ ++ p = section = fetchatt.s + 7; ++ if (!strncmp(p, "PEEK[", 5)) { ++ p = section += 5; ++ } ++ else if (!strncmp(p, "SIZE[", 5)) { ++ p = section += 5; ++ binsize = 1; ++ } ++ else { ++ fetchitems |= FETCH_SETSEEN; ++ } ++ while (isdigit((int) *p) || *p == '.') { ++ if (*p == '.' && !isdigit((int) p[-1])) break; ++ /* Part number can not begin with '0' */ ++ if (*p == '0' && !isdigit((int) p[-1])) break; ++ p++; ++ } ++ ++ if (*p != ']') { ++ prot_printf(imapd_out, "%s BAD Invalid binary section\r\n", tag); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ p++; ++ ++ if (!binsize) PARSE_PARTIAL(oi.start_octet, oi.octet_count); ++ ++ if (*p) { ++ prot_printf(imapd_out, "%s BAD Junk after binary section\r\n", tag); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ if (binsize) ++ appendstrlist_withdata(&fetchargs.sizesections, section, &oi, sizeof(oi)); ++ else ++ appendstrlist_withdata(&fetchargs.binsections, section, &oi, sizeof(oi)); ++ } ++ else if (!strcmp(fetchatt.s, "BODY")) { ++ fetchitems |= FETCH_BODY; ++ } ++ else if (!strcmp(fetchatt.s, "BODYSTRUCTURE")) { ++ fetchitems |= FETCH_BODYSTRUCTURE; ++ } ++ else if (!strncmp(fetchatt.s, "BODY[", 5) || ++ !strncmp(fetchatt.s, "BODY.PEEK[", 10)) { ++ p = section = fetchatt.s + 5; ++ if (!strncmp(p, "PEEK[", 5)) { ++ p = section += 5; ++ } ++ else { ++ fetchitems |= FETCH_SETSEEN; ++ } ++ while (isdigit((int) *p) || *p == '.') { ++ if (*p == '.' && !isdigit((int) p[-1])) break; ++ /* Obsolete section 0 can only occur before close brace */ ++ if (*p == '0' && !isdigit((int) p[-1]) && p[1] != ']') break; ++ p++; ++ } ++ ++ if (*p == 'H' && !strncmp(p, "HEADER.FIELDS", 13) && ++ (p == section || p[-1] == '.') && ++ (p[13] == '\0' || !strcmp(p+13, ".NOT"))) { ++ ++ /* ++ * If not top-level or a HEADER.FIELDS.NOT, can't pull ++ * the headers out of the cache. ++ */ ++ if (p != section || p[13] != '\0') { ++ fetchargs.cache_atleast = BIT32_MAX; ++ } ++ ++ if (c != ' ') { ++ prot_printf(imapd_out, ++ "%s BAD Missing required argument to %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ c = prot_getc(imapd_in); ++ if (c != '(') { ++ prot_printf(imapd_out, "%s BAD Missing required open parenthesis in %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ do { ++ c = getastring(imapd_in, imapd_out, &fieldname); ++ for (p = fieldname.s; *p; p++) { ++ if (*p <= ' ' || *p & 0x80 || *p == ':') break; ++ } ++ if (*p || !*fieldname.s) { ++ prot_printf(imapd_out, "%s BAD Invalid field-name in %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ appendstrlist(&newfields, fieldname.s); ++ if (fetchargs.cache_atleast < BIT32_MAX) { ++ bit32 this_ver = ++ mailbox_cached_header(fieldname.s); ++ if(this_ver > fetchargs.cache_atleast) ++ fetchargs.cache_atleast = this_ver; ++ } ++ } while (c == ' '); ++ if (c != ')') { ++ prot_printf(imapd_out, "%s BAD Missing required close parenthesis in %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ ++ /* Grab/parse the ] part */ ++ c = getword(imapd_in, &fieldname); ++ p = fieldname.s; ++ if (*p++ != ']') { ++ prot_printf(imapd_out, "%s BAD Missing required close bracket after %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ ++ PARSE_PARTIAL(oi.start_octet, oi.octet_count); ++ ++ if (*p) { ++ prot_printf(imapd_out, "%s BAD Junk after body section\r\n", tag); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ appendfieldlist(&fetchargs.fsections, ++ section, newfields, fieldname.s, ++ &oi, sizeof(oi)); ++ newfields = 0; ++ break; ++ } ++ ++ switch (*p) { ++ case 'H': ++ if (p != section && p[-1] != '.') break; ++ if (!strncmp(p, "HEADER]", 7)) p += 6; ++ break; ++ ++ case 'M': ++ if (!strncmp(p-1, ".MIME]", 6)) p += 4; ++ break; ++ ++ case 'T': ++ if (p != section && p[-1] != '.') break; ++ if (!strncmp(p, "TEXT]", 5)) p += 4; ++ break; ++ } ++ ++ if (*p != ']') { ++ prot_printf(imapd_out, "%s BAD Invalid body section\r\n", tag); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ p++; ++ ++ PARSE_PARTIAL(oi.start_octet, oi.octet_count); ++ ++ if (*p) { ++ prot_printf(imapd_out, "%s BAD Junk after body section\r\n", tag); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ appendstrlist_withdata(&fetchargs.bodysections, section, ++ &oi, sizeof(oi)); ++ } ++ else goto badatt; ++ break; ++ ++ case 'E': ++ if (!strcmp(fetchatt.s, "ENVELOPE")) { ++ fetchitems |= FETCH_ENVELOPE; ++ } ++ else goto badatt; ++ break; ++ ++ case 'F': ++ if (!inlist && !strcmp(fetchatt.s, "FAST")) { ++ fetchitems |= FETCH_FAST; ++ } ++ else if (!inlist && !strcmp(fetchatt.s, "FULL")) { ++ fetchitems |= FETCH_FULL; ++ } ++ else if (!strcmp(fetchatt.s, "FLAGS")) { ++ fetchitems |= FETCH_FLAGS; ++ } ++ else goto badatt; ++ break; ++ ++ case 'I': ++ if (!strcmp(fetchatt.s, "INTERNALDATE")) { ++ fetchitems |= FETCH_INTERNALDATE; ++ } ++ else goto badatt; ++ break; ++ ++ case 'R': ++ if (!strcmp(fetchatt.s, "RFC822")) { ++ fetchitems |= FETCH_RFC822|FETCH_SETSEEN; ++ } ++ else if (!strcmp(fetchatt.s, "RFC822.HEADER")) { ++ fetchitems |= FETCH_HEADER; ++ } ++ else if (!strcmp(fetchatt.s, "RFC822.PEEK")) { ++ fetchitems |= FETCH_RFC822; ++ } ++ else if (!strcmp(fetchatt.s, "RFC822.SIZE")) { ++ fetchitems |= FETCH_SIZE; ++ } ++ else if (!strcmp(fetchatt.s, "RFC822.TEXT")) { ++ fetchitems |= FETCH_TEXT|FETCH_SETSEEN; ++ } ++ else if (!strcmp(fetchatt.s, "RFC822.TEXT.PEEK")) { ++ fetchitems |= FETCH_TEXT; ++ } ++ else if (!strcmp(fetchatt.s, "RFC822.HEADER.LINES") || ++ !strcmp(fetchatt.s, "RFC822.HEADER.LINES.NOT")) { ++ if (c != ' ') { ++ prot_printf(imapd_out, "%s BAD Missing required argument to %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ c = prot_getc(imapd_in); ++ if (c != '(') { ++ prot_printf(imapd_out, "%s BAD Missing required open parenthesis in %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ do { ++ c = getastring(imapd_in, imapd_out, &fieldname); ++ for (p = fieldname.s; *p; p++) { ++ if (*p <= ' ' || *p & 0x80 || *p == ':') break; ++ } ++ if (*p || !*fieldname.s) { ++ prot_printf(imapd_out, "%s BAD Invalid field-name in %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ lcase(fieldname.s);; ++ /* 19 is magic number -- length of ++ * "RFC822.HEADERS.NOT" */ ++ appendstrlist(strlen(fetchatt.s) == 19 ? ++ &fetchargs.headers : &fetchargs.headers_not, ++ fieldname.s); ++ if (strlen(fetchatt.s) != 19) { ++ fetchargs.cache_atleast = BIT32_MAX; ++ } ++ if (fetchargs.cache_atleast < BIT32_MAX) { ++ bit32 this_ver = ++ mailbox_cached_header(fieldname.s); ++ if(this_ver > fetchargs.cache_atleast) ++ fetchargs.cache_atleast = this_ver; ++ } ++ } while (c == ' '); ++ if (c != ')') { ++ prot_printf(imapd_out, "%s BAD Missing required close parenthesis in %s %s\r\n", ++ tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ c = prot_getc(imapd_in); ++ } ++ else goto badatt; ++ break; ++ ++ case 'U': ++ if (!strcmp(fetchatt.s, "UID")) { ++ fetchitems |= FETCH_UID; ++ } ++ else goto badatt; ++ break; ++ ++ default: ++ badatt: ++ prot_printf(imapd_out, "%s BAD Invalid %s attribute %s\r\n", tag, cmd, fetchatt.s); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ ++ if (inlist && c == ' ') c = getword(imapd_in, &fetchatt); ++ else break; ++ } ++ ++ if (inlist && c == ')') { ++ inlist = 0; ++ c = prot_getc(imapd_in); ++ } ++ if (inlist) { ++ prot_printf(imapd_out, "%s BAD Missing close parenthesis in %s\r\n", tag, cmd); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, "%s BAD Unexpected extra arguments to %s\r\n", tag, cmd); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ ++ if (!fetchitems && !fetchargs.bodysections && !fetchargs.fsections && ++ !fetchargs.binsections && !fetchargs.sizesections && ++ !fetchargs.headers && !fetchargs.headers_not) { ++ prot_printf(imapd_out, "%s BAD Missing required argument to %s\r\n", tag, cmd); ++ goto freeargs; ++ } ++ ++ if (usinguid || config_getswitch(IMAPOPT_FLUSHSEENSTATE)) { ++ if (usinguid) fetchitems |= FETCH_UID; ++ index_check(imapd_mailbox, usinguid, /* update \Seen state from disk? */ ++ config_getswitch(IMAPOPT_FLUSHSEENSTATE) << 1 /* quiet */); ++ } ++ ++ fetchargs.fetchitems = fetchitems; ++ r = index_fetch(imapd_mailbox, sequence, usinguid, &fetchargs, ++ &fetchedsomething); ++ ++ snprintf(mytime, sizeof(mytime), "%2.3f", ++ (clock() - start) / (double) CLOCKS_PER_SEC); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag, ++ error_message(r), mytime); ++ } else if (fetchedsomething || usinguid) { ++ prot_printf(imapd_out, "%s OK %s (%s sec)\r\n", tag, ++ error_message(IMAP_OK_COMPLETED), mytime); ++ if (config_getswitch(IMAPOPT_FLUSHSEENSTATE) && ++ (fetchargs.fetchitems & FETCH_SETSEEN)) { ++ /* flush \Seen state to disk */ ++ index_check(imapd_mailbox, usinguid, 2 /* quiet */); ++ } ++ } else { ++ /* normal FETCH, nothing came back */ ++ prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag, ++ error_message(IMAP_NO_NOSUCHMSG), mytime); ++ } ++ ++ freeargs: ++ freestrlist(newfields); ++ freestrlist(fetchargs.bodysections); ++ freefieldlist(fetchargs.fsections); ++ freestrlist(fetchargs.headers); ++ freestrlist(fetchargs.headers_not); ++} ++ ++#undef PARSE_PARTIAL /* cleanup */ ++ ++/* ++ * Perform a PARTIAL command ++ */ ++void cmd_partial(const char *tag, const char *msgno, char *data, ++ const char *start, const char *count) ++{ ++ const char *pc; ++ char *p; ++ struct fetchargs fetchargs; ++ char *section; ++ int prev; ++ int fetchedsomething; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ prot_printf(backend_current->out, "%s Partial %s %s %s %s\r\n", ++ tag, msgno, data, start, count); ++ return; ++ } ++ ++ /* local mailbox */ ++ memset(&fetchargs, 0, sizeof(struct fetchargs)); ++ ++ for (pc = msgno; *pc; pc++) { ++ if (!isdigit((int) *pc)) break; ++ } ++ if (*pc || !*msgno) { ++ prot_printf(imapd_out, "%s BAD Invalid message number\r\n", tag); ++ return; ++ } ++ ++ lcase(data); ++ if (!strcmp(data, "rfc822")) { ++ fetchargs.fetchitems = FETCH_RFC822|FETCH_SETSEEN; ++ } ++ else if (!strcmp(data, "rfc822.header")) { ++ fetchargs.fetchitems = FETCH_HEADER; ++ } ++ else if (!strcmp(data, "rfc822.peek")) { ++ fetchargs.fetchitems = FETCH_RFC822; ++ } ++ else if (!strcmp(data, "rfc822.text")) { ++ fetchargs.fetchitems = FETCH_TEXT|FETCH_SETSEEN; ++ } ++ else if (!strcmp(data, "rfc822.text.peek")) { ++ fetchargs.fetchitems = FETCH_TEXT; ++ } ++ else if (!strncmp(data, "body[", 5) || ++ !strncmp(data, "body.peek[", 10)) { ++ p = section = data + 5; ++ if (!strncmp(p, "peek[", 5)) { ++ p = section += 5; ++ } ++ else { ++ fetchargs.fetchitems = FETCH_SETSEEN; ++ } ++ while (isdigit((int) *p) || *p == '.') { ++ if (*p == '.' && (p == section || !isdigit((int) p[1]))) break; ++ p++; ++ } ++ if (p == section || *p != ']' || p[1]) { ++ prot_printf(imapd_out, "%s BAD Invalid body section\r\n", tag); ++ freestrlist(fetchargs.bodysections); ++ return; ++ } ++ *(p+1) = '\0'; /* Keep the closing bracket in place */ ++ appendstrlist(&fetchargs.bodysections, section); ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid Partial item\r\n", tag); ++ freestrlist(fetchargs.bodysections); ++ return; ++ } ++ ++ for (pc = start; *pc; pc++) { ++ if (!isdigit((int) *pc)) break; ++ prev = fetchargs.start_octet; ++ fetchargs.start_octet = fetchargs.start_octet*10 + *pc - '0'; ++ if(fetchargs.start_octet < prev) { ++ fetchargs.start_octet = 0; ++ break; ++ } ++ } ++ if (*pc || !fetchargs.start_octet) { ++ prot_printf(imapd_out, "%s BAD Invalid starting octet\r\n", tag); ++ freestrlist(fetchargs.bodysections); ++ return; ++ } ++ fetchargs.start_octet--; /* Normalize to be 0-based */ ++ ++ prev = fetchargs.octet_count; ++ for (pc = count; *pc; pc++) { ++ if (!isdigit((int) *pc)) break; ++ prev = fetchargs.octet_count; ++ fetchargs.octet_count = fetchargs.octet_count*10 + *pc - '0'; ++ if(fetchargs.octet_count < prev) { ++ prev = -1; ++ break; ++ } ++ } ++ if (*pc || !*count || prev == -1) { ++ prot_printf(imapd_out, "%s BAD Invalid octet count\r\n", tag); ++ freestrlist(fetchargs.bodysections); ++ return; ++ } ++ ++ fetchargs.fetchitems |= FETCH_IS_PARTIAL; ++ ++ index_fetch(imapd_mailbox, msgno, 0, &fetchargs, &fetchedsomething); ++ ++ index_check(imapd_mailbox, 0, /* flush \Seen state to disk? */ ++ config_getswitch(IMAPOPT_FLUSHSEENSTATE) && ++ fetchedsomething && (fetchargs.fetchitems & FETCH_SETSEEN)); ++ ++ if (fetchedsomething) { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } else { ++ prot_printf(imapd_out, ++ "%s BAD Invalid sequence in PARTIAL command\r\n", ++ tag); ++ } ++ ++ freestrlist(fetchargs.bodysections); ++} ++ ++/* ++ * Parse and perform a STORE/UID STORE command ++ * The command has been parsed up to and including ++ * the FLAGS/+FLAGS/-FLAGS ++ */ ++void cmd_store(char *tag, char *sequence, char *operation, int usinguid) ++{ ++ const char *cmd = usinguid ? "UID Store" : "Store"; ++ struct storeargs storeargs; ++ static struct buf flagname; ++ int len, c; ++ char **flag = 0; ++ int nflags = 0, flagalloc = 0; ++ int flagsparsed = 0, inlist = 0; ++ int r; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ prot_printf(backend_current->out, "%s %s %s %s ", ++ tag, cmd, sequence, operation); ++ pipe_command(backend_current, 65536); ++ return; ++ } ++ ++ /* local mailbox */ ++ memset(&storeargs, 0, sizeof storeargs); ++ ++ lcase(operation); ++ ++ len = strlen(operation); ++ if (len > 7 && !strcmp(operation+len-7, ".silent")) { ++ storeargs.silent = 1; ++ operation[len-7] = '\0'; ++ } ++ ++ if (!strcmp(operation, "+flags")) { ++ storeargs.operation = STORE_ADD; ++ } ++ else if (!strcmp(operation, "-flags")) { ++ storeargs.operation = STORE_REMOVE; ++ } ++ else if (!strcmp(operation, "flags")) { ++ storeargs.operation = STORE_REPLACE; ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid %s attribute\r\n", tag, cmd); ++ eatline(imapd_in, ' '); ++ return; ++ } ++ ++ for (;;) { ++ c = getword(imapd_in, &flagname); ++ if (c == '(' && !flagname.s[0] && !flagsparsed && !inlist) { ++ inlist = 1; ++ continue; ++ } ++ ++ if (!flagname.s[0]) break; ++ ++ if (flagname.s[0] == '\\') { ++ lcase(flagname.s); ++ if (!strcmp(flagname.s, "\\seen")) { ++ storeargs.seen = 1; ++ } ++ else if (!strcmp(flagname.s, "\\answered")) { ++ storeargs.system_flags |= FLAG_ANSWERED; ++ } ++ else if (!strcmp(flagname.s, "\\flagged")) { ++ storeargs.system_flags |= FLAG_FLAGGED; ++ } ++ else if (!strcmp(flagname.s, "\\deleted")) { ++ storeargs.system_flags |= FLAG_DELETED; ++ } ++ else if (!strcmp(flagname.s, "\\draft")) { ++ storeargs.system_flags |= FLAG_DRAFT; ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid system flag in %s command\r\n", ++ tag, cmd); ++ eatline(imapd_in, c); ++ goto freeflags; ++ } ++ } ++ else if (!imparse_isatom(flagname.s)) { ++ prot_printf(imapd_out, "%s BAD Invalid flag name %s in %s command\r\n", ++ tag, flagname.s, cmd); ++ eatline(imapd_in, c); ++ goto freeflags; ++ } ++ else { ++ if (nflags == flagalloc) { ++ flagalloc += FLAGGROW; ++ flag = (char **)xrealloc((char *)flag, ++ flagalloc*sizeof(char *)); ++ } ++ flag[nflags] = xstrdup(flagname.s); ++ nflags++; ++ } ++ ++ flagsparsed++; ++ if (c != ' ') break; ++ } ++ ++ if (!inlist && !flagsparsed) { ++ prot_printf(imapd_out, "%s BAD Missing required argument to %s\r\n", tag, cmd); ++ eatline(imapd_in, c); ++ return; ++ } ++ if (inlist && c == ')') { ++ inlist = 0; ++ c = prot_getc(imapd_in); ++ } ++ if (inlist) { ++ prot_printf(imapd_out, "%s BAD Missing close parenthesis in %s\r\n", tag, cmd); ++ eatline(imapd_in, c); ++ goto freeflags; ++ } ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, "%s BAD Unexpected extra arguments to %s\r\n", tag, cmd); ++ eatline(imapd_in, c); ++ goto freeflags; ++ } ++ ++ r = index_store(imapd_mailbox, sequence, usinguid, &storeargs, ++ flag, nflags); ++ ++ if (config_getswitch(IMAPOPT_FLUSHSEENSTATE) && ++ (storeargs.seen || storeargs.operation == STORE_REPLACE)) { ++ /* flush \Seen state to disk */ ++ index_check(imapd_mailbox, usinguid, 1); ++ } ++ else if (usinguid) { ++ index_check(imapd_mailbox, 1, 0); ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ ++ /* We only need to log a MAILBOX event if we've changed ++ a flag other than \Seen */ ++ if (storeargs.system_flags || nflags || ++ storeargs.operation == STORE_REPLACE) { ++ sync_log_mailbox(imapd_mailbox->name); ++ } ++ } ++ ++ freeflags: ++ while (nflags--) { ++ free(flag[nflags]); ++ } ++ if (flag) free((char *)flag); ++} ++ ++void cmd_search(char *tag, int usinguid) ++{ ++ int c; ++ int charset = 0; ++ struct searchargs *searchargs; ++ clock_t start = clock(); ++ char mytime[100]; ++ int n; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ const char *cmd = usinguid ? "UID Search" : "Search"; ++ ++ prot_printf(backend_current->out, "%s %s ", tag, cmd); ++ pipe_command(backend_current, 65536); ++ return; ++ } ++ ++ /* local mailbox */ ++ searchargs = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); ++ ++ c = getsearchprogram(tag, searchargs, &charset, 1); ++ if (c == EOF) { ++ eatline(imapd_in, ' '); ++ freesearchargs(searchargs); ++ return; ++ } ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, "%s BAD Unexpected extra arguments to Search\r\n", tag); ++ eatline(imapd_in, c); ++ freesearchargs(searchargs); ++ return; ++ } ++ ++ index_check(imapd_mailbox, 1, 0); ++ ++ if (charset == -1) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, ++ error_message(IMAP_UNRECOGNIZED_CHARSET)); ++ } ++ else { ++ n = index_search(imapd_mailbox, searchargs, usinguid); ++ snprintf(mytime, sizeof(mytime), "%2.3f", ++ (clock() - start) / (double) CLOCKS_PER_SEC); ++ prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag, ++ error_message(IMAP_OK_COMPLETED), n, mytime); ++ } ++ ++ freesearchargs(searchargs); ++} ++ ++/* ++ * Perform a SORT/UID SORT command ++ */ ++void cmd_sort(char *tag, int usinguid) ++{ ++ int c; ++ struct sortcrit *sortcrit = NULL; ++ static struct buf arg; ++ int charset = 0; ++ struct searchargs *searchargs; ++ clock_t start = clock(); ++ char mytime[100]; ++ int n; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ char *cmd = usinguid ? "UID Sort" : "Sort"; ++ ++ prot_printf(backend_current->out, "%s %s ", tag, cmd); ++ pipe_command(backend_current, 65536); ++ return; ++ } ++ ++ /* local mailbox */ ++ c = getsortcriteria(tag, &sortcrit); ++ if (c == EOF) { ++ eatline(imapd_in, ' '); ++ freesortcrit(sortcrit); ++ return; ++ } ++ ++ /* get charset */ ++ if (c != ' ') { ++ prot_printf(imapd_out, "%s BAD Missing charset in Sort\r\n", ++ tag); ++ eatline(imapd_in, c); ++ freesortcrit(sortcrit); ++ return; ++ } ++ ++ c = getword(imapd_in, &arg); ++ if (c != ' ') { ++ prot_printf(imapd_out, "%s BAD Missing search criteria in Sort\r\n", ++ tag); ++ eatline(imapd_in, c); ++ freesortcrit(sortcrit); ++ return; ++ } ++ lcase(arg.s); ++ charset = charset_lookupname(arg.s); ++ ++ if (charset == -1) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, ++ error_message(IMAP_UNRECOGNIZED_CHARSET)); ++ eatline(imapd_in, c); ++ freesortcrit(sortcrit); ++ return; ++ } ++ ++ searchargs = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); ++ ++ c = getsearchprogram(tag, searchargs, &charset, 0); ++ if (c == EOF) { ++ eatline(imapd_in, ' '); ++ freesearchargs(searchargs); ++ freesortcrit(sortcrit); ++ return; ++ } ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to Sort\r\n", tag); ++ eatline(imapd_in, c); ++ freesearchargs(searchargs); ++ freesortcrit(sortcrit); ++ return; ++ } ++ ++ index_check(imapd_mailbox, 1, 0); ++ ++ n = index_sort(imapd_mailbox, sortcrit, searchargs, usinguid); ++ snprintf(mytime, sizeof(mytime), "%2.3f", ++ (clock() - start) / (double) CLOCKS_PER_SEC); ++ prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag, ++ error_message(IMAP_OK_COMPLETED), n, mytime); ++ ++ freesortcrit(sortcrit); ++ freesearchargs(searchargs); ++ return; ++} ++ ++/* ++ * Perform a THREAD/UID THREAD command ++ */ ++void cmd_thread(char *tag, int usinguid) ++{ ++ static struct buf arg; ++ int c; ++ int charset = 0; ++ int alg; ++ struct searchargs *searchargs; ++ clock_t start = clock(); ++ char mytime[100]; ++ int n; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ const char *cmd = usinguid ? "UID Thread" : "Thread"; ++ ++ prot_printf(backend_current->out, "%s %s ", tag, cmd); ++ pipe_command(backend_current, 65536); ++ return; ++ } ++ ++ /* local mailbox */ ++ /* get algorithm */ ++ c = getword(imapd_in, &arg); ++ if (c != ' ') { ++ prot_printf(imapd_out, "%s BAD Missing algorithm in Thread\r\n", tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ if ((alg = find_thread_algorithm(arg.s)) == -1) { ++ prot_printf(imapd_out, "%s BAD Invalid Thread algorithm %s\r\n", ++ tag, arg.s); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ /* get charset */ ++ c = getword(imapd_in, &arg); ++ if (c != ' ') { ++ prot_printf(imapd_out, "%s BAD Missing charset in Thread\r\n", ++ tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ lcase(arg.s); ++ charset = charset_lookupname(arg.s); ++ ++ if (charset == -1) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, ++ error_message(IMAP_UNRECOGNIZED_CHARSET)); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ searchargs = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); ++ ++ c = getsearchprogram(tag, searchargs, &charset, 0); ++ if (c == EOF) { ++ eatline(imapd_in, ' '); ++ freesearchargs(searchargs); ++ return; ++ } ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to Thread\r\n", tag); ++ eatline(imapd_in, c); ++ freesearchargs(searchargs); ++ return; ++ } ++ ++ index_check(imapd_mailbox, 1, 0); ++ ++ n = index_thread(imapd_mailbox, alg, searchargs, usinguid); ++ snprintf(mytime, sizeof(mytime), "%2.3f", ++ (clock() - start) / (double) CLOCKS_PER_SEC); ++ prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag, ++ error_message(IMAP_OK_COMPLETED), n, mytime); ++ ++ freesearchargs(searchargs); ++ return; ++} ++ ++/* ++ * Perform a COPY/UID COPY command ++ */ ++void cmd_copy(char *tag, char *sequence, char *name, int usinguid) ++{ ++ int r, myrights; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int mbtype; ++ char *server, *acl; ++ char *copyuid; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ if (!r) { ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &server, &acl, NULL); ++ } ++ ++ if (!r) myrights = cyrus_acl_myrights(imapd_authstate, acl); ++ ++ if (!r && backend_current) { ++ /* remote mailbox -> local or remote mailbox */ ++ ++ /* xxx start of separate proxy-only code ++ (remove when we move to a unified environment) */ ++ struct backend *s = NULL; ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ goto done; ++ } ++ ++ if (s != backend_current) { ++ /* this is the hard case; we have to fetch the messages and append ++ them to the other mailbox */ ++ ++ proxy_copy(tag, sequence, name, myrights, usinguid, s); ++ return; ++ } ++ /* xxx end of separate proxy-only code */ ++ ++ /* simply send the COPY to the backend */ ++ prot_printf(backend_current->out, "%s %s %s {%d+}\r\n%s\r\n", ++ tag, usinguid ? "UID Copy" : "Copy", ++ sequence, strlen(name), name); ++ ++ return; ++ } ++ else if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* local mailbox -> remote mailbox ++ * ++ * fetch the messages and APPEND them to the backend ++ * ++ * xxx completely untested ++ */ ++ struct backend *s = NULL; ++ int res; ++ ++ index_check(imapd_mailbox, usinguid, 0); ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ else if (!CAPA(s, CAPA_MULTIAPPEND)) { ++ /* we need MULTIAPPEND for atomicity */ ++ r = IMAP_REMOTE_NO_MULTIAPPEND; ++ } ++ ++ if (r) goto done; ++ ++ /* start the append */ ++ prot_printf(s->out, "%s Append {%d+}\r\n%s", tag, strlen(name), name); ++ ++ /* append the messages */ ++ r = index_copy_remote(imapd_mailbox, sequence, usinguid, s->out); ++ ++ if (!r) { ++ /* ok, finish the append; we need the UIDVALIDITY and UIDs ++ to return as part of our COPYUID response code */ ++ char *appenduid, *b; ++ ++ prot_printf(s->out, "\r\n"); ++ ++ res = pipe_until_tag(s, tag, 0); ++ ++ if (res == PROXY_OK) { ++ if (myrights & ACL_READ) { ++ appenduid = strchr(s->last_result.s, '['); ++ /* skip over APPENDUID */ ++ appenduid += strlen("[appenduid "); ++ b = strchr(appenduid, ']'); ++ *b = '\0'; ++ prot_printf(imapd_out, "%s OK [COPYUID %s] %s\r\n", tag, ++ appenduid, error_message(IMAP_OK_COMPLETED)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++ } else { ++ prot_printf(imapd_out, "%s %s", tag, s->last_result.s); ++ } ++ } else { ++ /* abort the append */ ++ prot_printf(s->out, " {0}\r\n"); ++ pipe_until_tag(s, tag, 0); ++ ++ /* report failure */ ++ prot_printf(imapd_out, "%s NO inter-server COPY failed\r\n", tag); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox -> local mailbox */ ++ if (!r) { ++ r = index_copy(imapd_mailbox, sequence, usinguid, mailboxname, ++ ©uid, !config_getswitch(IMAPOPT_SINGLEINSTANCESTORE)); ++ } ++ ++ index_check(imapd_mailbox, usinguid, 0); ++ ++ done: ++ if (r && !(usinguid && r == IMAP_NO_NOSUCHMSG)) { ++ prot_printf(imapd_out, "%s NO %s%s\r\n", tag, ++ (r == IMAP_MAILBOX_NONEXISTENT && ++ mboxlist_createmailboxcheck(mailboxname, 0, 0, ++ imapd_userisadmin, ++ imapd_userid, imapd_authstate, ++ (char **)0, (char **)0) == 0) ++ ? "[TRYCREATE] " : "", error_message(r)); ++ } ++ else if (copyuid) { ++ prot_printf(imapd_out, "%s OK [COPYUID %s] %s\r\n", tag, ++ copyuid, error_message(IMAP_OK_COMPLETED)); ++ free(copyuid); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++} ++ ++/* ++ * Perform an EXPUNGE command ++ * sequence == NULL if this isn't a UID EXPUNGE ++ */ ++void cmd_expunge(char *tag, char *sequence) ++{ ++ int r; ++ ++ if (backend_current) { ++ /* remote mailbox */ ++ if (sequence) { ++ prot_printf(backend_current->out, "%s UID Expunge %s\r\n", tag, ++ sequence); ++ } else { ++ prot_printf(backend_current->out, "%s Expunge\r\n", tag); ++ } ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!(imapd_mailbox->myrights & ACL_EXPUNGE)) r = IMAP_PERMISSION_DENIED; ++ else if (sequence) { ++ r = mailbox_expunge(imapd_mailbox, index_expungeuidlist, sequence, 0); ++ } ++ else { ++ r = mailbox_expunge(imapd_mailbox, (mailbox_decideproc_t *)0, ++ (void *)0, 0); ++ } ++ ++ index_check(imapd_mailbox, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ sync_log_mailbox(imapd_mailbox->name); ++ } ++} ++ ++/* ++ * Perform a CREATE command ++ */ ++void cmd_create(char *tag, char *name, char *partition, int localonly) ++{ ++ int r = 0; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int autocreatequota; ++ int sync_lockfd = (-1); ++ ++ if (partition && !imapd_userisadmin) { ++ r = IMAP_PERMISSION_DENIED; ++ } ++ ++ if (name[0] && name[strlen(name)-1] == imapd_namespace.hier_sep) { ++ /* We don't care about trailing hierarchy delimiters. */ ++ name[strlen(name)-1] = '\0'; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r && !localonly && config_mupdate_server) { ++ int guessedpart = 0; ++ ++ /* determine if we're creating locally or remotely */ ++ if (!partition) { ++ guessedpart = 1; ++ r = mboxlist_createmailboxcheck(mailboxname, 0, 0, ++ imapd_userisadmin, ++ imapd_userid, imapd_authstate, ++ NULL, &partition); ++ } ++ ++ if (!r && !config_partitiondir(partition)) { ++ /* invalid partition, assume its a server (remote mailbox) */ ++ char *server; ++ struct backend *s = NULL; ++ int res; ++ ++ /* check for a remote partition */ ++ server = partition; ++ partition = strchr(server, '!'); ++ if (partition) *partition++ = '\0'; ++ if (guessedpart) partition = NULL; ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ ++ if (!r) { ++ if (!CAPA(s, CAPA_MUPDATE)) { ++ /* reserve mailbox on MUPDATE */ ++ } ++ } ++ ++ if (!r) { ++ /* ok, send the create to that server */ ++ if (partition) ++ prot_printf(s->out, ++ "%s CREATE {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, strlen(name), name, ++ strlen(partition), partition); ++ else ++ prot_printf(s->out, "%s CREATE {%d+}\r\n%s\r\n", ++ tag, strlen(name), name); ++ res = pipe_until_tag(s, tag, 0); ++ ++ if (!CAPA(s, CAPA_MUPDATE)) { ++ /* do MUPDATE create operations */ ++ } ++ /* make sure we've seen the update */ ++ if (ultraparanoid && res == PROXY_OK) kick_mupdate(); ++ } ++ ++ imapd_check(s, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ /* we're allowed to reference last_result since the noop, if ++ sent, went to a different server */ ++ prot_printf(imapd_out, "%s %s", tag, s->last_result.s); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox -- fall through */ ++ if (guessedpart) partition = NULL; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ /* xxx we do forced user creates on LOCALCREATE to facilitate ++ * mailbox moves */ ++ r = mboxlist_createmailbox(mailboxname, 0, partition, ++ imapd_userisadmin, ++ imapd_userid, imapd_authstate, ++ localonly, localonly, 0); ++ ++ if (r == IMAP_PERMISSION_DENIED && !strcasecmp(name, "INBOX") && ++ (autocreatequota = config_getint(IMAPOPT_AUTOCREATEQUOTA))) { ++ ++ /* Auto create */ ++ r = mboxlist_createmailbox(mailboxname, 0, ++ partition, 1, imapd_userid, ++ imapd_authstate, 0, 0, 0); ++ ++ if (!r && autocreatequota > 0) { ++ (void) mboxlist_setquota(mailboxname, autocreatequota, 0); ++ } ++ } ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ else { ++ char *userid = NULL; ++ ++ if (config_mupdate_server && ++ (config_mupdate_config != IMAP_ENUM_MUPDATE_CONFIG_STANDARD)) { ++ kick_mupdate(); ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ ++ if ((userid = mboxname_isusermailbox(mailboxname, 1))) ++ sync_log_user(userid); ++ else ++ sync_log_mailbox(mailboxname); ++ } ++} ++ ++/* Callback for use by cmd_delete */ ++static int delmbox(char *name, ++ int matchlen __attribute__((unused)), ++ int maycreate __attribute__((unused)), ++ void *rock __attribute__((unused))) ++{ ++ int r; ++ ++ r = mboxlist_deletemailbox(name, imapd_userisadmin, ++ imapd_userid, imapd_authstate, ++ 0, 0, 0); ++ ++ if (!r) sync_log_mailbox(name); ++ ++ if(r) { ++ prot_printf(imapd_out, "* NO delete %s: %s\r\n", ++ name, error_message(r)); ++ } ++ ++ return 0; ++} ++ ++/* ++ * Perform a DELETE command ++ */ ++void cmd_delete(char *tag, char *name, int localonly, int force) ++{ ++ int r; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int mbtype; ++ char *server; ++ char *p; ++ int domainlen = 0; ++ int sync_lockfd = (-1); ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ if (!r) { ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &server, NULL, NULL); ++ } ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ struct backend *s = NULL; ++ int res; ++ ++ if (supports_referrals) { ++ imapd_refer(tag, server, name); ++ referral_kick = 1; ++ return; ++ } ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ ++ if (!r) { ++ prot_printf(s->out, "%s DELETE {%d+}\r\n%s\r\n", ++ tag, strlen(name), name); ++ res = pipe_until_tag(s, tag, 0); ++ ++ if (!CAPA(s, CAPA_MUPDATE) && res == PROXY_OK) { ++ /* do MUPDATE delete operations */ ++ } ++ ++ /* make sure we've seen the update */ ++ if (ultraparanoid && res == PROXY_OK) kick_mupdate(); ++ } ++ ++ imapd_check(s, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ /* we're allowed to reference last_result since the noop, if ++ sent, went to a different server */ ++ prot_printf(imapd_out, "%s %s", tag, s->last_result.s); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ if (config_virtdomains && (p = strchr(mailboxname, '!'))) ++ domainlen = p - mailboxname + 1; ++ ++ r = mboxlist_deletemailbox(mailboxname, imapd_userisadmin, ++ imapd_userid, imapd_authstate, 1-force, ++ localonly, 0); ++ } ++ ++ /* was it a top-level user mailbox? */ ++ /* localonly deletes are only per-mailbox */ ++ if (!r && !localonly && ++ !strncmp(mailboxname+domainlen, "user.", 5) && ++ !strchr(mailboxname+domainlen+5, '.')) { ++ int mailboxname_len = strlen(mailboxname); ++ ++ /* If we aren't too close to MAX_MAILBOX_NAME, append .* */ ++ p = mailboxname + mailboxname_len; /* end of mailboxname */ ++ if (mailboxname_len < sizeof(mailboxname) - 3) { ++ strcpy(p, ".*"); ++ } ++ ++ /* build a list of mailboxes - we're using internal names here */ ++ mboxlist_findall(NULL, mailboxname, imapd_userisadmin, imapd_userid, ++ imapd_authstate, delmbox, NULL); ++ ++ /* take care of deleting ACLs, subscriptions, seen state and quotas */ ++ *p = '\0'; /* clip off pattern */ ++ if ((!domainlen) || ++ (domainlen+1 < (sizeof(mailboxname) - mailboxname_len))) { ++ if (domainlen) { ++ /* fully qualify the userid */ ++ snprintf(p, (sizeof(mailboxname) - mailboxname_len), "@%.*s", ++ domainlen-1, mailboxname); ++ } ++ user_deletedata(mailboxname+domainlen+5, imapd_userid, ++ imapd_authstate, 1); ++ } ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ else { ++ if (config_mupdate_server && ++ (config_mupdate_config != IMAP_ENUM_MUPDATE_CONFIG_STANDARD)) { ++ kick_mupdate(); ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ /* XXX should sent a RESET here to cleanup meta-data */ ++ sync_log_mailbox(mailboxname); ++ } ++} ++ ++struct renrock ++{ ++ int ol; ++ int nl; ++ int rename_user; ++ char *olduser, *newuser; ++ char *acl_olduser, *acl_newuser; ++ char *newmailboxname; ++ char *partition; ++}; ++ ++/* Callback for use by cmd_rename */ ++static int renmbox(char *name, ++ int matchlen __attribute__((unused)), ++ int maycreate __attribute__((unused)), ++ void *rock) ++{ ++ char oldextname[MAX_MAILBOX_NAME+1]; ++ char newextname[MAX_MAILBOX_NAME+1]; ++ struct renrock *text = (struct renrock *)rock; ++ int r; ++ ++ if((text->nl + strlen(name + text->ol)) > MAX_MAILBOX_NAME) ++ return 0; ++ ++ strcpy(text->newmailboxname + text->nl, name + text->ol); ++ ++ r = mboxlist_renamemailbox(name, text->newmailboxname, ++ text->partition, ++ 1, imapd_userid, imapd_authstate); ++ ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, ++ name, ++ imapd_userid, oldextname); ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, ++ text->newmailboxname, ++ imapd_userid, newextname); ++ ++ if(r) { ++ prot_printf(imapd_out, "* NO rename %s %s: %s\r\n", ++ oldextname, newextname, error_message(r)); ++ if (RENAME_STOP_ON_ERROR) return r; ++ } else { ++ /* If we're renaming a user, change quotaroot and ACL */ ++ if (text->rename_user) { ++ user_copyquotaroot(name, text->newmailboxname); ++ user_renameacl(text->newmailboxname, ++ text->acl_olduser, text->acl_newuser); ++ } ++ ++ /* Rename mailbox annotations */ ++ annotatemore_rename(name, text->newmailboxname, ++ text->rename_user ? text->olduser : NULL, ++ text->newuser); ++ ++ prot_printf(imapd_out, "* OK rename %s %s\r\n", ++ oldextname, newextname); ++ ++ sync_log_mailbox_double(name, text->newmailboxname); ++ } ++ ++ prot_flush(imapd_out); ++ ++ return 0; ++} ++ ++/* ++ * Perform a RENAME command ++ */ ++void cmd_rename(char *tag, char *oldname, char *newname, char *partition) ++{ ++ int r = 0; ++ char oldmailboxname[MAX_MAILBOX_NAME+3]; ++ char newmailboxname[MAX_MAILBOX_NAME+2]; ++ char oldmailboxname2[MAX_MAILBOX_NAME+1]; ++ char newmailboxname2[MAX_MAILBOX_NAME+1]; ++ char oldextname[MAX_MAILBOX_NAME+1]; ++ char newextname[MAX_MAILBOX_NAME+1]; ++ int sync_lockfd = (-1); ++ int omlen, nmlen; ++ char *p; ++ int recursive_rename = 1; ++ int rename_user = 0; ++ char olduser[128], newuser[128]; ++ char acl_olduser[128], acl_newuser[128]; ++ int mbtype; ++ char *server; ++ ++ if (partition && !imapd_userisadmin) { ++ r = IMAP_PERMISSION_DENIED; ++ } ++ ++ /* canonicalize names */ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, oldname, ++ imapd_userid, oldmailboxname); ++ if (!r) ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, newname, ++ imapd_userid, newmailboxname); ++ ++ /* Keep temporary copy: master is trashed */ ++ strcpy(oldmailboxname2, oldmailboxname); ++ strcpy(newmailboxname2, newmailboxname); ++ ++ if (!r) { ++ r = mlookup(NULL, NULL, oldmailboxname, &mbtype, NULL, NULL, ++ &server, NULL, NULL); ++ } ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ struct backend *s = NULL; ++ int res; ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ ++ /* xxx start of separate proxy-only code ++ (remove when we move to a unified environment) */ ++ ++ /* Cross Server Rename */ ++ if (!r && partition) { ++ char *destpart; ++ ++ if (strcmp(oldname, newname)) { ++ prot_printf(imapd_out, ++ "%s NO Cross-server or cross-partition move w/rename not supported\r\n", ++ tag); ++ return; ++ } ++ ++ /* dest partition? */ ++ ++ destpart = strchr(partition,'!'); ++ if (destpart) { ++ char newserver[MAX_MAILBOX_NAME+1]; ++ if (strlen(partition) >= sizeof(newserver)) { ++ prot_printf(imapd_out, ++ "%s NO Partition name too long\r\n", tag); ++ return; ++ } ++ strcpy(newserver,partition); ++ newserver[destpart-partition]='\0'; ++ destpart++; ++ ++ if (!strcmp(server, newserver)) { ++ /* Same Server, different partition */ ++ /* xxx this would require administrative access to the ++ * backend, which we won't get */ ++ prot_printf(imapd_out, ++ "%s NO Can't move across partitions via a proxy\r\n", ++ tag); ++ return; ++ } else { ++ /* Cross Server */ ++ /* XFER */ ++ prot_printf(s->out, ++ "%s XFER {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, strlen(oldname), oldname, ++ strlen(newserver), newserver, ++ strlen(destpart), destpart); ++ } ++ ++ } else { ++ /* XFER */ ++ prot_printf(s->out, "%s XFER {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, strlen(oldname), oldname, ++ strlen(partition), partition); ++ } ++ ++ res = pipe_including_tag(s, tag, 0); ++ ++ /* make sure we've seen the update */ ++ if (ultraparanoid && res == PROXY_OK) kick_mupdate(); ++ ++ return; ++ } ++ /* xxx end of separate proxy-only code */ ++ ++ if (!r) { ++ if (!CAPA(s, CAPA_MUPDATE)) { ++ /* do MUPDATE create operations for new mailbox */ ++ } ++ ++ prot_printf(s->out, "%s RENAME {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, strlen(oldname), oldname, ++ strlen(newname), newname); ++ res = pipe_until_tag(s, tag, 0); ++ ++ if (!CAPA(s, CAPA_MUPDATE)) { ++ /* Activate/abort new mailbox in MUPDATE*/ ++ /* delete old mailbox from MUPDATE */ ++ } ++ ++ /* make sure we've seen the update */ ++ if (res == PROXY_OK) kick_mupdate(); ++ } ++ ++ imapd_check(s, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ /* we're allowed to reference last_result since the noop, if ++ sent, went to a different server */ ++ prot_printf(imapd_out, "%s %s", tag, s->last_result.s); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ ++ if (!r && partition && !config_partitiondir(partition)) { ++ /* invalid partition, assume its a server (remote destination) */ ++ char *server; ++ ++ if (strcmp(oldname, newname)) { ++ prot_printf(imapd_out, ++ "%s NO Cross-server or cross-partition move w/rename not supported\r\n", ++ tag); ++ return; ++ } ++ ++ /* dest partition? */ ++ server = partition; ++ partition = strchr(server, '!'); ++ if (partition) *partition++ = '\0'; ++ ++ cmd_xfer(tag, oldname, server, partition); ++ ++ return; ++ } ++ ++ /* local destination */ ++ ++ /* if this is my inbox, don't do recursive renames */ ++ if (!strcasecmp(oldname, "inbox")) { ++ recursive_rename = 0; ++ } ++ /* check if we're an admin renaming a user */ ++ else if (config_getswitch(IMAPOPT_ALLOWUSERMOVES) && ++ mboxname_isusermailbox(oldmailboxname, 1) && ++ mboxname_isusermailbox(newmailboxname, 1) && ++ strcmp(oldmailboxname, newmailboxname) && /* different user */ ++ imapd_userisadmin) { ++ rename_user = 1; ++ } ++ ++ /* if we're renaming something inside of something else, ++ don't recursively rename stuff */ ++ omlen = strlen(oldmailboxname); ++ nmlen = strlen(newmailboxname); ++ if (omlen < nmlen) { ++ if (!strncmp(oldmailboxname, newmailboxname, omlen) && ++ newmailboxname[omlen] == '.') { ++ recursive_rename = 0; ++ } ++ } else { ++ if (!strncmp(oldmailboxname, newmailboxname, nmlen) && ++ oldmailboxname[nmlen] == '.') { ++ recursive_rename = 0; ++ } ++ } ++ ++ /* verify that the mailbox doesn't have a wildcard in it */ ++ for (p = oldmailboxname; !r && *p; p++) { ++ if (*p == '*' || *p == '%') r = IMAP_MAILBOX_BADNAME; ++ } ++ ++ /* attempt to rename the base mailbox */ ++ if (!r) { ++ r = mboxlist_renamemailbox(oldmailboxname, newmailboxname, partition, ++ imapd_userisadmin, ++ imapd_userid, imapd_authstate); ++ } ++ ++ /* If we're renaming a user, take care of changing quotaroot, ACL, ++ seen state, subscriptions and sieve scripts */ ++ if (!r && rename_user) { ++ char *domain; ++ ++ /* create canonified userids */ ++ ++ domain = strchr(oldmailboxname, '!'); ++ strcpy(olduser, domain ? domain+6 : oldmailboxname+5); ++ if (domain) ++ sprintf(olduser+strlen(olduser), "@%.*s", ++ domain - oldmailboxname, oldmailboxname); ++ strcpy(acl_olduser, olduser); ++ ++ /* Translate any separators in source old userid (for ACLs) */ ++ mboxname_hiersep_toexternal(&imapd_namespace, acl_olduser, ++ config_virtdomains ? ++ strcspn(acl_olduser, "@") : 0); ++ ++ domain = strchr(newmailboxname, '!'); ++ strcpy(newuser, domain ? domain+6 : newmailboxname+5); ++ if (domain) ++ sprintf(newuser+strlen(newuser), "@%.*s", ++ domain - newmailboxname, newmailboxname); ++ strcpy(acl_newuser, newuser); ++ ++ /* Translate any separators in destination new userid (for ACLs) */ ++ mboxname_hiersep_toexternal(&imapd_namespace, acl_newuser, ++ config_virtdomains ? ++ strcspn(acl_newuser, "@") : 0); ++ ++ user_copyquotaroot(oldmailboxname, newmailboxname); ++ user_renameacl(newmailboxname, acl_olduser, acl_newuser); ++ user_renamedata(olduser, newuser, imapd_userid, imapd_authstate); ++ ++ /* XXX report status/progress of meta-data */ ++ } ++ ++ if (!r) { ++ /* Rename mailbox annotations */ ++ annotatemore_rename(oldmailboxname, newmailboxname, ++ rename_user ? olduser : NULL, ++ newuser); ++ } ++ ++ /* rename all mailboxes matching this */ ++ if (!r && recursive_rename) { ++ struct renrock rock; ++ int ol = omlen + 1; ++ int nl = nmlen + 1; ++ ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, ++ oldmailboxname, ++ imapd_userid, oldextname); ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, ++ newmailboxname, ++ imapd_userid, newextname); ++ ++ prot_printf(imapd_out, "* OK rename %s %s\r\n", ++ oldextname, newextname); ++ prot_flush(imapd_out); ++ ++ strcat(oldmailboxname, ".*"); ++ strcat(newmailboxname, "."); ++ ++ /* setup the rock */ ++ rock.newmailboxname = newmailboxname; ++ rock.ol = ol; ++ rock.nl = nl; ++ rock.olduser = olduser; ++ rock.newuser = newuser; ++ rock.acl_olduser = acl_olduser; ++ rock.acl_newuser = acl_newuser; ++ rock.partition = partition; ++ rock.rename_user = rename_user; ++ ++ /* add submailboxes; we pretend we're an admin since we successfully ++ renamed the parent - we're using internal names here */ ++ r = mboxlist_findall(NULL, oldmailboxname, 1, imapd_userid, ++ imapd_authstate, renmbox, &rock); ++ } ++ ++ /* take care of deleting old ACLs, subscriptions, seen state and quotas */ ++ if (!r && rename_user) ++ user_deletedata(olduser, imapd_userid, imapd_authstate, 1); ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ if (config_mupdate_server && ++ (config_mupdate_config != IMAP_ENUM_MUPDATE_CONFIG_STANDARD)) { ++ kick_mupdate(); ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ sync_log_mailbox_double(oldmailboxname2, newmailboxname2); ++ } ++} ++ ++/* ++ * Perform a RECONSTRUCT command ++ */ ++void cmd_reconstruct(const char *tag, const char *name, int recursive) ++{ ++ int r = 0; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ char quotaroot[MAX_MAILBOX_NAME+1]; ++ int mbtype; ++ char *server; ++ struct mailbox mailbox; ++ ++ /* administrators only please */ ++ if (!imapd_userisadmin) { ++ r = IMAP_PERMISSION_DENIED; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL, ++ &server, NULL, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ imapd_refer(tag, server, name); ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ int pid; ++ ++ /* Reconstruct it */ ++ ++ pid = fork(); ++ if(pid == -1) { ++ r = IMAP_SYS_ERROR; ++ } else if(pid == 0) { ++ char buf[4096]; ++ int ret; ++ ++ /* Child - exec reconstruct*/ ++ syslog(LOG_NOTICE, "Reconstructing '%s' (%s) for user '%s'", ++ mailboxname, recursive ? "recursive" : "not recursive", ++ imapd_userid); ++ ++ fclose(stdin); ++ fclose(stdout); ++ fclose(stderr); ++ ++ ret = snprintf(buf, sizeof(buf), "%s/reconstruct", SERVICE_PATH); ++ if(ret < 0 || ret >= sizeof(buf)) { ++ /* in child, so fatailing won't disconnect our user */ ++ fatal("reconstruct buffer not sufficiently big", EC_CONFIG); ++ } ++ ++ if(recursive) { ++ execl(buf, buf, "-C", config_filename, "-r", "-f", ++ mailboxname, NULL); ++ } else { ++ execl(buf, buf, "-C", config_filename, mailboxname, NULL); ++ } ++ ++ /* if we are here, we have a problem */ ++ exit(-1); ++ } else { ++ int status; ++ ++ /* Parent, wait on child */ ++ if(waitpid(pid, &status, 0) < 0) r = IMAP_SYS_ERROR; ++ ++ /* Did we fail? */ ++ if(WEXITSTATUS(status) != 0) r = IMAP_SYS_ERROR; ++ } ++ } ++ ++ /* Still in parent, need to re-quota the mailbox*/ ++ ++ /* Find its quota root */ ++ if (!r) { ++ r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox); ++ } ++ ++ if(!r) { ++ if(mailbox.quota.root) { ++ strcpy(quotaroot, mailbox.quota.root); ++ } else { ++ strcpy(quotaroot, mailboxname); ++ } ++ mailbox_close(&mailbox); ++ } ++ ++ /* Run quota -f */ ++ if (!r) { ++ int pid; ++ ++ pid = fork(); ++ if(pid == -1) { ++ r = IMAP_SYS_ERROR; ++ } else if(pid == 0) { ++ char buf[4096]; ++ int ret; ++ ++ /* Child - exec reconstruct*/ ++ syslog(LOG_NOTICE, ++ "Regenerating quota roots starting with '%s' for user '%s'", ++ mailboxname, imapd_userid); ++ ++ fclose(stdin); ++ fclose(stdout); ++ fclose(stderr); ++ ++ ret = snprintf(buf, sizeof(buf), "%s/quota", SERVICE_PATH); ++ if(ret < 0 || ret >= sizeof(buf)) { ++ /* in child, so fatailing won't disconnect our user */ ++ fatal("quota buffer not sufficiently big", EC_CONFIG); ++ } ++ ++ execl(buf, buf, "-C", config_filename, "-f", quotaroot, NULL); ++ ++ /* if we are here, we have a problem */ ++ exit(-1); ++ } else { ++ int status; ++ ++ /* Parent, wait on child */ ++ if(waitpid(pid, &status, 0) < 0) r = IMAP_SYS_ERROR; ++ ++ /* Did we fail? */ ++ if(WEXITSTATUS(status) != 0) r = IMAP_SYS_ERROR; ++ } ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ sync_log_user(imapd_userid); ++ } ++} ++ ++/* ++ * Perform a FIND command ++ */ ++void cmd_find(char *tag, char *namespace, char *pattern) ++{ ++ char *p; ++ lcase(namespace); ++ ++ for (p = pattern; *p; p++) { ++ if (*p == '%') *p = '?'; ++ } ++ ++ if (!strcasecmp(namespace, "mailboxes")) { ++ if (backend_inbox || (backend_inbox = proxy_findinboxserver())) { ++ /* remote INBOX */ ++ prot_printf(backend_inbox->out, ++ "%s Lsub \"\" {%d+}\r\n%s\r\n", ++ tag, strlen(pattern), pattern); ++ pipe_lsub(backend_inbox, tag, 0, "MAILBOX"); ++ } else { ++ /* local INBOX */ ++ int force = config_getswitch(IMAPOPT_ALLOWALLSUBSCRIBE); ++ ++ /* Translate any separators in pattern */ ++ mboxname_hiersep_tointernal(&imapd_namespace, pattern, ++ config_virtdomains ? ++ strcspn(pattern, "@") : 0); ++ ++ (*imapd_namespace.mboxlist_findsub)(&imapd_namespace, pattern, ++ imapd_userisadmin, imapd_userid, ++ imapd_authstate, mailboxdata, ++ NULL, force); ++ } ++ } ++ else if (!strcasecmp(namespace, "all.mailboxes")) { ++ /* Translate any separators in pattern */ ++ mboxname_hiersep_tointernal(&imapd_namespace, pattern, ++ config_virtdomains ? ++ strcspn(pattern, "@") : 0); ++ ++ (*imapd_namespace.mboxlist_findall)(&imapd_namespace, pattern, ++ imapd_userisadmin, imapd_userid, ++ imapd_authstate, mailboxdata, NULL); ++ } ++ else if (!strcasecmp(namespace, "bboards") ++ || !strcasecmp(namespace, "all.bboards")) { ++ ; ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid FIND subcommand\r\n", tag); ++ return; ++ } ++ ++ imapd_check(backend_inbox, 0, 0); ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++static int mstringdatacalls; ++ ++/* ++ * Perform a LIST or LSUB command ++ */ ++void cmd_list(char *tag, int listopts, char *reference, char *pattern) ++{ ++ char *buf = NULL; ++ int patlen = 0; ++ int reflen = 0; ++ static int ignorereference = 0; ++ clock_t start = clock(); ++ char mytime[100]; ++ int (*findall)(struct namespace *namespace, ++ const char *pattern, int isadmin, char *userid, ++ struct auth_state *auth_state, int (*proc)(), ++ void *rock); ++ int (*findsub)(struct namespace *namespace, ++ const char *pattern, int isadmin, char *userid, ++ struct auth_state *auth_state, int (*proc)(), ++ void *rock, int force); ++ ++ /* Ignore the reference argument? ++ (the behavior in 1.5.10 & older) */ ++ if (ignorereference == 0) { ++ ignorereference = config_getswitch(IMAPOPT_IGNOREREFERENCE); ++ } ++ ++ /* Reset state in mstringdata */ ++ mstringdata(NULL, NULL, 0, 0, 0); ++ ++ if (!pattern[0] && !(listopts & LIST_LSUB)) { ++ /* Special case: query top-level hierarchy separator */ ++ prot_printf(imapd_out, "* LIST (\\Noselect) \"%c\" \"\"\r\n", ++ imapd_namespace.hier_sep); ++ } else if ((listopts & (LIST_LSUB | LIST_SUBSCRIBED)) && ++ (backend_inbox || (backend_inbox = proxy_findinboxserver()))) { ++ /* remote INBOX */ ++ if ((listopts & LIST_SUBSCRIBED) && (listopts & LIST_EXT) && ++ CAPA(backend_inbox, CAPA_LISTSUBSCRIBED)) { ++ prot_printf(backend_inbox->out, "%s List (subscribed", tag); ++ if (listopts & LIST_CHILDREN) ++ prot_printf(backend_inbox->out, " children"); ++ if (listopts & LIST_REMOTE) ++ prot_printf(backend_inbox->out, " remote"); ++ prot_printf(backend_inbox->out, ") "); ++ } else { ++ prot_printf(backend_inbox->out, "%s Lsub ", tag); ++ } ++ prot_printf(backend_inbox->out, ++ "{%d+}\r\n%s {%d+}\r\n%s\r\n", ++ strlen(reference), reference, ++ strlen(pattern), pattern); ++ pipe_lsub(backend_inbox, tag, 0, (listopts & LIST_LSUB) ? "LSUB" : "LIST"); ++ } else { ++ /* Do we need to concatenate fields? */ ++ if (!ignorereference || pattern[0] == imapd_namespace.hier_sep) { ++ /* Either ++ * - name begins with dot ++ * - we're configured to honor the reference argument */ ++ ++ /* Allocate a buffer, figure out how to stick the arguments ++ together, do it, then do that instead of using pattern. */ ++ patlen = strlen(pattern); ++ reflen = strlen(reference); ++ ++ buf = xmalloc(patlen + reflen + 1); ++ buf[0] = '\0'; ++ ++ if (*reference) { ++ /* check for LIST A. .B, change to LIST "" A.B */ ++ if (reference[reflen-1] == imapd_namespace.hier_sep && ++ pattern[0] == imapd_namespace.hier_sep) { ++ reference[--reflen] = '\0'; ++ } ++ strcpy(buf, reference); ++ } ++ strcat(buf, pattern); ++ pattern = buf; ++ } ++ ++ /* Translate any separators in pattern */ ++ mboxname_hiersep_tointernal(&imapd_namespace, pattern, ++ config_virtdomains ? ++ strcspn(pattern, "@") : 0); ++ ++ /* Check to see if we should only list the personal namespace */ ++ if (!strcmp(pattern, "*") ++ && config_getswitch(IMAPOPT_FOOLSTUPIDCLIENTS)) { ++ if (buf) free(buf); ++ buf = xstrdup("INBOX*"); ++ pattern = buf; ++ findsub = mboxlist_findsub; ++ findall = mboxlist_findall; ++ } ++ else { ++ findsub = imapd_namespace.mboxlist_findsub; ++ findall = imapd_namespace.mboxlist_findall; ++ } ++ ++ if (listopts & (LIST_LSUB | LIST_SUBSCRIBED)) { ++ int force = config_getswitch(IMAPOPT_ALLOWALLSUBSCRIBE); ++ ++ (*findsub)(&imapd_namespace, pattern, ++ imapd_userisadmin, imapd_userid, imapd_authstate, ++ listdata, &listopts, force); ++ } ++ else { ++ (*findall)(&imapd_namespace, pattern, ++ imapd_userisadmin, imapd_userid, imapd_authstate, ++ listdata, &listopts); ++ } ++ ++ listdata((char *)0, 0, 0, &listopts); ++ ++ if (buf) free(buf); ++ } ++ ++ imapd_check(!(listopts & (LIST_LSUB | LIST_SUBSCRIBED)) ? ++ backend_inbox : NULL, 0, 0); ++ ++ snprintf(mytime, sizeof(mytime), "%2.3f", ++ (clock() - start) / (double) CLOCKS_PER_SEC); ++ prot_printf(imapd_out, "%s OK %s (%s secs %d calls)\r\n", tag, ++ error_message(IMAP_OK_COMPLETED), mytime, mstringdatacalls); ++} ++ ++/* ++ * Perform a SUBSCRIBE (add is nonzero) or ++ * UNSUBSCRIBE (add is zero) command ++ */ ++void cmd_changesub(char *tag, char *namespace, char *name, int add) ++{ ++ const char *cmd = add ? "Subscribe" : "Unsubscribe"; ++ int r = 0; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int force = config_getswitch(IMAPOPT_ALLOWALLSUBSCRIBE); ++ ++ if (backend_inbox || (backend_inbox = proxy_findinboxserver())) { ++ /* remote INBOX */ ++ if (add) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, ++ name, imapd_userid, ++ mailboxname); ++ if (!r) r = mlookup(NULL, NULL, mailboxname, ++ NULL, NULL, NULL, NULL, NULL, NULL); ++ ++ /* Doesn't exist on murder */ ++ } ++ ++ imapd_check(backend_inbox, 0, 0); ++ ++ if (!r) { ++ if (namespace) { ++ prot_printf(backend_inbox->out, ++ "%s %s {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, cmd, ++ strlen(namespace), namespace, ++ strlen(name), name); ++ } else { ++ prot_printf(backend_inbox->out, "%s %s {%d+}\r\n%s\r\n", ++ tag, cmd, ++ strlen(name), name); ++ } ++ if (backend_inbox != backend_current) ++ pipe_including_tag(backend_inbox, tag, 0); ++ } ++ else { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ ++ return; ++ } ++ ++ /* local INBOX */ ++ if (namespace) lcase(namespace); ++ if (!namespace || !strcmp(namespace, "mailbox")) { ++ int len = strlen(name); ++ if (force && imapd_namespace.isalt && ++ (((len == strlen(imapd_namespace.prefix[NAMESPACE_USER]) - 1) && ++ !strncmp(name, imapd_namespace.prefix[NAMESPACE_USER], len)) || ++ ((len == strlen(imapd_namespace.prefix[NAMESPACE_SHARED]) - 1) && ++ !strncmp(name, imapd_namespace.prefix[NAMESPACE_SHARED], len)))) { ++ r = 0; ++ } ++ else { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ if (!r) { ++ r = mboxlist_changesub(mailboxname, imapd_userid, ++ imapd_authstate, add, force); ++ } ++ } ++ } ++ else if (!strcmp(namespace, "bboard")) { ++ r = add ? IMAP_MAILBOX_NONEXISTENT : 0; ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid %s subcommand\r\n", tag, cmd); ++ return; ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s: %s\r\n", tag, cmd, error_message(r)); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ sync_log_subscribe(imapd_userid, mailboxname, add); ++ } ++} ++ ++/* ++ * Perform a GETACL command ++ */ ++void cmd_getacl(const char *tag, const char *name) ++{ ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int r, access; ++ char *acl; ++ char *rights, *nextid; ++ char str[ACL_MAXSTR]; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, NULL, NULL, NULL, NULL, &acl, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r) { ++ access = cyrus_acl_myrights(imapd_authstate, acl); ++ ++ if (!(access & ACL_ADMIN) && ++ !imapd_userisadmin && ++ !mboxname_userownsmailbox(imapd_userid, mailboxname)) { ++ r = (access&ACL_LOOKUP) ? ++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; ++ } ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ return; ++ } ++ ++ prot_printf(imapd_out, "* ACL "); ++ printastring(name); ++ ++ while (acl) { ++ rights = strchr(acl, '\t'); ++ if (!rights) break; ++ *rights++ = '\0'; ++ ++ nextid = strchr(rights, '\t'); ++ if (!nextid) break; ++ *nextid++ = '\0'; ++ ++ prot_printf(imapd_out, " "); ++ printastring(acl); ++ prot_printf(imapd_out, " "); ++ rights = cyrus_acl_masktostr(cyrus_acl_strtomask(rights), str, 1); ++ printastring(rights); ++ acl = nextid; ++ } ++ prot_printf(imapd_out, "\r\n"); ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Perform a LISTRIGHTS command ++ */ ++void ++cmd_listrights(tag, name, identifier) ++char *tag; ++char *name; ++char *identifier; ++{ ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int r, rights; ++ char *acl; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, NULL, NULL, NULL, NULL, &acl, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r) { ++ rights = cyrus_acl_myrights(imapd_authstate, acl); ++ ++ if (!rights && !imapd_userisadmin && ++ !mboxname_userownsmailbox(imapd_userid, mailboxname)) { ++ r = IMAP_MAILBOX_NONEXISTENT; ++ } ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (!r) { ++ struct auth_state *authstate = auth_newstate(identifier); ++ char *canon_identifier; ++ int canonidlen = 0; ++ int implicit; ++ char rightsdesc[100], optional[33]; ++ ++ if (global_authisa(authstate, IMAPOPT_ADMINS)) ++ canon_identifier = identifier; /* don't canonify global admins */ ++ else ++ canon_identifier = canonify_userid(identifier, imapd_userid, NULL); ++ auth_freestate(authstate); ++ ++ if (canon_identifier) canonidlen = strlen(canon_identifier); ++ ++ if (!canon_identifier) { ++ implicit = 0; ++ } ++ else if (mboxname_userownsmailbox(canon_identifier, mailboxname)) { ++ /* identifier's personal mailbox */ ++ implicit = config_implicitrights; ++ } ++ else if (mboxname_isusermailbox(mailboxname, 1)) { ++ /* anyone can post to an INBOX */ ++ implicit = ACL_POST; ++ } ++ else { ++ implicit = 0; ++ } ++ ++ /* calculate optional rights */ ++ cyrus_acl_masktostr(implicit ^ (canon_identifier ? ACL_FULL : 0), ++ optional, 1); ++ ++ /* build the rights string */ ++ if (implicit) { ++ cyrus_acl_masktostr(implicit, rightsdesc, 1); ++ } ++ else { ++ strcpy(rightsdesc, "\"\""); ++ } ++ ++ if (*optional) { ++ int i, n = strlen(optional); ++ char *p = rightsdesc + strlen(rightsdesc); ++ ++ for (i = 0; i < n; i++) { ++ *p++ = ' '; ++ *p++ = optional[i]; ++ } ++ *p = '\0'; ++ } ++ ++ prot_printf(imapd_out, "* LISTRIGHTS "); ++ printastring(name); ++ prot_putc(' ', imapd_out); ++ printastring(identifier); ++ prot_printf(imapd_out, " %s", rightsdesc); ++ ++ prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ return; ++ } ++ ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++} ++ ++/* ++ * Perform a MYRIGHTS command ++ */ ++void cmd_myrights(const char *tag, const char *name) ++{ ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int r, rights = 0; ++ char *acl; ++ char str[ACL_MAXSTR]; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, NULL, NULL, NULL, NULL, &acl, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r) { ++ rights = cyrus_acl_myrights(imapd_authstate, acl); ++ ++ /* Add in implicit rights */ ++ if (imapd_userisadmin) { ++ rights |= ACL_LOOKUP|ACL_ADMIN; ++ } ++ else if (mboxname_userownsmailbox(imapd_userid, mailboxname)) { ++ rights |= config_implicitrights; ++ } ++ ++ if (!(rights & (ACL_LOOKUP|ACL_READ|ACL_INSERT|ACL_CREATE|ACL_DELETEMBOX|ACL_ADMIN))) { ++ r = IMAP_MAILBOX_NONEXISTENT; ++ } ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ return; ++ } ++ ++ prot_printf(imapd_out, "* MYRIGHTS "); ++ printastring(name); ++ prot_printf(imapd_out, " "); ++ printastring(cyrus_acl_masktostr(rights, str, 1)); ++ prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Perform a SETACL command ++ */ ++void cmd_setacl(char *tag, const char *name, ++ const char *identifier, const char *rights) ++{ ++ int r; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ char *server; ++ int mbtype; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ /* is it remote? */ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL, ++ &server, NULL, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ struct backend *s = NULL; ++ int res; ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ ++ if (!r && imapd_userisadmin && supports_referrals) { ++ /* They aren't an admin remotely, so let's refer them */ ++ imapd_refer(tag, server, name); ++ referral_kick = 1; ++ return; ++ } else if (!r) { ++ if (rights) { ++ prot_printf(s->out, ++ "%s Setacl {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, strlen(name), name, ++ strlen(identifier), identifier, ++ strlen(rights), rights); ++ } else { ++ prot_printf(s->out, ++ "%s Deleteacl {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, strlen(name), name, ++ strlen(identifier), identifier); ++ } ++ res = pipe_until_tag(s, tag, 0); ++ ++ if (!CAPA(s, CAPA_MUPDATE) && res == PROXY_OK) { ++ /* setup new ACL in MUPDATE */ ++ } ++ /* make sure we've seen the update */ ++ if (ultraparanoid && res == PROXY_OK) kick_mupdate(); ++ } ++ ++ imapd_check(s, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ /* we're allowed to reference last_result since the noop, if ++ sent, went to a different server */ ++ prot_printf(imapd_out, "%s %s", tag, s->last_result.s); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ r = mboxlist_setacl(mailboxname, identifier, rights, ++ imapd_userisadmin, imapd_userid, imapd_authstate); ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ if (config_mupdate_server && ++ (config_mupdate_config != IMAP_ENUM_MUPDATE_CONFIG_STANDARD)) { ++ kick_mupdate(); ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ sync_log_acl(mailboxname); ++ } ++} ++ ++/* ++ * Callback for (get|set)quota, to ensure that all of the ++ * submailboxes are on the same server. ++ */ ++static int quota_cb(char *name, int matchlen __attribute__((unused)), ++ int maycreate __attribute__((unused)), void *rock) ++{ ++ int r; ++ char *this_server; ++ const char *servername = (const char *)rock; ++ ++ r = mlookup(NULL, NULL, name, NULL, NULL, NULL, &this_server, NULL, NULL); ++ if(r) return r; ++ ++ if(strcmp(servername, this_server)) { ++ /* Not on same server as the root */ ++ return IMAP_NOT_SINGULAR_ROOT; ++ } else { ++ return PROXY_OK; ++ } ++} ++ ++/* ++ * Perform a GETQUOTA command ++ */ ++void cmd_getquota(const char *tag, const char *name) ++{ ++ int r; ++ struct quota quota; ++ char quotarootbuf[MAX_MAILBOX_PATH+3]; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int mbtype; ++ char *server_rock = NULL, *server_rock_tmp = NULL; ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (!imapd_userisadmin) r = IMAP_PERMISSION_DENIED; ++ else { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r) { ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &server_rock_tmp, NULL, NULL); ++ } ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ server_rock = xstrdup(server_rock_tmp); ++ ++ snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname); ++ ++ r = mboxlist_findall(&imapd_namespace, quotarootbuf, ++ imapd_userisadmin, imapd_userid, ++ imapd_authstate, quota_cb, server_rock); ++ ++ if (!r) { ++ /* Do the referral */ ++ imapd_refer(tag, server_rock, name); ++ free(server_rock); ++ } else { ++ if(server_rock) free(server_rock); ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ quota.root = mailboxname; ++ r = quota_read("a, NULL, 0); ++ } ++ ++ if (!r) { ++ prot_printf(imapd_out, "* QUOTA "); ++ printastring(name); ++ prot_printf(imapd_out, " ("); ++ if (quota.limit >= 0) { ++ prot_printf(imapd_out, "STORAGE " UQUOTA_T_FMT " %d", ++ quota.used/QUOTA_UNITS, quota.limit); ++ } ++ prot_printf(imapd_out, ")\r\n"); ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ return; ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Perform a GETQUOTAROOT command ++ */ ++void cmd_getquotaroot(const char *tag, const char *name) ++{ ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ char *server; ++ int mbtype; ++ struct mailbox mailbox; ++ int r; ++ int doclose = 0; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL, ++ &server, NULL, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ ++ if (imapd_userisadmin) { ++ /* If they are an admin, they won't retain that privledge if we ++ * proxy for them, so we need to refer them -- even if they haven't ++ * told us they're able to handle it. */ ++ imapd_refer(tag, server, name); ++ } else { ++ struct backend *s; ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ ++ imapd_check(s, 0, 0); ++ ++ if (!r) { ++ prot_printf(s->out, "%s Getquotaroot {%d+}\r\n%s\r\n", ++ tag, strlen(name), name); ++ if (s != backend_current) pipe_including_tag(s, tag, 0); ++ } else { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox); ++ } ++ ++ if (!r) { ++ doclose = 1; ++ if (!imapd_userisadmin && !(mailbox.myrights & ACL_READ)) { ++ r = (mailbox.myrights & ACL_LOOKUP) ? ++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; ++ } ++ } ++ ++ if (!r) { ++ prot_printf(imapd_out, "* QUOTAROOT "); ++ printastring(name); ++ if (mailbox.quota.root) { ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, ++ mailbox.quota.root, ++ imapd_userid, mailboxname); ++ prot_printf(imapd_out, " "); ++ printastring(mailboxname); ++ r = quota_read(&mailbox.quota, NULL, 0); ++ if (!r) { ++ prot_printf(imapd_out, "\r\n* QUOTA "); ++ printastring(mailboxname); ++ prot_printf(imapd_out, " ("); ++ if (mailbox.quota.limit >= 0) { ++ prot_printf(imapd_out, "STORAGE " UQUOTA_T_FMT " %d", ++ mailbox.quota.used/QUOTA_UNITS, ++ mailbox.quota.limit); ++ } ++ prot_putc(')', imapd_out); ++ } ++ } ++ prot_printf(imapd_out, "\r\n"); ++ } ++ ++ if (doclose) mailbox_close(&mailbox); ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ return; ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Parse and perform a SETQUOTA command ++ * The command has been parsed up to the resource list ++ */ ++void cmd_setquota(const char *tag, const char *quotaroot) ++{ ++ int newquota = -1; ++ int badresource = 0; ++ int c; ++ int force = 0; ++ static struct buf arg; ++ char *p; ++ int r; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int mbtype; ++ char *server_rock_tmp = NULL; ++ ++ c = prot_getc(imapd_in); ++ if (c != '(') goto badlist; ++ ++ c = getword(imapd_in, &arg); ++ if (c != ')' || arg.s[0] != '\0') { ++ for (;;) { ++ if (c != ' ') goto badlist; ++ if (strcasecmp(arg.s, "storage") != 0) badresource = 1; ++ c = getword(imapd_in, &arg); ++ if (c != ' ' && c != ')') goto badlist; ++ if (arg.s[0] == '\0') goto badlist; ++ newquota = 0; ++ for (p = arg.s; *p; p++) { ++ if (!isdigit((int) *p)) goto badlist; ++ newquota = newquota * 10 + *p - '0'; ++ if (newquota < 0) goto badlist; /* overflow */ ++ } ++ if (c == ')') break; ++ } ++ } ++ c = prot_getc(imapd_in); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, "%s BAD Unexpected extra arguments to SETQUOTA\r\n", tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ if (badresource) r = IMAP_UNSUPPORTED_QUOTA; ++ else if (!imapd_userisadmin && !imapd_userisproxyadmin) { ++ /* need to allow proxies so that mailbox moves can set initial quota ++ * roots */ ++ r = IMAP_PERMISSION_DENIED; ++ } else { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, quotaroot, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r) { ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &server_rock_tmp, NULL, NULL); ++ } ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ char quotarootbuf[MAX_MAILBOX_NAME + 3]; ++ char *server_rock = xstrdup(server_rock_tmp); ++ ++ snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname); ++ ++ r = mboxlist_findall(&imapd_namespace, quotarootbuf, ++ imapd_userisadmin, imapd_userid, ++ imapd_authstate, quota_cb, server_rock); ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (!r) { ++ /* Do the referral */ ++ imapd_refer(tag, server_rock, quotaroot); ++ free(server_rock); ++ } else { ++ if (server_rock) free(server_rock); ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ /* are we forcing the creation of a quotaroot by having a leading +? */ ++ if (quotaroot[0] == '+') { ++ force = 1; ++ quotaroot++; ++ } ++ ++ r = mboxlist_setquota(mailboxname, newquota, force); ++ } ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ return; ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ sync_log_quota(mailboxname); ++ return; ++ ++ badlist: ++ prot_printf(imapd_out, "%s BAD Invalid quota list in Setquota\r\n", tag); ++ eatline(imapd_in, c); ++} ++ ++#ifdef HAVE_SSL ++/* ++ * this implements the STARTTLS command, as described in RFC 2595. ++ * one caveat: it assumes that no external layer is currently present. ++ * if a client executes this command, information about the external ++ * layer that was passed on the command line is disgarded. this should ++ * be fixed. ++ */ ++/* imaps - whether this is an imaps transaction or not */ ++void cmd_starttls(char *tag, int imaps) ++{ ++ int result; ++ int *layerp; ++ ++ char *auth_id; ++ sasl_ssf_t ssf; ++ ++ /* SASL and openssl have different ideas about whether ssf is signed */ ++ layerp = (int *) &ssf; ++ ++ if (imapd_starttls_done == 1) ++ { ++ prot_printf(imapd_out, "%s NO TLS already active\r\n", tag); ++ return; ++ } ++ ++ result=tls_init_serverengine("imap", ++ 5, /* depth to verify */ ++ !imaps, /* can client auth? */ ++ !imaps); /* TLS only? */ ++ ++ if (result == -1) { ++ ++ syslog(LOG_ERR, "error initializing TLS"); ++ ++ if (imaps == 0) { ++ prot_printf(imapd_out, "%s NO Error initializing TLS\r\n", tag); ++ } else { ++ fatal("tls_init() failed", EC_CONFIG); ++ } ++ ++ return; ++ } ++ ++ if (imaps == 0) ++ { ++ prot_printf(imapd_out, "%s OK Begin TLS negotiation now\r\n", tag); ++ /* must flush our buffers before starting tls */ ++ prot_flush(imapd_out); ++ } ++ ++ result=tls_start_servertls(0, /* read */ ++ 1, /* write */ ++ layerp, ++ &auth_id, ++ &tls_conn); ++ ++ /* if error */ ++ if (result==-1) { ++ if (imaps == 0) { ++ prot_printf(imapd_out, "%s NO Starttls negotiation failed\r\n", ++ tag); ++ syslog(LOG_NOTICE, "STARTTLS negotiation failed: %s", ++ imapd_clienthost); ++ return; ++ } else { ++ syslog(LOG_NOTICE, "imaps TLS negotiation failed: %s", ++ imapd_clienthost); ++ fatal("tls_start_servertls() failed", EC_TEMPFAIL); ++ return; ++ } ++ } ++ ++ /* tell SASL about the negotiated layer */ ++ result = sasl_setprop(imapd_saslconn, SASL_SSF_EXTERNAL, &ssf); ++ if (result != SASL_OK) { ++ fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL); ++ } ++ saslprops.ssf = ssf; ++ ++ result = sasl_setprop(imapd_saslconn, SASL_AUTH_EXTERNAL, auth_id); ++ if (result != SASL_OK) { ++ fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL); ++ } ++ if(saslprops.authid) { ++ free(saslprops.authid); ++ saslprops.authid = NULL; ++ } ++ if(auth_id) ++ saslprops.authid = xstrdup(auth_id); ++ ++ /* tell the prot layer about our new layers */ ++ prot_settls(imapd_in, tls_conn); ++ prot_settls(imapd_out, tls_conn); ++ ++ imapd_starttls_done = 1; ++} ++#else ++void cmd_starttls(char *tag, int imaps) ++{ ++ fatal("cmd_starttls() executed, but starttls isn't implemented!", ++ EC_SOFTWARE); ++} ++#endif /* HAVE_SSL */ ++ ++/* ++ * Parse and perform a STATUS command ++ * The command has been parsed up to the attribute list ++ */ ++void cmd_status(char *tag, char *name) ++{ ++ int c; ++ int statusitems = 0; ++ static struct buf arg; ++ struct mailbox mailbox; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int mbtype; ++ char *server; ++ int r = 0; ++ int doclose = 0; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL, ++ &server, NULL, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) { ++ /* Eat the argument */ ++ eatline(imapd_in, prot_getc(imapd_in)); ++ return; ++ } ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ ++ if (supports_referrals ++ && config_getswitch(IMAPOPT_PROXYD_ALLOW_STATUS_REFERRAL)) { ++ imapd_refer(tag, server, name); ++ /* Eat the argument */ ++ eatline(imapd_in, prot_getc(imapd_in)); ++ } ++ else { ++ struct backend *s; ++ ++ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!s) r = IMAP_SERVER_UNAVAILABLE; ++ ++ imapd_check(s, 0, 0); ++ ++ if (!r) { ++ prot_printf(s->out, "%s Status {%d+}\r\n%s ", tag, ++ strlen(name), name); ++ if (!pipe_command(s, 65536)) { ++ if (s != backend_current) pipe_including_tag(s, tag, 0); ++ } ++ } else { ++ eatline(imapd_in, prot_getc(imapd_in)); ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ } ++ ++ return; ++ } ++ ++ /* local mailbox */ ++ ++ /* ++ * Perform a full checkpoint of any open mailbox, in case we're ++ * doing a STATUS check of the current mailbox. ++ */ ++ imapd_check(NULL, 0, 1); ++ ++ c = prot_getc(imapd_in); ++ if (c != '(') goto badlist; ++ ++ c = getword(imapd_in, &arg); ++ if (arg.s[0] == '\0') goto badlist; ++ for (;;) { ++ lcase(arg.s); ++ if (!strcmp(arg.s, "messages")) { ++ statusitems |= STATUS_MESSAGES; ++ } ++ else if (!strcmp(arg.s, "recent")) { ++ statusitems |= STATUS_RECENT; ++ } ++ else if (!strcmp(arg.s, "uidnext")) { ++ statusitems |= STATUS_UIDNEXT; ++ } ++ else if (!strcmp(arg.s, "uidvalidity")) { ++ statusitems |= STATUS_UIDVALIDITY; ++ } ++ else if (!strcmp(arg.s, "unseen")) { ++ statusitems |= STATUS_UNSEEN; ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid Status attribute %s\r\n", ++ tag, arg.s); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ if (c == ' ') c = getword(imapd_in, &arg); ++ else break; ++ } ++ ++ if (c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Missing close parenthesis in Status\r\n", tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ c = prot_getc(imapd_in); ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to Status\r\n", tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ if (!r) { ++ r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox); ++ } ++ ++ if (!r) { ++ doclose = 1; ++ r = mailbox_open_index(&mailbox); ++ } ++ if (!r && !(mailbox.myrights & ACL_READ)) { ++ r = (imapd_userisadmin || (mailbox.myrights & ACL_LOOKUP)) ? ++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++ if (!r) { ++ r = index_status(&mailbox, name, statusitems); ++ } ++ ++ if (doclose) mailbox_close(&mailbox); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ return; ++ } ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ return; ++ ++ badlist: ++ prot_printf(imapd_out, "%s BAD Invalid status list in Status\r\n", tag); ++ eatline(imapd_in, c); ++} ++ ++#ifdef ENABLE_X_NETSCAPE_HACK ++/* ++ * Reply to Netscape's crock with a crock of my own ++ */ ++void cmd_netscrape(char *tag) ++{ ++ const char *url; ++ ++ url = config_getstring(IMAPOPT_NETSCAPEURL); ++ ++ /* I only know of three things to reply with: */ ++ prot_printf(imapd_out, ++ "* OK [NETSCAPE] Carnegie Mellon Cyrus IMAP\r\n" ++ "* VERSION %s\r\n", ++ CYRUS_VERSION); ++ if (url) prot_printf(imapd_out, "* ACCOUNT-URL %s\r\n", url); ++ prot_printf(imapd_out, "%s OK %s\r\n", ++ tag, error_message(IMAP_OK_COMPLETED)); ++} ++#endif /* ENABLE_X_NETSCAPE_HACK */ ++ ++/* Callback for cmd_namespace to be passed to mboxlist_findall. ++ * For each top-level mailbox found, print a bit of the response ++ * if it is a shared namespace. The rock is used as an integer in ++ * order to ensure the namespace response is correct on a server with ++ * no shared namespace. ++ */ ++static int namespacedata(char *name, ++ int matchlen __attribute__((unused)), ++ int maycreate __attribute__((unused)), ++ void *rock) ++{ ++ int* sawone = (int*) rock; ++ ++ if (!name) { ++ return 0; ++ } ++ ++ if ((!strncasecmp(name, "INBOX", 5) && (!name[5] || name[5] == '.'))) { ++ /* The user has a "personal" namespace. */ ++ sawone[NAMESPACE_INBOX] = 1; ++ } else if (mboxname_isusermailbox(name, 0)) { ++ /* The user can see the "other users" namespace. */ ++ sawone[NAMESPACE_USER] = 1; ++ } else { ++ /* The user can see the "shared" namespace. */ ++ sawone[NAMESPACE_SHARED] = 1; ++ } ++ ++ return 0; ++} ++ ++/* ++ * Print out a response to the NAMESPACE command defined by ++ * RFC 2342. ++ */ ++void cmd_namespace(tag) ++ char* tag; ++{ ++ int sawone[3] = {0, 0, 0}; ++ char* pattern; ++ ++ if (SLEEZY_NAMESPACE) { ++ char inboxname[MAX_MAILBOX_NAME+1]; ++ ++ if (strlen(imapd_userid) + 5 > MAX_MAILBOX_NAME) ++ sawone[NAMESPACE_INBOX] = 0; ++ else { ++ (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, "INBOX", ++ imapd_userid, inboxname); ++ sawone[NAMESPACE_INBOX] = ++ !mboxlist_lookup(inboxname, NULL, NULL); ++ } ++ sawone[NAMESPACE_USER] = 1; ++ sawone[NAMESPACE_SHARED] = 1; ++ } else { ++ pattern = xstrdup("%"); ++ /* now find all the exciting toplevel namespaces - ++ * we're using internal names here ++ */ ++ mboxlist_findall(NULL, pattern, imapd_userisadmin, imapd_userid, ++ imapd_authstate, namespacedata, (void*) sawone); ++ free(pattern); ++ } ++ ++ prot_printf(imapd_out, "* NAMESPACE"); ++ if (sawone[NAMESPACE_INBOX]) { ++ prot_printf(imapd_out, " ((\"%s\" \"%c\"))", ++ imapd_namespace.prefix[NAMESPACE_INBOX], ++ imapd_namespace.hier_sep); ++ } else { ++ prot_printf(imapd_out, " NIL"); ++ } ++ if (sawone[NAMESPACE_USER]) { ++ prot_printf(imapd_out, " ((\"%s\" \"%c\"))", ++ imapd_namespace.prefix[NAMESPACE_USER], ++ imapd_namespace.hier_sep); ++ } else { ++ prot_printf(imapd_out, " NIL"); ++ } ++ if (sawone[NAMESPACE_SHARED]) { ++ prot_printf(imapd_out, " ((\"%s\" \"%c\"))", ++ imapd_namespace.prefix[NAMESPACE_SHARED], ++ imapd_namespace.hier_sep); ++ } else { ++ prot_printf(imapd_out, " NIL"); ++ } ++ prot_printf(imapd_out, "\r\n"); ++ ++ imapd_check(NULL, 0, 0); ++ ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++} ++ ++/* ++ * Parse annotate fetch data. ++ * ++ * This is a generic routine which parses just the annotation data. ++ * Any surrounding command text must be parsed elsewhere, ie, ++ * GETANNOTATION, FETCH. ++ */ ++ ++int getannotatefetchdata(char *tag, ++ struct strlist **entries, struct strlist **attribs) ++{ ++ int c; ++ static struct buf arg; ++ ++ *entries = *attribs = NULL; ++ ++ c = prot_getc(imapd_in); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation entry\r\n", tag); ++ goto baddata; ++ } ++ else if (c == '(') { ++ /* entry list */ ++ do { ++ c = getqstring(imapd_in, imapd_out, &arg); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation entry\r\n", tag); ++ goto baddata; ++ } ++ ++ /* add the entry to the list */ ++ appendstrlist(entries, arg.s); ++ ++ } while (c == ' '); ++ ++ if (c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Missing close paren in annotation entry list \r\n", ++ tag); ++ goto baddata; ++ } ++ ++ c = prot_getc(imapd_in); ++ } ++ else { ++ /* single entry -- add it to the list */ ++ prot_ungetc(c, imapd_in); ++ c = getqstring(imapd_in, imapd_out, &arg); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation entry\r\n", tag); ++ goto baddata; ++ } ++ ++ appendstrlist(entries, arg.s); ++ } ++ ++ if (c != ' ' || (c = prot_getc(imapd_in)) == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation attribute(s)\r\n", tag); ++ goto baddata; ++ } ++ ++ if (c == '(') { ++ /* attrib list */ ++ do { ++ c = getnstring(imapd_in, imapd_out, &arg); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation attribute(s)\r\n", tag); ++ goto baddata; ++ } ++ ++ /* add the attrib to the list */ ++ appendstrlist(attribs, arg.s); ++ ++ } while (c == ' '); ++ ++ if (c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Missing close paren in " ++ "annotation attribute list\r\n", tag); ++ goto baddata; ++ } ++ ++ c = prot_getc(imapd_in); ++ } ++ else { ++ /* single attrib */ ++ prot_ungetc(c, imapd_in); ++ c = getqstring(imapd_in, imapd_out, &arg); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation attribute\r\n", tag); ++ goto baddata; ++ } ++ ++ appendstrlist(attribs, arg.s); ++ } ++ ++ return c; ++ ++ baddata: ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++} ++ ++/* ++ * Parse annotate store data. ++ * ++ * This is a generic routine which parses just the annotation data. ++ * Any surrounding command text must be parsed elsewhere, ie, ++ * SETANNOTATION, STORE, APPEND. ++ */ ++ ++int getannotatestoredata(char *tag, struct entryattlist **entryatts) ++{ ++ int c, islist = 0; ++ static struct buf entry, attrib, value; ++ struct attvaluelist *attvalues = NULL; ++ ++ *entryatts = NULL; ++ ++ c = prot_getc(imapd_in); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation entry\r\n", tag); ++ goto baddata; ++ } ++ else if (c == '(') { ++ /* entry list */ ++ islist = 1; ++ } ++ else { ++ /* single entry -- put the char back */ ++ prot_ungetc(c, imapd_in); ++ } ++ ++ do { ++ /* get entry */ ++ c = getqstring(imapd_in, imapd_out, &entry); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation entry\r\n", tag); ++ goto baddata; ++ } ++ ++ /* parse att-value list */ ++ if (c != ' ' || (c = prot_getc(imapd_in)) != '(') { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation attribute-values list\r\n", ++ tag); ++ goto baddata; ++ } ++ ++ do { ++ /* get attrib */ ++ c = getqstring(imapd_in, imapd_out, &attrib); ++ if (c == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation attribute\r\n", tag); ++ goto baddata; ++ } ++ ++ /* get value */ ++ if (c != ' ' || ++ (c = getnstring(imapd_in, imapd_out, &value)) == EOF) { ++ prot_printf(imapd_out, ++ "%s BAD Missing annotation value\r\n", tag); ++ goto baddata; ++ } ++ ++ /* add the attrib-value pair to the list */ ++ appendattvalue(&attvalues, attrib.s, value.s); ++ ++ } while (c == ' '); ++ ++ if (c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Missing close paren in annotation " ++ "attribute-values list\r\n", tag); ++ goto baddata; ++ } ++ ++ /* add the entry to the list */ ++ appendentryatt(entryatts, entry.s, attvalues); ++ attvalues = NULL; ++ ++ c = prot_getc(imapd_in); ++ ++ } while (c == ' '); ++ ++ if (islist) { ++ if (c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Missing close paren in annotation entry list \r\n", ++ tag); ++ goto baddata; ++ } ++ ++ c = prot_getc(imapd_in); ++ } ++ ++ return c; ++ ++ baddata: ++ if (attvalues) freeattvalues(attvalues); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++} ++ ++/* ++ * Output an entry/attribute-value list response. ++ * ++ * This is a generic routine which outputs just the annotation data. ++ * Any surrounding response text must be output elsewhere, ie, ++ * GETANNOTATION, FETCH. ++ */ ++void annotate_response(struct entryattlist *l) ++{ ++ int islist; /* do we have more than one entry? */ ++ ++ if (!l) return; ++ ++ islist = (l->next != NULL); ++ ++ if (islist) prot_printf(imapd_out, "("); ++ ++ while (l) { ++ prot_printf(imapd_out, "\"%s\"", l->entry); ++ ++ /* do we have attributes? solicited vs. unsolicited */ ++ if (l->attvalues) { ++ struct attvaluelist *av = l->attvalues; ++ ++ prot_printf(imapd_out, " ("); ++ while (av) { ++ prot_printf(imapd_out, "\"%s\" ", av->attrib); ++ if (!strcasecmp(av->value, "NIL")) ++ prot_printf(imapd_out, "NIL"); ++ else ++ prot_printf(imapd_out, "\"%s\"", av->value); ++ ++ if ((av = av->next) == NULL) ++ prot_printf(imapd_out, ")"); ++ else ++ prot_printf(imapd_out, " "); ++ } ++ } ++ ++ if ((l = l->next) != NULL) ++ prot_printf(imapd_out, " "); ++ } ++ ++ if (islist) prot_printf(imapd_out, ")"); ++} ++ ++/* ++ * Perform a GETANNOTATION command ++ * ++ * The command has been parsed up to the entries ++ */ ++void cmd_getannotation(char *tag, char *mboxpat) ++{ ++ int c, r = 0; ++ struct strlist *entries = NULL, *attribs = NULL; ++ ++ c = getannotatefetchdata(tag, &entries, &attribs); ++ if (c == EOF) { ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ /* check for CRLF */ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to Getannotation\r\n", ++ tag); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ ++ r = annotatemore_fetch(mboxpat, entries, attribs, &imapd_namespace, ++ imapd_userisadmin || imapd_userisproxyadmin, ++ imapd_userid, imapd_authstate, imapd_out); ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", ++ tag, error_message(IMAP_OK_COMPLETED)); ++ } ++ ++ freeargs: ++ if (entries) freestrlist(entries); ++ if (attribs) freestrlist(attribs); ++ ++ return; ++} ++ ++/* ++ * Perform a SETANNOTATION command ++ * ++ * The command has been parsed up to the entry-att list ++ */ ++void cmd_setannotation(char *tag, char *mboxpat) ++{ ++ int c, r = 0; ++ struct entryattlist *entryatts = NULL; ++ ++ c = getannotatestoredata(tag, &entryatts); ++ if (c == EOF) { ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ /* check for CRLF */ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to Setannotation\r\n", ++ tag); ++ eatline(imapd_in, c); ++ goto freeargs; ++ } ++ ++ r = annotatemore_store(mboxpat, ++ entryatts, &imapd_namespace, imapd_userisadmin, ++ imapd_userid, imapd_authstate); ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++ ++ freeargs: ++ if (entryatts) freeentryatts(entryatts); ++ return; ++} ++ ++/* ++ * Parse a search program ++ */ ++int getsearchprogram(tag, searchargs, charset, parsecharset) ++char *tag; ++struct searchargs *searchargs; ++int *charset; ++int parsecharset; ++{ ++ int c; ++ ++ do { ++ c = getsearchcriteria(tag, searchargs, charset, parsecharset); ++ parsecharset = 0; ++ } while (c == ' '); ++ return c; ++} ++ ++/* ++ * Parse a search criteria ++ */ ++int getsearchcriteria(tag, searchargs, charset, parsecharset) ++char *tag; ++struct searchargs *searchargs; ++int *charset; ++int parsecharset; ++{ ++ static struct buf criteria, arg; ++ struct searchargs *sub1, *sub2; ++ char *p, *str; ++ int c, flag; ++ unsigned size; ++ time_t start, end; ++ ++ c = getword(imapd_in, &criteria); ++ lcase(criteria.s); ++ switch (criteria.s[0]) { ++ case '\0': ++ if (c != '(') goto badcri; ++ c = getsearchprogram(tag, searchargs, charset, 0); ++ if (c == EOF) return EOF; ++ if (c != ')') { ++ prot_printf(imapd_out, "%s BAD Missing required close paren in Search command\r\n", ++ tag); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ } ++ c = prot_getc(imapd_in); ++ break; ++ ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ case '*': ++ if (imparse_issequence(criteria.s)) { ++ appendstrlist(&searchargs->sequence, criteria.s); ++ } ++ else goto badcri; ++ break; ++ ++ case 'a': ++ if (!strcmp(criteria.s, "answered")) { ++ searchargs->system_flags_set |= FLAG_ANSWERED; ++ } ++ else if (!strcmp(criteria.s, "all")) { ++ break; ++ } ++ else goto badcri; ++ break; ++ ++ case 'b': ++ if (!strcmp(criteria.s, "before")) { ++ if (c != ' ') goto missingarg; ++ c = getsearchdate(&start, &end); ++ if (c == EOF) goto baddate; ++ if (!searchargs->before || searchargs->before > start) { ++ searchargs->before = start; ++ } ++ } ++ else if (!strcmp(criteria.s, "bcc")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(&searchargs->bcc, str); ++ } ++ } ++ else if (!strcmp(criteria.s, "body")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(&searchargs->body, str); ++ } ++ } ++ else goto badcri; ++ break; ++ ++ case 'c': ++ if (!strcmp(criteria.s, "cc")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(&searchargs->cc, str); ++ } ++ } ++ else if (parsecharset && !strcmp(criteria.s, "charset")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c != ' ') goto missingarg; ++ lcase(arg.s); ++ *charset = charset_lookupname(arg.s); ++ } ++ else goto badcri; ++ break; ++ ++ case 'd': ++ if (!strcmp(criteria.s, "deleted")) { ++ searchargs->system_flags_set |= FLAG_DELETED; ++ } ++ else if (!strcmp(criteria.s, "draft")) { ++ searchargs->system_flags_set |= FLAG_DRAFT; ++ } ++ else goto badcri; ++ break; ++ ++ case 'f': ++ if (!strcmp(criteria.s, "flagged")) { ++ searchargs->system_flags_set |= FLAG_FLAGGED; ++ } ++ else if (!strcmp(criteria.s, "from")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(&searchargs->from, str); ++ } ++ } ++ else goto badcri; ++ break; ++ ++ case 'h': ++ if (!strcmp(criteria.s, "header")) { ++ struct strlist **patlist; ++ ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c != ' ') goto missingarg; ++ lcase(arg.s); ++ ++ /* some headers can be reduced to search terms */ ++ if (!strcmp(arg.s, "bcc")) { ++ patlist = &searchargs->bcc; ++ } ++ else if (!strcmp(arg.s, "cc")) { ++ patlist = &searchargs->cc; ++ } ++ else if (!strcmp(arg.s, "to")) { ++ patlist = &searchargs->to; ++ } ++ else if (!strcmp(arg.s, "from")) { ++ patlist = &searchargs->from; ++ } ++ else if (!strcmp(arg.s, "subject")) { ++ patlist = &searchargs->subject; ++ } ++ ++ /* we look message-id up in the envelope */ ++ else if (!strcmp(arg.s, "message-id")) { ++ patlist = &searchargs->messageid; ++ } ++ ++ /* all other headers we handle normally */ ++ else { ++ if (searchargs->cache_atleast < BIT32_MAX) { ++ bit32 this_ver = ++ mailbox_cached_header(arg.s); ++ if(this_ver > searchargs->cache_atleast) ++ searchargs->cache_atleast = this_ver; ++ } ++ appendstrlist(&searchargs->header_name, arg.s); ++ patlist = &searchargs->header; ++ } ++ ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(patlist, str); ++ } ++ } ++ else goto badcri; ++ break; ++ ++ case 'k': ++ if (!strcmp(criteria.s, "keyword")) { ++ if (c != ' ') goto missingarg; ++ c = getword(imapd_in, &arg); ++ if (!imparse_isatom(arg.s)) goto badflag; ++ lcase(arg.s); ++ for (flag=0; flag < MAX_USER_FLAGS; flag++) { ++ if (imapd_mailbox->flagname[flag] && ++ !strcasecmp(imapd_mailbox->flagname[flag], arg.s)) break; ++ } ++ if (flag == MAX_USER_FLAGS) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ break; ++ } ++ searchargs->user_flags_set[flag/32] |= 1<<(flag&31); ++ } ++ else goto badcri; ++ break; ++ ++ case 'l': ++ if (!strcmp(criteria.s, "larger")) { ++ if (c != ' ') goto missingarg; ++ c = getword(imapd_in, &arg); ++ size = 0; ++ for (p = arg.s; *p && isdigit((int) *p); p++) { ++ size = size * 10 + *p - '0'; ++ /* if (size < 0) goto badnumber; */ ++ } ++ if (!arg.s || *p) goto badnumber; ++ if (size > searchargs->larger) searchargs->larger = size; ++ } ++ else goto badcri; ++ break; ++ ++ case 'n': ++ if (!strcmp(criteria.s, "not")) { ++ if (c != ' ') goto missingarg; ++ sub1 = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); ++ c = getsearchcriteria(tag, sub1, charset, 0); ++ if (c == EOF) { ++ freesearchargs(sub1); ++ return EOF; ++ } ++ ++ appendsearchargs(searchargs, sub1, (struct searchargs *)0); ++ } ++ else if (!strcmp(criteria.s, "new")) { ++ searchargs->flags |= (SEARCH_SEEN_UNSET|SEARCH_RECENT_SET); ++ } ++ else goto badcri; ++ break; ++ ++ case 'o': ++ if (!strcmp(criteria.s, "or")) { ++ if (c != ' ') goto missingarg; ++ sub1 = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); ++ c = getsearchcriteria(tag, sub1, charset, 0); ++ if (c == EOF) { ++ freesearchargs(sub1); ++ return EOF; ++ } ++ if (c != ' ') goto missingarg; ++ sub2 = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); ++ c = getsearchcriteria(tag, sub2, charset, 0); ++ if (c == EOF) { ++ freesearchargs(sub1); ++ freesearchargs(sub2); ++ return EOF; ++ } ++ appendsearchargs(searchargs, sub1, sub2); ++ } ++ else if (!strcmp(criteria.s, "old")) { ++ searchargs->flags |= SEARCH_RECENT_UNSET; ++ } ++ else if (!strcmp(criteria.s, "on")) { ++ if (c != ' ') goto missingarg; ++ c = getsearchdate(&start, &end); ++ if (c == EOF) goto baddate; ++ if (!searchargs->before || searchargs->before > end) { ++ searchargs->before = end; ++ } ++ if (!searchargs->after || searchargs->after < start) { ++ searchargs->after = start; ++ } ++ } ++ else goto badcri; ++ break; ++ ++ case 'r': ++ if (!strcmp(criteria.s, "recent")) { ++ searchargs->flags |= SEARCH_RECENT_SET; ++ } ++ else goto badcri; ++ break; ++ ++ case 's': ++ if (!strcmp(criteria.s, "seen")) { ++ searchargs->flags |= SEARCH_SEEN_SET; ++ } ++ else if (!strcmp(criteria.s, "sentbefore")) { ++ if (c != ' ') goto missingarg; ++ c = getsearchdate(&start, &end); ++ if (c == EOF) goto baddate; ++ if (!searchargs->sentbefore || searchargs->sentbefore > start) { ++ searchargs->sentbefore = start; ++ } ++ } ++ else if (!strcmp(criteria.s, "senton")) { ++ if (c != ' ') goto missingarg; ++ c = getsearchdate(&start, &end); ++ if (c == EOF) goto baddate; ++ if (!searchargs->sentbefore || searchargs->sentbefore > end) { ++ searchargs->sentbefore = end; ++ } ++ if (!searchargs->sentafter || searchargs->sentafter < start) { ++ searchargs->sentafter = start; ++ } ++ } ++ else if (!strcmp(criteria.s, "sentsince")) { ++ if (c != ' ') goto missingarg; ++ c = getsearchdate(&start, &end); ++ if (c == EOF) goto baddate; ++ if (!searchargs->sentafter || searchargs->sentafter < start) { ++ searchargs->sentafter = start; ++ } ++ } ++ else if (!strcmp(criteria.s, "since")) { ++ if (c != ' ') goto missingarg; ++ c = getsearchdate(&start, &end); ++ if (c == EOF) goto baddate; ++ if (!searchargs->after || searchargs->after < start) { ++ searchargs->after = start; ++ } ++ } ++ else if (!strcmp(criteria.s, "smaller")) { ++ if (c != ' ') goto missingarg; ++ c = getword(imapd_in, &arg); ++ size = 0; ++ for (p = arg.s; *p && isdigit((int) *p); p++) { ++ size = size * 10 + *p - '0'; ++ /* if (size < 0) goto badnumber; */ ++ } ++ if (!arg.s || *p) goto badnumber; ++ if (size == 0) size = 1; ++ if (!searchargs->smaller || size < searchargs->smaller) ++ searchargs->smaller = size; ++ } ++ else if (!strcmp(criteria.s, "subject")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(&searchargs->subject, str); ++ } ++ } ++ else goto badcri; ++ break; ++ ++ case 't': ++ if (!strcmp(criteria.s, "to")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(&searchargs->to, str); ++ } ++ } ++ else if (!strcmp(criteria.s, "text")) { ++ if (c != ' ') goto missingarg; ++ c = getastring(imapd_in, imapd_out, &arg); ++ if (c == EOF) goto missingarg; ++ str = charset_convert(arg.s, *charset, NULL, 0); ++ if (strchr(str, EMPTY)) { ++ /* Force failure */ ++ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); ++ } ++ else { ++ appendstrlistpat(&searchargs->text, str); ++ } ++ } ++ else goto badcri; ++ break; ++ ++ case 'u': ++ if (!strcmp(criteria.s, "uid")) { ++ if (c != ' ') goto missingarg; ++ c = getword(imapd_in, &arg); ++ if (!imparse_issequence(arg.s)) goto badcri; ++ appendstrlist(&searchargs->uidsequence, arg.s); ++ } ++ else if (!strcmp(criteria.s, "unseen")) { ++ searchargs->flags |= SEARCH_SEEN_UNSET; ++ } ++ else if (!strcmp(criteria.s, "unanswered")) { ++ searchargs->system_flags_unset |= FLAG_ANSWERED; ++ } ++ else if (!strcmp(criteria.s, "undeleted")) { ++ searchargs->system_flags_unset |= FLAG_DELETED; ++ } ++ else if (!strcmp(criteria.s, "undraft")) { ++ searchargs->system_flags_unset |= FLAG_DRAFT; ++ } ++ else if (!strcmp(criteria.s, "unflagged")) { ++ searchargs->system_flags_unset |= FLAG_FLAGGED; ++ } ++ else if (!strcmp(criteria.s, "unkeyword")) { ++ if (c != ' ') goto missingarg; ++ c = getword(imapd_in, &arg); ++ if (!imparse_isatom(arg.s)) goto badflag; ++ lcase(arg.s); ++ for (flag=0; flag < MAX_USER_FLAGS; flag++) { ++ if (imapd_mailbox->flagname[flag] && ++ !strcasecmp(imapd_mailbox->flagname[flag], arg.s)) break; ++ } ++ if (flag != MAX_USER_FLAGS) { ++ searchargs->user_flags_unset[flag/32] |= 1<<(flag&31); ++ } ++ } ++ else goto badcri; ++ break; ++ ++ default: ++ badcri: ++ prot_printf(imapd_out, "%s BAD Invalid Search criteria\r\n", tag); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ } ++ ++ return c; ++ ++ missingarg: ++ prot_printf(imapd_out, "%s BAD Missing required argument to Search %s\r\n", ++ tag, criteria.s); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ ++ badflag: ++ prot_printf(imapd_out, "%s BAD Invalid flag name %s in Search command\r\n", ++ tag, arg.s); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ ++ baddate: ++ prot_printf(imapd_out, "%s BAD Invalid date in Search command\r\n", tag); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ ++ badnumber: ++ prot_printf(imapd_out, "%s BAD Invalid number in Search command\r\n", tag); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++} ++ ++void cmd_dump(char *tag, char *name, int uid_start) ++{ ++ int r = 0; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ char *path, *mpath, *acl; ++ ++ /* administrators only please */ ++ if (!imapd_userisadmin) { ++ r = IMAP_PERMISSION_DENIED; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, NULL, &path, &mpath, ++ NULL, &acl, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if(!r) { ++ r = dump_mailbox(tag, mailboxname, path, mpath, acl, uid_start, ++ imapd_in, imapd_out, imapd_authstate); ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++} ++ ++void cmd_undump(char *tag, char *name) ++{ ++ int r = 0; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ char *path, *mpath, *acl; ++ ++ /* administrators only please */ ++ if (!imapd_userisadmin) { ++ r = IMAP_PERMISSION_DENIED; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, NULL, &path, &mpath, ++ NULL, &acl, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if(!r) { ++ /* save this stuff from additional mlookups */ ++ char *safe_path = xstrdup(path); ++ char *safe_mpath = mpath ? xstrdup(mpath) : NULL; ++ char *safe_acl = xstrdup(acl); ++ r = undump_mailbox(mailboxname, safe_path, safe_mpath, safe_acl, ++ imapd_in, imapd_out, imapd_authstate); ++ free(safe_path); ++ if (safe_mpath) free(safe_mpath); ++ free(safe_acl); ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s%s\r\n", ++ tag, ++ (r == IMAP_MAILBOX_NONEXISTENT && ++ mboxlist_createmailboxcheck(mailboxname, 0, 0, ++ imapd_userisadmin, ++ imapd_userid, imapd_authstate, ++ NULL, NULL) == 0) ++ ? "[TRYCREATE] " : "", error_message(r)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++} ++ ++static int getresult(struct protstream *p, char *tag) ++{ ++ char buf[4096]; ++ char *str = (char *) buf; ++ ++ while(1) { ++ if (!prot_fgets(str, sizeof(buf), p)) { ++ return IMAP_SERVER_UNAVAILABLE; ++ } ++ if (!strncmp(str, tag, strlen(tag))) { ++ str += strlen(tag); ++ if(!*str) { ++ /* We got a tag, but no response */ ++ return IMAP_SERVER_UNAVAILABLE; ++ } ++ str++; ++ if (!strncasecmp(str, "OK ", 3)) { return 0; } ++ if (!strncasecmp(str, "NO ", 3)) { return IMAP_REMOTE_DENIED; } ++ return IMAP_SERVER_UNAVAILABLE; /* huh? */ ++ } ++ /* skip this line, we don't really care */ ++ } ++} ++ ++/* given 2 protstreams and a mailbox, gets the acl and then wipes it */ ++static int trashacl(struct protstream *pin, struct protstream *pout, ++ char *mailbox) ++{ ++ int i=0, j=0; ++ char tagbuf[128]; ++ int c; /* getword() returns an int */ ++ struct buf tag, 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)); ++ ++ prot_printf(pout, "ACL0 GETACL {%d+}\r\n%s\r\n", ++ strlen(mailbox), mailbox); ++ ++ while(1) { ++ c = getword(pin, &tag); ++ if (c == EOF) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ break; ++ } ++ ++ 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)) { ++ while(c != '\n') { ++ /* An ACL response, we should send a DELETEACL command */ ++ c = getastring(pin, pout, &tmp); ++ if (c == EOF) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ goto cleanup; ++ } ++ ++ if(c == '\r') { ++ c = prot_getc(pin); ++ if(c != '\n') { ++ r = IMAP_SERVER_UNAVAILABLE; ++ goto cleanup; ++ } ++ } ++ if(c == '\n') goto cleanup; ++ ++ c = getastring(pin, pout, &user); ++ if (c == EOF) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ goto cleanup; ++ } ++ ++ snprintf(tagbuf, sizeof(tagbuf), "ACL%d", ++i); ++ ++ prot_printf(pout, "%s DELETEACL {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tagbuf, strlen(mailbox), mailbox, ++ strlen(user.s), user.s); ++ if(c == '\r') { ++ c = prot_getc(pin); ++ if(c != '\n') { ++ r = IMAP_SERVER_UNAVAILABLE; ++ goto cleanup; ++ } ++ } ++ /* 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; ++ } ++ } ++ ++ cleanup: ++ ++ /* 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++; ++ } ++ } ++ } ++ ++ if(r) eatline(pin, c); ++ ++ freebuf(&user); ++ freebuf(&tmp); ++ freebuf(&cmd); ++ freebuf(&tag); ++ ++ return r; ++} ++ ++static int dumpacl(struct protstream *pin, struct protstream *pout, ++ char *mailbox, char *acl_in) ++{ ++ int r = 0; ++ int c; /* getword() returns an int */ ++ char tag[128]; ++ int tagnum = 1; ++ char *rights, *nextid; ++ int mailboxlen = strlen(mailbox); ++ 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'); ++ if (!rights) break; ++ *rights++ = '\0'; ++ ++ nextid = strchr(rights, '\t'); ++ if (!nextid) break; ++ *nextid++ = '\0'; ++ ++ snprintf(tag, sizeof(tag), "SACL%d", tagnum++); ++ ++ prot_printf(pout, "%s SETACL {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n", ++ tag, ++ mailboxlen, mailbox, ++ 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; ++ } ++ ++ /* Eat the line and get the next one */ ++ eatline(pin, c); ++ acl = nextid; ++ } ++ ++ freebuf(&inbuf); ++ if(acl_safe) free(acl_safe); ++ ++ return r; ++} ++ ++static int do_xfer_single(char *toserver, char *topart, ++ char *name, char *mailboxname, ++ int mbflags, ++ char *path, char *mpath, char *part, char *acl, ++ int prereserved, ++ mupdate_handle *h_in, ++ struct backend *be_in) ++{ ++ int r = 0, rerr = 0; ++ char buf[MAX_PARTITION_LEN+HOSTNAME_SIZE+2]; ++ struct backend *be = NULL; ++ mupdate_handle *mupdate_h = NULL; ++ int backout_mupdate = 0; ++ int backout_remotebox = 0; ++ int backout_remoteflag = 0; ++ ++ /* Make sure we're given a sane value */ ++ if(topart && !imparse_isatom(topart)) { ++ return IMAP_PARTITION_UNKNOWN; ++ } ++ ++ if(!strcmp(toserver, config_servername)) { ++ return IMAP_BAD_SERVER; ++ } ++ ++ /* Okay, we have the mailbox, now the order of steps is: ++ * ++ * 1) Connect to remote server. ++ * 2) LOCALCREATE on remote server ++ * 2.5) Set mailbox as REMOTE on local server ++ * 3) mupdate.DEACTIVATE(mailbox, remoteserver) xxx what partition? ++ * 4) undump mailbox from local to remote ++ * 5) Sync remote acl ++ * 6) mupdate.ACTIVATE(mailbox, remoteserver) ++ * ** MAILBOX NOW LIVING ON REMOTE SERVER ++ * 6.5) force remote server to push the final mupdate entry to ensure ++ * that the state of the world is correct (required if we do not ++ * know the remote partition, but worst case it will be caught ++ * when they next sync) ++ * 7) local delete of mailbox ++ * 8) remove local remote mailbox entry?????? ++ */ ++ ++ /* Step 1: Connect to remote server */ ++ if(!r && !be_in) { ++ /* Just authorize as the IMAP server, so pass "" as our authzid */ ++ be = backend_connect(NULL, toserver, &protocol[PROTOCOL_IMAP], ++ "", NULL, NULL); ++ if(!be) r = IMAP_SERVER_UNAVAILABLE; ++ if(r) syslog(LOG_ERR, ++ "Could not move mailbox: %s, Backend connect failed", ++ mailboxname); ++ } else if(!r) { ++ be = be_in; ++ } ++ ++ /* Step 1a: Connect to mupdate (as needed) */ ++ if(h_in) { ++ mupdate_h = h_in; ++ } else if (config_mupdate_server) { ++ r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL); ++ if(r) { ++ syslog(LOG_ERR, ++ "Could not move mailbox: %s, MUPDATE connect failed", ++ mailboxname); ++ goto done; ++ } ++ ++ } ++ ++ /* Step 2: LOCALCREATE on remote server */ ++ if(!r) { ++ if(topart) { ++ /* need to send partition as an atom */ ++ prot_printf(be->out, "LC1 LOCALCREATE {%d+}\r\n%s %s\r\n", ++ strlen(name), name, topart); ++ } else { ++ prot_printf(be->out, "LC1 LOCALCREATE {%d+}\r\n%s\r\n", ++ strlen(name), name); ++ } ++ r = getresult(be->in, "LC1"); ++ if(r) syslog(LOG_ERR, "Could not move mailbox: %s, LOCALCREATE failed", ++ mailboxname); ++ else backout_remotebox = 1; ++ } ++ ++ /* Step 2.5: Set mailbox as REMOTE on local server */ ++ if(!r) { ++ snprintf(buf, sizeof(buf), "%s!%s", toserver, part); ++ r = mboxlist_update(mailboxname, mbflags|MBTYPE_MOVING, buf, acl, 1); ++ if(r) syslog(LOG_ERR, "Could not move mailbox: %s, " \ ++ "mboxlist_update failed", mailboxname); ++ } ++ ++ /* Step 3: mupdate.DEACTIVATE(mailbox, newserver) */ ++ /* (only if mailbox has not been already deactivated by our caller) */ ++ if(!r && mupdate_h && !prereserved) { ++ backout_remoteflag = 1; ++ ++ /* Note we are making the reservation on OUR host so that recovery ++ * make sense */ ++ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part); ++ r = mupdate_deactivate(mupdate_h, mailboxname, buf); ++ if(r) syslog(LOG_ERR, ++ "Could not move mailbox: %s, MUPDATE DEACTIVATE failed", ++ mailboxname); ++ } ++ ++ /* Step 4: Dump local -> remote */ ++ if(!r) { ++ backout_mupdate = 1; ++ ++ prot_printf(be->out, "D01 UNDUMP {%d+}\r\n%s ", strlen(name), name); ++ ++ r = dump_mailbox(NULL, mailboxname, path, mpath, acl, ++ 0, be->in, be->out, imapd_authstate); ++ ++ if(r) ++ syslog(LOG_ERR, ++ "Could not move mailbox: %s, dump_mailbox() failed", ++ mailboxname); ++ } ++ ++ if(!r) { ++ r = getresult(be->in, "D01"); ++ if(r) syslog(LOG_ERR, "Could not move mailbox: %s, UNDUMP failed", ++ mailboxname); ++ } ++ ++ /* Step 5: Set ACL on remote */ ++ if(!r) { ++ r = trashacl(be->in, be->out, name); ++ if(r) syslog(LOG_ERR, "Could not clear remote acl on %s", ++ mailboxname); ++ } ++ if(!r) { ++ r = dumpacl(be->in, be->out, name, acl); ++ if(r) syslog(LOG_ERR, "Could not set remote acl on %s", ++ mailboxname); ++ } ++ ++ /* Step 6: mupdate.activate(mailbox, remote) */ ++ /* We do this from the local server first so that recovery is easier */ ++ if(!r && mupdate_h) { ++ /* Note the flag that we don't have a valid partiton at the moment */ ++ snprintf(buf, sizeof(buf), "%s!MOVED", toserver); ++ r = mupdate_activate(mupdate_h, mailboxname, buf, acl); ++ } ++ ++ /* MAILBOX NOW LIVES ON REMOTE */ ++ if(!r) { ++ backout_remotebox = 0; ++ backout_mupdate = 0; ++ backout_remoteflag = 0; ++ ++ /* 6.5) Kick remote server to correct mupdate entry */ ++ /* Note that we don't really care if this succeeds or not */ ++ if (mupdate_h) { ++ prot_printf(be->out, "MP1 MUPDATEPUSH {%d+}\r\n%s\r\n", ++ strlen(name), name); ++ rerr = getresult(be->in, "MP1"); ++ if(rerr) { ++ syslog(LOG_ERR, ++ "Could not trigger remote push to mupdate server" \ ++ "during move of %s", ++ mailboxname); ++ } ++ } ++ } ++ ++ /* 7) local delete of mailbox ++ * & remove local "remote" mailboxlist entry */ ++ if(!r) { ++ /* Note that we do not check the ACL, and we don't update MUPDATE */ ++ /* note also that we need to remember to let proxyadmins do this */ ++ r = mboxlist_deletemailbox(mailboxname, ++ imapd_userisadmin || imapd_userisproxyadmin, ++ imapd_userid, imapd_authstate, 0, 1, 0); ++ if(r) syslog(LOG_ERR, ++ "Could not delete local mailbox during move of %s", ++ mailboxname); ++ ++ if (!r) { ++ /* Delete mailbox annotations */ ++ annotatemore_delete(mailboxname); ++ } ++ } ++ ++done: ++ if(r && mupdate_h && backout_mupdate) { ++ rerr = 0; ++ /* xxx if the mupdate server is what failed, then this won't ++ help any! */ ++ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part); ++ rerr = mupdate_activate(mupdate_h, mailboxname, buf, acl); ++ if(rerr) { ++ syslog(LOG_ERR, ++ "Could not back out mupdate during move of %s (%s)", ++ mailboxname, error_message(rerr)); ++ } ++ } ++ if(r && backout_remotebox) { ++ rerr = 0; ++ prot_printf(be->out, "LD1 LOCALDELETE {%d+}\r\n%s\r\n", ++ strlen(name), name); ++ rerr = getresult(be->in, "LD1"); ++ if(rerr) { ++ syslog(LOG_ERR, ++ "Could not back out remote mailbox during move of %s (%s)", ++ name, error_message(rerr)); ++ } ++ } ++ if(r && backout_remoteflag) { ++ rerr = 0; ++ ++ rerr = mboxlist_update(mailboxname, mbflags, part, acl, 1); ++ if(rerr) syslog(LOG_ERR, "Could not unset remote flag on mailbox: %s", ++ mailboxname); ++ } ++ ++ /* release the handles we got locally if necessary */ ++ if(mupdate_h && !h_in) ++ mupdate_disconnect(&mupdate_h); ++ if(be && !be_in) ++ backend_disconnect(be); ++ ++ return r; ++} ++ ++struct xfer_user_rock ++{ ++ char *toserver; ++ char *topart; ++ mupdate_handle *h; ++ struct backend *be; ++}; ++ ++static int xfer_user_cb(char *name, ++ int matchlen __attribute__((unused)), ++ int maycreate __attribute__((unused)), ++ void *rock) ++{ ++ mupdate_handle *mupdate_h = ((struct xfer_user_rock *)rock)->h; ++ char *toserver = ((struct xfer_user_rock *)rock)->toserver; ++ char *topart = ((struct xfer_user_rock *)rock)->topart; ++ struct backend *be = ((struct xfer_user_rock *)rock)->be; ++ char externalname[MAX_MAILBOX_NAME+1]; ++ int mbflags; ++ int r = 0; ++ char *inpath, *inmpath, *inpart, *inacl; ++ char *path = NULL, *mpath = NULL, *part = NULL, *acl = NULL; ++ ++ if (!r) { ++ /* NOTE: NOT mlookup() because we don't want to issue a referral */ ++ /* xxx but what happens if they are remote ++ * mailboxes? */ ++ r = mboxlist_detail(name, &mbflags, ++ &inpath, &inmpath, &inpart, &inacl, NULL); ++ } ++ ++ if (!r) { ++ path = xstrdup(inpath); ++ if (inmpath) mpath = xstrdup(inmpath); ++ part = xstrdup(inpart); ++ acl = xstrdup(inacl); ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, ++ name, ++ imapd_userid, ++ externalname); ++ } ++ ++ if(!r) { ++ r = do_xfer_single(toserver, topart, externalname, name, mbflags, ++ path, mpath, part, acl, 0, mupdate_h, be); ++ } ++ ++ if(path) free(path); ++ if(mpath) free(mpath); ++ if(part) free(part); ++ if(acl) free(acl); ++ ++ return r; ++} ++ ++ ++void cmd_xfer(char *tag, char *name, char *toserver, char *topart) ++{ ++ int r = 0; ++ char buf[MAX_PARTITION_LEN+HOSTNAME_SIZE+2]; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ int mbflags; ++ int moving_user = 0; ++ int backout_mupdate = 0; ++ mupdate_handle *mupdate_h = NULL; ++ char *inpath, *inmpath, *inpart, *inacl; ++ char *path = NULL, *mpath = NULL, *part = NULL, *acl = NULL; ++ char *p, *mbox = mailboxname; ++ ++ /* administrators only please */ ++ /* however, proxys can do this, if their authzid is an admin */ ++ if (!imapd_userisadmin && !imapd_userisproxyadmin) { ++ r = IMAP_PERMISSION_DENIED; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, ++ name, ++ imapd_userid, ++ mailboxname); ++ } ++ ++ /* NOTE: Since XFER can only be used by an admin, and we always connect ++ * to the destination backend as an admin, we take advantage of the fact ++ * that admins *always* use a consistent mailbox naming scheme. ++ * So, 'name' should be used in any command we send to a backend, and ++ * 'mailboxname' is the internal name to be used for mupdate and findall. ++ */ ++ ++ if (config_virtdomains && (p = strchr(mailboxname, '!'))) { ++ /* pointer to mailbox w/o domain prefix */ ++ mbox = p + 1; ++ } ++ ++ if(!strncmp(mbox, "user.", 5) && !strchr(mbox+5, '.')) { ++ if ((strlen(mbox+5) == (strlen(imapd_userid) - (mbox - mailboxname))) && ++ !strncmp(mbox+5, imapd_userid, strlen(mbox+5))) { ++ /* don't move your own inbox, that could be troublesome */ ++ r = IMAP_MAILBOX_NOTSUPPORTED; ++ } else if (!config_getswitch(IMAPOPT_ALLOWUSERMOVES)) { ++ /* not configured to allow user moves */ ++ r = IMAP_MAILBOX_NOTSUPPORTED; ++ } else { ++ moving_user = 1; ++ } ++ } ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, &mbflags, ++ &inpath, &inmpath, &inpart, &inacl, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ if (!r) { ++ path = xstrdup(inpath); ++ if (inmpath) mpath = xstrdup(inmpath); ++ part = xstrdup(inpart); ++ acl = xstrdup(inacl); ++ } ++ ++ /* if we are not moving a user, just move the one mailbox */ ++ if(!r && !moving_user) { ++ r = do_xfer_single(toserver, topart, name, mailboxname, mbflags, ++ path, mpath, part, acl, 0, NULL, NULL); ++ } else if (!r) { ++ struct backend *be = NULL; ++ ++ /* we need to reserve the users inbox - connect to mupdate */ ++ if(!r && config_mupdate_server) { ++ r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL); ++ if(r) { ++ syslog(LOG_ERR, ++ "Could not move mailbox: %s, MUPDATE connect failed", ++ mailboxname); ++ goto done; ++ } ++ } ++ ++ /* Get a single connection to the remote backend */ ++ be = backend_connect(NULL, toserver, &protocol[PROTOCOL_IMAP], ++ "", NULL, NULL); ++ if(!be) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ syslog(LOG_ERR, ++ "Could not move mailbox: %s, " \ ++ "Initial backend connect failed", ++ mailboxname); ++ } ++ ++ /* deactivate their inbox */ ++ if(!r && mupdate_h) { ++ /* Note we are making the reservation on OUR host so that recovery ++ * make sense */ ++ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part); ++ r = mupdate_deactivate(mupdate_h, mailboxname, buf); ++ if(r) syslog(LOG_ERR, ++ "Could deactivate mailbox: %s, during move", ++ mailboxname); ++ else backout_mupdate = 1; ++ } ++ ++ /* If needed, set an uppermost quota root */ ++ if(!r) { ++ struct quota quota; ++ ++ quota.root = mailboxname; ++ r = quota_read("a, NULL, 0); ++ ++ if(!r) { ++ /* note use of + to force the setting of a nonexistant ++ * quotaroot */ ++ prot_printf(be->out, "Q01 SETQUOTA {%d+}\r\n" \ ++ "+%s (STORAGE %d)\r\n", ++ strlen(name)+1, name, quota.limit); ++ r = getresult(be->in, "Q01"); ++ if(r) syslog(LOG_ERR, ++ "Could not move mailbox: %s, " \ ++ "failed setting initial quota root\r\n", ++ mailboxname); ++ } ++ else if (r == IMAP_QUOTAROOT_NONEXISTENT) r = 0; ++ } ++ ++ ++ /* recursively move all sub-mailboxes, using internal names */ ++ if(!r) { ++ struct xfer_user_rock rock; ++ ++ rock.toserver = toserver; ++ rock.topart = topart; ++ rock.h = mupdate_h; ++ rock.be = be; ++ ++ snprintf(buf, sizeof(buf), "%s.*", mailboxname); ++ r = mboxlist_findall(NULL, buf, 1, imapd_userid, ++ imapd_authstate, xfer_user_cb, ++ &rock); ++ } ++ ++ /* xxx how do you back out if one of the above moves fails? */ ++ ++ /* move this mailbox */ ++ /* ...and seen file, and subs file, and sieve scripts... */ ++ if(!r) { ++ r = do_xfer_single(toserver, topart, name, mailboxname, mbflags, ++ path, mpath, part, acl, 1, mupdate_h, be); ++ } ++ ++ if(be) { ++ backend_disconnect(be); ++ free(be); ++ } ++ ++ if(r && mupdate_h && backout_mupdate) { ++ int rerr = 0; ++ /* xxx if the mupdate server is what failed, then this won't ++ help any! */ ++ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part); ++ rerr = mupdate_activate(mupdate_h, mailboxname, buf, acl); ++ if(rerr) { ++ syslog(LOG_ERR, ++ "Could not back out mupdate during move of %s (%s)", ++ mailboxname, error_message(rerr)); ++ } ++ } else if(!r) { ++ /* this was a successful user delete, and we need to delete ++ certain user meta-data (but not seen state!) */ ++ user_deletedata(mailboxname+5, imapd_userid, imapd_authstate, 0); ++ } ++ ++ if(!r && mupdate_h) { ++ mupdate_disconnect(&mupdate_h); ++ } ++ } ++ ++ done: ++ if(part) free(part); ++ if(path) free(path); ++ if(mpath) free(mpath); ++ if(acl) free(acl); ++ ++ imapd_check(NULL, 0, 0); ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", ++ tag, ++ error_message(r)); ++ } else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++ ++ return; ++} ++ ++/* ++ * Parse a "date", for SEARCH criteria ++ * The time_t's pointed to by 'start' and 'end' are set to the ++ * times of the start and end of the parsed date. ++ */ ++int getsearchdate(start, end) ++time_t *start, *end; ++{ ++ int c; ++ struct tm tm; ++ int quoted = 0; ++ char month[4]; ++ ++ memset(&tm, 0, sizeof tm); ++ ++ c = prot_getc(imapd_in); ++ if (c == '\"') { ++ quoted++; ++ c = prot_getc(imapd_in); ++ } ++ ++ /* Day of month */ ++ if (!isdigit(c)) goto baddate; ++ tm.tm_mday = c - '0'; ++ c = prot_getc(imapd_in); ++ if (isdigit(c)) { ++ tm.tm_mday = tm.tm_mday * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ } ++ ++ if (c != '-') goto baddate; ++ c = prot_getc(imapd_in); ++ ++ /* Month name */ ++ if (!isalpha(c)) goto baddate; ++ month[0] = c; ++ c = prot_getc(imapd_in); ++ if (!isalpha(c)) goto baddate; ++ month[1] = c; ++ c = prot_getc(imapd_in); ++ if (!isalpha(c)) goto baddate; ++ month[2] = c; ++ c = prot_getc(imapd_in); ++ month[3] = '\0'; ++ lcase(month); ++ ++ for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { ++ if (!strcmp(month, monthname[tm.tm_mon])) break; ++ } ++ if (tm.tm_mon == 12) goto baddate; ++ ++ if (c != '-') goto baddate; ++ c = prot_getc(imapd_in); ++ ++ /* Year */ ++ if (!isdigit(c)) goto baddate; ++ tm.tm_year = c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_year = tm.tm_year * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (isdigit(c)) { ++ if (tm.tm_year < 19) goto baddate; ++ tm.tm_year -= 19; ++ tm.tm_year = tm.tm_year * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_year = tm.tm_year * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ } ++ ++ if (quoted) { ++ if (c != '\"') goto baddate; ++ c = prot_getc(imapd_in); ++ } ++ ++ tm.tm_isdst = -1; ++ *start = mktime(&tm); ++ ++ tm.tm_sec = tm.tm_min = 59; ++ tm.tm_hour = 23; ++ tm.tm_isdst = -1; ++ *end = mktime(&tm); ++ ++ return c; ++ ++ baddate: ++ prot_ungetc(c, imapd_in); ++ return EOF; ++} ++ ++#define SORTGROWSIZE 10 ++ ++/* ++ * Parse sort criteria ++ */ ++int getsortcriteria(char *tag, struct sortcrit **sortcrit) ++{ ++ int c; ++ static struct buf criteria; ++ int nsort, n; ++ ++ *sortcrit = NULL; ++ ++ c = prot_getc(imapd_in); ++ if (c != '(') goto missingcrit; ++ ++ c = getword(imapd_in, &criteria); ++ if (criteria.s[0] == '\0') goto missingcrit; ++ ++ nsort = 0; ++ n = 0; ++ for (;;) { ++ if (n >= nsort - 1) { /* leave room for implicit criterion */ ++ /* (Re)allocate an array for sort criteria */ ++ nsort += SORTGROWSIZE; ++ *sortcrit = ++ (struct sortcrit *) xrealloc(*sortcrit, ++ nsort * sizeof(struct sortcrit)); ++ /* Zero out the newly added sortcrit */ ++ memset((*sortcrit)+n, 0, SORTGROWSIZE * sizeof(struct sortcrit)); ++ } ++ ++ lcase(criteria.s); ++ if (!strcmp(criteria.s, "reverse")) { ++ (*sortcrit)[n].flags |= SORT_REVERSE; ++ goto nextcrit; ++ } ++ else if (!strcmp(criteria.s, "arrival")) ++ (*sortcrit)[n].key = SORT_ARRIVAL; ++ else if (!strcmp(criteria.s, "cc")) ++ (*sortcrit)[n].key = SORT_CC; ++ else if (!strcmp(criteria.s, "date")) ++ (*sortcrit)[n].key = SORT_DATE; ++ else if (!strcmp(criteria.s, "from")) ++ (*sortcrit)[n].key = SORT_FROM; ++ else if (!strcmp(criteria.s, "size")) ++ (*sortcrit)[n].key = SORT_SIZE; ++ else if (!strcmp(criteria.s, "subject")) ++ (*sortcrit)[n].key = SORT_SUBJECT; ++ else if (!strcmp(criteria.s, "to")) ++ (*sortcrit)[n].key = SORT_TO; ++#if 0 ++ else if (!strcmp(criteria.s, "annotation")) { ++ (*sortcrit)[n].key = SORT_ANNOTATION; ++ if (c != ' ') goto missingarg; ++ c = getstring(imapd_in, &arg); ++ if (c != ' ') goto missingarg; ++ (*sortcrit)[n].args.annot.entry = xstrdup(arg.s); ++ c = getstring(imapd_in, &arg); ++ if (c == EOF) goto missingarg; ++ (*sortcrit)[n].args.annot.attrib = xstrdup(arg.s); ++ } ++#endif ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid Sort criterion %s\r\n", ++ tag, criteria.s); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ } ++ ++ n++; ++ ++ nextcrit: ++ if (c == ' ') c = getword(imapd_in, &criteria); ++ else break; ++ } ++ ++ if ((*sortcrit)[n].flags & SORT_REVERSE && !(*sortcrit)[n].key) { ++ prot_printf(imapd_out, ++ "%s BAD Missing Sort criterion to reverse\r\n", tag); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ } ++ ++ if (c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Missing close parenthesis in Sort\r\n", tag); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++ } ++ ++ /* Terminate the list with the implicit sort criterion */ ++ (*sortcrit)[n++].key = SORT_SEQUENCE; ++ ++ c = prot_getc(imapd_in); ++ ++ return c; ++ ++ missingcrit: ++ prot_printf(imapd_out, "%s BAD Missing Sort criteria\r\n", tag); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++#if 0 /* For annotations stuff above */ ++ missingarg: ++ prot_printf(imapd_out, "%s BAD Missing argument to Sort criterion %s\r\n", ++ tag, criteria.s); ++ if (c != EOF) prot_ungetc(c, imapd_in); ++ return EOF; ++#endif ++} ++ ++#ifdef ENABLE_LISTEXT ++/* ++ * Parse LIST options. ++ * The command has been parsed up to and including the opening '('. ++ */ ++int getlistopts(char *tag, int *listopts) ++{ ++ int c; ++ static struct buf arg; ++ ++ *listopts = LIST_EXT; ++ ++ for (;;) { ++ c = getword(imapd_in, &arg); ++ if (!arg.s[0]) break; ++ ++ lcase(arg.s); ++ if (!strcmp(arg.s, "subscribed")) { ++ *listopts |= LIST_SUBSCRIBED; ++ } ++ else if (!strcmp(arg.s, "children")) { ++ *listopts |= LIST_CHILDREN; ++ } ++ else if (!strcmp(arg.s, "remote")) { ++ *listopts |= LIST_REMOTE; ++ } ++ else { ++ prot_printf(imapd_out, "%s BAD Invalid List option %s\r\n", ++ tag, arg.s); ++ return EOF; ++ } ++ ++ if (c != ' ') break; ++ } ++ ++ if (c != ')') { ++ prot_printf(imapd_out, ++ "%s BAD Missing close parenthesis in List\r\n", tag); ++ return EOF; ++ } ++ ++ c = prot_getc(imapd_in); ++ ++ return c; ++} ++#endif /* ENABLE_LISTEXT */ ++ ++/* ++ * Parse a date_time, for the APPEND command ++ */ ++int getdatetime(date) ++time_t *date; ++{ ++ int c; ++ struct tm tm; ++ int old_format = 0; ++ char month[4], zone[4], *p; ++ time_t tmp_gmtime; ++ int zone_off; ++ ++ memset(&tm, 0, sizeof tm); ++ ++ c = prot_getc(imapd_in); ++ if (c != '\"') goto baddate; ++ ++ /* Day of month */ ++ c = prot_getc(imapd_in); ++ if (c == ' ') c = '0'; ++ if (!isdigit(c)) goto baddate; ++ tm.tm_mday = c - '0'; ++ c = prot_getc(imapd_in); ++ if (isdigit(c)) { ++ tm.tm_mday = tm.tm_mday * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if(tm.tm_mday <= 0 || tm.tm_mday > 31) ++ goto baddate; ++ } ++ ++ if (c != '-') goto baddate; ++ c = prot_getc(imapd_in); ++ ++ /* Month name */ ++ if (!isalpha(c)) goto baddate; ++ month[0] = c; ++ c = prot_getc(imapd_in); ++ if (!isalpha(c)) goto baddate; ++ month[1] = c; ++ c = prot_getc(imapd_in); ++ if (!isalpha(c)) goto baddate; ++ month[2] = c; ++ c = prot_getc(imapd_in); ++ month[3] = '\0'; ++ lcase(month); ++ ++ for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { ++ if (!strcmp(month, monthname[tm.tm_mon])) break; ++ } ++ if (tm.tm_mon == 12) goto baddate; ++ /* xxx this doesn't quite work in leap years */ ++ if (tm.tm_mday > max_monthdays[tm.tm_mon]) goto baddate; ++ ++ if (c != '-') goto baddate; ++ c = prot_getc(imapd_in); ++ ++ /* Year */ ++ if (!isdigit(c)) goto baddate; ++ tm.tm_year = c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_year = tm.tm_year * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (isdigit(c)) { ++ if (tm.tm_year < 19) goto baddate; ++ tm.tm_year -= 19; ++ tm.tm_year = tm.tm_year * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_year = tm.tm_year * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ } ++ else old_format++; ++ ++ /* Hour */ ++ if (c != ' ') goto baddate; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_hour = c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_hour = tm.tm_hour * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (tm.tm_hour > 23) goto baddate; ++ ++ /* Minute */ ++ if (c != ':') goto baddate; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_min = c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_min = tm.tm_min * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (tm.tm_min > 59) goto baddate; ++ ++ /* Second */ ++ if (c != ':') goto baddate; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_sec = c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ tm.tm_sec = tm.tm_sec * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (tm.tm_min > 60) goto baddate; ++ ++ /* Time zone */ ++ if (old_format) { ++ if (c != '-') goto baddate; ++ c = prot_getc(imapd_in); ++ ++ if (!isalpha(c)) goto baddate; ++ zone[0] = c; ++ c = prot_getc(imapd_in); ++ ++ if (c == '\"') { ++ /* Military (single-char) zones */ ++ zone[1] = '\0'; ++ lcase(zone); ++ if (zone[0] <= 'm') { ++ zone_off = (zone[0] - 'a' + 1)*60; ++ } ++ else if (zone[0] < 'z') { ++ zone_off = ('m' - zone[0])*60; ++ } ++ else zone_off = 0; ++ } ++ else { ++ /* UT (universal time) */ ++ zone[1] = c; ++ c = prot_getc(imapd_in); ++ if (c == '\"') { ++ zone[2] = '\0'; ++ lcase(zone); ++ if (!strcmp(zone, "ut")) goto baddate; ++ zone_off = 0; ++ } ++ else { ++ /* 3-char time zone */ ++ zone[2] = c; ++ c = prot_getc(imapd_in); ++ if (c != '\"') goto baddate; ++ zone[3] = '\0'; ++ lcase(zone); ++ p = strchr("aecmpyhb", zone[0]); ++ if (c != '\"' || zone[2] != 't' || !p) goto baddate; ++ zone_off = (strlen(p) - 12)*60; ++ if (zone[1] == 'd') zone_off -= 60; ++ else if (zone[1] != 's') goto baddate; ++ } ++ } ++ } ++ else { ++ if (c != ' ') goto baddate; ++ c = prot_getc(imapd_in); ++ ++ if (c != '+' && c != '-') goto baddate; ++ zone[0] = c; ++ ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ zone_off = c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ zone_off = zone_off * 10 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ zone_off = zone_off * 6 + c - '0'; ++ c = prot_getc(imapd_in); ++ if (!isdigit(c)) goto baddate; ++ zone_off = zone_off * 10 + c - '0'; ++ ++ if (zone[0] == '-') zone_off = -zone_off; ++ ++ c = prot_getc(imapd_in); ++ if (c != '\"') goto baddate; ++ ++ } ++ ++ c = prot_getc(imapd_in); ++ ++ tm.tm_isdst = -1; ++ ++ tmp_gmtime = mkgmtime(&tm); ++ if(tmp_gmtime == -1) goto baddate; ++ ++ *date = tmp_gmtime - zone_off*60; ++ ++ return c; ++ ++ baddate: ++ prot_ungetc(c, imapd_in); ++ return EOF; ++} ++ ++/* ++ * Print 's' as a quoted-string or literal (but not an atom) ++ */ ++void ++printstring(s) ++const char *s; ++{ ++ const char *p; ++ int len = 0; ++ ++ /* Look for any non-QCHAR characters */ ++ for (p = s; *p && len < 1024; p++) { ++ len++; ++ if (*p & 0x80 || *p == '\r' || *p == '\n' ++ || *p == '\"' || *p == '%' || *p == '\\') break; ++ } ++ ++ /* if it's too long, literal it */ ++ if (*p || len >= 1024) { ++ prot_printf(imapd_out, "{%u}\r\n%s", strlen(s), s); ++ } else { ++ prot_printf(imapd_out, "\"%s\"", s); ++ } ++} ++ ++/* ++ * Print 's' as an atom, quoted-string, or literal ++ */ ++void ++printastring(s) ++const char *s; ++{ ++ const char *p; ++ int len = 0; ++ ++ if (imparse_isatom(s)) { ++ prot_printf(imapd_out, "%s", s); ++ return; ++ } ++ ++ /* Look for any non-QCHAR characters */ ++ for (p = s; *p && len < 1024; p++) { ++ len++; ++ if (*p & 0x80 || *p == '\r' || *p == '\n' ++ || *p == '\"' || *p == '%' || *p == '\\') break; ++ } ++ ++ /* if it's too long, literal it */ ++ if (*p || len >= 1024) { ++ prot_printf(imapd_out, "{%u}\r\n%s", strlen(s), s); ++ } else { ++ prot_printf(imapd_out, "\"%s\"", s); ++ } ++} ++ ++/* ++ * Append 'section', 'fields', 'trail' to the fieldlist 'l'. ++ */ ++void ++appendfieldlist(struct fieldlist **l, char *section, ++ struct strlist *fields, char *trail, ++ void *d, size_t size) ++{ ++ struct fieldlist **tail = l; ++ ++ while (*tail) tail = &(*tail)->next; ++ ++ *tail = (struct fieldlist *)xmalloc(sizeof(struct fieldlist)); ++ (*tail)->section = xstrdup(section); ++ (*tail)->fields = fields; ++ (*tail)->trail = xstrdup(trail); ++ if(d && size) { ++ (*tail)->rock = xmalloc(size); ++ memcpy((*tail)->rock, d, size); ++ } else { ++ (*tail)->rock = NULL; ++ } ++ (*tail)->next = 0; ++} ++ ++ ++/* ++ * Free the fieldlist 'l' ++ */ ++void freefieldlist(struct fieldlist *l) ++{ ++ struct fieldlist *n; ++ ++ while (l) { ++ n = l->next; ++ free(l->section); ++ freestrlist(l->fields); ++ free(l->trail); ++ if (l->rock) free(l->rock); ++ free((char *)l); ++ l = n; ++ } ++} ++ ++/* ++ * Append the searchargs 's1' and 's2' to the sublist of 's' ++ */ ++void ++appendsearchargs(s, s1, s2) ++struct searchargs *s, *s1, *s2; ++{ ++ struct searchsub **tail = &s->sublist; ++ ++ while (*tail) tail = &(*tail)->next; ++ ++ *tail = (struct searchsub *)xmalloc(sizeof(struct searchsub)); ++ (*tail)->sub1 = s1; ++ (*tail)->sub2 = s2; ++ (*tail)->next = 0; ++} ++ ++ ++/* ++ * Free the searchargs 's' ++ */ ++void ++freesearchargs(s) ++struct searchargs *s; ++{ ++ struct searchsub *sub, *n; ++ ++ if (!s) return; ++ ++ freestrlist(s->sequence); ++ freestrlist(s->uidsequence); ++ freestrlist(s->from); ++ freestrlist(s->to); ++ freestrlist(s->cc); ++ freestrlist(s->bcc); ++ freestrlist(s->subject); ++ freestrlist(s->body); ++ freestrlist(s->text); ++ freestrlist(s->header_name); ++ freestrlist(s->header); ++ ++ for (sub = s->sublist; sub; sub = n) { ++ n = sub->next; ++ freesearchargs(sub->sub1); ++ freesearchargs(sub->sub2); ++ free(sub); ++ } ++ free(s); ++} ++ ++/* ++ * Free an array of sortcrit ++ */ ++static void freesortcrit(struct sortcrit *s) ++{ ++ int i = 0; ++ ++ if (!s) return; ++ do { ++ switch (s[i].key) { ++ case SORT_ANNOTATION: ++ free(s[i].args.annot.entry); ++ free(s[i].args.annot.attrib); ++ break; ++ } ++ i++; ++ } while (s[i].key != SORT_SEQUENCE); ++ free(s); ++} ++ ++/* ++ * Issue a MAILBOX untagged response ++ */ ++static int mailboxdata(char *name, ++ int matchlen __attribute__((unused)), ++ int maycreate __attribute__((unused)), ++ void *rock __attribute__((unused))) ++{ ++ char mboxname[MAX_MAILBOX_PATH+1]; ++ ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, name, ++ imapd_userid, mboxname); ++ prot_printf(imapd_out, "* MAILBOX %s\r\n", mboxname); ++ return 0; ++} ++ ++/* ++ * Issue a LIST or LSUB untagged response ++ */ ++static void mstringdata(char *cmd, char *name, int matchlen, int maycreate, ++ int listopts) ++{ ++ static char lastname[MAX_MAILBOX_PATH+1]; ++ static int lastnamedelayed = 0; ++ static int lastnamenoinferiors = 0; ++ static int nonexistent = 0; ++ static int sawuser = 0; ++ int lastnamehassub = 0; ++ int c, mbtype; ++ char mboxname[MAX_MAILBOX_PATH+1]; ++ ++ /* We have to reset the sawuser flag before each list command. ++ * Handle it as a dirty hack. ++ */ ++ if (cmd == NULL) { ++ sawuser = 0; ++ mstringdatacalls = 0; ++ return; ++ } ++ mstringdatacalls++; ++ ++ if (lastnamedelayed) { ++ /* Check if lastname has children */ ++ if (name && strncmp(lastname, name, strlen(lastname)) == 0 && ++ name[strlen(lastname)] == '.') { ++ lastnamehassub = 1; ++ } ++ prot_printf(imapd_out, "* %s (", cmd); ++ if (nonexistent == IMAP_MAILBOX_RESERVED) { ++ /* LISTEXT wants \\PlaceHolder instead of \\Noselect */ ++ if (listopts & LIST_EXT) ++ prot_printf(imapd_out, "\\PlaceHolder"); ++ else ++ prot_printf(imapd_out, "\\Noselect"); ++ } else if (nonexistent) { ++ prot_printf(imapd_out, "\\NonExistent"); ++ } ++ if (lastnamenoinferiors) { ++ prot_printf(imapd_out, "%s\\Noinferiors", nonexistent ? " " : ""); ++ } ++ else if ((listopts & LIST_CHILDREN) && ++ /* we can't determine \HasNoChildren for subscriptions */ ++ (lastnamehassub || ++ !(listopts & (LIST_LSUB | LIST_SUBSCRIBED)))) { ++ prot_printf(imapd_out, "%s%s", nonexistent ? " " : "", ++ lastnamehassub ? "\\HasChildren" : "\\HasNoChildren"); ++ } ++ prot_printf(imapd_out, ") \"%c\" ", imapd_namespace.hier_sep); ++ ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, lastname, ++ imapd_userid, mboxname); ++ printstring(mboxname); ++ prot_printf(imapd_out, "\r\n"); ++ lastnamedelayed = lastnamenoinferiors = nonexistent = 0; ++ } ++ ++ /* Special-case to flush any final state */ ++ if (!name) { ++ lastname[0] = '\0'; ++ return; ++ } ++ ++ /* Suppress any output of a partial match */ ++ if ((name[matchlen] ++ && strncmp(lastname, name, matchlen) == 0 ++ && (lastname[matchlen] == '\0' || lastname[matchlen] == '.'))) { ++ return; ++ } ++ ++ /* ++ * We can get a partial match for "user" multiple times with ++ * other matches inbetween. Handle it as a special case ++ */ ++ if (matchlen == 4 && strncasecmp(name, "user", 4) == 0) { ++ if (sawuser) return; ++ sawuser = 1; ++ } ++ ++ strlcpy(lastname, name, sizeof(lastname)); ++ lastname[matchlen] = '\0'; ++ nonexistent = 0; ++ ++ /* Now we need to see if this mailbox exists */ ++ /* first convert "INBOX" to "user." */ ++ if (!strncasecmp(lastname, "inbox", 5)) { ++ (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, "INBOX", ++ imapd_userid, mboxname); ++ strlcat(mboxname, lastname+5, sizeof(mboxname)); ++ } ++ else ++ strlcpy(mboxname, lastname, sizeof(mboxname)); ++ ++ /* Look it up */ ++ nonexistent = mboxlist_detail(mboxname, &mbtype, ++ NULL, NULL, NULL, NULL, NULL); ++ if(!nonexistent && (mbtype & MBTYPE_RESERVE)) ++ nonexistent = IMAP_MAILBOX_RESERVED; ++ ++ if (!name[matchlen]) { ++ lastnamedelayed = 1; ++ if (!maycreate) lastnamenoinferiors = 1; ++ return; ++ } ++ ++ c = name[matchlen]; ++ if (c) name[matchlen] = '\0'; ++ prot_printf(imapd_out, "* %s (", cmd); ++ if (c) { ++ /* Handle namespace prefix as a special case */ ++ if (!strcmp(name, "user") || ++ !strcmp(name, imapd_namespace.prefix[NAMESPACE_SHARED])) { ++ prot_printf(imapd_out, "\\Noselect"); ++ if (listopts & LIST_EXT) ++ prot_printf(imapd_out, " \\PlaceHolder"); ++ } ++ else { ++ if (nonexistent) ++ prot_printf(imapd_out, "\\NonExistent"); ++ /* LISTEXT uses \PlaceHolder instead of \Noselect */ ++ if (listopts & LIST_EXT) ++ prot_printf(imapd_out, "%s\\PlaceHolder", nonexistent ? " " : ""); ++ else ++ prot_printf(imapd_out, "%s\\Noselect", nonexistent ? " " : ""); ++ } ++ if (listopts & LIST_CHILDREN) ++ prot_printf(imapd_out, " \\HasChildren"); ++ } ++ prot_printf(imapd_out, ") \"%c\" ", imapd_namespace.hier_sep); ++ ++ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, name, ++ imapd_userid, mboxname); ++ printstring(mboxname); ++ prot_printf(imapd_out, "\r\n"); ++ if (c) name[matchlen] = c; ++ return; ++} ++ ++/* ++ * Issue a LIST untagged response ++ */ ++static int listdata(char *name, int matchlen, int maycreate, void *rock) ++{ ++ int listopts = *((int *)rock); ++ ++ mstringdata(((listopts & LIST_LSUB) ? "LSUB" : "LIST"), ++ name, matchlen, maycreate, listopts); ++ ++ return 0; ++} ++ ++/* Reset the given sasl_conn_t to a sane state */ ++static int reset_saslconn(sasl_conn_t **conn) ++{ ++ int ret; ++ sasl_security_properties_t *secprops = NULL; ++ ++ sasl_dispose(conn); ++ /* do initialization typical of service_main */ ++ ret = sasl_server_new("imap", config_servername, ++ NULL, NULL, NULL, ++ NULL, 0, conn); ++ if(ret != SASL_OK) return ret; ++ ++ if(saslprops.ipremoteport) ++ ret = sasl_setprop(*conn, SASL_IPREMOTEPORT, ++ saslprops.ipremoteport); ++ if(ret != SASL_OK) return ret; ++ ++ if(saslprops.iplocalport) ++ ret = sasl_setprop(*conn, SASL_IPLOCALPORT, ++ saslprops.iplocalport); ++ if(ret != SASL_OK) return ret; ++ ++ secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT); ++ ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops); ++ if(ret != SASL_OK) return ret; ++ /* end of service_main initialization excepting SSF */ ++ ++ /* If we have TLS/SSL info, set it */ ++ if(saslprops.ssf) { ++ ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf); ++ } else { ++ ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &extprops_ssf); ++ } ++ if(ret != SASL_OK) return ret; ++ ++ if(saslprops.authid) { ++ ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid); ++ if(ret != SASL_OK) return ret; ++ } ++ /* End TLS/SSL Info */ ++ ++ return SASL_OK; ++} ++ ++void cmd_mupdatepush(char *tag, char *name) ++{ ++ int r = 0; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ char *part, *acl; ++ mupdate_handle *mupdate_h = NULL; ++ char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2]; ++ ++ if (!imapd_userisadmin) { ++ r = IMAP_PERMISSION_DENIED; ++ } ++ if (!config_mupdate_server) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, ++ imapd_userid, mailboxname); ++ } ++ ++ if (!r) { ++ r = mlookup(tag, name, mailboxname, NULL, NULL, NULL, ++ &part, &acl, NULL); ++ } ++ if (r == IMAP_MAILBOX_MOVED) return; ++ ++ /* Push mailbox to mupdate server */ ++ if (!r) { ++ r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL); ++ } ++ ++ if (!r) { ++ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part); ++ ++ r = mupdate_activate(mupdate_h, mailboxname, buf, acl); ++ } ++ ++ if(mupdate_h) { ++ mupdate_disconnect(&mupdate_h); ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++} ++ ++#ifdef HAVE_SSL ++/* Convert the ASCII hex into binary data ++ * ++ * 'bin' MUST be able to accomodate at least strlen(hex)/2 bytes ++ */ ++void hex2bin(const char *hex, unsigned char *bin, unsigned int *binlen) ++{ ++ int i; ++ const char *c; ++ unsigned char msn, lsn; ++ ++ for (c = hex, i = 0; *c && isxdigit((int) *c); c++) { ++ msn = (*c > '9') ? tolower((int) *c) - 'a' + 10 : *c - '0'; ++ c++; ++ lsn = (*c > '9') ? tolower((int) *c) - 'a' + 10 : *c - '0'; ++ ++ bin[i++] = (unsigned char) (msn << 4) | lsn; ++ } ++ *binlen = i; ++} ++ ++enum { ++ URLAUTH_ALG_HMAC_SHA1 = 0 /* HMAC-SHA1 */ ++}; ++ ++void cmd_urlfetch(char *tag) ++{ ++ struct mboxkey *mboxkey_db; ++ int c, r, doclose; ++ static struct buf arg; ++ struct imapurl url; ++ char mailboxname[MAX_MAILBOX_NAME+1]; ++ struct mailbox mboxstruct, *mailbox; ++ unsigned msgno; ++ unsigned int token_len; ++ int mbtype; ++ char *newserver; ++ time_t now = time(NULL); ++ ++ prot_printf(imapd_out, "* URLFETCH"); ++ ++ do { ++ c = getastring(imapd_in, imapd_out, &arg); ++ prot_putc(' ', imapd_out); ++ printstring(arg.s); ++ prot_putc(' ', imapd_out); ++ ++ r = doclose = 0; ++ imapurl_fromURL(&url, arg.s); ++ ++ /* validate the URL */ ++ if (!url.user || !url.server || !url.mailbox || !url.uid || ++ (url.urlauth.access && !(url.urlauth.mech && url.urlauth.token))) { ++ /* missing info */ ++ r = IMAP_BADURL; ++ } else if (strcmp(url.server, config_servername)) { ++ /* wrong server */ ++ r = IMAP_BADURL; ++ } else if (url.urlauth.expire && ++ url.urlauth.expire < mktime(gmtime(&now))) { ++ /* expired */ ++ r = IMAP_BADURL; ++ } else if (url.urlauth.access) { ++ /* check mechanism & authorization */ ++ int authorized = 0; ++ ++ if (!strcasecmp(url.urlauth.mech, "INTERNAL")) { ++ if (!strncasecmp(url.urlauth.access, "submit+", 7) && ++ global_authisa(imapd_authstate, IMAPOPT_SUBMITSERVERS)) { ++ /* authorized submit server */ ++ authorized = 1; ++ } else if (!strncasecmp(url.urlauth.access, "user+", 5) && ++ !strcmp(url.urlauth.access+5, imapd_userid)) { ++ /* currently authorized user */ ++ authorized = 1; ++ } else if (!strcasecmp(url.urlauth.access, "authuser") && ++ strcmp(imapd_userid, "anonymous")) { ++ /* any non-anonymous authorized user */ ++ authorized = 1; ++ } else if (!strcasecmp(url.urlauth.access, "anonymous")) { ++ /* anyone */ ++ authorized = 1; ++ } ++ } ++ ++ if (!authorized) r = IMAP_BADURL; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, ++ url.mailbox, ++ url.user, mailboxname); ++ } ++ if (!r) { ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &newserver, NULL, NULL); ++ } ++ ++ if (!r && (mbtype & MBTYPE_REMOTE)) { ++ /* remote mailbox */ ++ struct backend *be; ++ ++ be = proxy_findserver(newserver, &protocol[PROTOCOL_IMAP], ++ proxy_userid, &backend_cached, ++ &backend_current, &backend_inbox, imapd_in); ++ if (!be) { ++ r = IMAP_SERVER_UNAVAILABLE; ++ } else { ++ /* XXX proxy command to backend */ ++ } ++ ++ free(url.freeme); ++ ++ continue; ++ } ++ ++ /* local mailbox */ ++ if (!r) { ++ if (url.urlauth.token) { ++ /* validate the URLAUTH token */ ++ hex2bin(url.urlauth.token, ++ (unsigned char *) url.urlauth.token, &token_len); ++ ++ /* first byte is the algorithm used to create token */ ++ switch (url.urlauth.token[0]) { ++ case URLAUTH_ALG_HMAC_SHA1: { ++ const char *key; ++ size_t keylen; ++ unsigned char vtoken[EVP_MAX_MD_SIZE]; ++ unsigned int vtoken_len; ++ ++ r = mboxkey_open(url.user, 0, &mboxkey_db); ++ r = mboxkey_read(mboxkey_db, mailboxname, &key, &keylen); ++ HMAC(EVP_sha1(), key, keylen, arg.s, url.urlauth.rump_len, ++ vtoken, &vtoken_len); ++ mboxkey_close(mboxkey_db); ++ ++ if (memcmp(vtoken, url.urlauth.token+1, vtoken_len)) { ++ r = IMAP_BADURL; ++ } ++ ++ break; ++ } ++ default: ++ r = IMAP_BADURL; ++ break; ++ } ++ } ++ ++ if (!r) { ++ if (!imapd_mailbox || strcmp(imapd_mailbox->name, mailboxname)) { ++ /* not the currently selected mailbox, so try to open it */ ++ ++ r = mailbox_open_header(mailboxname, imapd_authstate, ++ &mboxstruct); ++ ++ if (!r) { ++ doclose = 1; ++ r = mailbox_open_index(&mboxstruct); ++ } ++ ++ if (!r && !url.urlauth.access && ++ !(mboxstruct.myrights & ACL_READ)) { ++ r = (imapd_userisadmin || ++ (mboxstruct.myrights & ACL_LOOKUP)) ? ++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++ if (!r) { ++ mailbox = &mboxstruct; ++ index_operatemailbox(mailbox); ++ } ++ } else { ++ mailbox = imapd_mailbox; ++ } ++ } ++ ++ if (r) { ++ /* nothing to do, handled up top */ ++ } else if (url.uidvalidity && ++ (mailbox->uidvalidity != url.uidvalidity)) { ++ r = IMAP_BADURL; ++ } else if (!url.uid || !(msgno = index_finduid(url.uid)) || ++ (index_getuid(msgno) != url.uid)) { ++ r = IMAP_BADURL; ++ } else { ++ r = index_urlfetch(mailbox, msgno, url.section, ++ url.start_octet, url.octet_count, ++ imapd_out, NULL); ++ } ++ ++ free(url.freeme); ++ ++ if (doclose) { ++ mailbox_close(&mboxstruct); ++ if (imapd_mailbox) index_operatemailbox(imapd_mailbox); ++ } ++ } ++ ++ if (r) prot_printf(imapd_out, "NIL"); ++ ++ } while (c == ' '); ++ ++ prot_printf(imapd_out, "\r\n"); ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to URLFETCH\r\n", tag); ++ eatline(imapd_in, c); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++} ++ ++/* Convert the binary data into ASCII hex ++ * ++ * 'hex' MUST be able to accomodate at least 2*binlen+1 bytes ++ */ ++void bin2hex(unsigned char *bin, int binlen, char *hex) ++{ ++ int i; ++ unsigned char c; ++ ++ for (i = 0; i < binlen; i++) { ++ c = (bin[i] >> 4) & 0xf; ++ hex[i*2] = (c > 9) ? ('a' + c - 10) : ('0' + c); ++ c = bin[i] & 0xf; ++ hex[i*2+1] = (c > 9) ? ('a' + c - 10) : ('0' + c); ++ } ++ hex[i*2] = '\0'; ++} ++ ++#define MBOX_KEY_LEN 16 /* 128 bits */ ++ ++void cmd_genurlauth(char *tag) ++{ ++ struct mboxkey *mboxkey_db; ++ int first = 1; ++ int c, r, doclose; ++ static struct buf arg1, arg2; ++ struct imapurl url; ++ char mailboxname[MAX_MAILBOX_NAME+1], *urlauth = NULL; ++ char newkey[MBOX_KEY_LEN]; ++ const char *key; ++ size_t keylen; ++ unsigned char token[EVP_MAX_MD_SIZE+1]; /* +1 for algorithm */ ++ unsigned int token_len; ++ int mbtype; ++ char *newserver; ++ time_t now = time(NULL); ++ ++ r = mboxkey_open(imapd_userid, MBOXKEY_CREATE, &mboxkey_db); ++ if (r) { ++ prot_printf(imapd_out, ++ "%s NO Can not open mailbox key db for %s: %s\r\n", ++ tag, imapd_userid, error_message(r)); ++ return; ++ } ++ ++ do { ++ c = getastring(imapd_in, imapd_out, &arg1); ++ if (c != ' ') { ++ prot_printf(imapd_out, ++ "%s BAD Missing required argument to Genurlauth\r\n", ++ tag); ++ eatline(imapd_in, c); ++ return; ++ } ++ c = getword(imapd_in, &arg2); ++ if (strcasecmp(arg2.s, "INTERNAL")) { ++ prot_printf(imapd_out, ++ "%s BAD Unknown auth mechanism to Genurlauth %s\r\n", ++ tag, arg2.s); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ r = 0; ++ imapurl_fromURL(&url, arg1.s); ++ ++ /* validate the URL */ ++ if (!url.user || !url.server || !url.mailbox || !url.uid || ++ !url.urlauth.access) { ++ r = IMAP_BADURL; ++ } else if (strcmp(url.user, imapd_userid)) { ++ /* not using currently authorized user's namespace */ ++ r = IMAP_BADURL; ++ } else if (strcmp(url.server, config_servername)) { ++ /* wrong server */ ++ r = IMAP_BADURL; ++ } else if (url.urlauth.expire && ++ url.urlauth.expire < mktime(gmtime(&now))) { ++ /* already expired */ ++ r = IMAP_BADURL; ++ } ++ ++ if (!r) { ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, ++ url.mailbox, ++ imapd_userid, mailboxname); ++ } ++ if (!r) { ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &newserver, NULL, NULL); ++ } ++ if (r) { ++ prot_printf(imapd_out, ++ "%s BAD Poorly specified URL to Genurlauth %s\r\n", ++ tag, arg1.s); ++ eatline(imapd_in, c); ++ return; ++ } ++ ++ if (mbtype & MBTYPE_REMOTE) { ++ /* XXX proxy to backend */ ++ continue; ++ } ++ ++ /* lookup key */ ++ r = mboxkey_read(mboxkey_db, mailboxname, &key, &keylen); ++ if (r) { ++ syslog(LOG_ERR, "DBERROR: error fetching mboxkey: %s", ++ cyrusdb_strerror(r)); ++ } ++ else if (!key) { ++ /* create a new key */ ++ RAND_bytes(newkey, MBOX_KEY_LEN); ++ key = newkey; ++ keylen = MBOX_KEY_LEN; ++ r = mboxkey_write(mboxkey_db, mailboxname, key, keylen); ++ if (r) { ++ syslog(LOG_ERR, "DBERROR: error writing new mboxkey: %s", ++ cyrusdb_strerror(r)); ++ } ++ } ++ ++ if (r) { ++ eatline(imapd_in, c); ++ prot_printf(imapd_out, ++ "%s NO Error authorizing %s: %s\r\n", ++ tag, arg1.s, cyrusdb_strerror(r)); ++ return; ++ } ++ ++ /* first byte is the algorithm used to create token */ ++ token[0] = URLAUTH_ALG_HMAC_SHA1; ++ HMAC(EVP_sha1(), key, keylen, arg1.s, strlen(arg1.s), ++ token+1, &token_len); ++ token_len++; ++ ++ urlauth = xrealloc(urlauth, strlen(arg1.s) + 10 + ++ 2 * (EVP_MAX_MD_SIZE+1) + 1); ++ strcpy(urlauth, arg1.s); ++ strcat(urlauth, ":internal:"); ++ bin2hex(token, token_len, urlauth+strlen(urlauth)); ++ ++ if (first) { ++ prot_printf(imapd_out, "* GENURLAUTH"); ++ first = 0; ++ } ++ prot_putc(' ', imapd_out); ++ printstring(urlauth); ++ } while (c == ' '); ++ ++ if (!first) prot_printf(imapd_out, "\r\n"); ++ ++ if (c == '\r') c = prot_getc(imapd_in); ++ if (c != '\n') { ++ prot_printf(imapd_out, ++ "%s BAD Unexpected extra arguments to GENURLAUTH\r\n", tag); ++ eatline(imapd_in, c); ++ } ++ else { ++ prot_printf(imapd_out, "%s OK %s\r\n", tag, ++ error_message(IMAP_OK_COMPLETED)); ++ } ++ ++ mboxkey_close(mboxkey_db); ++} ++ ++void cmd_resetkey(char *tag, char *mailbox, ++ char *mechanism __attribute__((unused))) ++/* XXX we don't support any external mechanisms, so we ignore it */ ++{ ++ int r; ++ ++ if (mailbox) { ++ /* delete key for specified mailbox */ ++ char mailboxname[MAX_MAILBOX_NAME+1], *newserver; ++ int mbtype; ++ struct mboxkey *mboxkey_db; ++ ++ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, ++ mailbox, ++ imapd_userid, mailboxname); ++ if (!r) { ++ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL, ++ &newserver, NULL, NULL); ++ } ++ if (r) { ++ prot_printf(imapd_out, "%s NO Error removing key: %s\r\n", ++ tag, error_message(r)); ++ return; ++ } ++ ++ if (mbtype & MBTYPE_REMOTE) { ++ /* XXX proxy to backend */ ++ return; ++ } ++ ++ r = mboxkey_open(imapd_userid, MBOXKEY_CREATE, &mboxkey_db); ++ if (!r) { ++ r = mboxkey_write(mboxkey_db, mailboxname, NULL, 0); ++ mboxkey_close(mboxkey_db); ++ } ++ ++ if (r) { ++ prot_printf(imapd_out, "%s NO Error removing key: %s\r\n", ++ tag, cyrusdb_strerror(r)); ++ } else { ++ prot_printf(imapd_out, ++ "%s OK [URLMECH INTERNAL] key removed\r\n", tag); ++ } ++ } ++ else { ++ /* delete ALL keys */ ++ /* XXX what do we do about multiple backends? */ ++ r = mboxkey_delete_user(imapd_userid); ++ if (r) { ++ prot_printf(imapd_out, "%s NO Error removing keys: %s\r\n", ++ tag, cyrusdb_strerror(r)); ++ } else { ++ prot_printf(imapd_out, "%s OK All keys removed\r\n", tag); ++ } ++ } ++} ++#endif /* HAVE_SSL */ +--- cyrus-imapd-2.3.7/imap/lmtpd.c.autocreate0 2006-05-23 15:09:36.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/lmtpd.c 2006-07-23 12:35:41.000000000 +0200 +@@ -115,6 +115,8 @@ + static FILE *spoolfile(message_data_t *msgdata); + static void removespool(message_data_t *msgdata); + ++static int autocreate_inbox(const char *user, const char *domain); ++ + /* current namespace */ + static struct namespace lmtpd_namespace; + +@@ -937,6 +939,86 @@ + exit(code); + } + ++ ++/* ++ * Autocreate Inbox and subfolders upon login ++ */ ++int autocreate_inbox(const char *user, const char *domain) ++{ ++ struct auth_state *auth_state; ++ char inboxname[MAX_MAILBOX_NAME+1]; ++ char *rcpt_userid = NULL; ++ int autocreatequota; ++ int r = 0; ++ ++ if (user == NULL) ++ return IMAP_MAILBOX_NONEXISTENT; ++ ++ if (domain != NULL) { ++ int k; ++ ++ rcpt_userid = (char *) xmalloc((strlen(user) + strlen(domain) + 2) * sizeof(char)); ++ k = strlcpy(rcpt_userid, user, strlen(user) + 1); ++ *(rcpt_userid + k) = '@'; ++ strlcpy(rcpt_userid + k + 1, domain, strlen(domain) + 1); ++ } else { ++ rcpt_userid = (char *) user; ++ } ++ ++ ++ /* ++ * Exclude anonymous ++ */ ++ if (!strcmp(rcpt_userid, "anonymous")) { ++ if (rcpt_userid != user) { ++ free(rcpt_userid); ++ } ++ ++ return IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++ /* ++ * Check for autocreatequota and createonpost ++ */ ++ if (!(autocreatequota = config_getint(IMAPOPT_AUTOCREATEQUOTA)) || ++ !(config_getswitch(IMAPOPT_CREATEONPOST))) { ++ ++ if (rcpt_userid != user) { ++ free(rcpt_userid); ++ } ++ ++ return IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++ ++ /* ++ * Exclude admin's accounts ++ */ ++ auth_state = auth_newstate(rcpt_userid); ++ ++ if (global_authisa(auth_state, IMAPOPT_ADMINS)) { ++ if (rcpt_userid != user) { ++ free(rcpt_userid); ++ } ++ ++ return IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++ r = (*lmtpd_namespace.mboxname_tointernal) (&lmtpd_namespace, ++ "INBOX", rcpt_userid, inboxname); ++ ++ if (!r) ++ r = mboxlist_autocreateinbox(&lmtpd_namespace, rcpt_userid, ++ auth_state, inboxname, autocreatequota); ++ ++ if (rcpt_userid != user) { ++ free(rcpt_userid); ++ } ++ ++ return r; ++} ++ ++ + static int verify_user(const char *user, const char *domain, char *mailbox, + long quotacheck, struct auth_state *authstate) + { +@@ -980,6 +1062,15 @@ + */ + r = mlookup(namebuf, &server, &acl, NULL); + ++ /* If user mailbox does not exist, then invoke autocreate inbox function */ ++ if (r == IMAP_MAILBOX_NONEXISTENT) { ++ r = autocreate_inbox(user, domain); ++ ++ /* Try to locate the mailbox again */ ++ if (!r) ++ r = mlookup(namebuf, &server, &acl, NULL); ++ } ++ + if (r == IMAP_MAILBOX_NONEXISTENT && !user && + config_getswitch(IMAPOPT_LMTP_FUZZY_MAILBOX_MATCH) && + /* see if we have a mailbox whose name is close */ +@@ -1006,6 +1097,7 @@ + aclcheck, (quotacheck < 0) + || config_getswitch(IMAPOPT_LMTP_STRICT_QUOTA) ? + quotacheck : 0); ++ + } + } + +--- cyrus-imapd-2.3.7/imap/mboxlist.c.autocreate0 2006-05-22 22:37:25.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/mboxlist.c 2006-07-23 12:35:41.000000000 +0200 +@@ -81,6 +81,12 @@ + #include "mboxlist.h" + #include "quota.h" + ++#ifdef USE_SIEVE ++extern int autoadd_sieve(char *userid, ++ const char *source_script); ++#endif ++ ++ + #define DB config_mboxlist_db + #define SUBDB config_subscription_db + +@@ -98,11 +104,29 @@ + static int mboxlist_changequota(const char *name, int matchlen, int maycreate, + void *rock); + ++static int mboxlist_autochangesub(char *name, int matchlen, int maycreate, ++ void *rock); ++ ++static int mboxlist_autosubscribe_sharedfolders(struct namespace *namespace, ++ char *userid, char *auth_userid, ++ struct auth_state *auth_state); ++ + struct change_rock { + struct quota *quota; + struct txn **tid; + }; + ++/* ++ * Struct needed to be passed as void *rock to ++ * mboxlist_autochangesub(); ++ */ ++struct changesub_rock_st { ++ char *userid; ++ char *auth_userid; ++ struct auth_state *auth_state; ++}; ++ ++ + #define FNAME_SUBSSUFFIX ".sub" + + /* +@@ -3241,3 +3265,349 @@ + + return DB->abort(mbdb, tid); + } ++ ++/* ++ * Automatically subscribe user to *ALL* shared folders, ++ * one has permissions to be subscribed to. ++ * INBOX subfolders are excluded. ++ */ ++static int mboxlist_autochangesub(char *name, int matchlen, int maycreate, ++ void *rock) { ++ ++ struct changesub_rock_st *changesub_rock = (struct changesub_rock_st *) rock; ++ char *userid = changesub_rock->userid; ++ char *auth_userid = changesub_rock->auth_userid; ++ struct auth_state *auth_state = changesub_rock->auth_state; ++ int r; ++ ++ ++ if((strlen(name) == 5 && !strncmp(name, "INBOX", 5)) || /* Exclude INBOX */ ++ (strlen(name) > 5 && !strncmp(name, "INBOX.",6)) || /* Exclude INBOX subfolders */ ++ (strlen(name) > 4 && !strncmp(name, "user.", 5))) /* Exclude other users' folders */ ++ return 0; ++ ++ ++ r = mboxlist_changesub(name, userid, auth_state, 1, 0); ++ ++ if (r) { ++ syslog(LOG_WARNING, ++ "autosubscribe: User %s to folder %s, subscription failed: %s", ++ auth_userid, name, error_message(r)); ++ } else { ++ syslog(LOG_NOTICE, ++ "autosubscribe: User %s to folder %s, subscription succeeded", ++ auth_userid, name); ++ } ++ ++ return 0; ++} ++ ++#define SEP '|' ++ ++/* ++ * Automatically subscribe user to a shared folder. ++ * Subscription is done successfully, if the shared ++ * folder exists and the user has the necessary ++ * permissions. ++ */ ++static int mboxlist_autosubscribe_sharedfolders(struct namespace *namespace, ++ char *userid, char *auth_userid, ++ struct auth_state *auth_state) { ++ ++ const char *sub ; ++ char *p, *q, *next_sub; ++ char folder[MAX_MAILBOX_NAME+1], name[MAX_MAILBOX_NAME+1], mailboxname[MAX_MAILBOX_NAME+1]; ++ int len; ++ int r = 0; ++ int subscribe_all_sharedfolders = 0; ++ ++ subscribe_all_sharedfolders = config_getswitch(IMAPOPT_AUTOSUBSCRIBE_ALL_SHAREDFOLDERS); ++ ++ /* ++ * If subscribeallsharedfolders is set to yes in imapd.conf, then ++ * subscribe user to every shared folder one has the apropriate ++ * permissions. ++ */ ++ if(subscribe_all_sharedfolders) { ++ char pattern[MAX_MAILBOX_PATH+1]; ++ struct changesub_rock_st changesub_rock; ++ ++ strcpy(pattern, "*"); ++ changesub_rock.userid = userid; ++ changesub_rock.auth_userid = auth_userid; ++ changesub_rock.auth_state = auth_state; ++ ++ r = mboxlist_findall(namespace, pattern, 0, userid, ++ auth_state, mboxlist_autochangesub, &changesub_rock); ++ ++ return r; ++ } ++ ++ if ((sub=config_getstring(IMAPOPT_AUTOSUBSCRIBESHAREDFOLDERS)) == NULL) ++ return r; ++ ++ next_sub = (char *) sub; ++ while (*next_sub) { ++ for (p = next_sub ; isspace((int) *p) || *p == SEP ; p++); ++ for (next_sub = p ; *next_sub && *next_sub != SEP ; next_sub++); ++ for (q = next_sub ; q > p && (isspace((int) *q) || *q == SEP || !*q) ; q--); ++ if (!*p ) continue; ++ ++ len = q - p + 1; ++ /* Check for folder length */ ++ if (len > sizeof(folder)-1) ++ continue; ++ ++ if (!r) { ++ strncpy(folder, p, len); ++ folder[len] = '\0'; ++ ++ strlcpy(name, namespace->prefix[NAMESPACE_SHARED], sizeof(name)); ++ len = strlcat(name, folder, sizeof(name)); ++ ++ r = (namespace->mboxname_tointernal) (namespace, name, userid, ++ mailboxname); ++ } ++ ++ if (!r) ++ r = mboxlist_changesub(mailboxname, userid, auth_state, 1, 0); ++ ++ if (!r) { ++ syslog(LOG_NOTICE, "autosubscribe: User %s to %s succeeded", ++ userid, folder); ++ } else { ++ syslog(LOG_WARNING, "autosubscribe: User %s to %s failed: %s", ++ userid, folder, error_message(r)); ++ r = 0; ++ } ++ } ++ ++ return r; ++} ++ ++ ++ ++int mboxlist_autocreateinbox(struct namespace *namespace, ++ char *userid, ++ struct auth_state *auth_state, ++ char *mailboxname, int autocreatequota) { ++ char name [MAX_MAILBOX_NAME+1]; ++ char folder [MAX_MAILBOX_NAME+1]; ++ char *auth_userid = NULL; ++ char *partition = NULL; ++ const char *crt; ++ const char *sub; ++ char *p, *q, *next_crt, *next_sub; ++ int len; ++ int r = 0; ++ int numcrt = 0; ++ int numsub = 0; ++#ifdef USE_SIEVE ++ const char *source_script; ++#endif ++ ++ ++ ++ auth_userid = auth_canonuser(auth_state); ++ if (auth_userid == NULL) { ++ /* ++ * Couldn't get cannon userid ++ */ ++ syslog(LOG_ERR, ++ "autocreateinbox: Could not get canonified userid for user %s", userid); ++ return IMAP_PARTITION_UNKNOWN; ++ } ++ ++ /* Added this for debug information. */ ++ syslog(LOG_DEBUG, "autocreateinbox: autocreate inbox for user %s was called", auth_userid); ++ ++ /* ++ * While this is not needed for admins ++ * and imap_admins accounts, it would be ++ * better to separate *all* admins and ++ * proxyservers from normal accounts ++ * (accounts that have mailboxes). ++ * UOA Specific note(1): Even if we do not ++ * exclude these servers-classes here, ++ * UOA specific code, will neither return ++ * role, nor create INBOX, because none of these ++ * administrative accounts belong to the ++ * mailRecipient objectclass, or have imapPartition. ++ * UOA Specific note(2): Another good reason for doing ++ * this, is to prevent the code, from getting into ++ * cyrus_ldap.c because of the continues MSA logins to LMTPd. ++ */ ++ ++ /* ++ * admins and the coresponding imap ++ * service, had already been excluded. ++ */ ++ ++ /* ++ * Do we really need group membership ++ * for admins or service_admins? ++ */ ++ if (global_authisa(auth_state, IMAPOPT_ADMINS)) return 0; ++ ++ /* ++ * Do we really need group membership ++ * for proxyservers? ++ */ ++ if (global_authisa(auth_state, IMAPOPT_PROXYSERVERS)) return 0; ++ ++ /* ++ * Check if user belongs to the autocreate_users group. This option ++ * controls for whom the mailbox may be automatically created. Default ++ * value for this option is 'anyone'. So, if not declared, all mailboxes ++ * will be created. ++ */ ++ if (!global_authisa(auth_state, IMAPOPT_AUTOCREATE_USERS)) { ++ syslog(LOG_DEBUG, "autocreateinbox: User %s does not belong to the autocreate_users. No mailbox is created", ++ auth_userid); ++ return IMAP_MAILBOX_NONEXISTENT; ++ } ++ ++#if 0 ++ /* ++ * Get Partition info or return. ++ * (Here you should propably use ++ * you own "get_partition(char *userid)" ++ * function. Otherwise all new INBOXes will be ++ * created into whatever partition has been declared ++ * as default in your imapd.conf) ++ */ ++ ++ partition = get_partition(userid); ++ if (partition == NULL) { ++ /* ++ * Couldn't get partition info ++ */ ++ syslog(LOG_ERR, ++ "Could not get imapPartition info for user %s", userid); ++ return IMAP_PARTITION_UNKNOWN; ++ } ++#endif ++ ++ r = mboxlist_createmailbox(mailboxname, MAILBOX_FORMAT_NORMAL, NULL, ++ 1, userid, auth_state, 0, 0, 0); ++ ++ if (!r && autocreatequota > 0) ++ r = mboxlist_setquota(mailboxname, autocreatequota, 0); ++ ++ if (!r) ++ r = mboxlist_changesub(mailboxname, userid, ++ auth_state, 1, 1); ++ ++ if (!r) { ++ syslog(LOG_NOTICE, "autocreateinbox: User %s, INBOX was successfully created in partition %s", ++ auth_userid, partition == NULL ? "default" : partition); ++ } else { ++ syslog(LOG_ERR, "autocreateinbox: User %s, INBOX failed. %s", ++ auth_userid, error_message(r)); ++ } ++ ++#if 0 ++ /* Allocated from get_partition, and not needed any more */ ++ free_partition(partition); ++#endif ++ ++ if (r) return r; ++ ++ /* INBOX's subfolders */ ++ if ((crt=config_getstring(IMAPOPT_AUTOCREATEINBOXFOLDERS))) ++ sub=config_getstring(IMAPOPT_AUTOSUBSCRIBEINBOXFOLDERS); ++ ++ /* Roll through crt */ ++ next_crt = (char *) crt; ++ while (next_crt!=NULL && *next_crt) { ++ for (p = next_crt ; isspace((int) *p) || *p == SEP ; p++); ++ for (next_crt = p ; *next_crt && *next_crt != SEP ; next_crt++); ++ for (q = next_crt ; q > p && (isspace((int) *q) || *q == SEP || !*q); q--); ++ ++ if (!*p) continue; ++ ++ len = q - p + 1; ++ ++ /* First time we check for length */ ++ if (len > sizeof(folder) - 5) ++ r = IMAP_MAILBOX_BADNAME; ++ ++ if (!r) { ++ strncpy(folder, p, len); ++ folder[len] = '\0'; ++ ++ strlcpy(name, namespace->prefix[NAMESPACE_INBOX], sizeof(name)); ++ len = strlcat(name, folder, sizeof(name)); ++ } ++ ++ if (!r) ++ r = (namespace->mboxname_tointernal) (namespace, name, userid, ++ mailboxname); ++ if (!r) ++ r = mboxlist_createmailbox(mailboxname, MAILBOX_FORMAT_NORMAL, NULL, ++ 1, userid, auth_state, 0, 0, 0); ++ ++ if (!r) { ++ numcrt++; ++ syslog(LOG_NOTICE, "autocreateinbox: User %s, subfolder %s creation succeeded.", ++ auth_userid, name); ++ } else { ++ syslog(LOG_WARNING, "autocreateinbox: User %s, subfolder %s creation failed. %s", ++ auth_userid, name, error_message(r)); ++ r=0; ++ continue; ++ } ++ ++ /* Roll through sub */ ++ next_sub = (char *) sub; ++ while (next_sub!=NULL && *next_sub) { ++ for (p = next_sub ; isspace((int) *p) || *p == SEP ; p++); ++ for (next_sub = p ; *next_sub && *next_sub != SEP ; next_sub++); ++ for (q = next_sub ; q > p && (isspace((int) *q) || *q == SEP || !*q) ; q--); ++ if (!*p ) continue; ++ ++ len = q - p + 1; ++ ++ if (len != strlen(folder) || strncmp(folder, p, len)) ++ continue; ++ ++ r = mboxlist_changesub(mailboxname, userid, auth_state, 1, 1); ++ ++ if (!r) { ++ numsub++; ++ syslog(LOG_NOTICE,"autocreateinbox: User %s, subscription to %s succeeded", ++ auth_userid, name); ++ } else ++ syslog(LOG_WARNING, "autocreateinbox: User %s, subscription to %s failed. %s", ++ auth_userid, name, error_message(r)); ++ ++ break; ++ } ++ } ++ ++ if (crt!=NULL && *crt) ++ syslog(LOG_INFO, "User %s, Inbox subfolders, created %d, subscribed %d", ++ auth_userid, numcrt, numsub); ++ ++ /* ++ * Check if shared folders are available for subscription. ++ */ ++ mboxlist_autosubscribe_sharedfolders(namespace, userid, auth_userid, auth_state); ++ ++#ifdef USE_SIEVE ++ /* ++ * Here the autocreate sieve script feature is iniated from. ++ */ ++ source_script = config_getstring(IMAPOPT_AUTOCREATE_SIEVE_SCRIPT); ++ ++ if (source_script) { ++ if (!autoadd_sieve(userid, source_script)) ++ syslog(LOG_NOTICE, "autocreate_sieve: User %s, default sieve script creation succeeded", auth_userid); ++ else ++ syslog(LOG_WARNING, "autocreate_sieve: User %s, default sieve script creation failed", auth_userid); ++ } ++#endif ++ ++ return r; ++} ++ +--- cyrus-imapd-2.3.7/imap/mboxlist.h.autocreate0 2005-02-21 20:25:40.000000000 +0100 ++++ cyrus-imapd-2.3.7/imap/mboxlist.h 2006-07-23 12:35:41.000000000 +0200 +@@ -203,4 +203,10 @@ + int mboxlist_commit(struct txn *tid); + int mboxlist_abort(struct txn *tid); + ++int mboxlist_autocreateinbox(struct namespace *namespace, ++ char *userid, ++ struct auth_state *auth_state, ++ char *mailboxname, int autocreatequota); ++ ++ + #endif +--- cyrus-imapd-2.3.7/imap/pop3d.c.autocreate0 2006-05-26 17:50:09.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/pop3d.c 2006-07-23 12:35:41.000000000 +0200 +@@ -156,6 +156,8 @@ + static char popd_apop_chal[45 + MAXHOSTNAMELEN + 1]; /* */ + static void cmd_apop(char *response); + ++static int autocreate_inbox(char *inboxname, char *userid); ++ + static void cmd_auth(char *arg); + static void cmd_capa(void); + static void cmd_pass(char *pass); +@@ -1224,6 +1226,7 @@ + popd_userid = xstrdup(userbuf); + prot_printf(popd_out, "+OK Name is a valid mailbox\r\n"); + } ++ + } + + void cmd_pass(char *pass) +@@ -1498,6 +1501,43 @@ + } + + /* ++ * Autocreate Inbox and subfolders upon login ++ */ ++int autocreate_inbox(char *inboxname, char *auth_userid) ++{ ++ struct auth_state *auth_state; ++ int autocreatequota; ++ int r; ++ ++ if (inboxname == NULL || auth_userid == NULL) ++ return IMAP_MAILBOX_NONEXISTENT; ++ ++ /* ++ * Exclude anonymous ++ */ ++ if (!strcmp(popd_userid, "anonymous")) ++ return IMAP_MAILBOX_NONEXISTENT; ++ ++ /* ++ * Check for autocreatequota ++ */ ++ if (!(autocreatequota = config_getint(IMAPOPT_AUTOCREATEQUOTA))) ++ return IMAP_MAILBOX_NONEXISTENT; ++ ++ /* ++ * Exclude admin's accounts ++ */ ++ auth_state = auth_newstate(popd_userid); ++ if (global_authisa(auth_state, IMAPOPT_ADMINS)) ++ return IMAP_MAILBOX_NONEXISTENT; ++ ++ r = mboxlist_autocreateinbox(&popd_namespace, auth_userid, ++ auth_state, inboxname, autocreatequota); ++ return r; ++} ++ ++ ++/* + * Complete the login process by opening and locking the user's inbox + */ + int openinbox(void) +@@ -1526,6 +1566,12 @@ + + if (!r) r = mboxlist_detail(inboxname, &type, NULL, NULL, + &server, &acl, NULL); ++ ++ /* Try once again after autocreate_inbox */ ++ if (r == IMAP_MAILBOX_NONEXISTENT && !(r = autocreate_inbox(inboxname, userid))) ++ r = mboxlist_detail(inboxname, &type, NULL, NULL, ++ &server, &acl, NULL); ++ + if (!r && (config_popuseacl = config_getswitch(IMAPOPT_POPUSEACL)) && + (!acl || + !((myrights = cyrus_acl_myrights(popd_authstate, acl)) & ACL_READ))) { +--- cyrus-imapd-2.3.7/notifyd/Makefile.in.autocreate0 2004-05-31 20:22:59.000000000 +0200 ++++ cyrus-imapd-2.3.7/notifyd/Makefile.in 2006-07-23 12:35:41.000000000 +0200 +@@ -69,10 +69,11 @@ + SERVICE=../master/service.o + + IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ ++SIEVE_LIBS = @SIEVE_LIBS@ + IMAP_COM_ERR_LIBS = @IMAP_COM_ERR_LIBS@ + LIB_WRAP = @LIB_WRAP@ + LIBS = @ZEPHYR_LIBS@ @LIBS@ $(IMAP_COM_ERR_LIBS) +-DEPLIBS=../imap/mutex_fake.o ../imap/libimap.a ../lib/libcyrus.a ../lib/libcyrus_min.a @DEPLIBS@ ++DEPLIBS=../imap/mutex_fake.o ../imap/libimap.a $(SIEVE_LIBS) ../lib/libcyrus.a ../lib/libcyrus_min.a @DEPLIBS@ + + PURIFY=/usr/local/bin/purify + PUREOPT=-best-effort +--- cyrus-imapd-2.3.7/notifyd/notifyd.c.autocreate0 2005-04-13 17:43:36.000000000 +0200 ++++ cyrus-imapd-2.3.7/notifyd/notifyd.c 2006-07-23 12:35:41.000000000 +0200 +@@ -97,7 +97,7 @@ + + #define NOTIFY_MAXSIZE 8192 + +-int do_notify() ++static int do_notify() + { + struct sockaddr_un sun_data; + socklen_t sunlen = sizeof(sun_data); +--- cyrus-imapd-2.3.7/ptclient/Makefile.in.autocreate0 2006-06-19 18:00:18.000000000 +0200 ++++ cyrus-imapd-2.3.7/ptclient/Makefile.in 2006-07-23 12:35:41.000000000 +0200 +@@ -57,10 +57,11 @@ + AFS_LDFLAGS = @AFS_LDFLAGS@ @COM_ERR_LDFLAGS@ + AFS_LIBS = @AFS_LIBS@ + IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ ++SIEVE_LIBS = @SIEVE_LIBS@ + LIBS = $(IMAP_LIBS) @COM_ERR_LIBS@ + LIB_SASL = @LIB_SASL@ + LIB_WRAP = @LIB_WRAP@ +-DEPLIBS = ../imap/libimap.a ../lib/libcyrus.a ../lib/libcyrus_min.a @DEPLIBS@ ++DEPLIBS = ../imap/libimap.a $(SIEVE_LIBS) ../lib/libcyrus.a ../lib/libcyrus_min.a @DEPLIBS@ + UTIL_LIBS = ../imap/mutex_fake.o ../imap/cli_fatal.o + + LDAP_LIBS=@LDAP_LIBS@ +--- cyrus-imapd-2.3.7/lib/auth.c.autocreate0 2005-02-16 22:06:50.000000000 +0100 ++++ cyrus-imapd-2.3.7/lib/auth.c 2006-07-23 12:35:41.000000000 +0200 +@@ -117,3 +117,11 @@ + + auth->freestate(auth_state); + } ++ ++char *auth_canonuser(struct auth_state *auth_state) ++{ ++ struct auth_mech *auth = auth_fromname(); ++ ++ return auth->auth_canonuser(auth_state); ++} ++ +--- cyrus-imapd-2.3.7/lib/auth.h.autocreate0 2005-02-16 22:06:50.000000000 +0100 ++++ cyrus-imapd-2.3.7/lib/auth.h 2006-07-23 12:35:41.000000000 +0200 +@@ -54,6 +54,7 @@ + const char *identifier); + struct auth_state *(*newstate)(const char *identifier); + void (*freestate)(struct auth_state *auth_state); ++ char *(*auth_canonuser)(struct auth_state *auth_state); + }; + + extern struct auth_mech *auth_mechs[]; +@@ -76,5 +77,6 @@ + const char *identifier); + struct auth_state *auth_newstate(const char *identifier); + void auth_freestate(struct auth_state *auth_state); ++char *auth_canonuser(struct auth_state *auth_state); + + #endif /* INCLUDED_AUTH_H */ +--- cyrus-imapd-2.3.7/lib/auth_krb.c.autocreate0 2005-12-14 14:52:09.000000000 +0100 ++++ cyrus-imapd-2.3.7/lib/auth_krb.c 2006-07-23 12:35:41.000000000 +0200 +@@ -339,6 +339,15 @@ + free((char *)auth_state); + } + ++static char *mycanonuser(struct auth_state *auth_state) ++{ ++ if (auth_state) ++ return auth_state->userid; ++ ++ return NULL; ++} ++ ++ + #else /* HAVE_KRB */ + + static int mymemberof( +@@ -367,6 +376,13 @@ + fatal("Authentication mechanism (krb) not compiled in", EC_CONFIG); + } + ++static char *mycanonuser( ++ struct auth_state *auth_state __attribute__((unused))) ++{ ++ fatal("Authentication mechanism (krb) not compiled in", EC_CONFIG); ++} ++ ++ + #endif + + struct auth_mech auth_krb = +@@ -377,4 +393,5 @@ + &mymemberof, + &mynewstate, + &myfreestate, ++ &mycanonuser, + }; +--- cyrus-imapd-2.3.7/lib/auth_krb5.c.autocreate0 2005-02-16 22:06:50.000000000 +0100 ++++ cyrus-imapd-2.3.7/lib/auth_krb5.c 2006-07-23 12:35:41.000000000 +0200 +@@ -197,6 +197,14 @@ + free(auth_state); + } + ++static char *mycanonuser(struct auth_state *auth_state) ++{ ++ if (auth_state) ++ return auth_state->userid; ++ ++ return NULL; ++} ++ + #else /* HAVE_GSSAPI_H */ + + static int mymemberof( +@@ -225,6 +233,12 @@ + fatal("Authentication mechanism (krb5) not compiled in", EC_CONFIG); + } + ++static char *mycanonuser( ++ struct auth_state *auth_state __attribute__((unused))) ++{ ++ fatal("Authentication mechanism (krb5) not compiled in", EC_CONFIG); ++} ++ + #endif + + struct auth_mech auth_krb5 = +@@ -235,4 +249,5 @@ + &mymemberof, + &mynewstate, + &myfreestate, ++ &mycanonuser, + }; +--- cyrus-imapd-2.3.7/lib/auth_pts.c.autocreate0 2006-03-17 17:05:15.000000000 +0100 ++++ cyrus-imapd-2.3.7/lib/auth_pts.c 2006-07-23 12:35:41.000000000 +0200 +@@ -503,6 +503,14 @@ + free(auth_state); + } + ++static char *mycanonuser(struct auth_state *auth_state) ++{ ++ if (auth_state) ++ return auth_state->userid.id; ++ ++ return NULL; ++} ++ + struct auth_mech auth_pts = + { + "pts", /* name */ +@@ -511,4 +519,5 @@ + &mymemberof, + &mynewstate, + &myfreestate, ++ &mycanonuser, + }; +--- cyrus-imapd-2.3.7/lib/auth_unix.c.autocreate0 2005-02-16 22:06:50.000000000 +0100 ++++ cyrus-imapd-2.3.7/lib/auth_unix.c 2006-07-23 12:35:41.000000000 +0200 +@@ -264,6 +264,16 @@ + free((char *)auth_state); + } + ++static char *mycanonuser(auth_state) ++ struct auth_state *auth_state; ++{ ++ if (auth_state) ++ return auth_state->userid; ++ ++ return NULL; ++} ++ ++ + + struct auth_mech auth_unix = + { +@@ -273,4 +283,5 @@ + &mymemberof, + &mynewstate, + &myfreestate, ++ &mycanonuser, + }; +--- cyrus-imapd-2.3.7/lib/imapoptions.autocreate0 2006-06-27 17:58:42.000000000 +0200 ++++ cyrus-imapd-2.3.7/lib/imapoptions 2006-07-23 12:35:41.000000000 +0200 +@@ -172,6 +172,55 @@ + /* Number of seconds to wait before returning a timeout failure when + performing a client connection (e.g. in a murder environment) */ + ++{ "createonpost", 0, SWITCH } ++/* If yes, when lmtpd receives an incoming mail for an INBOX that does not exist, ++ then the INBOX is automatically created by lmtpd. */ ++ ++{ "autocreateinboxfolders", NULL, STRING } ++/* If a user does not have an INBOX created then the INBOX as well as some INBOX ++ subfolders are created under two conditions. ++ 1. The user logins via the IMAP or the POP3 protocol. (autocreatequota option must have a nonzero value) ++ 2. A message arrives for the user through the LMTPD protocol.(createonpost option must be yes) ++ autocreateinboxfolders is a list of INBOX's subfolders separated by a "|", that ++ are automatically created by the server under the previous two situations. */ ++ ++{ "autosubscribeinboxfolders", NULL, STRING } ++/* A list of folder names, separated by "|", that the users get automatically subscribed to, ++ when their INBOX is created. These folder names must have been included in the ++ autocreateinboxfolders option of the imapd.conf. */ ++ ++{ "autosubscribesharedfolders", NULL, STRING } ++/* A list of shared folders (bulletin boards), separated by "|", that the users get ++ automatically subscribed to, after their INBOX is created. The shared folder must ++ have been created and the user must have the required permissions to get subscribed ++ to it. Otherwise, subscribing to the shared folder fails. */ ++ ++{ "autosubscribe_all_sharedfolders", 0, SWITCH } ++/* If set to yes, the user is automatically subscribed to all shared folders, one has permission ++ to subscribe to. */ ++ ++{ "autocreate_sieve_script", NULL, STRING } ++/* The full path of a file that contains a sieve script. This script automatically becomes a ++ user's initial default sieve filter script. When this option is not defined, no default ++ sieve filter is created. The file must be readable by the cyrus daemon. */ ++ ++{ "autocreate_sieve_compiledscript", NULL, STRING } ++/* The full path of a file that contains a compiled in bytecode sieve script. This script ++ automatically becomes a user's initial default sieve filter script. If this option is ++ not specified, or the filename doesn't exist then the script defined by ++ autocreate_sieve_script is compiled on the fly and installed as the user's default ++ sieve script */ ++ ++{ "generate_compiled_sieve_script", 0, SWITCH } ++/* If set to yes and no compiled sieve script file exists, the sieve script which is ++ compiled on the fly will be saved in the file name that autocreate_sieve_compiledscript ++ option points to. In order a compiled script to be generated, autocreate_sieve_script and ++ autocreate_sieve_compiledscript must have valid values */ ++ ++{ "autocreate_users", "anyone", STRING } ++/* A space separated list of users and/or groups that are allowed their INBOX to be ++ automatically created. */ ++ + { "configdirectory", NULL, STRING } + /* The pathname of the IMAP configuration directory. This field is + required. */ +--- /dev/null 2006-07-21 18:50:55.248316500 +0200 ++++ cyrus-imapd-2.3.7/lib/imapoptions.orig 2006-07-23 12:35:41.000000000 +0200 +@@ -0,0 +1,1006 @@ ++# things inside of C comments get copied to the manpage ++# things starting with # are ignored ++ ++/* .\" -*- nroff -*- ++.TH IMAPD.CONF 5 "Project Cyrus" CMU ++.\" ++.\" Copyright (c) 1998-2000 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 other legal ++.\" details, please contact ++.\" Office of Technology Transfer ++.\" Carnegie Mellon University ++.\" 5000 Forbes Avenue ++.\" Pittsburgh, PA 15213-3890 ++.\" (412) 268-4387, fax: (412) 268-7395 ++.\" tech-transfer@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. ++.\" ++.\" $Id$ ++.SH NAME ++imapd.conf \- IMAP configuration file ++.SH DESCRIPTION ++\fB/etc/imapd.conf\fR ++is the configuration file for the Cyrus IMAP server. It defines ++local parameters for IMAP. ++.PP ++Each line of the \fB/etc/imapd.conf\fR file has the form ++.IP ++\fIoption\fR: \fIvalue\fR ++.PP ++where \fIoption\fR is the name of the configuration option being set ++and \fIvalue\fR is the value that the configuration option is being ++set to. ++.PP ++Blank lines and lines beginning with ``#'' are ignored. ++.PP ++For boolean and enumerated options, the values ``yes'', ``on'', ``t'', ++``true'' and ``1'' turn the option on, the values ``no'', ``off'', ++``f'', ``false'' and ``0'' turn the option off. ++.SH FIELD DESCRIPTIONS ++.PP ++The sections below detail options that can be placed in the ++\fB/etc/imapd.conf\fR file, and show each option's default value. ++Some options have no default value, these are listed with ++``''. Some options default to the empty string, these ++are listed with ``''. ++*/ ++ ++# OPTIONS ++ ++{ "admins", "", STRING } ++/* The list of userids with administrative rights. Separate each userid ++ with a space. Sites using Kerberos authentication may use ++ separate "admin" instances. ++.PP ++ Note that accounts used by users should not be administrators. ++ Administrative accounts should not receive mail. That is, if user ++ "jbRo" is a user reading mail, he should not also be in the admins line. ++ Some problems may occur otherwise, most notably the ability of ++ administrators to create top-level mailboxes visible to users, ++ but not writable by users. */ ++ ++{ "afspts_localrealms", NULL, STRING } ++/* The list of realms which are to be treated as local, and thus stripped ++ during identifier canonicalization (for the AFSPTS ptloader module). ++ This is different from loginrealms in that it occurs later in the ++ authorization process (as the user id is canonified for PTS lookup) */ ++ ++{ "afspts_mycell", NULL, STRING } ++/* Cell to use for AFS PTS lookups. Defaults to the local cell. */ ++ ++{ "allowallsubscribe", 0, SWITCH } ++/* Allow subscription to nonexistent mailboxes. This option is ++ typically used on backend servers in a Murder so that users can ++ subscribe to mailboxes that don't reside on their "home" server. ++ This option can also be used as a workaround for IMAP clients which ++ don't play well with nonexistent or unselectable mailboxes (eg. ++ Microsoft Outlook). */ ++ ++{ "allowanonymouslogin", 0, SWITCH } ++/* Permit logins by the user "anonymous" using any password. Also ++ allows use of the SASL ANONYMOUS mechanism. */ ++ ++{ "allowapop", 1, SWITCH } ++/* Allow use of the POP3 APOP authentication command. ++.PP ++ Note that this command requires that SASL is compiled with APOP ++ support, that the plaintext passwords are available in a SASL auxprop ++ backend (eg. sasldb), and that the system can provide enough entropy ++ (eg. from /dev/urandom) to create a challenge in the banner. */ ++ ++{ "allownewnews", 0, SWITCH } ++/* Allow use of the NNTP NEWNEWS command. ++.PP ++ Note that this is a very expensive command and should only be ++ enabled when absolutely necessary. */ ++ ++{ "allowplaintext", 1, SWITCH } ++/* Allow the use of cleartext passwords on the wire. */ ++ ++{ "allowusermoves", 0, SWITCH } ++/* Allow moving user accounts (with associated meta-data) via RENAME ++ or XFER. ++.PP ++ Note that measures should be taken to make sure that the user being ++ moved is not logged in, and can not login during the move. Failure ++ to do so may result in the user's meta-data (seen state, ++ subscriptions, etc) being corrupted or out of date. */ ++ ++{ "altnamespace", 0, SWITCH } ++/* Use the alternate IMAP namespace, where personal folders reside at the ++ same level in the hierarchy as INBOX. ++.PP ++ This option ONLY applies where interaction takes place with the ++ client/user. Currently this is limited to the IMAP protocol (imapd) ++ and Sieve scripts (lmtpd). This option does NOT apply to admin tools ++ such as cyradm (admins ONLY), reconstruct, quota, etc., NOR does it ++ affect LMTP delivery of messages directly to mailboxes via ++ plus-addressing. */ ++ ++{ "annotation_db", "skiplist", STRINGLIST("berkeley", "berkeley-hash", "skiplist")} ++/* The cyrusdb backend to use for mailbox annotations. */ ++ ++{ "auth_mech", "unix", STRINGLIST("unix", "pts", "krb", "krb5")} ++/* The authorization mechanism to use. */ ++ ++{ "autocreatequota", 0, INT } ++/* If nonzero, normal users may create their own IMAP accounts by ++ creating the mailbox INBOX. The user's quota is set to the value ++ if it is positive, otherwise the user has unlimited quota. */ ++ ++{ "berkeley_cachesize", 512, INT } ++/* Size (in kilobytes) of the shared memory buffer pool (cache) used ++ by the berkeley environment. The minimum allowed value is 20. The ++ maximum allowed value is 4194303 (4GB). */ ++ ++{ "berkeley_locks_max", 50000, INT } ++/* Maximum number of locks to be held or requested in the berkeley ++ environment. */ ++ ++{ "berkeley_txns_max", 100, INT } ++/* Maximum number of transactions to be supported in the berkeley ++ environment. */ ++ ++{ "client_timeout", 10, INT } ++/* Number of seconds to wait before returning a timeout failure when ++ performing a client connection (e.g. in a murder environment) */ ++ ++{ "configdirectory", NULL, STRING } ++/* The pathname of the IMAP configuration directory. This field is ++ required. */ ++ ++{ "debug_command", NULL, STRING } ++/* Debug command to be used by processes started with -D option. The string ++ is a C format string that gets 3 options: the first is the name of the ++ executable (without path). The second is the pid (integer) and the third ++ is the service ID. Example: /usr/local/bin/gdb /usr/cyrus/bin/%s %d */ ++ ++{ "defaultacl", "anyone lrs", STRING } ++/* The Access Control List (ACL) placed on a newly-created (non-user) ++ mailbox that does not have a parent mailbox. */ ++ ++{ "defaultdomain", NULL, STRING } ++/* The default domain for virtual domain support */ ++ ++{ "defaultpartition", "default", STRING } ++/* The partition name used by default for new mailboxes. */ ++ ++{ "deleteright", "c", STRING } ++/* Deprecated - only used for backwards compatibility with existing ++ installations. Lists the old RFC 2086 right which was used to ++ grant the user the ability to delete a mailbox. If a user has this ++ right, they will automatically be given the new 'x' right. */ ++ ++{ "duplicate_db", "berkeley-nosync", STRINGLIST("berkeley", "berkeley-nosync", "berkeley-hash", "berkeley-hash-nosync", "skiplist")} ++/* The cyrusdb backend to use for the duplicate delivery suppression ++ and sieve. */ ++ ++{ "duplicatesuppression", 1, SWITCH } ++/* If enabled, lmtpd will suppress delivery of a message to a mailbox if ++ a message with the same message-id (or resent-message-id) is recorded ++ as having already been delivered to the mailbox. Records the mailbox ++ and message-id/resent-message-id of all successful deliveries. */ ++ ++{ "expunge_mode", "immediate", ENUM("immediate", "delayed") } ++/* The mode in which messages (and their corresponding cache entries) ++ are expunged. "Immediate" mode is the default behavior in which the ++ message files and cache entries are purged at the time of the ++ EXPUNGE. In "delayed" mode, the messages are removed from the ++ mailbox index at the time of the EXPUNGE (hiding them from the ++ client), but the message files and cache entries are left behind, ++ to be purged at a later time by "cyr_expire". This reduces the ++ amount of I/O that takes place at the time of EXPUNGE and should ++ result in greater responsiveness for the client, especially when ++ expunging a large number of messages. */ ++ ++{ "flushseenstate", 0, SWITCH } ++/* If enabled, changes to the seen state will be flushed to disk ++ immediately, otherwise changes will be cached and flushed when the ++ mailbox is closed. This option may be used to fix the problem of ++ previously read messages being marked as unread in Microsoft ++ Outlook, at the expense of a loss of performance/scalability. */ ++ ++{ "foolstupidclients", 0, SWITCH } ++/* If enabled, only list the personal namespace when a LIST "*" is performed. ++ (it changes the request to a LIST "INBOX*" */ ++ ++{ "force_sasl_client_mech", NULL, STRING } ++/* Force preference of a given SASL mechanism for client side operations ++ (e.g. murder environments). This is separate from (and overridden by) ++ the ability to use the _mechs option to set preferred ++ mechanisms for a specific host */ ++ ++{ "fulldirhash", 0, SWITCH } ++/* If enabled, uses an improved directory hashing scheme which hashes ++ the entire username instead of using just the first letter. This ++ changes hash algorithm used for quota and user directories and if ++ \fIhashimapspool\fR is enabled, the entire mail spool. ++.PP ++ Note that this option can NOT be changed on a live system. The ++ server must be quiesced and then the directories moved with the ++ \fBrehash\fR utility. */ ++ ++{ "hashimapspool", 0, SWITCH } ++/* If enabled, the partitions will also be hashed, in addition to the ++ hashing done on configuration directories. This is recommended if ++ one partition has a very bushy mailbox tree. */ ++ ++# Commented out - there's no such thing as "hostname_mechs", but we need ++# this for the man page ++# { "hostname_mechs", NULL, STRING } ++/* Force a particular list of SASL mechanisms to be used when authenticating ++ to the backend server hostname (where hostname is the short hostname of ++ the server in question). If it is not specified it will query the server ++ for available mechanisms and pick one to use. - Cyrus Murder */ ++ ++# Commented out - there's no such thing as "hostname_password", but we need ++# this for the man page ++# { "hostname_password", NULL, STRING } ++/* The password to use for authentication to the backend server hostname ++ (where hostname is the short hostname of the server) - Cyrus Murder */ ++ ++{ "idlesocket", "{configdirectory}/socket/idle", STRING } ++/* Unix domain socket that idled listens on. */ ++ ++{ "ignorereference", 0, SWITCH } ++/* For backwards compatibility with Cyrus 1.5.10 and earlier -- ignore ++ the reference argument in LIST or LSUB commands. */ ++ ++{ "imapidlepoll", 60, INT } ++/* The interval (in seconds) for polling for mailbox changes and ++ ALERTs while running the IDLE command. This option is used when ++ idled is not enabled or can not be contacted. The minimum value is ++ 1. A value of 0 will disable IDLE. */ ++ ++{ "imapidresponse", 1, SWITCH } ++/* If enabled, the server responds to an ID command with a parameter ++ list containing: version, vendor, support-url, os, os-version, ++ command, arguments, environment. Otherwise the server returns NIL. */ ++ ++{ "imapmagicplus", 0, SWITCH } ++/* Only list a restricted set of mailboxes via IMAP by using ++ userid+namespace syntax as the authentication/authorization id. ++ Using userid+ (with an empty namespace) will list only subscribed ++ mailboxes. */ ++ ++{ "implicit_owner_rights", "lca", STRING } ++/* The implicit Access Control List (ACL) for the owner of a mailbox. */ ++ ++# Commented out - there's no such thing as "@include", but we need ++# this for the man page ++# { "@include", NULL, STRING } ++/* Directive which includes the specified file as part of the ++ configuration. If the path to the file is not absolute, CYRUS_PATH ++ is prepended. */ ++ ++{ "ldap_authz", NULL, STRING } ++/* SASL authorization ID for the LDAP server */ ++ ++{ "ldap_base", "", STRING } ++/* Contains the LDAP base dn for the LDAP ptloader module */ ++ ++{ "ldap_bind_dn", NULL, STRING } ++/* Bind DN for the connection to the LDAP server (simple bind). ++ Do not use for anonymous simple binds */ ++ ++{ "ldap_deref", "never", STRINGLIST("search", "find", "always", "never") } ++/* Specify how aliases dereferencing is handled during search. */ ++ ++{ "ldap_filter", "(uid=%u)", STRING } ++/* Specify a filter that searches user identifiers. The following tokens can be ++ used in the filter string: ++ ++ %% = % ++ %u = user ++ %U = user portion of %u (%U = test when %u = test@domain.tld) ++ %d = domain portion of %u if available (%d = domain.tld when %u = ++ %test@domain.tld), otherwise same as %r ++ %D = user dn. (use when ldap_member_method: filter) ++ %1-9 = domain tokens (%1 = tld, %2 = domain when %d = domain.tld) ++ ++ ldap_filter is not used when ldap_sasl is enabled. */ ++ ++{ "ldap_group_base", "", STRING } ++/* LDAP base dn for ldap_group_filter. */ ++ ++{ "ldap_group_filter", "(cn=%u)", STRING } ++/* Specify a filter that searches for group identifiers. ++ See ldap_filter for more options. */ ++ ++{ "ldap_group_scope", "sub", STRINGLIST("sub", "one", "base") } ++/* Specify search scope for ldap_group_filter. */ ++ ++{ "ldap_id", NULL, STRING } ++/* SASL authentication ID for the LDAP server */ ++ ++{ "ldap_mech", NULL, STRING } ++/* SASL mechanism for LDAP authentication */ ++ ++{ "ldap_member_attribute", NULL, STRING } ++/* See ldap_member_method. */ ++ ++{ "ldap_member_base", "", STRING } ++/* LDAP base dn for ldap_member_filter. */ ++ ++{ "ldap_member_filter", "(member=%D)", STRING } ++/* Specify a filter for "ldap_member_method: filter". ++ See ldap_filter for more options. */ ++ ++{ "ldap_member_method", "attribute", STRINGLIST("attribute", "filter") } ++/* Specify a group method. The "attribute" method retrieves groups from ++ a multi-valued attribute specified in ldap_member_attribute. ++ ++ The "filter" method uses a filter, specified by ldap_member_filter, to find ++ groups; ldap_member_attribute is a single-value attribute group name. */ ++ ++{ "ldap_member_scope", "sub", STRINGLIST("sub", "one", "base") } ++/* Specify search scope for ldap_member_filter. */ ++ ++{ "ldap_password", NULL, STRING } ++/* Password for the connection to the LDAP server (SASL and simple bind). ++ Do not use for anonymous simple binds */ ++ ++{ "ldap_realm", NULL, STRING } ++/* SASL realm for LDAP authentication */ ++ ++{ "ldap_referrals", 0, SWITCH } ++/* Specify whether or not the client should follow referrals. */ ++ ++{ "ldap_restart", 1, SWITCH } ++/* Specify whether or not LDAP I/O operations are automatically restarted ++ if they abort prematurely. */ ++ ++{ "ldap_sasl", 1, SWITCH } ++/* Use SASL for LDAP binds in the LDAP PTS module. */ ++ ++{ "ldap_sasl_authc", NULL, STRING } ++/* Deprecated. Use ldap_id */ ++ ++{ "ldap_sasl_authz", NULL, STRING } ++/* Deprecated. Use ldap_authz */ ++ ++{ "ldap_sasl_mech", NULL, STRING } ++/* Deprecated. Use ldap_mech */ ++ ++{ "ldap_sasl_password", NULL, STRING } ++/* Deprecated. User ldap_password */ ++ ++{ "ldap_sasl_realm", NULL, STRING } ++/* Deprecated. Use ldap_realm */ ++ ++{ "ldap_scope", "sub", STRINGLIST("sub", "one", "base") } ++/* Specify search scope. */ ++ ++{ "ldap_servers", "ldap://localhost/", STRING } ++/* Deprecated. Use ldap_uri */ ++ ++{ "ldap_size_limit", 1, INT } ++/* Specify a number of entries for a search request to return. */ ++ ++{ "ldap_start_tls", 0, SWITCH } ++/* Use StartTLS extended operation. Do not use ldaps: ldap_uri when ++ this option is enabled. */ ++ ++{ "ldap_time_limit", 5, INT } ++/* Specify a number of seconds for a search request to complete. */ ++ ++{ "ldap_timeout", 5, INT } ++/* Specify a number of seconds a search can take before timing out. */ ++ ++{ "ldap_tls_cacert_dir", NULL, STRING } ++/* Path to directory with CA (Certificate Authority) certificates. */ ++ ++{ "ldap_tls_cacert_file", NULL, STRING } ++/* File containing CA (Certificate Authority) certificate(s). */ ++ ++{ "ldap_tls_cert", NULL, STRING } ++/* File containing the client certificate. */ ++ ++{ "ldap_tls_check_peer", 0, SWITCH } ++/* Require and verify server certificate. If this option is yes, ++ you must specify ldap_tls_cacert_file or ldap_tls_cacert_dir. */ ++ ++{ "ldap_tls_ciphers", NULL, STRING } ++/* List of SSL/TLS ciphers to allow. The format of the string is ++ described in ciphers(1). */ ++ ++{ "ldap_tls_key", NULL, STRING } ++/* File containing the private client key. */ ++ ++{ "ldap_uri", NULL, STRING } ++/* Contains a list of the URLs of all the LDAP servers when using the ++ LDAP PTS module. */ ++ ++{ "ldap_version", 3, INT } ++/* Specify the LDAP protocol version. If ldap_start_tls and/or ++ ldap_use_sasl are enabled, ldap_version will be automatically ++ set to 3. */ ++ ++{ "lmtp_downcase_rcpt", 0, SWITCH } ++/* If enabled, lmtpd will convert the recipient address to lowercase ++ (up to a '+' character, if present). */ ++ ++{ "lmtp_fuzzy_mailbox_match", 0, SWITCH } ++/* If enabled, and the mailbox specified in the detail part of the ++ recipient (everything after the '+') does not exist, lmtpd will try ++ to find the closest match (ignoring case, ignoring whitespace, ++ falling back to parent) to the specified mailbox name. */ ++ ++{ "lmtp_over_quota_perm_failure", 0, SWITCH } ++/* If enabled, lmtpd returns a permanent failure code when a user's ++ mailbox is over quota. By default, the failure is temporary, ++ causing the MTA to queue the message and retry later. */ ++ ++{ "lmtp_strict_quota", 0, SWITCH } ++/* If enabled, lmtpd returns a failure code when the incoming message ++ will cause the user's mailbox to exceed its quota. By default, the ++ failure won't occur until the mailbox is already over quota. */ ++ ++{ "lmtpsocket", "{configdirectory}/socket/lmtp", STRING } ++/* Unix domain socket that lmtpd listens on, used by deliver(8). This should ++ match the path specified in cyrus.conf(5). */ ++ ++# xxx how does this tie into virtual domains? ++{ "loginrealms", "", STRING } ++/* The list of remote realms whose users may authenticate using cross-realm ++ authentication identifiers. Separate each realm name by a space. (A ++ cross-realm identity is considered any identity returned by SASL ++ with an "@" in it.). */ ++ ++{ "loginuseacl", 0, SWITCH } ++/* If enabled, any authentication identity which has \fBa\fR rights on a ++ user's INBOX may log in as that user. */ ++ ++{ "logtimestamps", 0, SWITCH } ++/* Include notations in the protocol telemetry logs indicating the number of ++ seconds since the last command or response. */ ++ ++{ "mailnotifier", NULL, STRING } ++/* Notifyd(8) method to use for "MAIL" notifications. If not set, "MAIL" ++ notifications are disabled. */ ++ ++{ "maxmessagesize", 0, INT } ++/* Maximum incoming LMTP message size. If non-zero, lmtpd will reject ++ messages larger than \fImaxmessagesize\fR bytes. If set to 0, this ++ will allow messages of any size (the default). */ ++ ++{ "mboxkey_db", "skiplist", STRINGLIST("berkeley", "skiplist") } ++/* The cyrusdb backend to use for mailbox keys. */ ++ ++{ "mboxlist_db", "skiplist", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist")} ++/* The cyrusdb backend to use for the mailbox list. */ ++ ++{ "metapartition_files", "", BITFIELD("header", "index", "cache", "expunge", "squat") } ++/* Space-separated list of metadata files to be stored on a ++ \fImetapartition\fR rather than in the mailbox directory on a spool ++ partition. */ ++ ++# Commented out - there's no such thing as "metapartition-name", ++# but we need this for the man page ++# { "metapartition-name", NULL, STRING } ++/* The pathname of the metadata partition \fIname\fR, corresponding to ++ spool partition \fBpartition-name\fR. For any mailbox residing in ++ a directory on \fBpartition-name\fR, the metadata files listed in ++ \fImetapartition_files\fR will be stored in a corresponding directory on ++ \fBmetapartition-name\fR. Note that not every ++ \fBpartition-name\fR option is required to have a corresponding ++ \fBmetapartition-name\fR option, so that you can selectively choose ++ which spool partitions will have separate metadata partitions. */ ++ ++{ "mupdate_authname", NULL, STRING } ++/* The SASL username (Authentication Name) to use when authenticating to the ++ mupdate server (if needed). */ ++ ++{ "mupdate_config", "standard", ENUM("standard", "unified", "replicated") } ++/* The configuration of the mupdate servers in the Cyrus Murder. ++ The "standard" config is one in which there are discreet frontend ++ (proxy) and backend servers. The "unified" config is one in which ++ a server can be both a frontend and backend. The "replicated" ++ config is one in which multiple backend servers all share the same ++ mailspool, but each have their own "replicated" copy of ++ mailboxes.db. */ ++ ++{ "md5_dir", NULL, STRING } ++/* Top level directory for MD5 store manipulated by make_md5. File ++ structure within this directory is one file for each user on the system, ++ hashed on the first letter of the userid (e.g: /var/imap/md5/d/dpc22). */ ++ ++{ "md5_user_map", NULL, STRING } ++/* Map file (cdb) to allow partial make_md5 runs. Maps username to UID */ ++ ++# xxx badly worded ++{ "mupdate_connections_max", 128, INT } ++/* The max number of connections that a mupdate process will allow, this ++ is related to the number of file descriptors in the mupdate process. ++ Beyond this number connections will be immediately issued a BYE response. */ ++ ++{ "mupdate_password", NULL, STRING } ++/* The SASL password (if needed) to use when authenticating to the ++ mupdate server. */ ++ ++{ "mupdate_port", 3905, INT } ++/* The port of the mupdate server for the Cyrus Murder */ ++ ++{ "mupdate_realm", NULL, STRING } ++/* The SASL realm (if needed) to use when authenticating to the mupdate ++ server. */ ++ ++{ "mupdate_retry_delay", 20, INT } ++/* The base time to wait between connection retries to the mupdate server. */ ++ ++{ "mupdate_server", NULL, STRING } ++/* The mupdate server for the Cyrus Murder */ ++ ++{ "mupdate_username", "", STRING } ++/* The SASL username (Authorization Name) to use when authenticating to ++ the mupdate server */ ++ ++{ "mupdate_workers_max", 50, INT } ++/* The maximum number of mupdate worker threads (overall) */ ++ ++{ "mupdate_workers_maxspare", 10, INT } ++/* The maximum number of idle mupdate worker threads */ ++ ++{ "mupdate_workers_minspare", 2, INT } ++/* The minimum number of idle mupdate worker threads */ ++ ++{ "mupdate_workers_start", 5, INT } ++/* The number of mupdate worker threads to start */ ++ ++{ "netscapeurl", "http://asg.web.cmu.edu/cyrus/imapd/netscape-admin.html", STRING } ++/* If enabled at compile time, this specifies a URL to reply when ++ Netscape asks the server where the mail administration HTTP server ++ is. The default is a site at CMU with a hopefully informative ++ message; administrators should set this to a local resource with ++ some information of greater use. */ ++ ++{ "newsmaster", "news", STRING } ++/* Userid that is used for checking access controls when executing ++ Usenet control messages. For instance, to allow articles to be ++ automatically deleted by cancel messages, give the "news" user ++ the 'd' right on the desired mailboxes. To allow newsgroups to be ++ automatically created, deleted and renamed by the corresponding ++ control messages, give the "news" user the 'c' right on the desired ++ mailbox hierarchies. */ ++ ++{ "newspeer", NULL, STRING } ++/* A list of whitespace-separated news server specifications to which ++ articles should be fed. Each server specification is a string of ++ the form [user[:pass]@]host[:port][/wildmat] where 'host' is the fully ++ qualified hostname of the server, 'port' is the port on which the ++ server is listening, 'user' and 'pass' are the authentication ++ credentials and 'wildmat' is a pattern that specifies which groups ++ should be fed. If no 'port' is specified, port 119 is used. If ++ no 'wildmat' is specified, all groups are fed. If 'user' is specified ++ (even if empty), then the NNTP POST command will be used to feed ++ the article to the server, otherwise the IHAVE command will be ++ used. ++.br ++.sp ++ A '@' may be used in place of '!' in the wildmat to prevent feeding ++ articles cross-posted to the given group, otherwise cross-posted ++ articles are fed if any part of the wildmat matches. For example, ++ the string "peer.example.com:*,!control.*,@local.*" would feed all ++ groups except control messages and local groups to ++ peer.example.com. In the case of cross-posting to local groups, ++ these articles would not be fed. */ ++ ++{ "newspostuser", NULL, STRING } ++/* Userid used to deliver usenet articles to newsgroup folders ++ (usually via lmtp2nntp). For example, if set to "post", email sent ++ to "post+comp.mail.imap" would be delivered to the "comp.mail.imap" ++ folder. ++.br ++.sp ++ When set, the Cyrus NNTP server will add a \fITo:\fR header to each ++ incoming usenet article. This \fITo:\fR header will contain email ++ delivery addresses corresponding to each newsgroup in the ++ \fINewsgroups:\fR header. By default, a \fITo:\fR header is not ++ added to usenet articles. */ ++ ++{ "newsprefix", NULL, STRING } ++/* Prefix to be prepended to newsgroup names to make the corresponding ++ IMAP mailbox names. */ ++ ++{ "nntptimeout", 3, INT } ++/* Set the length of the NNTP server's inactivity autologout timer, ++ in minutes. The minimum value is 3, the default. */ ++ ++{ "notifysocket", "{configdirectory}/socket/notify", STRING } ++/* Unix domain socket that the new mail notification daemon listens on. */ ++ ++# Commented out - there's no such thing as "partition-name", but we need ++# this for the man page ++# { "partition-name", NULL, STRING } ++/* The pathname of the partition \fIname\fR. At least one field, for the ++ partition named in the \fBdefaultpartition\fR option, is required. ++ For example, if the value of the \fBdefaultpartion\fR option is ++ \fBdefault\fR, then the \fBpartition-default\fR field is required. */ ++ ++{ "plaintextloginpause", 0, INT } ++/* Number of seconds to pause after a successful plaintext login. For ++ systems that support strong authentication, this permits users to ++ perceive a cost of using plaintext passwords. (This does not ++ affect the use of PLAIN in SASL authentications.) */ ++ ++{ "plaintextloginalert", NULL, STRING } ++/* Message to send to client after a successful plaintext login. */ ++ ++{ "popexpiretime", -1, INT } ++/* The number of days advertised as being the minimum a message may be ++ left on the POP server before it is deleted (via the CAPA command, ++ defined in the POP3 Extension Mechanism, which some clients may ++ support). "NEVER", the default, may be specified with a negative ++ number. The Cyrus POP3 server never deletes mail, no matter what ++ the value of this parameter is. However, if a site implements a ++ less liberal policy, it needs to change this parameter ++ accordingly. */ ++ ++{ "popminpoll", 0, INT } ++/* Set the minimum amount of time the server forces users to wait ++ between successive POP logins, in minutes. */ ++ ++{ "popsubfolders", 0, SWITCH } ++/* Allow access to subfolders of INBOX via POP3 by using ++ userid+subfolder syntax as the authentication/authorization id. */ ++ ++{ "poppollpadding", 1, INT } ++/* Create a softer minimum poll restriction. Allows \fIpoppollpadding\fR ++ connections before the minpoll restriction is triggered. Additionally, ++ one padding entry is recovered every \fIpopminpoll\fR minutes. ++ This allows for the occasional polling rate faster than popminpoll, ++ (i.e. for clients that require a send/receive to send mail) but still ++ enforces the rate long-term. Default is 1 (disabled). ++.br ++.sp ++ The easiest way to think of it is a queue of past connections, with one ++ slot being filled for every connection, and one slot being cleared ++ every \fIpopminpoll\fR minutes. When the queue is full, the user ++ will not be able to check mail again until a slot is cleared. If the ++ user waits a sufficient amount of time, they will get back many or all ++ of the slots. */ ++ ++{ "poptimeout", 10, INT } ++/* Set the length of the POP server's inactivity autologout timer, ++ in minutes. The minimum value is 10, the default. */ ++ ++{ "popuseacl", 0, SWITCH } ++/* Enforce IMAP ACLs in the pop server. Due to the nature of the POP3 ++ protocol, the only rights which are used by the pop server are 'r' ++ and 'd' for the owner of the mailbox. The 'r' right allows the ++ user to open the mailbox and list/retrieve messages. The 'd' right ++ allows the user to delete messages. */ ++ ++{ "postmaster", "postmaster", STRING } ++/* Username that is used as the 'From' address in rejection MDNs produced ++ by sieve. */ ++ ++{ "postspec", NULL, STRING } ++ ++{ "postuser", "", STRING } ++/* Userid used to deliver messages to shared folders. For example, if ++ set to "bb", email sent to "bb+shared.blah" would be delivered to ++ the "shared.blah" folder. By default, an email address of ++ "+shared.blah" would be used. */ ++ ++{ "proxy_authname", "proxy", STRING } ++/* The authentication name to use when authenticating to a backend server ++ in the Cyrus Murder. */ ++ ++{ "proxy_password", NULL, STRING } ++/* The default password to use when authenticating to a backend server ++ in the Cyrus Murder. May be overridden on a host-specific basis using ++ the hostname_password option. */ ++ ++{ "proxy_realm", NULL, STRING } ++/* The authentication realm to use when authenticating to a backend server ++ in the Cyrus Murder */ ++ ++{ "proxyd_allow_status_referral", 0, SWITCH } ++/* Set to true to allow proxyd to issue referrals to clients that support it ++ when answering the STATUS command. This is disabled by default since ++ some clients issue many STATUS commands in a row, and do not cache the ++ connections that these referrals would cause, thus resulting in a higher ++ authentication load on the respective backend server. */ ++ ++{ "proxyservers", NULL, STRING } ++/* A list of users and groups that are allowed to proxy for other ++ users, separated by spaces. Any user listed in this will be ++ allowed to login for any other user: use with caution. */ ++ ++{ "pts_module", "afskrb", STRINGLIST("afskrb", "ldap") } ++/* The PTS module to use. */ ++ ++{ "ptloader_sock", NULL, STRING } ++/* Unix domain socket that ptloader listens on. ++ (defaults to configdir/ptclient/ptsock) */ ++ ++{ "ptscache_db", "berkeley", STRINGLIST("berkeley", "berkeley-hash", "skiplist")} ++/* The cyrusdb backend to use for the pts cache. */ ++ ++{ "ptscache_timeout", 10800, INT } ++/* The timeout (in seconds) for the PTS cache database when using the ++ auth_krb_pts authorization method (default: 3 hours). */ ++ ++{ "ptskrb5_convert524", 1, SWITCH } ++/* When using the AFSKRB ptloader module with Kerberos 5 canonicalization, ++ do the final 524 conversion to get a n AFS style name (using '.' instead ++ of '/', and using short names */ ++ ++{ "ptskrb5_strip_default_realm", 1, SWITCH } ++/* When using the AFSKRB ptloader module with Kerberos 5 canonicalization, ++ strip the default realm from the userid (this does not affect the stripping ++ of realms specified by the afspts_localrealms option) */ ++ ++{ "quota_db", "quotalegacy", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist", "quotalegacy")} ++/* The cyrusdb backend to use for quotas. */ ++ ++{ "quotawarn", 90, INT } ++/* The percent of quota utilization over which the server generates ++ warnings. */ ++ ++{ "quotawarnkb", 0, INT } ++/* The maximum amount of free space (in kB) in which to give a quota ++ warning (if this value is 0, or if the quota is smaller than this ++ amount, than warnings are always given). */ ++ ++{ "reject8bit", 0, SWITCH } ++/* If enabled, lmtpd rejects messages with 8-bit characters in the ++ headers. Otherwise, 8-bit characters are changed to `X'. (A ++ proper solution to non-ASCII characters in headers is offered by ++ RFC 2047 and its predecessors.) */ ++ ++{ "rfc2046_strict", 0, SWITCH } ++/* If enabled, imapd will be strict (per RFC 2046) when matching MIME ++ boundary strings. This means that boundaries containing other ++ boundaries as substrings will be treated as identical. Since ++ enabling this option will break some messages created by Eudora 5.1 ++ (and earlier), it is recommended that it be left disabled unless ++ there is good reason to do otherwise. */ ++ ++{ "rfc3028_strict", 1, SWITCH } ++/* If enabled, Sieve will be strict (per RFC 3028) with regards to ++ which headers are allowed to be used in address and envelope tests. ++ This means that only those headers which are defined to contain addresses ++ will be allowed in address tests and only "to" and "from" will be ++ allowed in envelope tests. When disabled, ANY grammatically correct header ++ will be allowed. */ ++ ++# Commented out - used by libsasl ++# { "sasl_auto_transition", 0, SWITCH } ++/* If enabled, the SASL library will automatically create authentication ++ secrets when given a plaintext password. See the SASL documentation. */ ++ ++{ "sasl_maximum_layer", 256, INT } ++/* Maximum SSF (security strength factor) that the server will allow a ++ client to negotiate. */ ++ ++{ "sasl_minimum_layer", 0, INT } ++/* The minimum SSF that the server will allow a client to negotiate. ++ A value of 1 requires integrity protection; any higher value ++ requires some amount of encryption. */ ++ ++# Commented out - used by libsasl ++# { "sasl_option", 0, STRING } ++/* Any SASL option can be set by preceding it with "sasl_". This ++ file overrides the SASL configuration file. */ ++ ++# Commented out - used by libsasl ++# { "sasl_pwcheck_method", NULL, STRING } ++/* The mechanism used by the server to verify plaintext passwords. ++ Possible values include "auxprop", "saslauthd", and "pwcheck". */ ++ ++{ "seenstate_db", "skiplist", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist")} ++/* The cyrusdb backend to use for the seen state. */ ++ ++{ "sendmail", "/usr/lib/sendmail", STRING } ++/* The pathname of the sendmail executable. Sieve invokes sendmail ++ for sending rejections, redirects and vacation responses. */ ++ ++{ "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. */ ++ ++{ "sharedprefix", "Shared Folders", STRING } ++/* If using the alternate IMAP namespace, the prefix for the shared ++ namespace. The hierarchy delimiter will be automatically appended. */ ++ ++{ "sieve_extensions", "fileinto reject vacation imapflags notify envelope relational regex subaddress copy", BITFIELD("fileinto", "reject", "vacation", "imapflags", "notify", "include", "envelope", "body", "relational", "regex", "subaddress", "copy") } ++/* Space-separated list of Sieve extensions allowed to be used in ++ sieve scripts, enforced at submission by timsieved(8). Any ++ previously installed script will be unaffected by this option and ++ will continue to execute regardless of the extensions used. This ++ option has no effect on options that are disabled at compile time ++ (e.g. "regex"). */ ++ ++{ "sieve_maxscriptsize", 32, INT } ++/* Maximum size (in kilobytes) any sieve script can be, enforced at ++ submission by timsieved(8). */ ++ ++{ "sieve_maxscripts", 5, INT } ++/* Maximum number of sieve scripts any user may have, enforced at ++ submission by timsieved(8). */ ++ ++{ "sievedir", "/usr/sieve", STRING } ++/* If sieveusehomedir is false, this directory is searched for Sieve ++ scripts. */ ++ ++{ "sievenotifier", NULL, STRING } ++/* Notifyd(8) method to use for "SIEVE" notifications. If not set, "SIEVE" ++ notifications are disabled. ++.PP ++ This method is only used when no method is specified in the script. */ ++ ++{ "sieveusehomedir", 0, SWITCH } ++/* If enabled, lmtpd will look for Sieve scripts in user's home ++ directories: ~user/.sieve. */ ++ ++{ "singleinstancestore", 1, SWITCH } ++/* If enabled, imapd, lmtpd and nntpd attempt to only write one copy ++ of a message per partition and create hard links, resulting in a ++ potentially large disk savings. */ ++ ++{ "skiplist_unsafe", 0, SWITCH } ++/* If enabled, this option forces the skiplist cyrusdb backend to ++ not sync writes to the disk. Enabling this option is NOT RECOMMENDED. */ ++ ++{ "soft_noauth", 1, SWITCH } ++/* If enabled, lmtpd returns temporary failures if the client does not ++ successfully authenticate. Otherwise lmtpd returns permanent failures ++ (causing the mail to bounce immediately). */ ++ ++{ "srvtab", "", STRING } ++/* The pathname of \fIsrvtab\fR file containing the server's private ++ key. This option is passed to the SASL library and overrides its ++ default setting. */ ++ ++{ "submitservers", NULL, STRING } ++/* A list of users and groups that are allowed to resolve "urlauth=submit+" ++ IMAP URLs, separated by spaces. Any user listed in this will be ++ allowed to fetch the contents of any valid "urlauth=submit+" IMAP URL: ++ use with caution. */ ++ ++{ "subscription_db", "flat", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist")} ++/* The cyrusdb backend to use for the subscriptions list. */ ++ ++{ "sync_authname", NULL, STRING } ++/* The authentication name to use when authenticating to a sync server. */ ++ ++{ "sync_host", NULL, STRING } ++/* Name of the host (replica running sync_server(8)) to which ++ replication actions will be sent by sync_client(8). */ ++ ++{ "sync_log", 0, SWITCH } ++/* Enable replication action logging by lmtpd(8), imapd(8), pop3d(8), ++ and nntpd(8). The log {configdirectory}/sync/log is used by ++ sync_client(8) for "rolling" replication. */ ++ ++{ "sync_machineid", -1, INT } ++/* Machine ID of this server which must be unique within a cluster. ++ Any negative number, the default, will disable the use of UUIDs for ++ replication. */ ++ ++{ "sync_password", NULL, STRING } ++/* The default password to use when authenticating to a sync server. */ ++ ++{ "sync_realm", NULL, STRING } ++/* The authentication realm to use when authenticating to a sync server. */ ++ ++{ "sync_repeat_interval", 1, INT } ++/* Minimum interval (in seconds) between replication runs in rolling ++ replication mode. If a replication run takes longer than this ++ time, we repeat immediately. */ ++ ++{ "sync_shutdown_file", NULL, STRING } ++/* Simple latch used to tell sync_client(8) that it should shut down at the ++ next opportunity. Safer than sending signals to running processes */ ++ ++{ "syslog_prefix", NULL, STRING } ++/* String to be appended to the process name in syslog entries. */ ++ ++{ "temp_path", "/tmp", STRING } ++/* The pathname to store temporary files in */ ++ ++{ "timeout", 30, INT } ++/* The length of the IMAP server's inactivity autologout timer, ++ in minutes. The minimum value is 30, the default. */ ++ ++{ "tls_ca_file", NULL, STRING } ++/* File containing one or more Certificate Authority (CA) certificates. */ ++ ++{ "tls_ca_path", NULL, STRING } ++/* Path to directory with certificates of CAs. This directory must ++ have filenames with the hashed value of the certificate (see ++ openssl(XXX)). */ ++ ++{ "tlscache_db", "berkeley-nosync", STRINGLIST("berkeley", "berkeley-nosync", "berkeley-hash", "berkeley-hash-nosync", "skiplist")} ++/* The cyrusdb backend to use for the TLS cache. */ ++ ++{ "tls_cert_file", NULL, STRING } ++/* File containing the certificate presented for server authentication ++ during STARTTLS. A value of "disabled" will disable SSL/TLS. */ ++ ++{ "tls_cipher_list", "DEFAULT", STRING } ++/* The list of SSL/TLS ciphers to allow. The format of the string is ++ described in ciphers(1). */ ++ ++{ "tls_key_file", NULL, STRING } ++/* File containing the private key belonging to the server ++ certificate. A value of "disabled" will disable SSL/TLS. */ ++ ++{ "tls_require_cert", 0, SWITCH } ++/* Require a client certificate for ALL services (imap, pop3, lmtp, sieve). */ ++ ++{ "tls_session_timeout", 1440, INT } ++/* The length of time (in minutes) that a TLS session will be cached ++ for later reuse. The maximum value is 1440 (24 hours), the ++ default. A value of 0 will disable session caching. */ ++ ++{ "umask", "077", STRING } ++/* The umask value used by various Cyrus IMAP programs. */ ++ ++{ "username_tolower", 1, SWITCH } ++/* Convert usernames to all lowercase before login/authenticate. This ++ is useful with authentication backends which ignore case during ++ username lookups (such as LDAP). */ ++ ++{ "userprefix", "Other Users", STRING } ++/* If using the alternate IMAP namespace, the prefix for the other users ++ namespace. The hierarchy delimiter will be automatically appended. */ ++ ++# xxx badly worded ++{ "unix_group_enable", 1, SWITCH } ++/* Should we look up groups when using auth_unix (disable this if you are ++ not using groups in ACLs for your IMAP server, and you are using auth_unix ++ with a backend (such as LDAP) that can make getgrent() calls very ++ slow) */ ++ ++{ "unixhierarchysep", 0, SWITCH } ++/* Use the UNIX separator character '/' for delimiting levels of ++ mailbox hierarchy. The default is to use the netnews separator ++ character '.'. */ ++ ++{ "virtdomains", "off", ENUM("off", "userid", "on") } ++/* Enable virtual domain support. If enabled, the user's domain will ++ be determined by splitting a fully qualified userid at the last '@' ++ or '%' symbol. If the userid is unqualified, and the virtdomains ++ option is set to "on", then the domain will be determined by doing ++ a reverse lookup on the IP address of the incoming network ++ interface, otherwise the user is assumed to be in the default ++ domain (if set). */ ++ ++/* ++.SH SEE ALSO ++.PP ++\fBimapd(8)\fR, \fBpop3d(8)\fR, \fBnntpd(8)\fR, \fBlmtpd(8)\fR, ++\fBtimsieved(8)\fR, \fBidled(8)\fR, \fBnotifyd(8)\fR, ++\fBdeliver(8)\fR, \fBmaster(8)\fR, \fBciphers(1)\fR ++*/ +--- /dev/null 2006-07-21 18:50:55.248316500 +0200 ++++ cyrus-imapd-2.3.7/README.autocreate 2006-07-23 12:35:41.000000000 +0200 +@@ -0,0 +1,211 @@ ++Cyrus IMAP autocreate Inbox patch ++---------------------------------- ++ ++NOTE : This patch has been created at the University of Athens. For more info, as well ++as more patches on Cyrus IMAPD server, please visit http://email.uoa.gr/ ++ ++The design of Cyrus IMAP server does not predict the automatic creation of users' ++INBOX folders. The creation of a user's INBOX is considered to be an external task, ++that has to be completed as part of the user email account creation procedure. ++Hence, to create a new email account the site administrator has to: ++ ++ a) Include the new account in the user database for the authentication procedure ++ (e.g. sasldb, shadow, mysql, ldap). ++ b) Create the corresponding INBOX folder. ++ ++Alternatively, the user, if succesfully authenticated, may create his own INBOX folder, ++as long as the configuration of the site allows it (see "autocreatequota" in imapd.conf). ++Unlike what not careful readers may think, enabling the "autocreatequota" option, doesn't ++lead to the automatic INBOX folder creation by Cyrus IMAP server. ++In fact, "autocreate" means that the IMAP clients are allowed to automatically create ++the user INBOX. ++ ++This patch adds the functionality of automatic creation of the users' INBOX folders into ++the Cyrus IMAP server. It is implemented as two features, namely the "create on login" ++and "create on post". ++ ++ ++ ++Create on login ++=============== ++This feauture provides automatic creation of a user's INBOX folder when all of the ++following requirements are met: ++ ++i) The user has succesfully passed the authentication procedure. ++ ++ii) The user's authorisation ID (typically the same as the user's ++authentication ID) doesn't belong to the imap_admins or admins ++accounts (see imapd.conf). ++ ++iii) The "autocreatequota" option in the imap configuration file ++has been set to a non zero value. ++ ++iv) The corresponding to the user's authorisation ID INBOX folder ++does not exist. ++ ++The user's first login is the most typical case when all four requirements are met. ++Note that if the authenticated ID is allowed to proxy to another account for which ++all of the above requirements are met, the corresponding INBOX folder for that account ++will be created. ++ ++ ++ ++Create on post ++============== ++This feauture provides automatic creation of a user's INBOX folder when all of the ++following requirements are met. ++ ++i) An email message addressed to the user has been received. ++ ++ii) The recipient is not any of the imap_admins or admins accounts. ++Note that passing emails to admins or imap_admins accounts from ++the MTA to LMTP should be avoided in any case. ++ ++iii) The recipient's INBOX does not exist. ++ ++iv) The "autocreatequota" option in the imap configuration file ++has been set to a non zero value. ++ ++v) The "createonpost" option in the imap configuration file ++has been switched on. ++ ++ ++Besides the automatic creation of INBOX folder, additional functionalities are ++provided: ++ ++ (A) Automatic creation of INBOX subfolders controlled by "autocreateinboxfolders" ++configuration option. eg ++ ++autocreateinboxfolders: sent|drafts|spam|templates ++ ++ (B) Automatic subscription of INBOX subfolders controlled by "autosubscribeinboxfolders" ++configuration option. eg ++ ++autosubscribeinboxfolders: sent|spam ++ ++Obviously, only subscription to subfolders included in the "autocreateinboxfolder" ++list is meaningful. ++ ++ (C) Automatic subscription to shared folders (bulletin boards). The user gets ++automatically subscribed to the shared folders declared in the "autosubscribesharedfolders" ++configuration option in imapd.conf. ++eg autosubscribesharedfolders: public_folder | public_folder.subfolder ++ ++In order the above action to succeed, the shared folder has to pre-exist the INBOX creation ++and the user must have the appropriate permissions in order to be able to subscribe to the ++shared folder. ++ ++* A new config option has been added. 'autosubscribe_all_sharedfolders' is a yes/no ++option. When set to yes, the user is automatically subscribed to all shared folders one ++has permission to subscribe to. Please, note that when this option is set to yes, then ++'autosubscribesharedfolders' option is overriden. ++ ++ (D) Automatic creation of a predefined default sieve script. ++ ++This is very useful when a default sieve script is used for every user. Usually, a ++default anti-spam script may me be written in a file and copied to each user ++sieve scripts upon the INBOX creation. The imapd.conf options that have been added ++are 'autocreate_sieve_script', 'autocreate_sieve_compiledscript' and ++'generate_compiled_sieve_script'. ++ ++autocreate_sieve_script configuration option refers to the full path of the file ++that contains the sieve script. The default value is null and if no file is defined, ++then no default script is created upon INBOX creation. (The feature is disabled) ++eg autocreate_sieve_script: /etc/default_sieve_script ++ ++autocreate_sieve_compiledscript configuration option refers to the full path of the ++file that contains the bytecode compiled sieve script. If this filename is defined ++in imapd.conf and the file exists, then it is automatically copied in the user's sieve ++directory. If it is not defined, then a bytecode sieve script gets on the fly compiled ++by the daemon. ++eg autocreate_sieve_compiledscript: /etc/default_sieve_script.bc ++ ++generate_compiled_sieve_script is a boolean option that triggers the compilation of the ++source sieve script to bytecode sieve script. The file that the bytecode script will ++be saved is pointed by autocreate_sieve_compiledscript. ++ ++Ways of compiling a sieve script : ++1. Compile a sieve script using the standard sievec utility, distributed by CMU ++2. Compile a sieve script using the compile_sieve utility, released by UoA. This ++ tool is almost identical to the sievec utility, with the difference that it ++ reads the input and output file from autocreate_sieve_script and ++ autocreate_sieve_compiledscript options in imapd.conf ++3. Let cyrus create a compiled sieve script using a source script. Cyrus can be ++ instructed to save the compiled script any time a compiled script does not exist. ++ ++NOTES : ++1. In order this functionality to work, the following requirements must have been met: ++ - 'sieveusehomedir' option must be 'no' in the configuration (default). ++ - 'sievedir' option must have a valid value. ++2. Currently, this patch checks the validity of the source script while generating a ++ bytecode compiled script, but not the validity of the bytecode sieve script file. ++ The administrator should make sure that the provided files contain a valid sieve ++ script as well as the compiled script is updated every time the source script changes. ++ ++ ++ (E) The administrator may control for which users and/or groups may the INBOXes ++automatically be created. The autocreate_users option restricts the groups ++for which the patch will create the mailboxes. ++ ++The default value of autocreate_users is anyone. So, if not set at all, the patch will ++work for all users. However, one may set: ++ ++autocreate_users: user1 user2 group:group1 group:group2 ++ ++In that case, the INBOX will be created only for user1, user2 and the users that belong ++to group1 and group2. ++ ++More refined control per service is provided by the options imap_autocreate_users, ++pop3_autocreate_users and lmtp_autocreate_users. These options override the ++autocreate_users option and offer per service control. ++ ++Example: ++One may want to restrict the create on post functionality only for a specific group ++of users. To achieve this, the following lines must be added in the imapd.conf file: ++ ++createonpost: yes ++lmtp_autocreate_users: group:groupname ++ ++ ++ ++Issues to be considered ++======================= ++ ++I) In order to use the create on post feauture one should be absolutely sure that: ++a) The MTA checks the validity of the email recipient before sending the email to ++LMTP. This is an RFC821 requirement. This usually expands to "the mta should be ++able to use the account database as user mailbox database". ++b) Only authorised accounts/services can talk to LMTP. ++ ++II) Especially in the case of imap logins, the current patch implementation checks ++for the INBOX folder existence upon login, causing an extra mailbox lookup in most ++of the cases. ++A better approach would be to chase the "IMAP_MAILBOX_NONEXISTENT" error code and ++check if the error is associated with an INBOX folder. However, this would mess up ++Cyrus code. The way it was implemented may not have been the most performance ++optimised, but it produces a much cleaner and simple patch. ++ ++ ++ ++Virtual Domains Support ++======================= ++ ++Virtual domains are supported by all versions of the patch for cyrus-imapd-2.2.1-BETA and ++later. However, it is not possible to declare different INBOX subfolders to be created or ++shared folders to be subscribed to for every domain. ++ ++ ++ ++Things to be done ++================= ++ ++1. Support MUPDATE ++ ++It is within the developers' intentions to support the mupdate protocol, but serious ++design issues on future cyrus releases have to resolved first. ++ ++2. Select different attributes (quota, partition, sieve filter, etc) depending on the group ++a user belongs to. ++ ++For more information and updates please visit http://email.uoa.gr/projects/cyrus/autocreate ++ diff --git a/cyrus-imapd-2.3.7-rmquota+deletemailbox.patch b/cyrus-imapd-2.3.7-rmquota+deletemailbox.patch new file mode 100644 index 0000000..d024152 --- /dev/null +++ b/cyrus-imapd-2.3.7-rmquota+deletemailbox.patch @@ -0,0 +1,537 @@ +--- cyrus-imapd-2.3.7/imap/ctl_cyrusdb.c.rmquota 2005-02-16 22:06:18.000000000 +0100 ++++ cyrus-imapd-2.3.7/imap/ctl_cyrusdb.c 2006-07-23 12:52:14.000000000 +0200 +@@ -133,7 +133,7 @@ + /* if it is MBTYPE_RESERVED, unset it & call mboxlist_delete */ + if(!r && (mbtype & MBTYPE_RESERVE)) { + if(!r) { +- r = mboxlist_deletemailbox(name, 1, NULL, NULL, 0, 0, 1); ++ r = mboxlist_deletemailbox(name, 1, NULL, NULL, 0, 0, 1, 1); + if(r) { + /* log the error */ + syslog(LOG_ERR, +--- cyrus-imapd-2.3.7/imap/ctl_mboxlist.c.rmquota 2006-04-06 17:42:10.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/ctl_mboxlist.c 2006-07-23 12:52:14.000000000 +0200 +@@ -457,7 +457,7 @@ + + wipe_head = wipe_head->next; + +- ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, 0, 1, 1); ++ ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, 0, 1, 1, 1); + if(ret) { + fprintf(stderr, "couldn't delete defunct mailbox %s\n", + me->mailbox); +--- cyrus-imapd-2.3.7/imap/imapd.c.rmquota 2006-07-23 12:52:14.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/imapd.c 2006-07-23 12:55:14.000000000 +0200 +@@ -4985,7 +4985,7 @@ + + r = mboxlist_deletemailbox(name, imapd_userisadmin, + imapd_userid, imapd_authstate, +- 0, 0, 0); ++ 0, 0, 0, 1); + + if (!r) sync_log_mailbox(name); + +@@ -5009,6 +5009,12 @@ + char *p; + int domainlen = 0; + int sync_lockfd = (-1); ++ int keepQuota = 1; ++ ++ if(name && *name == '+') { ++ keepQuota = 0; ++ name++; ++ } + + r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, + imapd_userid, mailboxname); +@@ -5067,7 +5073,7 @@ + + r = mboxlist_deletemailbox(mailboxname, imapd_userisadmin, + imapd_userid, imapd_authstate, 1-force, +- localonly, 0); ++ localonly, 0, keepQuota); + } + + /* was it a top-level user mailbox? */ +@@ -6426,6 +6432,7 @@ + { + int newquota = -1; + int badresource = 0; ++ int rmquota = 0; + int c; + int force = 0; + static struct buf arg; +@@ -6442,7 +6449,8 @@ + if (c != ')' || arg.s[0] != '\0') { + for (;;) { + if (c != ' ') goto badlist; +- if (strcasecmp(arg.s, "storage") != 0) badresource = 1; ++ if (strcasecmp(arg.s, "remove") == 0) rmquota = 1; ++ else if (strcasecmp(arg.s, "storage") != 0) badresource = 1; + c = getword(imapd_in, &arg); + if (c != ' ' && c != ')') goto badlist; + if (arg.s[0] == '\0') goto badlist; +@@ -6511,7 +6519,10 @@ + + /* local mailbox */ + if (!r || (r == IMAP_MAILBOX_NONEXISTENT)) { +- r = mboxlist_setquota(mailboxname, newquota, force); ++ if (!rmquota) ++ r = mboxlist_setquota(mailboxname, newquota, force); ++ else ++ r = mboxlist_unsetquota(mailboxname); + } + + imapd_check(NULL, 0, 0); +@@ -8224,7 +8235,7 @@ + /* note also that we need to remember to let proxyadmins do this */ + r = mboxlist_deletemailbox(mailboxname, + imapd_userisadmin || imapd_userisproxyadmin, +- imapd_userid, imapd_authstate, 0, 1, 0); ++ imapd_userid, imapd_authstate, 0, 1, 0, 1); + if(r) syslog(LOG_ERR, + "Could not delete local mailbox during move of %s", + mailboxname); +--- cyrus-imapd-2.3.7/imap/mailbox.c.rmquota 2006-06-02 20:55:06.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/mailbox.c 2006-07-23 12:52:14.000000000 +0200 +@@ -2689,27 +2689,7 @@ + + seen_delete_mailbox(mailbox); + +- if (delete_quota_root && !rquota) { +- quota_delete(&mailbox->quota, &tid); +- free(mailbox->quota.root); +- mailbox->quota.root = NULL; +- } else if (!rquota) { +- /* Free any quota being used by this mailbox */ +- if (mailbox->quota.used >= mailbox->quota_mailbox_used) { +- mailbox->quota.used -= mailbox->quota_mailbox_used; +- } +- else { +- mailbox->quota.used = 0; +- } +- r = quota_write(&mailbox->quota, &tid); +- if (r) { +- syslog(LOG_ERR, +- "LOSTQUOTA: unable to record free of " UQUOTA_T_FMT " bytes in quota %s", +- mailbox->quota_mailbox_used, mailbox->quota.root); +- } +- else +- quota_commit(&tid); +- } ++ mailbox_updatequota(mailbox,NULL); + + /* remove data (message file) directory */ + path = mailbox->path; +@@ -3331,3 +3311,49 @@ + if (*p == '.') *p = '/'; + } + } ++ ++ ++/* This function is used to update the quota. Can be used to replace ++ * identical parts of the code, and can be quite handy some times ++ * The tid is used in order to make possible to make the quota update ++ * being a part of a bigger transaction to the quota db */ ++int mailbox_updatequota(struct mailbox *mailbox, struct txn **tid) ++{ ++ int r = 0, havetid = 0; ++ struct txn **ltid = NULL; ++ ++ if(tid) { ++ ltid = tid; ++ havetid = 1; ++ } ++ /* Ensure that we are locked */ ++ if(!mailbox->header_lock_count) return IMAP_INTERNAL; ++ ++ ++ if(mailbox->quota.root) { ++ r = quota_read(&mailbox->quota, ltid, 1); ++ if( r == 0 ) { ++ if (mailbox->quota.used >= mailbox->quota_mailbox_used) { ++ mailbox->quota.used -= mailbox->quota_mailbox_used; ++ } ++ else { ++ mailbox->quota.used = 0; ++ } ++ r = quota_write(&mailbox->quota, ltid); ++ if (r) { ++ syslog(LOG_ERR, ++ "LOSTQUOTA: unable to record free of %lu bytes in quota %s", ++ mailbox->quota_mailbox_used, mailbox->quota.root); ++ } ++ else if(!havetid) ++ quota_commit(tid); ++ } ++ /* It is not a big mistake not to have quota .. just remove from the mailbox */ ++ else if ( r == IMAP_QUOTAROOT_NONEXISTENT) { ++ free(mailbox->quota.root); ++ r = 0; ++ } ++ } ++ return r; ++} ++ +--- cyrus-imapd-2.3.7/imap/mailbox.h.rmquota 2006-06-02 18:41:57.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/mailbox.h 2006-07-23 12:52:14.000000000 +0200 +@@ -364,6 +364,8 @@ + struct mailbox *mailboxp); + extern int mailbox_delete(struct mailbox *mailbox, int delete_quota_root); + ++extern int mailbox_updatequota(struct mailbox *mailbox, struct txn **tid); ++ + extern int mailbox_rename_copy(struct mailbox *oldmailbox, + const char *newname, char *newpartition, + bit32 *olduidvalidityp, bit32 *newuidvalidityp, +--- cyrus-imapd-2.3.7/imap/mboxlist.c.rmquota 2006-07-23 12:52:14.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/mboxlist.c 2006-07-23 12:52:14.000000000 +0200 +@@ -99,6 +99,11 @@ + static int mboxlist_opensubs(); + static void mboxlist_closesubs(); + ++static int child_cb(char *name, ++ int matchlen __attribute__((unused)), ++ int maycreate __attribute__((unused)), ++ void *rock); ++ + static int mboxlist_rmquota(const char *name, int matchlen, int maycreate, + void *rock); + static int mboxlist_changequota(const char *name, int matchlen, int maycreate, +@@ -113,6 +118,7 @@ + + struct change_rock { + struct quota *quota; ++ struct quota *oldquota; + struct txn **tid; + }; + +@@ -911,9 +917,9 @@ + */ + int mboxlist_deletemailbox(const char *name, int isadmin, char *userid, + struct auth_state *auth_state, int checkacl, +- int local_only, int force) ++ int local_only, int force, int keepQuota) + { +- int r; ++ int r, has_children = 0; + char *acl; + long access; + struct mailbox mailbox; +@@ -924,6 +930,7 @@ + int mbtype; + const char *p; + mupdate_handle *mupdate_h = NULL; ++ char *quotaroot = NULL; + + if(!isadmin && force) return IMAP_PERMISSION_DENIED; + +@@ -1036,13 +1043,44 @@ + + if ((r && !force) || isremote) goto done; + +- if (!r || force) r = mailbox_delete(&mailbox, deletequotaroot); ++ if (!r || force) { ++ /* first we have to keep the previous quota root in order to delete it */ ++ if(mailbox.quota.root) ++ quotaroot = xstrdup(mailbox.quota.root); ++ r = mailbox_delete(&mailbox, deletequotaroot); ++ } + + /* + * See if we have to remove mailbox's quota root + */ +- if (!r && mailbox.quota.root != NULL) { ++ if (!r && quotaroot != NULL) { + /* xxx look for any other mailboxes in this quotaroot */ ++ /* If we have not asked to remove the quota (default behaviour), we check ++ * whether there are any subfolders beneeth the quota root. If there aren't ++ * any subfolders the reasonable thing is to delete the quota */ ++ if(keepQuota) { ++ char pattern[MAX_MAILBOX_PATH+1]; ++ strlcpy(pattern, quotaroot, sizeof(pattern)); ++ if (config_virtdomains && name[strlen(name)-1] == '!') { ++ strlcat(pattern, "*", sizeof(pattern)); ++ } ++ else { ++ strlcat(pattern, ".*", sizeof(pattern)); ++ } ++ /* find if there are subfolders. Then we want to ++ * keep the existing quota */ ++ mboxlist_findall(NULL, pattern, isadmin, userid, ++ auth_state, child_cb, (void *) &has_children); ++ ++ if(!has_children) ++ if(!mboxlist_mylookup(quotaroot, NULL, NULL, NULL, NULL, NULL, NULL, 0 )) ++ has_children = 1; ++ } ++ /* If we want to remove the quota explicitely or the quota root folder has no subfolders ++ * we execute the rmquota patch */ ++ if(!keepQuota || !has_children ) ++ mboxlist_unsetquota(quotaroot); ++ free(quotaroot); + } + + done: +@@ -2498,6 +2536,7 @@ + if (r) return r; + + crock.quota = "a; ++ crock.oldquota = NULL; + crock.tid = &tid; + /* top level mailbox */ + if(have_mailbox) +@@ -2516,17 +2555,21 @@ + */ + int mboxlist_unsetquota(const char *root) + { ++ char newquota[MAX_MAILBOX_PATH+1]; + char pattern[MAX_MAILBOX_PATH+1]; + struct quota quota; +- int r=0; ++ struct change_rock crock; ++ int r=0, k=0; + + if (!root[0] || root[0] == '.' || strchr(root, '/') + || strchr(root, '*') || strchr(root, '%') || strchr(root, '?')) { + return IMAP_MAILBOX_BADNAME; + } ++ ++ crock.tid=NULL; + + quota.root = (char *) root; +- r = quota_read("a, NULL, 0); ++ r = quota_read("a, crock.tid, 0); + if (r == IMAP_QUOTAROOT_NONEXISTENT) { + /* already unset */ + return 0; +@@ -2543,13 +2586,45 @@ + } + else + strlcat(pattern, ".*", sizeof(pattern)); +- +- /* top level mailbox */ +- mboxlist_rmquota(root, 0, 0, (void *)root); +- /* submailboxes - we're using internal names here */ +- mboxlist_findall(NULL, pattern, 1, 0, 0, mboxlist_rmquota, (void *)root); + +- r = quota_delete("a, NULL); ++ r = quota_delete("a, crock.tid); ++ ++ /* If we cannot delete the quota then abort the operation */ ++ if(!r) { ++ /* quota_findroot performs several checks that we can ++ * assume that are already done, and don't have to perform ++ * them again. One of them is that it returns 1 only if ++ * quotaroot exists. ++ */ ++ if(quota_findroot(newquota, sizeof(newquota), root)) { ++ struct quota rootquota; ++ rootquota.root = newquota; ++ k = quota_read(&rootquota, crock.tid, 0); ++ if (!k) { ++ crock.quota = &rootquota; ++ crock.oldquota = "a; ++ /* top level mailbox */ ++ k = mboxlist_changequota(root, 0, 0, &crock); ++ } ++ /* submailboxes - we're using internal names here */ ++ if (!k) ++ k = mboxlist_findall(NULL, pattern, 1, 0, 0, mboxlist_changequota, &crock); ++ if(!k) ++ k = quota_write(&rootquota, crock.tid); ++ ++ } ++ else { ++ /* top level mailbox */ ++ mboxlist_rmquota(root, 0, 0, (void *)root); ++ /* submailboxes - we're using internal names here */ ++ mboxlist_findall(NULL, pattern, 1, 0, 0, mboxlist_rmquota, (void *)root); ++ } ++ } ++ ++ if(!r && !k) ++ quota_commit(crock.tid); ++ else ++ quota_abort(crock.tid); + + return r; + } +@@ -2647,6 +2722,7 @@ + struct mailbox mailbox; + struct change_rock *crock = (struct change_rock *) rock; + struct quota *mboxlist_newquota = crock->quota; ++ struct quota *mboxlist_oldquota = crock->oldquota; + struct txn **tid = crock->tid; + + assert(rock != NULL); +@@ -2664,27 +2740,24 @@ + if (r) goto error; + + if (mailbox.quota.root) { +- if (strlen(mailbox.quota.root) >= strlen(mboxlist_newquota->root)) { +- /* Part of a child quota root */ +- mailbox_close(&mailbox); +- return 0; +- } +- +- r = quota_read(&mailbox.quota, tid, 1); +- if (r) goto error; +- if (mailbox.quota.used >= mailbox.quota_mailbox_used) { +- mailbox.quota.used -= mailbox.quota_mailbox_used; ++ if(mboxlist_oldquota) { ++ if (strlen(mailbox.quota.root) > strlen(mboxlist_oldquota->root)) { ++ /* Part of a child quota root */ ++ mailbox_close(&mailbox); ++ return 0; ++ } + } + else { +- mailbox.quota.used = 0; +- } +- r = quota_write(&mailbox.quota, tid); +- if (r) { +- syslog(LOG_ERR, +- "LOSTQUOTA: unable to record free of " UQUOTA_T_FMT " bytes in quota %s", +- mailbox.quota_mailbox_used, mailbox.quota.root); ++ if (strlen(mailbox.quota.root) >= strlen(mboxlist_newquota->root)) { ++ /* Part of a child quota root */ ++ mailbox_close(&mailbox); ++ return 0; ++ } + } +- free(mailbox.quota.root); ++ ++ r = mailbox_updatequota(&mailbox,tid); ++ if (r) ++ goto error; + } + + mailbox.quota.root = xstrdup(mboxlist_newquota->root); +@@ -2694,18 +2767,24 @@ + mboxlist_newquota->used += mailbox.quota_mailbox_used; + mailbox_close(&mailbox); + return 0; +- ++ + error: + mailbox_close(&mailbox); ++ syslog(LOG_ERR, "LOSTQUOTA: unable to change quota root for %s to %s: %s. \ ++ Command aborted. Run reconstruct to make sure mailboxes \ ++ are in consistent state", ++ name, mboxlist_newquota->root, error_message(r)); ++ return 1; + error_noclose: + syslog(LOG_ERR, "LOSTQUOTA: unable to change quota root for %s to %s: %s", +- name, mboxlist_newquota->root, error_message(r)); ++ name, mboxlist_newquota->root, error_message(r)); + + /* Note, we're a callback, and it's not a huge tragedy if we + * fail, so we don't ever return a failure */ + return 0; + } + ++ + /* must be called after cyrus_init */ + void mboxlist_init(int myflags) + { +--- cyrus-imapd-2.3.7/imap/mboxlist.h.rmquota 2006-07-23 12:52:14.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/mboxlist.h 2006-07-23 12:52:14.000000000 +0200 +@@ -125,7 +125,7 @@ + * the planet */ + int mboxlist_deletemailbox(const char *name, int isadmin, char *userid, + struct auth_state *auth_state, int checkacl, +- int local_only, int force); ++ int local_only, int force, int keepQuota); + + /* Rename/move a mailbox (hierarchical) */ + int mboxlist_renamemailbox(char *oldname, char *newname, char *partition, +--- cyrus-imapd-2.3.7/imap/mupdate.c.rmquota 2006-03-15 19:56:31.000000000 +0100 ++++ cyrus-imapd-2.3.7/imap/mupdate.c 2006-07-23 12:52:14.000000000 +0200 +@@ -2297,7 +2297,7 @@ + remote_boxes.head = r->next; + } else if (ret < 0) { + /* Local without corresponding remote, delete it */ +- mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0, 0); ++ mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0, 0, 1); + local_boxes.head = l->next; + } else /* (ret > 0) */ { + /* Remote without corresponding local, insert it */ +@@ -2312,7 +2312,7 @@ + if(l && !r) { + /* we have more deletes to do */ + while(l) { +- mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0, 0); ++ mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0, 0, 1); + local_boxes.head = l->next; + l = local_boxes.head; + } +--- cyrus-imapd-2.3.7/imap/nntpd.c.rmquota 2006-05-26 17:50:07.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/nntpd.c 2006-07-23 12:52:14.000000000 +0200 +@@ -3356,7 +3356,7 @@ + /* XXX should we delete right away, or wait until empty? */ + + r = mboxlist_deletemailbox(mailboxname, 0, +- newsmaster, newsmaster_authstate, 1, 0, 0); ++ newsmaster, newsmaster_authstate, 1, 0, 0, 1); + + if (!r) sync_log_mailbox(mailboxname); + +--- cyrus-imapd-2.3.7/imap/sync_reset.c.rmquota 2005-12-13 16:31:10.000000000 +0100 ++++ cyrus-imapd-2.3.7/imap/sync_reset.c 2006-07-23 12:52:14.000000000 +0200 +@@ -254,7 +254,7 @@ + if (r) goto fail; + + for (item = list->head ; item ; item = item->next) { +- r=mboxlist_deletemailbox(item->name, 1, NULL, sync_authstate, 1, 0, 0); ++ r=mboxlist_deletemailbox(item->name, 1, NULL, sync_authstate, 1, 0, 0, 1); + + if (r) goto fail; + } +@@ -270,7 +270,7 @@ + if (r) goto fail; + + for (item = list->head ; item ; item = item->next) { +- r=mboxlist_deletemailbox(item->name, 1, NULL, sync_authstate, 1, 0, 0); ++ r=mboxlist_deletemailbox(item->name, 1, NULL, sync_authstate, 1, 0, 0, 1); + + if (r) goto fail; + } +@@ -278,7 +278,7 @@ + + /* Nuke inbox (recursive nuke possible?) */ + snprintf(buf, sizeof(buf)-1, "user.%s", user); +- r = mboxlist_deletemailbox(buf, 1, "cyrus", sync_authstate, 1, 0, 0); ++ r = mboxlist_deletemailbox(buf, 1, "cyrus", sync_authstate, 1, 0, 0, 1); + if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail; + + if ((r=user_deletedata(user, sync_userid, sync_authstate, 1))) +--- cyrus-imapd-2.3.7/imap/sync_server.c.rmquota 2006-06-12 20:56:42.000000000 +0200 ++++ cyrus-imapd-2.3.7/imap/sync_server.c 2006-07-23 12:52:14.000000000 +0200 +@@ -1576,7 +1576,7 @@ + + for (item = list->head ; item ; item = item->next) { + r=mboxlist_deletemailbox(item->name, sync_userisadmin, sync_userid, +- sync_authstate, 0, 0, 1); ++ sync_authstate, 0, 0, 1, 1); + + if (r) goto fail; + } +@@ -1586,7 +1586,7 @@ + (sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX", + user, buf); + r = mboxlist_deletemailbox(buf, sync_userisadmin, sync_userid, +- sync_authstate, 0, 0, 1); ++ sync_authstate, 0, 0, 1, 1); + if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail; + + if ((r=user_deletedata(user, sync_userid, sync_authstate, 1))) +@@ -2508,7 +2508,7 @@ + + /* Delete with admin priveleges */ + r = mboxlist_deletemailbox(name, sync_userisadmin, sync_userid, +- sync_authstate, 0, 0, 1); ++ sync_authstate, 0, 0, 1, 1); + + if (r) + prot_printf(sync_out, "NO Failed to delete %s: %s\r\n", diff --git a/cyrus-imapd-2.3.7.tar.gz.sig b/cyrus-imapd-2.3.7.tar.gz.sig new file mode 100644 index 0000000..aabd1a4 Binary files /dev/null and b/cyrus-imapd-2.3.7.tar.gz.sig differ diff --git a/cyrus-imapd.pam-config b/cyrus-imapd.pam-config index 6452ee0..29b1f15 100644 --- a/cyrus-imapd.pam-config +++ b/cyrus-imapd.pam-config @@ -1,3 +1,3 @@ #%PAM-1.0 -auth required pam_stack.so service=system-auth -account required pam_stack.so service=system-auth +auth include system-auth +account include system-auth diff --git a/cyrus-imapd.spec b/cyrus-imapd.spec index 8f0de94..8a40160 100644 --- a/cyrus-imapd.spec +++ b/cyrus-imapd.spec @@ -1,6 +1,6 @@ Name: cyrus-imapd -Version: 2.3.1 -Release: 2.6%{?dist} +Version: 2.3.7 +Release: 1%{?dist} # ********************** BUILD TIME OPTIONS START ********************** @@ -8,7 +8,7 @@ Release: 2.6%{?dist} # rpm --rebuild --define='SEEN_DB skiplist' cyrus-imapd-2.x.x-x.src.rpm # use saslauth group -%{!?SASLGROUP: %define SASLGROUP 0} +%{!?SASLGROUP: %define SASLGROUP 1} # include deliver-wrapper %{!?DEL_WRAP: %define DEL_WRAP 0} # use preforking cyrus.conf @@ -20,7 +20,7 @@ Release: 2.6%{?dist} # enable IDLED support %{!?IDLED: %define IDLED 1} # enable SNMP support -%{!?SNMP: %define SNMP 0} +%{!?SNMP: %define SNMP 1} # force syncronous updates on ext3 %{!?FORCESYNCEXT3: %define FORCESYNCEXT3 0} # include autocreate feature @@ -32,25 +32,36 @@ Release: 2.6%{?dist} # used syslog facility for logging %{!?SYSLOGFACILITY: %define SYSLOGFACILITY MAIL} # use -fpie for linking -%{!?USEPIE: %define USEPIE 0} +%{!?USEPIE: %define USEPIE 1} # ********************** BUILD TIME OPTIONS END ************************ -%define _acversion 2.59 +#%define _acversion 2.59 %define _use_internal_dependency_generator 0 -%define _rhver %(eval rpm -q --queryformat '%{VERSION}' $(rpm -qf /etc/redhat-release)) -%define _rhrelease %(eval %{__cat} /etc/redhat-release) +#%define _rhver %(eval rpm -q --queryformat '%{VERSION}' $(rpm -qf /etc/redhat-release)) +#%define _rhrelease %(eval %{__cat} /etc/redhat-release) %define _dbver db4 -%define _dbrpmver %(eval "rpm -q %{_dbver}") +#%define _dbrpmver %(eval "rpm -q %{_dbver}") # Do we need the perl install hack for RedHat > 7.3 ? -%define _perlhack %(eval [ %{_rhver} = "6.2" -o %{_rhver} = "7.0" -o %{_rhver} = "7.1" -o %{_rhver} = "7.2" -o %{_rhver} = "7.3" -o %{_rhver} = "2.1AS" -o %{_rhver} = "2.1ES" ] && echo 0 || echo 1) + +%define _perlhack 1 +#(eval [ %{_rhver} = "6.2" -o %{_rhver} = "7.0" -o %{_rhver} = "7.1" -o %{_rhver} = "7.2" -o %{_rhver} = "7.3" -o %{_rhver} = "2.1AS" -o %{_rhver} = "2.1ES" ] && echo 0 || echo 1) + %define _perl_man3dir %(eval "$(perl -V:man3dir)"; echo $man3dir) -%define _withldap %(eval "rpm -q --requires openldap | grep -q ^libsasl2 && echo 1 || echo 0") -%define _snmpver %(eval "rpm -q --quiet ucd-snmp && echo ucd || echo net") + +%define _withldap 1 +#(eval "rpm -q --requires openldap | grep -q ^libsasl2 && echo 1 || echo 0") + +%define _snmpver net +#(eval "rpm -q --quiet ucd-snmp && echo ucd || echo net") + # Disable -debuginfo package generation #define debug_package %{nil} # Do we have filesystem >= 2.3.2 (new pki location) ? -%define use_etc_pki %(eval [ $(rpm -q --queryformat '%{VERSION}' filesystem \| sed -e "s/\\.//g") -ge 232 ] && echo 1 || echo 0 ) + +%define use_etc_pki 1 +#(eval [ $(rpm -q --queryformat '%{VERSION}' filesystem \| sed -e "s/\\.//g") -ge 232 ] && echo 1 || echo 0 ) + %if %{use_etc_pki} %define certs_dir %{_sysconfdir}/pki/tls/certs %define pki_dir %{_sysconfdir}/pki/%{name} @@ -58,6 +69,7 @@ Release: 2.6%{?dist} %define certs_dir %{_datadir}/ssl/certs %define pki_dir %{_datadir}/ssl/certs %endif + %define ssl_pem_file %{pki_dir}/%{name}.pem %define uid 76 %if %{SASLGROUP} @@ -77,7 +89,7 @@ Release: 2.6%{?dist} %define _cyrusconf %{_confdir}/normal.conf %endif -Summary: A high-performance mail server with IMAP, POP3, NNTP and SIEVE support. +Summary: A high-performance mail server with IMAP, POP3, NNTP and SIEVE support License: BSD Group: System Environment/Daemons URL: http://asg.web.cmu.edu/cyrus/imapd/ @@ -86,7 +98,7 @@ URL: http://asg.web.cmu.edu/cyrus/imapd/ #Distribution: Invoca Linux Server Source0: ftp://ftp.andrew.cmu.edu/pub/cyrus/%{name}-%{version}.tar.gz Source1: ftp://ftp.andrew.cmu.edu/pub/cyrus/%{name}-%{version}.tar.gz.sig -Source2: http://ftp.gnu.org/gnu/autoconf/autoconf-%{_acversion}.tar.gz +#Source2: http://ftp.gnu.org/gnu/autoconf/autoconf-%{_acversion}.tar.gz Source3: cyrus-deliver-wrapper.c Source4: cyrus-user-procmailrc.template Source5: cyrus-imapd.logrotate @@ -119,9 +131,9 @@ Source31: cyrus-imapd-README.groupcache Source32: cyrus-imapd.upd_groupcache Source33: cyrus-imapd-README.skiplist_recovery Patch0: cyrus-imapd-2.2.12-no_transfig.patch -Patch1: http://email.uoa.gr/download/cyrus/cyrus-imapd-2.3.1/cyrus-imapd-2.3.1-autocreate-0.10-0.diff -Patch2: http://email.uoa.gr/download/cyrus/cyrus-imapd-2.3.1/cyrus-imapd-2.3.1-autosievefolder-0.6-0.diff -Patch3: http://email.uoa.gr/download/cyrus/cyrus-imapd-2.3.1/cyrus-imapd-2.3.1-rmquota+deletemailbox-0.2-1.diff +Patch1: cyrus-imapd-2.3.7-autocreate-0.10-0.diff +Patch2: http://email.uoa.gr/download/cyrus/cyrus-imapd-2.3.3/cyrus-imapd-2.3.3-autosieve-0.6.0.diff +Patch3: cyrus-imapd-2.3.7-rmquota+deletemailbox.patch Patch4: http://servercc.oakton.edu/~jwade/cyrus/cyrus-imapd-2.1.3/cyrus-imapd-2.1.3-flock.patch Patch5: cyrus-imapd-2.2.12-munge8bit.patch Patch6: cyrus-imapd-2.1.16-getrlimit.patch @@ -138,20 +150,22 @@ Patch15: cyrus-imapd-2.3.1-make_md5_defaults.patch Patch100: cyrus-imapd-2.3.1-make_md5.patch Patch101: cyrus-imapd-2.3.1-backend_sigsegv.patch Patch102: cyrus-imapd-2.3.1-replication_policycheck.patch -BuildRoot: %{_tmppath}/%{name}-%{version}-root -BuildPrereq: cyrus-sasl-devel >= 2.1.15-1, perl, tcp_wrappers -BuildPrereq: %{_dbver}-devel, openssl-devel, pkgconfig -BuildPrereq: flex, bison, groff >= 1.15-8, automake -%if %{_withldap} -BuildPrereq: openldap-devel -%endif +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildRequires: autoconf >= 2.59 +BuildRequires: cyrus-sasl-devel >= 2.1.15-1, perl, tcp_wrappers +BuildRequires: %{_dbver}-devel, openssl-devel, pkgconfig +BuildRequires: flex, bison, groff >= 1.15-8, automake +BuildRequires: openldap-devel %if %{SNMP} -BuildPrereq: %{_snmpver}-snmp-devel, lm_sensors-devel +BuildRequires: %{_snmpver}-snmp-devel, lm_sensors-devel %endif -Prereq: e2fsprogs, /sbin/chkconfig, /sbin/service -Prereq: %{name}-utils = %{version}-%{release} +Requires(post): e2fsprogs, /sbin/chkconfig, /sbin/service, perl, grep, coreutils, findutils +Requires(preun): /sbin/chkconfig, /sbin/service, coreutils +Requires(postun): /sbin/service +Requires: %{name}-utils = %{version}-%{release} Requires: cyrus-sasl-lib >= 2.1.15, file >= 3.35-1, %{_dbver}-utils -Obsoletes: %{name}-murder, %{name}-nntp +Obsoletes: cyrus-murder, cyrus-nntp +Provides: cyrus-murder, cyrus-nntp %description The %{name} package contains the core of the Cyrus IMAP server. @@ -183,26 +197,30 @@ recipients, SIEVE provides server side email filtering. %package devel Group: Development/Libraries -Summary: Cyrus IMAP server development files. +Summary: Cyrus IMAP server development files Requires: %{name} = %{version}-%{release} %description devel The %{name}-devel package contains header files and libraries necessary for developing applications which use the imclient library. -%package -n perl-Cyrus +%package -n cyrus-imapd-perl +Provides: perl-Cyrus +Obsoletes: perl-Cyrus Group: System Environment/Libraries -Summary: Cyrus IMAP server utility Perl modules. +Summary: Cyrus IMAP server utility Perl modules -%description -n perl-Cyrus -The perl-Cyrus package contains Perl modules necessary to use the +%description -n cyrus-imapd-perl +This package contains Perl modules necessary to use the Cyrus IMAP server administration utilities. %package utils Group: Applications/System -Summary: Cyrus IMAP server administration utilities. -Prereq: diffutils, findutils, grep, sed -Requires: perl-Cyrus = %{version}-%{release} +Summary: Cyrus IMAP server administration utilities +Requires(pre): /usr/sbin/groupadd, /usr/sbin/useradd +Requires(post): grep, coreutils, make, openssl +Requires(postun): /usr/sbin/userdel, /usr/sbin/groupdel +Requires: cyrus-imapd-perl = %{version}-%{release} %description utils The %{name}-utils package contains administrative tools for the @@ -210,15 +228,20 @@ Cyrus IMAP server. It can be installed on systems other than the one running the server. %prep -%setup -q -a 2 +%setup -q %patch0 -p1 -b .no_transfig %if %{AUTOCREATE} +#%patch1 -p1 -b .autocreate +#%patch2 -p1 -b .autosievefolder +#%patch3 -p1 -b .rmquota %patch1 -p1 -b .autocreate -%patch2 -p1 -b .autosievefolder +#%patch2 -p1 -b .autocreate1 +%patch2 -p1 -b .autosieve %patch3 -p1 -b .rmquota %endif %patch4 -p1 -b .flock -%patch5 -p1 -b .munge8bit +# superseded by upstream munge8bit support +#%patch5 -p1 -b .munge8bit %patch6 -p1 -b .getrlimit %if %{NO_BARE_NL} %patch7 -p1 -b .nobarenewlinescheck @@ -226,24 +249,24 @@ one running the server. %if %{GROUPCACHE} %patch8 -p1 -b .groupcache %endif -%patch9 -p1 -b .config_defaults +#%patch9 -p1 -b .config_defaults %patch10 -p1 -b .acceptinvalidfrom -%patch11 -p1 -b .dam_invalid_id +# fixed upstream already +#%patch11 -p1 -b .dam_invalid_id %patch12 -p1 -b .notify_sms %patch13 -p0 -b .allow_auth_plain %patch14 -p1 -b .authid_normalize %patch15 -p1 -b .make_md5_defaults -%patch100 -p1 -b .make_md5 -%patch101 -p1 -b .backend_sigsegv -%patch102 -p1 -b .replication_policycheck +# fixed upstream +#%patch100 -p1 -b .make_md5 +# fixed upstream +#%patch101 -p1 -b .backend_sigsegv +# not applicable -- the policy check is not done upstream anymore at all +# commented out with a comment: XXX is this really necessary since only sync_client talks to us? +# probably fixes same problem as our patch, but in a different way +# may be a risk of regressions though +#%patch102 -p1 -b .replication_policycheck -# build and run private autoconf -autodir=$(pwd)/auto-instroot -pushd autoconf-%{_acversion} -./configure --prefix=$autodir -make all install -popd -PATH=${autodir}/bin:${PATH} aclocal -I cmulocal autoheader autoconf -f @@ -324,60 +347,6 @@ find doc -name "*.html.*" -type f | xargs -r %{__rm} -f %{__rm} -f doc/Makefile.dist* %{__rm} -f doc/text/htmlstrip.c -# Create README.buildoptions -%{__cat} << EOF >> doc/README.buildoptions -This RPM has been built on %{_rhrelease} using the following options: - -use saslauth group -SASLGROUP : %{SASLGROUP} - -include deliver-wrapper -DEL_WRAP : %{DEL_WRAP} - -use preforking cyrus.conf -PREFORK : %{PREFORK} - -enable Netscape hack (recommended) -NETSCAPEHACK : %{NETSCAPEHACK} - -enable IMAP4 LIST extensions -LISTEXT : %{LISTEXT} - -enable IDLED support -IDLED : %{IDLED} - -enable SNMP support -SNMP : %{SNMP} - -force syncronous updates on ext3 -FORCESYNCEXT3 : %{FORCESYNCEXT3} - -include autocreate feature -AUTOCREATE : %{AUTOCREATE} - -include groupcache feature -GROUPCACHE : %{GROUPCACHE} - -include nobarenewlinescheck patch -NO_BARE_NL : %{NO_BARE_NL} - -used syslog facility for logging -SYSLOGFACILITY : %{SYSLOGFACILITY} - -used -fpie for linking -USEPIE : %{USEPIE} - -enable LDAP support -(autodetected) : %{_withldap} - -Berkeley DB version -(autodetected) : %{_dbrpmver} - - -To modify parameters, edit the .spec file or build like this: -rpm --rebuild --define='FULLDIRHASH 1' %{name}-%{version}-%{release}.src.rpm -EOF - %{__install} -m 644 %{SOURCE23} doc/README.RPM %{__install} -m 644 %{SOURCE24} doc/README.HOWTO-recover-mailboxes.db %if %{GROUPCACHE} @@ -410,10 +379,10 @@ EOF # This hack is needed on RedHat > 7.3 to install the perl files correctly %if %{_perlhack} pushd perl/imap - %{__perl} Makefile.PL PREFIX=%{buildroot}%{_prefix} + %{__perl} Makefile.PL PREFIX=%{buildroot}%{_prefix} INSTALLDIRS=vendor popd pushd perl/sieve/managesieve - %{__perl} Makefile.PL PREFIX=%{buildroot}%{_prefix} + %{__perl} Makefile.PL PREFIX=%{buildroot}%{_prefix} INSTALLDIRS=vendor popd # Do what the regular make install does @@ -467,6 +436,9 @@ done %{__install} -m 644 %{SOURCE22} %{_contribdir}/ %{__install} -m 644 %{SOURCE30} %{_contribdir}/README +# fix permissions on perl .so files +find %{buildroot}%{_libdir}/perl5/ -type f -name "*.so" -exec %{__chmod} 755 {} \; + # Install config files %{__install} -m 644 %{_cyrusconf} %{buildroot}%{_sysconfdir}/cyrus.conf %{__install} -m 644 %{SOURCE6} %{buildroot}%{_sysconfdir}/imapd.conf @@ -525,20 +497,17 @@ touch %{buildroot}%{ssl_pem_file} # Create filelist for perl package, compress manpages before [ -x /usr/lib/rpm/brp-compress ] && /usr/lib/rpm/brp-compress -find %{buildroot}%{perl_sitearch}/Cyrus %{buildroot}%{perl_sitearch}/auto/Cyrus -type f -print | +find %{buildroot}%{perl_vendorarch}/Cyrus %{buildroot}%{perl_vendorarch}/auto/Cyrus -type f -print | %{__sed} "s@^%{buildroot}@@g" | %{__grep} -v perllocal.pod | - %{__grep} -v "\.packlist" > perl-Cyrus-%{version}-filelist + %{__grep} -v "\.packlist" > cyrus-imapd-perl-%{version}-filelist find %{buildroot}%{_perl_man3dir} -type f -name "Cyrus*" -print | - %{__sed} "s@^%{buildroot}@@g" >> perl-Cyrus-%{version}-filelist -if [ "$(%{__cat} perl-Cyrus-%{version}-filelist)X" = "X" ] ; then + %{__sed} "s@^%{buildroot}@@g" >> cyrus-imapd-perl-%{version}-filelist +if [ "$(%{__cat} cyrus-imapd-perl-%{version}-filelist)X" = "X" ] ; then echo "ERROR: EMPTY FILE LIST" exit -1 fi -# Strip binaries -%{__strip} --strip-unneeded %{buildroot}%{_cyrexecdir}/* ||: - # Remove installed but not packaged files %{__rm} -f %{buildroot}%{_cyrexecdir}/not-mkdep %{__rm} -f %{buildroot}%{_cyrexecdir}/config2header* @@ -738,6 +707,7 @@ fi %attr(0755,root,root) %{_cyrexecdir}/imapd %attr(0755,root,root) %{_cyrexecdir}/ipurge %attr(0755,root,root) %{_cyrexecdir}/lmtpd +%attr(0755,root,root) %{_cyrexecdir}/lmtpproxyd %attr(0755,root,root) %{_cyrexecdir}/masssievec %attr(0755,root,root) %{_cyrexecdir}/mbexamine %attr(0755,root,root) %{_cyrexecdir}/mbpath @@ -821,7 +791,7 @@ fi %{_libdir}/lib*.a %{_mandir}/man3/imclient.3* -%files -n perl-Cyrus -f perl-Cyrus-%{version}-filelist +%files -n cyrus-imapd-perl -f cyrus-imapd-perl-%{version}-filelist %defattr(-,root,root) %doc perl/imap/README %doc perl/imap/Changes @@ -833,6 +803,31 @@ fi %{_mandir}/man1/* %changelog +* Sun Jul 23 2006 Petr Rockai - 2.3.7-1 +- update to latest upstream version, fixes a fair amount of issues +- forward-port the autocreate and rmquota patches (used latest + upstream patches, those are for 2.3.3) + +* Tue Jul 18 2006 Petr Rockai - 2.3.1-3 +- install perl modules into vendor_perl instead of site_perl +- change mode of perl .so files to 755 instead of 555 +- update pam configuration to use include directive instead + of deprecated pam_stack +- change prereq on cyrus-imapd-utils to requires + +* Tue Jul 11 2006 Petr Rockai - 2.3.1-2.99.test1 +- address bunch of rpmlint errors and warnings +- rename perl-Cyrus to cyrus-imapd-perl to be consistent with rest + of package (the cyrus modules are not part of cpan) +- added provides on cyrus-nntp and cyrus-murder (the functionality + is part of main package now) +- removed generation of README.buildoptions +- the two above made it possible to get rid of most build-time parameter + guessing from environment +- get rid of internal autoconf (iew) +- don't strip binaries, renders -debuginfo useless... +- remove prereq's in favour of newly added requires(...) + * Tue Feb 28 2006 John Dennis - 2.3.1-2 - bring up to Simon Matter's 2.3.1-2 release - fix bug #173319, require cyrus-sasl-lib instead of cyrus-sasl diff --git a/sources b/sources index 40118cf..f6183c3 100644 --- a/sources +++ b/sources @@ -1,3 +1,2 @@ +07990f3a78a3a9d728cae627e31a9caf cyrus-imapd-2.3.7.tar.gz 8f7a26b0556369827bb5c8084a3e3ea1 cyrus_sharedbackup-0.1.tar.gz -cde15876d5c953bba9ad9f7811aff695 cyrus-imapd-2.3.1.tar.gz -d4d45eaa1769d45e59dcb131a4af17a0 autoconf-2.59.tar.gz