Blob Blame History Raw
--- 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <time.h>
+#include <syslog.h>
+#include <com_err.h>
+#include <config.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+
+#include <config.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <time.h>
+#include <com_err.h>
+
+#include "global.h"
+
+#include "util.h"
+#include "mailbox.h"
+#include "imap_err.h"
+#include "sieve_interface.h"
+#include "script.h"
+
+#include <pwd.h>
+
+#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 <altconfig>] [-i <infile> -o <outfile>]\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 <infile> 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: cyrus-imapd-2.3.7-autocreate-0.10-0.diff,v 1.1 2006/07/23 12:03:32 prockai Exp $ */
+
+#include <config.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <syslog.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <sasl/sasl.h>
+
+#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(&params, 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 ]<x.y> 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,
+		       &copyuid, !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 */
+		    /* <tag> XFER <name> <dest server> <dest partition> */
+		    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 {
+		/* <tag> XFER <name> <dest server> */
+		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(&quota, 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(&quota, 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.<userid>" */
+    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]; /* <rand.time@hostname> */
 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: cyrus-imapd-2.3.7-autocreate-0.10-0.diff,v 1.1 2006/07/23 12:03:32 prockai Exp $
+.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
+``<no default>''.  Some options default to the empty string, these
+are listed with ``<none>''.
+*/
+
+# 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 <host shortname>_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
+