--- 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 +