f397365bd3
- forward-port the autocreate and rmquota patches (used latest upstream patches, those are for 2.3.3) Tue Jul 18 2006 Petr Rockai <prockai@redhat.com> - 2.3.1-3 - install perl modules into vendor_perl instead of site_perl - change mode of perl .so files to 755 instead of 555 - update pam configuration to use include directive instead of deprecated pam_stack - change prereq on cyrus-imapd-utils to requires Tue Jul 11 2006 Petr Rockai <prockai@redhat.com> - 2.3.1-2.99.test1 - address bunch of rpmlint errors and warnings - rename perl-Cyrus to cyrus-imapd-perl to be consistent with rest of package (the cyrus modules are not part of cpan) - added provides on cyrus-nntp and cyrus-murder (the functionality is part of main package now) - removed generation of README.buildoptions - the two above made it possible to get rid of most build-time parameter guessing from environment - get rid of internal autoconf (iew) - don't strip binaries, renders -debuginfo useless... - remove prereq's in favour of newly added requires(...)
12930 lines
377 KiB
Diff
12930 lines
377 KiB
Diff
--- 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$ */
|
|
+
|
|
+#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(¶ms, field.s, arg.s);
|
|
+ }
|
|
+
|
|
+ if (error || c != ')') {
|
|
+ /* erp! */
|
|
+ eatline(imapd_in, c);
|
|
+ freeattvalues(params);
|
|
+ failed_id++;
|
|
+ return;
|
|
+ }
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+
|
|
+ /* check for CRLF */
|
|
+ if (c == '\r') c = prot_getc(imapd_in);
|
|
+ if (c != '\n') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Unexpected extra arguments to Id\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+ freeattvalues(params);
|
|
+ failed_id++;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* log the client's ID string.
|
|
+ eventually this should be a callback or something. */
|
|
+ if (npair && logged_id < MAXIDLOG) {
|
|
+ char logbuf[MAXIDLOGLEN + 1] = "";
|
|
+ struct attvaluelist *pptr;
|
|
+
|
|
+ for (pptr = params; pptr; pptr = pptr->next) {
|
|
+ /* should we check for and format literals here ??? */
|
|
+ snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
|
|
+ " \"%s\" ", pptr->attrib);
|
|
+ if (!strcmp(pptr->value, "NIL"))
|
|
+ snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
|
|
+ "NIL");
|
|
+ else
|
|
+ snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
|
|
+ "\"%s\"", pptr->value);
|
|
+ }
|
|
+
|
|
+ syslog(LOG_INFO, "client id:%s", logbuf);
|
|
+
|
|
+ logged_id++;
|
|
+ }
|
|
+
|
|
+ freeattvalues(params);
|
|
+
|
|
+ /* spit out our ID string.
|
|
+ eventually this might be configurable. */
|
|
+ if (config_getswitch(IMAPOPT_IMAPIDRESPONSE)) {
|
|
+ id_response(imapd_out);
|
|
+ prot_printf(imapd_out, ")\r\n");
|
|
+ }
|
|
+ else
|
|
+ prot_printf(imapd_out, "* ID NIL\r\n");
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+
|
|
+ failed_id = 0;
|
|
+ did_id = 1;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform an IDLE command
|
|
+ */
|
|
+void cmd_idle(char *tag)
|
|
+{
|
|
+ int c = EOF;
|
|
+ static struct buf arg;
|
|
+ static int idle_period = -1;
|
|
+
|
|
+ if (!backend_current) { /* Local mailbox */
|
|
+ /* Setup for doing mailbox updates */
|
|
+ if (!idle_init(idle_update)) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s NO cannot start idling\r\n", tag);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Tell client we are idling and waiting for end of command */
|
|
+ prot_printf(imapd_out, "+ idling\r\n");
|
|
+ prot_flush(imapd_out);
|
|
+
|
|
+ /* Start doing mailbox updates */
|
|
+ if (imapd_mailbox) index_check(imapd_mailbox, 0, 1);
|
|
+ idle_start(imapd_mailbox);
|
|
+
|
|
+ /* Get continuation data */
|
|
+ c = getword(imapd_in, &arg);
|
|
+
|
|
+ /* Stop updates and do any necessary cleanup */
|
|
+ idle_done(imapd_mailbox);
|
|
+ }
|
|
+ else { /* Remote mailbox */
|
|
+ int done = 0, shutdown = 0;
|
|
+ char buf[2048];
|
|
+
|
|
+ /* get polling period */
|
|
+ if (idle_period == -1) {
|
|
+ idle_period = config_getint(IMAPOPT_IMAPIDLEPOLL);
|
|
+ }
|
|
+
|
|
+ if (CAPA(backend_current, CAPA_IDLE)) {
|
|
+ /* Start IDLE on backend */
|
|
+ prot_printf(backend_current->out, "%s IDLE\r\n", tag);
|
|
+ if (!prot_fgets(buf, sizeof(buf), backend_current->in)) {
|
|
+
|
|
+ /* If we received nothing from the backend, fail */
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag,
|
|
+ error_message(IMAP_SERVER_UNAVAILABLE));
|
|
+ return;
|
|
+ }
|
|
+ if (buf[0] != '+') {
|
|
+ /* If we received anything but a continuation response,
|
|
+ spit out what we received and quit */
|
|
+ prot_write(imapd_out, buf, strlen(buf));
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Tell client we are idling and waiting for end of command */
|
|
+ prot_printf(imapd_out, "+ idling\r\n");
|
|
+ prot_flush(imapd_out);
|
|
+
|
|
+ /* Pipe updates to client while waiting for end of command */
|
|
+ while (!done) {
|
|
+ /* Flush any buffered output */
|
|
+ prot_flush(imapd_out);
|
|
+
|
|
+ /* Check for shutdown file */
|
|
+ if (!imapd_userisadmin && shutdown_file(buf, sizeof(buf))) {
|
|
+ shutdown = done = 1;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ done = proxy_check_input(protin, imapd_in, imapd_out,
|
|
+ backend_current->in, NULL, idle_period);
|
|
+
|
|
+ /* If not running IDLE on backend, poll the mailbox for updates */
|
|
+ if (!CAPA(backend_current, CAPA_IDLE)) {
|
|
+ imapd_check(NULL, 0, 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Get continuation data */
|
|
+ c = getword(imapd_in, &arg);
|
|
+
|
|
+ done:
|
|
+ if (CAPA(backend_current, CAPA_IDLE)) {
|
|
+ /* Either the client timed out, or ended the command.
|
|
+ In either case we're done, so terminate IDLE on backend */
|
|
+ prot_printf(backend_current->out, "Done\r\n");
|
|
+ pipe_until_tag(backend_current, tag, 0);
|
|
+ }
|
|
+
|
|
+ if (shutdown) {
|
|
+ char *p;
|
|
+
|
|
+ for (p = buf; *p == '['; p++); /* can't have [ be first char */
|
|
+ prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p);
|
|
+ shut_down(0);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ imapd_check(NULL, 0, 1);
|
|
+
|
|
+ if (c != EOF) {
|
|
+ if (!strcasecmp(arg.s, "Done") &&
|
|
+ (c = (c == '\r') ? prot_getc(imapd_in) : c) == '\n') {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+ else {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Invalid Idle continuation\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Send unsolicited untagged responses to the client */
|
|
+void idle_update(idle_flags_t flags)
|
|
+{
|
|
+ if ((flags & IDLE_MAILBOX) && imapd_mailbox)
|
|
+ index_check(imapd_mailbox, 0, 1);
|
|
+
|
|
+ if (flags & IDLE_ALERT) {
|
|
+ char shut[1024];
|
|
+ if (! imapd_userisadmin && shutdown_file(shut, sizeof(shut))) {
|
|
+ char *p;
|
|
+ for (p = shut; *p == '['; p++); /* can't have [ be first char */
|
|
+ prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p);
|
|
+ shut_down(0);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ prot_flush(imapd_out);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a CAPABILITY command
|
|
+ */
|
|
+void cmd_capability(char *tag)
|
|
+{
|
|
+ const char *sasllist; /* the list of SASL mechanisms */
|
|
+ int mechcount;
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ prot_printf(imapd_out, "* CAPABILITY " CAPABILITY_STRING);
|
|
+
|
|
+ if (idle_enabled()) {
|
|
+ prot_printf(imapd_out, " IDLE");
|
|
+ }
|
|
+
|
|
+ if (tls_enabled() && !imapd_starttls_done && !imapd_authstate) {
|
|
+ prot_printf(imapd_out, " STARTTLS");
|
|
+ }
|
|
+ if (imapd_authstate ||
|
|
+ (!imapd_starttls_done && !config_getswitch(IMAPOPT_ALLOWPLAINTEXT))) {
|
|
+ prot_printf(imapd_out, " LOGINDISABLED");
|
|
+ }
|
|
+
|
|
+ if(config_mupdate_server) {
|
|
+ prot_printf(imapd_out, " MUPDATE=mupdate://%s/", config_mupdate_server);
|
|
+ }
|
|
+
|
|
+ /* add the SASL mechs */
|
|
+ if (!imapd_authstate &&
|
|
+ sasl_listmech(imapd_saslconn, NULL,
|
|
+ "AUTH=", " AUTH=", " SASL-IR",
|
|
+ &sasllist,
|
|
+ NULL, &mechcount) == SASL_OK && mechcount > 0) {
|
|
+ prot_printf(imapd_out, " %s", sasllist);
|
|
+ } else {
|
|
+ /* else don't show anything */
|
|
+ }
|
|
+
|
|
+#ifdef ENABLE_LISTEXT
|
|
+ prot_printf(imapd_out, " LISTEXT LIST-SUBSCRIBED");
|
|
+#endif /* ENABLE_LISTEXT */
|
|
+
|
|
+#ifdef ENABLE_X_NETSCAPE_HACK
|
|
+ prot_printf(imapd_out, " X-NETSCAPE");
|
|
+#endif
|
|
+
|
|
+#ifdef HAVE_SSL
|
|
+ prot_printf(imapd_out, " URLAUTH");
|
|
+#endif
|
|
+ prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse and perform an APPEND command.
|
|
+ * The command has been parsed up to and including
|
|
+ * the mailbox name.
|
|
+ */
|
|
+static int isokflag(char *s, int *isseen)
|
|
+{
|
|
+ if (s[0] == '\\') {
|
|
+ lcase(s);
|
|
+ if (!strcmp(s, "\\seen")) {
|
|
+ *isseen = 1;
|
|
+ return 1;
|
|
+ }
|
|
+ if (!strcmp(s, "\\answered")) return 1;
|
|
+ if (!strcmp(s, "\\flagged")) return 1;
|
|
+ if (!strcmp(s, "\\draft")) return 1;
|
|
+ if (!strcmp(s, "\\deleted")) return 1;
|
|
+
|
|
+ /* uh oh, system flag i don't recognize */
|
|
+ return 0;
|
|
+ } else {
|
|
+ /* valid user flag? */
|
|
+ return imparse_isatom(s);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int getliteralsize(char *p, int c,
|
|
+ unsigned *size, const char **parseerr)
|
|
+
|
|
+{
|
|
+ int sawdigit = 0;
|
|
+ int isnowait = 0;
|
|
+
|
|
+ /* Check for literal8 */
|
|
+ if (*p == '~') {
|
|
+ p++;
|
|
+ /* We don't support binary append yet */
|
|
+ return IMAP_NO_UNKNOWN_CTE;
|
|
+ }
|
|
+ if (*p != '{') {
|
|
+ *parseerr = "Missing required argument to Append command";
|
|
+ return IMAP_PROTOCOL_ERROR;
|
|
+ }
|
|
+
|
|
+ /* Read size from literal */
|
|
+ isnowait = 0;
|
|
+ *size = 0;
|
|
+ for (++p; *p && isdigit((int) *p); p++) {
|
|
+ sawdigit++;
|
|
+ if (*size > (UINT_MAX - (*p - '0')) / 10)
|
|
+ return IMAP_MESSAGE_TOO_LARGE;
|
|
+ *size = (*size)*10 + *p - '0';
|
|
+#if 0
|
|
+ if (*size < 0) {
|
|
+ lose();
|
|
+ }
|
|
+#endif
|
|
+ }
|
|
+ if (*p == '+') {
|
|
+ isnowait++;
|
|
+ p++;
|
|
+ }
|
|
+
|
|
+ if (c == '\r') {
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+ else {
|
|
+ prot_ungetc(c, imapd_in);
|
|
+ c = ' '; /* Force a syntax error */
|
|
+ }
|
|
+
|
|
+ if (*p != '}' || p[1] || c != '\n' || !sawdigit) {
|
|
+ *parseerr = "Invalid literal in Append command";
|
|
+ return IMAP_PROTOCOL_ERROR;
|
|
+ }
|
|
+
|
|
+ if (!isnowait) {
|
|
+ /* Tell client to send the message */
|
|
+ prot_printf(imapd_out, "+ go ahead\r\n");
|
|
+ prot_flush(imapd_out);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int catenate_text(FILE *f, unsigned *totalsize, const char **parseerr)
|
|
+{
|
|
+ int c;
|
|
+ static struct buf arg;
|
|
+ unsigned size = 0;
|
|
+ char buf[4096+1];
|
|
+ int n;
|
|
+ int r;
|
|
+
|
|
+ c = getword(imapd_in, &arg);
|
|
+
|
|
+ /* Read size from literal */
|
|
+ r = getliteralsize(arg.s, c, &size, parseerr);
|
|
+ if (r) return r;
|
|
+
|
|
+ if (*totalsize > UINT_MAX - size) r = IMAP_MESSAGE_TOO_LARGE;
|
|
+
|
|
+ /* Catenate message part to stage */
|
|
+ while (size) {
|
|
+ n = prot_read(imapd_in, buf, size > 4096 ? 4096 : size);
|
|
+ if (!n) {
|
|
+ syslog(LOG_ERR,
|
|
+ "IOERROR: reading message: unexpected end of file");
|
|
+ return IMAP_IOERROR;
|
|
+ }
|
|
+
|
|
+ buf[n] = '\0';
|
|
+ if (n != strlen(buf)) r = IMAP_MESSAGE_CONTAINSNULL;
|
|
+
|
|
+ size -= n;
|
|
+ if (r) continue;
|
|
+
|
|
+ /* XXX do we want to try and validate the message like
|
|
+ we do in message_copy_strict()? */
|
|
+
|
|
+ if (f) fwrite(buf, n, 1, f);
|
|
+ }
|
|
+
|
|
+ *totalsize += size;
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int catenate_url(const char *s, const char *cur_name, FILE *f,
|
|
+ unsigned *totalsize, const char **parseerr)
|
|
+{
|
|
+ struct imapurl url;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ struct mailbox mboxstruct, *mailbox;
|
|
+ unsigned msgno;
|
|
+ int r = 0, doclose = 0;
|
|
+ unsigned long size = 0;
|
|
+
|
|
+ imapurl_fromURL(&url, s);
|
|
+
|
|
+ if (url.server) {
|
|
+ *parseerr = "Only relative URLs are supported";
|
|
+ r = IMAP_BADURL;
|
|
+#if 0
|
|
+ } else if (url.server && strcmp(url.server, config_servername)) {
|
|
+ *parseerr = "Can not catenate messages from another server";
|
|
+ r = IMAP_BADURL;
|
|
+#endif
|
|
+ } else if (!url.mailbox && !imapd_mailbox && !cur_name) {
|
|
+ *parseerr = "No mailbox is selected or specified";
|
|
+ r = IMAP_BADURL;
|
|
+ } else if (url.mailbox || (url.mailbox = cur_name)) {
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
|
|
+ url.mailbox,
|
|
+ imapd_userid, mailboxname);
|
|
+ if (!r) {
|
|
+ if (!imapd_mailbox || strcmp(imapd_mailbox->name, mailboxname)) {
|
|
+ /* not the currently selected mailbox, so try to open it */
|
|
+ int mbtype;
|
|
+ char *newserver;
|
|
+
|
|
+ /* lookup the location of the mailbox */
|
|
+ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL,
|
|
+ &newserver, NULL, NULL);
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ /* remote mailbox */
|
|
+ struct backend *be;
|
|
+
|
|
+ be = proxy_findserver(newserver, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox, imapd_in);
|
|
+ if (!s) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ } else {
|
|
+ r = proxy_catenate_url(be, &url, f, &size, parseerr);
|
|
+ if (*totalsize > UINT_MAX - size)
|
|
+ r = IMAP_MESSAGE_TOO_LARGE;
|
|
+ else
|
|
+ *totalsize += size;
|
|
+ }
|
|
+
|
|
+ free(url.freeme);
|
|
+
|
|
+ return r;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!r) {
|
|
+ r = mailbox_open_header(mailboxname, imapd_authstate,
|
|
+ &mboxstruct);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ doclose = 1;
|
|
+ r = mailbox_open_index(&mboxstruct);
|
|
+ }
|
|
+
|
|
+ if (!r && !(mboxstruct.myrights & ACL_READ)) {
|
|
+ r = (imapd_userisadmin || (mboxstruct.myrights & ACL_LOOKUP)) ?
|
|
+ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ mailbox = &mboxstruct;
|
|
+ index_operatemailbox(mailbox);
|
|
+ }
|
|
+ } else {
|
|
+ mailbox = imapd_mailbox;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (r) {
|
|
+ *parseerr = error_message(r);
|
|
+ r = IMAP_BADURL;
|
|
+ }
|
|
+ } else {
|
|
+ mailbox = imapd_mailbox;
|
|
+ }
|
|
+
|
|
+ if (r) {
|
|
+ /* nothing to do, handled up top */
|
|
+ } else if (url.uidvalidity &&
|
|
+ (mailbox->uidvalidity != url.uidvalidity)) {
|
|
+ *parseerr = "Uidvalidity of mailbox has changed";
|
|
+ r = IMAP_BADURL;
|
|
+ } else if (!url.uid || !(msgno = index_finduid(url.uid)) ||
|
|
+ (index_getuid(msgno) != url.uid)) {
|
|
+ *parseerr = "No such message in mailbox";
|
|
+ r = IMAP_BADURL;
|
|
+ } else {
|
|
+ /* Catenate message part to stage */
|
|
+ struct protstream *s = prot_new(fileno(f), 1);
|
|
+
|
|
+ r = index_urlfetch(mailbox, msgno, url.section,
|
|
+ url.start_octet, url.octet_count, s, &size);
|
|
+ if (r == IMAP_BADURL)
|
|
+ *parseerr = "No such message part";
|
|
+ else if (!r) {
|
|
+ if (*totalsize > UINT_MAX - size)
|
|
+ r = IMAP_MESSAGE_TOO_LARGE;
|
|
+ else
|
|
+ *totalsize += size;
|
|
+ }
|
|
+
|
|
+ prot_flush(s);
|
|
+ prot_free(s);
|
|
+
|
|
+ /* XXX do we want to try and validate the message like
|
|
+ we do in message_copy_strict()? */
|
|
+ }
|
|
+
|
|
+ free(url.freeme);
|
|
+
|
|
+ if (doclose) {
|
|
+ mailbox_close(&mboxstruct);
|
|
+ if (imapd_mailbox) index_operatemailbox(imapd_mailbox);
|
|
+ }
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int append_catenate(FILE *f, const char *cur_name, unsigned *totalsize,
|
|
+ const char **parseerr, const char **url)
|
|
+{
|
|
+ int c, r = 0;
|
|
+ static struct buf arg;
|
|
+
|
|
+ do {
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (c != ' ') {
|
|
+ *parseerr = "Missing message part data in Append command";
|
|
+ return IMAP_PROTOCOL_ERROR;
|
|
+ }
|
|
+
|
|
+ if (!strcasecmp(arg.s, "TEXT")) {
|
|
+ int r1 = catenate_text(!r ? f : NULL, totalsize, parseerr);
|
|
+ if (r1) return r1;
|
|
+
|
|
+ /* if we see a SP, we're trying to catenate more than one part */
|
|
+
|
|
+ /* Parse newline terminating command */
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+ else if (!strcasecmp(arg.s, "URL")) {
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c != ' ' && c != ')') {
|
|
+ *parseerr = "Missing URL in Append command";
|
|
+ return IMAP_PROTOCOL_ERROR;
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = catenate_url(arg.s, cur_name, f, totalsize, parseerr);
|
|
+ if (r) *url = arg.s;
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ *parseerr = "Invalid message part type in Append command";
|
|
+ return IMAP_PROTOCOL_ERROR;
|
|
+ }
|
|
+
|
|
+ fflush(f);
|
|
+ } while (c == ' ');
|
|
+
|
|
+ if (c != ')') {
|
|
+ *parseerr = "Missing space or ) after catenate list in Append command";
|
|
+ return IMAP_PROTOCOL_ERROR;
|
|
+ }
|
|
+
|
|
+ if (ferror(f) || fsync(fileno(f))) {
|
|
+ syslog(LOG_ERR, "IOERROR: writing message: %m");
|
|
+ return IMAP_IOERROR;
|
|
+ }
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+/* If an APPEND is proxied from another server,
|
|
+ * 'cur_name' is the name of the currently selected mailbox (if any)
|
|
+ * in case we have to resolve relative URLs
|
|
+ */
|
|
+#define FLAGGROW 10
|
|
+void cmd_append(char *tag, char *name, const char *cur_name)
|
|
+{
|
|
+ int c;
|
|
+ static struct buf arg;
|
|
+ char *p;
|
|
+ time_t now = time(NULL);
|
|
+ unsigned size, totalsize = 0;
|
|
+ int sync_seen = 0;
|
|
+ int r, i;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ struct appendstate mailbox;
|
|
+ unsigned long uidvalidity;
|
|
+ unsigned long firstuid, num;
|
|
+ long doappenduid = 0;
|
|
+ const char *parseerr = NULL, *url = NULL;
|
|
+ int mbtype;
|
|
+ char *newserver;
|
|
+ FILE *f;
|
|
+ int numalloc = 5;
|
|
+ struct appendstage *curstage;
|
|
+
|
|
+ /* See if we can append */
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+ if (!r) {
|
|
+ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL,
|
|
+ &newserver, NULL, NULL);
|
|
+ }
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ /* remote mailbox */
|
|
+ struct backend *s = NULL;
|
|
+
|
|
+ if (supports_referrals) {
|
|
+ imapd_refer(tag, newserver, name);
|
|
+ /* Eat the argument */
|
|
+ eatline(imapd_in, prot_getc(imapd_in));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ s = proxy_findserver(newserver, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox, imapd_in);
|
|
+ if (!s) r = IMAP_SERVER_UNAVAILABLE;
|
|
+
|
|
+ imapd_check(s, 0, 0);
|
|
+
|
|
+ if (!r) {
|
|
+ int is_active = 1;
|
|
+ s->context = (void*) &is_active;
|
|
+ if (imapd_mailbox) {
|
|
+ prot_printf(s->out, "%s Localappend {%d+}\r\n%s {%d+}\r\n%s ",
|
|
+ tag, strlen(name), name,
|
|
+ strlen(imapd_mailbox->name), imapd_mailbox->name);
|
|
+ } else {
|
|
+ prot_printf(s->out, "%s Localappend {%d+}\r\n%s {%d+}\r\n%s ",
|
|
+ tag, strlen(name), name, 0, "");
|
|
+ }
|
|
+ if (!(r = pipe_command(s, 16384))) {
|
|
+ if (s != backend_current) pipe_including_tag(s, tag, 0);
|
|
+ }
|
|
+ s->context = NULL;
|
|
+ } else {
|
|
+ eatline(imapd_in, prot_getc(imapd_in));
|
|
+ }
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag,
|
|
+ prot_error(imapd_in) ? prot_error(imapd_in) :
|
|
+ error_message(r));
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!r) {
|
|
+ r = append_check(mailboxname, MAILBOX_FORMAT_NORMAL,
|
|
+ imapd_authstate, ACL_INSERT, totalsize);
|
|
+ }
|
|
+ if (r) {
|
|
+ eatline(imapd_in, ' ');
|
|
+ prot_printf(imapd_out, "%s NO %s%s\r\n",
|
|
+ tag,
|
|
+ (r == IMAP_MAILBOX_NONEXISTENT &&
|
|
+ mboxlist_createmailboxcheck(mailboxname, 0, 0,
|
|
+ imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate,
|
|
+ (char **)0, (char **)0) == 0)
|
|
+ ? "[TRYCREATE] " : "", error_message(r));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ stage = xmalloc(numalloc * sizeof(struct appendstage *));
|
|
+
|
|
+ c = ' '; /* just parsed a space */
|
|
+ /* we loop, to support MULTIAPPEND */
|
|
+ while (!r && c == ' ') {
|
|
+ /* Grow the stage array, if necessary */
|
|
+ if (numstage == numalloc) {
|
|
+ /* Avoid integer wrap as arg to xrealloc */
|
|
+ if (numalloc > INT_MAX/(2*sizeof(struct appendstage *)))
|
|
+ goto done;
|
|
+ numalloc *= 2;
|
|
+ stage = xrealloc(stage, numalloc * sizeof(struct appendstage *));
|
|
+ }
|
|
+ curstage = stage[numstage] = xzmalloc(sizeof(struct appendstage));
|
|
+ numstage++;
|
|
+ /* Parse flags */
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (c == '(' && !arg.s[0]) {
|
|
+ curstage->nflags = 0;
|
|
+ do {
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (!curstage->nflags && !arg.s[0] && c == ')') break; /* empty list */
|
|
+ if (!isokflag(arg.s, &sync_seen)) {
|
|
+ parseerr = "Invalid flag in Append command";
|
|
+ r = IMAP_PROTOCOL_ERROR;
|
|
+ goto done;
|
|
+ }
|
|
+ if (curstage->nflags == curstage->flagalloc) {
|
|
+ curstage->flagalloc += FLAGGROW;
|
|
+ curstage->flag =
|
|
+ (char **) xrealloc((char *) curstage->flag,
|
|
+ curstage->flagalloc * sizeof(char *));
|
|
+ }
|
|
+ curstage->flag[curstage->nflags] = xstrdup(arg.s);
|
|
+ curstage->nflags++;
|
|
+ } while (c == ' ');
|
|
+ if (c != ')') {
|
|
+ parseerr =
|
|
+ "Missing space or ) after flag name in Append command";
|
|
+ r = IMAP_PROTOCOL_ERROR;
|
|
+ goto done;
|
|
+ }
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != ' ') {
|
|
+ parseerr = "Missing space after flag list in Append command";
|
|
+ r = IMAP_PROTOCOL_ERROR;
|
|
+ goto done;
|
|
+ }
|
|
+ c = getword(imapd_in, &arg);
|
|
+ }
|
|
+
|
|
+ /* Parse internaldate */
|
|
+ if (c == '\"' && !arg.s[0]) {
|
|
+ prot_ungetc(c, imapd_in);
|
|
+ c = getdatetime(&(curstage->internaldate));
|
|
+ if (c != ' ') {
|
|
+ parseerr = "Invalid date-time in Append command";
|
|
+ r = IMAP_PROTOCOL_ERROR;
|
|
+ goto done;
|
|
+ }
|
|
+ c = getword(imapd_in, &arg);
|
|
+ } else {
|
|
+ curstage->internaldate = now;
|
|
+ }
|
|
+
|
|
+ /* Stage the message */
|
|
+ f = append_newstage(mailboxname, now, numstage, &(curstage->stage));
|
|
+ if (!f) {
|
|
+ r = IMAP_IOERROR;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ if (!strcasecmp(arg.s, "CATENATE")) {
|
|
+ if (c != ' ' || (c = prot_getc(imapd_in) != '(')) {
|
|
+ parseerr = "Missing message part(s) in Append command";
|
|
+ r = IMAP_PROTOCOL_ERROR;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* Catenate the message part(s) to stage */
|
|
+ size = 0;
|
|
+ r = append_catenate(f, cur_name, &size, &parseerr, &url);
|
|
+ if (r) goto done;
|
|
+ }
|
|
+ else {
|
|
+ /* Read size from literal */
|
|
+ r = getliteralsize(arg.s, c, &size, &parseerr);
|
|
+ if (r) goto done;
|
|
+
|
|
+ /* Copy message to stage */
|
|
+ r = message_copy_strict(imapd_in, f, size);
|
|
+ }
|
|
+ totalsize += size;
|
|
+ fclose(f);
|
|
+
|
|
+ /* if we see a SP, we're trying to append more than one message */
|
|
+
|
|
+ /* Parse newline terminating command */
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+
|
|
+ done:
|
|
+ if (r) {
|
|
+ eatline(imapd_in, c);
|
|
+ } else {
|
|
+ /* we should be looking at the end of the line */
|
|
+ if (c == '\r') c = prot_getc(imapd_in);
|
|
+ if (c != '\n') {
|
|
+ parseerr = "junk after literal";
|
|
+ r = IMAP_PROTOCOL_ERROR;
|
|
+ eatline(imapd_in, c);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Append from the stage(s) */
|
|
+ if (!r) {
|
|
+ r = append_setup(&mailbox, mailboxname, MAILBOX_FORMAT_NORMAL,
|
|
+ imapd_userid, imapd_authstate, ACL_INSERT, totalsize);
|
|
+ }
|
|
+ if (!r) {
|
|
+ struct body *body = NULL;
|
|
+
|
|
+ doappenduid = (mailbox.m.myrights & ACL_READ);
|
|
+
|
|
+ for (i = 0; !r && i < numstage; i++) {
|
|
+ r = append_fromstage(&mailbox, &body, stage[i]->stage, stage[i]->internaldate,
|
|
+ (const char **) stage[i]->flag, stage[i]->nflags, 0);
|
|
+ if (body) message_free_body(body);
|
|
+ }
|
|
+ if (body) free(body);
|
|
+
|
|
+ if (!r) {
|
|
+ r = append_commit(&mailbox, totalsize, &uidvalidity, &firstuid, &num);
|
|
+ if (!r) {
|
|
+ sync_log_append(mailboxname);
|
|
+ if (sync_seen) sync_log_seen(imapd_userid, mailboxname);
|
|
+ }
|
|
+ } else {
|
|
+ append_abort(&mailbox);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Cleanup the stage(s) */
|
|
+ while (numstage) {
|
|
+ curstage = stage[--numstage];
|
|
+
|
|
+ append_removestage(curstage->stage);
|
|
+ while (curstage->nflags--) {
|
|
+ free(curstage->flag[curstage->nflags]);
|
|
+ }
|
|
+ if (curstage->flag) free((char *) curstage->flag);
|
|
+ free(curstage);
|
|
+ }
|
|
+ if (stage) free(stage);
|
|
+ stage = NULL;
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r == IMAP_PROTOCOL_ERROR && parseerr) {
|
|
+ prot_printf(imapd_out, "%s BAD %s\r\n", tag, parseerr);
|
|
+ } else if (r == IMAP_BADURL) {
|
|
+ prot_printf(imapd_out, "%s NO [BADURL \"%s\"] %s\r\n",
|
|
+ tag, url, parseerr);
|
|
+ } else if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s%s\r\n",
|
|
+ tag,
|
|
+ (r == IMAP_MAILBOX_NONEXISTENT &&
|
|
+ mboxlist_createmailboxcheck(mailboxname, 0, 0,
|
|
+ imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate,
|
|
+ (char **)0, (char **)0) == 0)
|
|
+ ? "[TRYCREATE] " : r == IMAP_MESSAGE_TOO_LARGE
|
|
+ ? "[TOOBIG]" : "", error_message(r));
|
|
+ } else if (doappenduid) {
|
|
+ /* is this a space seperated list or sequence list? */
|
|
+ prot_printf(imapd_out, "%s OK [APPENDUID %lu", tag, uidvalidity);
|
|
+ if (num == 1) {
|
|
+ prot_printf(imapd_out, " %lu", firstuid);
|
|
+ } else {
|
|
+ prot_printf(imapd_out, " %lu:%lu", firstuid, firstuid + num - 1);
|
|
+ }
|
|
+ prot_printf(imapd_out, "] %s\r\n", error_message(IMAP_OK_COMPLETED));
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a SELECT/EXAMINE/BBOARD command
|
|
+ */
|
|
+void cmd_select(char *tag, char *cmd, char *name)
|
|
+{
|
|
+ struct mailbox mailbox;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ int r = 0;
|
|
+ double usage;
|
|
+ int doclose = 0;
|
|
+ int mbtype;
|
|
+ char *newserver;
|
|
+ struct backend *backend_next = NULL;
|
|
+ static char lastqr[MAX_MAILBOX_PATH+1] = "";
|
|
+ static time_t nextalert = 0;
|
|
+
|
|
+ if (imapd_mailbox) {
|
|
+ index_closemailbox(imapd_mailbox);
|
|
+ mailbox_close(imapd_mailbox);
|
|
+ imapd_mailbox = 0;
|
|
+ }
|
|
+
|
|
+ if (cmd[0] == 'B') {
|
|
+ /* BBoard namespace is empty */
|
|
+ r = IMAP_MAILBOX_NONEXISTENT;
|
|
+ }
|
|
+ else {
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL,
|
|
+ &newserver, NULL, NULL);
|
|
+ }
|
|
+ if (r == IMAP_MAILBOX_MOVED) return;
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ if (supports_referrals) {
|
|
+ imapd_refer(tag, newserver, name);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ backend_next = proxy_findserver(newserver, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox,
|
|
+ imapd_in);
|
|
+ if (!backend_next) r = IMAP_SERVER_UNAVAILABLE;
|
|
+
|
|
+ if (backend_current && backend_current != backend_next) {
|
|
+ char mytag[128];
|
|
+
|
|
+ /* remove backend_current from the protgroup */
|
|
+ protgroup_delete(protin, backend_current->in);
|
|
+
|
|
+ /* switching servers; flush old server output */
|
|
+ proxy_gentag(mytag, sizeof(mytag));
|
|
+ prot_printf(backend_current->out, "%s Unselect\r\n", mytag);
|
|
+ /* do not fatal() here, because we don't really care about this
|
|
+ * server anymore anyway */
|
|
+ pipe_until_tag(backend_current, mytag, 1);
|
|
+ }
|
|
+ backend_current = backend_next;
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ prot_printf(backend_current->out, "%s %s {%d+}\r\n%s\r\n", tag, cmd,
|
|
+ strlen(name), name);
|
|
+ switch (pipe_including_tag(backend_current, tag, 0)) {
|
|
+ case PROXY_OK:
|
|
+ proc_register("imapd", imapd_clienthost, imapd_userid, mailboxname);
|
|
+ syslog(LOG_DEBUG, "open: user %s opened %s on %s",
|
|
+ imapd_userid, name, newserver);
|
|
+
|
|
+ /* add backend_current to the protgroup */
|
|
+ protgroup_insert(protin, backend_current->in);
|
|
+ break;
|
|
+ default:
|
|
+ syslog(LOG_DEBUG, "open: user %s failed to open %s", imapd_userid,
|
|
+ name);
|
|
+ /* not successfully selected */
|
|
+ backend_current = NULL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (backend_current) {
|
|
+ char mytag[128];
|
|
+
|
|
+ /* remove backend_current from the protgroup */
|
|
+ protgroup_delete(protin, backend_current->in);
|
|
+
|
|
+ /* switching servers; flush old server output */
|
|
+ proxy_gentag(mytag, sizeof(mytag));
|
|
+ prot_printf(backend_current->out, "%s Unselect\r\n", mytag);
|
|
+ /* do not fatal() here, because we don't really care about this
|
|
+ * server anymore anyway */
|
|
+ pipe_until_tag(backend_current, mytag, 1);
|
|
+ }
|
|
+ backend_current = NULL;
|
|
+
|
|
+ if (!r) {
|
|
+ r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ doclose = 1;
|
|
+ r = mailbox_open_index(&mailbox);
|
|
+ }
|
|
+ if (!r && !(mailbox.myrights & ACL_READ)) {
|
|
+ r = (imapd_userisadmin || (mailbox.myrights & ACL_LOOKUP)) ?
|
|
+ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
|
|
+ }
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ if (doclose) mailbox_close(&mailbox);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mboxstruct = mailbox;
|
|
+ imapd_mailbox = &mboxstruct;
|
|
+
|
|
+ index_newmailbox(imapd_mailbox, cmd[0] == 'E');
|
|
+
|
|
+ /* Examine command puts mailbox in read-only mode */
|
|
+ if (cmd[0] == 'E') {
|
|
+ imapd_mailbox->myrights &= ~(ACL_SEEN|ACL_WRITE|ACL_DELETEMSG|ACL_EXPUNGE);
|
|
+ }
|
|
+
|
|
+ if (imapd_mailbox->myrights & ACL_EXPUNGE) {
|
|
+ time_t now = time(NULL);
|
|
+
|
|
+ /* Warn if mailbox is close to or over quota */
|
|
+ r = quota_read(&imapd_mailbox->quota, NULL, 0);
|
|
+ if (!r && imapd_mailbox->quota.limit > 0 &&
|
|
+ (strcmp(imapd_mailbox->quota.root, lastqr) || now > nextalert)) {
|
|
+ /* Warn if the following possibilities occur:
|
|
+ * - quotawarnkb not set + quotawarn hit
|
|
+ * - quotawarnkb set larger than mailbox + quotawarn hit
|
|
+ * - quotawarnkb set + hit + quotawarn hit
|
|
+ */
|
|
+ int warnsize = config_getint(IMAPOPT_QUOTAWARNKB);
|
|
+ if (warnsize <= 0 || warnsize >= imapd_mailbox->quota.limit ||
|
|
+ ((uquota_t) (imapd_mailbox->quota.limit - warnsize)) * QUOTA_UNITS <
|
|
+ imapd_mailbox->quota.used) {
|
|
+ usage = ((double) imapd_mailbox->quota.used * 100.0) / (double)
|
|
+ ((uquota_t) imapd_mailbox->quota.limit * QUOTA_UNITS);
|
|
+ if (usage >= 100.0) {
|
|
+ prot_printf(imapd_out, "* NO [ALERT] %s\r\n",
|
|
+ error_message(IMAP_NO_OVERQUOTA));
|
|
+ }
|
|
+ else if (usage > config_getint(IMAPOPT_QUOTAWARN)) {
|
|
+ int usageint = (int) usage;
|
|
+ prot_printf(imapd_out, "* NO [ALERT] ");
|
|
+ prot_printf(imapd_out, error_message(IMAP_NO_CLOSEQUOTA),
|
|
+ usageint);
|
|
+ prot_printf(imapd_out, "\r\n");
|
|
+ }
|
|
+ }
|
|
+ strlcpy(lastqr, imapd_mailbox->quota.root, sizeof(lastqr));
|
|
+ nextalert = now + 600; /* ALERT every 10 min regardless */
|
|
+ }
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK [READ-%s] %s\r\n", tag,
|
|
+ (imapd_mailbox->myrights & (ACL_INSERT|ACL_EXPUNGE|ACL_WRITE|ACL_DELETEMSG)) ?
|
|
+ "WRITE" : "ONLY", error_message(IMAP_OK_COMPLETED));
|
|
+
|
|
+ proc_register("imapd", imapd_clienthost, imapd_userid, mailboxname);
|
|
+ syslog(LOG_DEBUG, "open: user %s opened %s", imapd_userid, name);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a CLOSE command
|
|
+ */
|
|
+void cmd_close(char *tag)
|
|
+{
|
|
+ int r;
|
|
+
|
|
+ if (backend_current) {
|
|
+ /* remote mailbox */
|
|
+ prot_printf(backend_current->out, "%s Close\r\n", tag);
|
|
+ /* xxx do we want this to say OK if the connection is gone?
|
|
+ * saying NO is clearly wrong, hense the fatal request. */
|
|
+ pipe_including_tag(backend_current, tag, 0);
|
|
+
|
|
+ /* remove backend_current from the protgroup */
|
|
+ protgroup_delete(protin, backend_current->in);
|
|
+
|
|
+ backend_current = NULL;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!(imapd_mailbox->myrights & ACL_EXPUNGE)) r = 0;
|
|
+ else {
|
|
+ r = mailbox_expunge(imapd_mailbox, (int (*)())0, (char *)0, 0);
|
|
+ if (!r) sync_log_mailbox(imapd_mailbox->name);
|
|
+ }
|
|
+
|
|
+ index_closemailbox(imapd_mailbox);
|
|
+ mailbox_close(imapd_mailbox);
|
|
+ imapd_mailbox = 0;
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ }
|
|
+ else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform an UNSELECT command -- for some support of IMAP proxy.
|
|
+ * Just like close except no expunge.
|
|
+ */
|
|
+void cmd_unselect(char *tag)
|
|
+{
|
|
+ if (backend_current) {
|
|
+ /* remote mailbox */
|
|
+ prot_printf(backend_current->out, "%s Unselect\r\n", tag);
|
|
+ /* xxx do we want this to say OK if the connection is gone?
|
|
+ * saying NO is clearly wrong, hense the fatal request. */
|
|
+ pipe_including_tag(backend_current, tag, 0);
|
|
+ backend_current = NULL;
|
|
+
|
|
+ /* remove backend_current from the protgroup */
|
|
+ protgroup_delete(protin, backend_current->in);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ index_closemailbox(imapd_mailbox);
|
|
+ mailbox_close(imapd_mailbox);
|
|
+ imapd_mailbox = 0;
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse the syntax for a partial fetch:
|
|
+ * "<" number "." nz-number ">"
|
|
+ */
|
|
+#define PARSE_PARTIAL(start_octet, octet_count) \
|
|
+ (start_octet) = (octet_count) = 0; \
|
|
+ if (*p == '<' && isdigit((int) p[1])) { \
|
|
+ (start_octet) = p[1] - '0'; \
|
|
+ p += 2; \
|
|
+ while (isdigit((int) *p)) { \
|
|
+ (start_octet) = \
|
|
+ (start_octet) * 10 + *p++ - '0'; \
|
|
+ } \
|
|
+ \
|
|
+ if (*p == '.' && p[1] >= '1' && p[1] <= '9') { \
|
|
+ (octet_count) = p[1] - '0'; \
|
|
+ p[0] = '>'; p[1] = '\0'; /* clip off the octet count \
|
|
+ (its not used in the reply) */ \
|
|
+ p += 2; \
|
|
+ while (isdigit((int) *p)) { \
|
|
+ (octet_count) = \
|
|
+ (octet_count) * 10 + *p++ - '0'; \
|
|
+ } \
|
|
+ } \
|
|
+ else p--; \
|
|
+ \
|
|
+ if (*p != '>') { \
|
|
+ prot_printf(imapd_out, \
|
|
+ "%s BAD Invalid body partial\r\n", tag); \
|
|
+ eatline(imapd_in, c); \
|
|
+ goto freeargs; \
|
|
+ } \
|
|
+ p++; \
|
|
+ }
|
|
+
|
|
+/*
|
|
+ * Parse and perform a FETCH/UID FETCH command
|
|
+ * The command has been parsed up to and including
|
|
+ * the sequence
|
|
+ */
|
|
+void cmd_fetch(char *tag, char *sequence, int usinguid)
|
|
+{
|
|
+ const char *cmd = usinguid ? "UID Fetch" : "Fetch";
|
|
+ static struct buf fetchatt, fieldname;
|
|
+ int c;
|
|
+ int inlist = 0;
|
|
+ int fetchitems = 0;
|
|
+ struct fetchargs fetchargs;
|
|
+ struct octetinfo oi;
|
|
+ struct strlist *newfields = 0;
|
|
+ char *p, *section;
|
|
+ int fetchedsomething, r;
|
|
+ clock_t start = clock();
|
|
+ char mytime[100];
|
|
+
|
|
+ if (backend_current) {
|
|
+ /* remote mailbox */
|
|
+ prot_printf(backend_current->out, "%s %s %s ", tag, cmd, sequence);
|
|
+ pipe_command(backend_current, 65536);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ memset(&fetchargs, 0, sizeof(struct fetchargs));
|
|
+
|
|
+ c = getword(imapd_in, &fetchatt);
|
|
+ if (c == '(' && !fetchatt.s[0]) {
|
|
+ inlist = 1;
|
|
+ c = getword(imapd_in, &fetchatt);
|
|
+ }
|
|
+ for (;;) {
|
|
+ ucase(fetchatt.s);
|
|
+ switch (fetchatt.s[0]) {
|
|
+ case 'A':
|
|
+ if (!inlist && !strcmp(fetchatt.s, "ALL")) {
|
|
+ fetchitems |= FETCH_ALL;
|
|
+ }
|
|
+ else goto badatt;
|
|
+ break;
|
|
+
|
|
+ case 'B':
|
|
+ if (!strncmp(fetchatt.s, "BINARY[", 7) ||
|
|
+ !strncmp(fetchatt.s, "BINARY.PEEK[", 12) ||
|
|
+ !strncmp(fetchatt.s, "BINARY.SIZE[", 12)) {
|
|
+ int binsize = 0;
|
|
+
|
|
+ p = section = fetchatt.s + 7;
|
|
+ if (!strncmp(p, "PEEK[", 5)) {
|
|
+ p = section += 5;
|
|
+ }
|
|
+ else if (!strncmp(p, "SIZE[", 5)) {
|
|
+ p = section += 5;
|
|
+ binsize = 1;
|
|
+ }
|
|
+ else {
|
|
+ fetchitems |= FETCH_SETSEEN;
|
|
+ }
|
|
+ while (isdigit((int) *p) || *p == '.') {
|
|
+ if (*p == '.' && !isdigit((int) p[-1])) break;
|
|
+ /* Part number can not begin with '0' */
|
|
+ if (*p == '0' && !isdigit((int) p[-1])) break;
|
|
+ p++;
|
|
+ }
|
|
+
|
|
+ if (*p != ']') {
|
|
+ prot_printf(imapd_out, "%s BAD Invalid binary section\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+ p++;
|
|
+
|
|
+ if (!binsize) PARSE_PARTIAL(oi.start_octet, oi.octet_count);
|
|
+
|
|
+ if (*p) {
|
|
+ prot_printf(imapd_out, "%s BAD Junk after binary section\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+ if (binsize)
|
|
+ appendstrlist_withdata(&fetchargs.sizesections, section, &oi, sizeof(oi));
|
|
+ else
|
|
+ appendstrlist_withdata(&fetchargs.binsections, section, &oi, sizeof(oi));
|
|
+ }
|
|
+ else if (!strcmp(fetchatt.s, "BODY")) {
|
|
+ fetchitems |= FETCH_BODY;
|
|
+ }
|
|
+ else if (!strcmp(fetchatt.s, "BODYSTRUCTURE")) {
|
|
+ fetchitems |= FETCH_BODYSTRUCTURE;
|
|
+ }
|
|
+ else if (!strncmp(fetchatt.s, "BODY[", 5) ||
|
|
+ !strncmp(fetchatt.s, "BODY.PEEK[", 10)) {
|
|
+ p = section = fetchatt.s + 5;
|
|
+ if (!strncmp(p, "PEEK[", 5)) {
|
|
+ p = section += 5;
|
|
+ }
|
|
+ else {
|
|
+ fetchitems |= FETCH_SETSEEN;
|
|
+ }
|
|
+ while (isdigit((int) *p) || *p == '.') {
|
|
+ if (*p == '.' && !isdigit((int) p[-1])) break;
|
|
+ /* Obsolete section 0 can only occur before close brace */
|
|
+ if (*p == '0' && !isdigit((int) p[-1]) && p[1] != ']') break;
|
|
+ p++;
|
|
+ }
|
|
+
|
|
+ if (*p == 'H' && !strncmp(p, "HEADER.FIELDS", 13) &&
|
|
+ (p == section || p[-1] == '.') &&
|
|
+ (p[13] == '\0' || !strcmp(p+13, ".NOT"))) {
|
|
+
|
|
+ /*
|
|
+ * If not top-level or a HEADER.FIELDS.NOT, can't pull
|
|
+ * the headers out of the cache.
|
|
+ */
|
|
+ if (p != section || p[13] != '\0') {
|
|
+ fetchargs.cache_atleast = BIT32_MAX;
|
|
+ }
|
|
+
|
|
+ if (c != ' ') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing required argument to %s %s\r\n",
|
|
+ tag, cmd, fetchatt.s);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != '(') {
|
|
+ prot_printf(imapd_out, "%s BAD Missing required open parenthesis in %s %s\r\n",
|
|
+ tag, cmd, fetchatt.s);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+ do {
|
|
+ c = getastring(imapd_in, imapd_out, &fieldname);
|
|
+ for (p = fieldname.s; *p; p++) {
|
|
+ if (*p <= ' ' || *p & 0x80 || *p == ':') break;
|
|
+ }
|
|
+ if (*p || !*fieldname.s) {
|
|
+ prot_printf(imapd_out, "%s BAD Invalid field-name in %s %s\r\n",
|
|
+ tag, cmd, fetchatt.s);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+ appendstrlist(&newfields, fieldname.s);
|
|
+ if (fetchargs.cache_atleast < BIT32_MAX) {
|
|
+ bit32 this_ver =
|
|
+ mailbox_cached_header(fieldname.s);
|
|
+ if(this_ver > fetchargs.cache_atleast)
|
|
+ fetchargs.cache_atleast = this_ver;
|
|
+ }
|
|
+ } while (c == ' ');
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out, "%s BAD Missing required close parenthesis in %s %s\r\n",
|
|
+ tag, cmd, fetchatt.s);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+
|
|
+ /* Grab/parse the ]<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,
|
|
+ ©uid, !config_getswitch(IMAPOPT_SINGLEINSTANCESTORE));
|
|
+ }
|
|
+
|
|
+ index_check(imapd_mailbox, usinguid, 0);
|
|
+
|
|
+ done:
|
|
+ if (r && !(usinguid && r == IMAP_NO_NOSUCHMSG)) {
|
|
+ prot_printf(imapd_out, "%s NO %s%s\r\n", tag,
|
|
+ (r == IMAP_MAILBOX_NONEXISTENT &&
|
|
+ mboxlist_createmailboxcheck(mailboxname, 0, 0,
|
|
+ imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate,
|
|
+ (char **)0, (char **)0) == 0)
|
|
+ ? "[TRYCREATE] " : "", error_message(r));
|
|
+ }
|
|
+ else if (copyuid) {
|
|
+ prot_printf(imapd_out, "%s OK [COPYUID %s] %s\r\n", tag,
|
|
+ copyuid, error_message(IMAP_OK_COMPLETED));
|
|
+ free(copyuid);
|
|
+ }
|
|
+ else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform an EXPUNGE command
|
|
+ * sequence == NULL if this isn't a UID EXPUNGE
|
|
+ */
|
|
+void cmd_expunge(char *tag, char *sequence)
|
|
+{
|
|
+ int r;
|
|
+
|
|
+ if (backend_current) {
|
|
+ /* remote mailbox */
|
|
+ if (sequence) {
|
|
+ prot_printf(backend_current->out, "%s UID Expunge %s\r\n", tag,
|
|
+ sequence);
|
|
+ } else {
|
|
+ prot_printf(backend_current->out, "%s Expunge\r\n", tag);
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!(imapd_mailbox->myrights & ACL_EXPUNGE)) r = IMAP_PERMISSION_DENIED;
|
|
+ else if (sequence) {
|
|
+ r = mailbox_expunge(imapd_mailbox, index_expungeuidlist, sequence, 0);
|
|
+ }
|
|
+ else {
|
|
+ r = mailbox_expunge(imapd_mailbox, (mailbox_decideproc_t *)0,
|
|
+ (void *)0, 0);
|
|
+ }
|
|
+
|
|
+ index_check(imapd_mailbox, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ }
|
|
+ else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ sync_log_mailbox(imapd_mailbox->name);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a CREATE command
|
|
+ */
|
|
+void cmd_create(char *tag, char *name, char *partition, int localonly)
|
|
+{
|
|
+ int r = 0;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ int autocreatequota;
|
|
+ int sync_lockfd = (-1);
|
|
+
|
|
+ if (partition && !imapd_userisadmin) {
|
|
+ r = IMAP_PERMISSION_DENIED;
|
|
+ }
|
|
+
|
|
+ if (name[0] && name[strlen(name)-1] == imapd_namespace.hier_sep) {
|
|
+ /* We don't care about trailing hierarchy delimiters. */
|
|
+ name[strlen(name)-1] = '\0';
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+ }
|
|
+
|
|
+ if (!r && !localonly && config_mupdate_server) {
|
|
+ int guessedpart = 0;
|
|
+
|
|
+ /* determine if we're creating locally or remotely */
|
|
+ if (!partition) {
|
|
+ guessedpart = 1;
|
|
+ r = mboxlist_createmailboxcheck(mailboxname, 0, 0,
|
|
+ imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate,
|
|
+ NULL, &partition);
|
|
+ }
|
|
+
|
|
+ if (!r && !config_partitiondir(partition)) {
|
|
+ /* invalid partition, assume its a server (remote mailbox) */
|
|
+ char *server;
|
|
+ struct backend *s = NULL;
|
|
+ int res;
|
|
+
|
|
+ /* check for a remote partition */
|
|
+ server = partition;
|
|
+ partition = strchr(server, '!');
|
|
+ if (partition) *partition++ = '\0';
|
|
+ if (guessedpart) partition = NULL;
|
|
+
|
|
+ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox, imapd_in);
|
|
+ if (!s) r = IMAP_SERVER_UNAVAILABLE;
|
|
+
|
|
+ if (!r) {
|
|
+ if (!CAPA(s, CAPA_MUPDATE)) {
|
|
+ /* reserve mailbox on MUPDATE */
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ /* ok, send the create to that server */
|
|
+ if (partition)
|
|
+ prot_printf(s->out,
|
|
+ "%s CREATE {%d+}\r\n%s {%d+}\r\n%s\r\n",
|
|
+ tag, strlen(name), name,
|
|
+ strlen(partition), partition);
|
|
+ else
|
|
+ prot_printf(s->out, "%s CREATE {%d+}\r\n%s\r\n",
|
|
+ tag, strlen(name), name);
|
|
+ res = pipe_until_tag(s, tag, 0);
|
|
+
|
|
+ if (!CAPA(s, CAPA_MUPDATE)) {
|
|
+ /* do MUPDATE create operations */
|
|
+ }
|
|
+ /* make sure we've seen the update */
|
|
+ if (ultraparanoid && res == PROXY_OK) kick_mupdate();
|
|
+ }
|
|
+
|
|
+ imapd_check(s, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ } else {
|
|
+ /* we're allowed to reference last_result since the noop, if
|
|
+ sent, went to a different server */
|
|
+ prot_printf(imapd_out, "%s %s", tag, s->last_result.s);
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox -- fall through */
|
|
+ if (guessedpart) partition = NULL;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!r) {
|
|
+ /* xxx we do forced user creates on LOCALCREATE to facilitate
|
|
+ * mailbox moves */
|
|
+ r = mboxlist_createmailbox(mailboxname, 0, partition,
|
|
+ imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate,
|
|
+ localonly, localonly, 0);
|
|
+
|
|
+ if (r == IMAP_PERMISSION_DENIED && !strcasecmp(name, "INBOX") &&
|
|
+ (autocreatequota = config_getint(IMAPOPT_AUTOCREATEQUOTA))) {
|
|
+
|
|
+ /* Auto create */
|
|
+ r = mboxlist_createmailbox(mailboxname, 0,
|
|
+ partition, 1, imapd_userid,
|
|
+ imapd_authstate, 0, 0, 0);
|
|
+
|
|
+ if (!r && autocreatequota > 0) {
|
|
+ (void) mboxlist_setquota(mailboxname, autocreatequota, 0);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ }
|
|
+ else {
|
|
+ char *userid = NULL;
|
|
+
|
|
+ if (config_mupdate_server &&
|
|
+ (config_mupdate_config != IMAP_ENUM_MUPDATE_CONFIG_STANDARD)) {
|
|
+ kick_mupdate();
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+
|
|
+ if ((userid = mboxname_isusermailbox(mailboxname, 1)))
|
|
+ sync_log_user(userid);
|
|
+ else
|
|
+ sync_log_mailbox(mailboxname);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Callback for use by cmd_delete */
|
|
+static int delmbox(char *name,
|
|
+ int matchlen __attribute__((unused)),
|
|
+ int maycreate __attribute__((unused)),
|
|
+ void *rock __attribute__((unused)))
|
|
+{
|
|
+ int r;
|
|
+
|
|
+ r = mboxlist_deletemailbox(name, imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate,
|
|
+ 0, 0, 0);
|
|
+
|
|
+ if (!r) sync_log_mailbox(name);
|
|
+
|
|
+ if(r) {
|
|
+ prot_printf(imapd_out, "* NO delete %s: %s\r\n",
|
|
+ name, error_message(r));
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a DELETE command
|
|
+ */
|
|
+void cmd_delete(char *tag, char *name, int localonly, int force)
|
|
+{
|
|
+ int r;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ int mbtype;
|
|
+ char *server;
|
|
+ char *p;
|
|
+ int domainlen = 0;
|
|
+ int sync_lockfd = (-1);
|
|
+
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL,
|
|
+ &server, NULL, NULL);
|
|
+ }
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ /* remote mailbox */
|
|
+ struct backend *s = NULL;
|
|
+ int res;
|
|
+
|
|
+ if (supports_referrals) {
|
|
+ imapd_refer(tag, server, name);
|
|
+ referral_kick = 1;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox, imapd_in);
|
|
+ if (!s) r = IMAP_SERVER_UNAVAILABLE;
|
|
+
|
|
+ if (!r) {
|
|
+ prot_printf(s->out, "%s DELETE {%d+}\r\n%s\r\n",
|
|
+ tag, strlen(name), name);
|
|
+ res = pipe_until_tag(s, tag, 0);
|
|
+
|
|
+ if (!CAPA(s, CAPA_MUPDATE) && res == PROXY_OK) {
|
|
+ /* do MUPDATE delete operations */
|
|
+ }
|
|
+
|
|
+ /* make sure we've seen the update */
|
|
+ if (ultraparanoid && res == PROXY_OK) kick_mupdate();
|
|
+ }
|
|
+
|
|
+ imapd_check(s, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ } else {
|
|
+ /* we're allowed to reference last_result since the noop, if
|
|
+ sent, went to a different server */
|
|
+ prot_printf(imapd_out, "%s %s", tag, s->last_result.s);
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!r) {
|
|
+ if (config_virtdomains && (p = strchr(mailboxname, '!')))
|
|
+ domainlen = p - mailboxname + 1;
|
|
+
|
|
+ r = mboxlist_deletemailbox(mailboxname, imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate, 1-force,
|
|
+ localonly, 0);
|
|
+ }
|
|
+
|
|
+ /* was it a top-level user mailbox? */
|
|
+ /* localonly deletes are only per-mailbox */
|
|
+ if (!r && !localonly &&
|
|
+ !strncmp(mailboxname+domainlen, "user.", 5) &&
|
|
+ !strchr(mailboxname+domainlen+5, '.')) {
|
|
+ int mailboxname_len = strlen(mailboxname);
|
|
+
|
|
+ /* If we aren't too close to MAX_MAILBOX_NAME, append .* */
|
|
+ p = mailboxname + mailboxname_len; /* end of mailboxname */
|
|
+ if (mailboxname_len < sizeof(mailboxname) - 3) {
|
|
+ strcpy(p, ".*");
|
|
+ }
|
|
+
|
|
+ /* build a list of mailboxes - we're using internal names here */
|
|
+ mboxlist_findall(NULL, mailboxname, imapd_userisadmin, imapd_userid,
|
|
+ imapd_authstate, delmbox, NULL);
|
|
+
|
|
+ /* take care of deleting ACLs, subscriptions, seen state and quotas */
|
|
+ *p = '\0'; /* clip off pattern */
|
|
+ if ((!domainlen) ||
|
|
+ (domainlen+1 < (sizeof(mailboxname) - mailboxname_len))) {
|
|
+ if (domainlen) {
|
|
+ /* fully qualify the userid */
|
|
+ snprintf(p, (sizeof(mailboxname) - mailboxname_len), "@%.*s",
|
|
+ domainlen-1, mailboxname);
|
|
+ }
|
|
+ user_deletedata(mailboxname+domainlen+5, imapd_userid,
|
|
+ imapd_authstate, 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ }
|
|
+ else {
|
|
+ if (config_mupdate_server &&
|
|
+ (config_mupdate_config != IMAP_ENUM_MUPDATE_CONFIG_STANDARD)) {
|
|
+ kick_mupdate();
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ /* XXX should sent a RESET here to cleanup meta-data */
|
|
+ sync_log_mailbox(mailboxname);
|
|
+ }
|
|
+}
|
|
+
|
|
+struct renrock
|
|
+{
|
|
+ int ol;
|
|
+ int nl;
|
|
+ int rename_user;
|
|
+ char *olduser, *newuser;
|
|
+ char *acl_olduser, *acl_newuser;
|
|
+ char *newmailboxname;
|
|
+ char *partition;
|
|
+};
|
|
+
|
|
+/* Callback for use by cmd_rename */
|
|
+static int renmbox(char *name,
|
|
+ int matchlen __attribute__((unused)),
|
|
+ int maycreate __attribute__((unused)),
|
|
+ void *rock)
|
|
+{
|
|
+ char oldextname[MAX_MAILBOX_NAME+1];
|
|
+ char newextname[MAX_MAILBOX_NAME+1];
|
|
+ struct renrock *text = (struct renrock *)rock;
|
|
+ int r;
|
|
+
|
|
+ if((text->nl + strlen(name + text->ol)) > MAX_MAILBOX_NAME)
|
|
+ return 0;
|
|
+
|
|
+ strcpy(text->newmailboxname + text->nl, name + text->ol);
|
|
+
|
|
+ r = mboxlist_renamemailbox(name, text->newmailboxname,
|
|
+ text->partition,
|
|
+ 1, imapd_userid, imapd_authstate);
|
|
+
|
|
+ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
|
|
+ name,
|
|
+ imapd_userid, oldextname);
|
|
+ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
|
|
+ text->newmailboxname,
|
|
+ imapd_userid, newextname);
|
|
+
|
|
+ if(r) {
|
|
+ prot_printf(imapd_out, "* NO rename %s %s: %s\r\n",
|
|
+ oldextname, newextname, error_message(r));
|
|
+ if (RENAME_STOP_ON_ERROR) return r;
|
|
+ } else {
|
|
+ /* If we're renaming a user, change quotaroot and ACL */
|
|
+ if (text->rename_user) {
|
|
+ user_copyquotaroot(name, text->newmailboxname);
|
|
+ user_renameacl(text->newmailboxname,
|
|
+ text->acl_olduser, text->acl_newuser);
|
|
+ }
|
|
+
|
|
+ /* Rename mailbox annotations */
|
|
+ annotatemore_rename(name, text->newmailboxname,
|
|
+ text->rename_user ? text->olduser : NULL,
|
|
+ text->newuser);
|
|
+
|
|
+ prot_printf(imapd_out, "* OK rename %s %s\r\n",
|
|
+ oldextname, newextname);
|
|
+
|
|
+ sync_log_mailbox_double(name, text->newmailboxname);
|
|
+ }
|
|
+
|
|
+ prot_flush(imapd_out);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a RENAME command
|
|
+ */
|
|
+void cmd_rename(char *tag, char *oldname, char *newname, char *partition)
|
|
+{
|
|
+ int r = 0;
|
|
+ char oldmailboxname[MAX_MAILBOX_NAME+3];
|
|
+ char newmailboxname[MAX_MAILBOX_NAME+2];
|
|
+ char oldmailboxname2[MAX_MAILBOX_NAME+1];
|
|
+ char newmailboxname2[MAX_MAILBOX_NAME+1];
|
|
+ char oldextname[MAX_MAILBOX_NAME+1];
|
|
+ char newextname[MAX_MAILBOX_NAME+1];
|
|
+ int sync_lockfd = (-1);
|
|
+ int omlen, nmlen;
|
|
+ char *p;
|
|
+ int recursive_rename = 1;
|
|
+ int rename_user = 0;
|
|
+ char olduser[128], newuser[128];
|
|
+ char acl_olduser[128], acl_newuser[128];
|
|
+ int mbtype;
|
|
+ char *server;
|
|
+
|
|
+ if (partition && !imapd_userisadmin) {
|
|
+ r = IMAP_PERMISSION_DENIED;
|
|
+ }
|
|
+
|
|
+ /* canonicalize names */
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, oldname,
|
|
+ imapd_userid, oldmailboxname);
|
|
+ if (!r)
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, newname,
|
|
+ imapd_userid, newmailboxname);
|
|
+
|
|
+ /* Keep temporary copy: master is trashed */
|
|
+ strcpy(oldmailboxname2, oldmailboxname);
|
|
+ strcpy(newmailboxname2, newmailboxname);
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(NULL, NULL, oldmailboxname, &mbtype, NULL, NULL,
|
|
+ &server, NULL, NULL);
|
|
+ }
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ /* remote mailbox */
|
|
+ struct backend *s = NULL;
|
|
+ int res;
|
|
+
|
|
+ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox, imapd_in);
|
|
+ if (!s) r = IMAP_SERVER_UNAVAILABLE;
|
|
+
|
|
+ /* xxx start of separate proxy-only code
|
|
+ (remove when we move to a unified environment) */
|
|
+
|
|
+ /* Cross Server Rename */
|
|
+ if (!r && partition) {
|
|
+ char *destpart;
|
|
+
|
|
+ if (strcmp(oldname, newname)) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s NO Cross-server or cross-partition move w/rename not supported\r\n",
|
|
+ tag);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* dest partition? */
|
|
+
|
|
+ destpart = strchr(partition,'!');
|
|
+ if (destpart) {
|
|
+ char newserver[MAX_MAILBOX_NAME+1];
|
|
+ if (strlen(partition) >= sizeof(newserver)) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s NO Partition name too long\r\n", tag);
|
|
+ return;
|
|
+ }
|
|
+ strcpy(newserver,partition);
|
|
+ newserver[destpart-partition]='\0';
|
|
+ destpart++;
|
|
+
|
|
+ if (!strcmp(server, newserver)) {
|
|
+ /* Same Server, different partition */
|
|
+ /* xxx this would require administrative access to the
|
|
+ * backend, which we won't get */
|
|
+ prot_printf(imapd_out,
|
|
+ "%s NO Can't move across partitions via a proxy\r\n",
|
|
+ tag);
|
|
+ return;
|
|
+ } else {
|
|
+ /* Cross Server */
|
|
+ /* <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("a, NULL, 0);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ prot_printf(imapd_out, "* QUOTA ");
|
|
+ printastring(name);
|
|
+ prot_printf(imapd_out, " (");
|
|
+ if (quota.limit >= 0) {
|
|
+ prot_printf(imapd_out, "STORAGE " UQUOTA_T_FMT " %d",
|
|
+ quota.used/QUOTA_UNITS, quota.limit);
|
|
+ }
|
|
+ prot_printf(imapd_out, ")\r\n");
|
|
+ }
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a GETQUOTAROOT command
|
|
+ */
|
|
+void cmd_getquotaroot(const char *tag, const char *name)
|
|
+{
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ char *server;
|
|
+ int mbtype;
|
|
+ struct mailbox mailbox;
|
|
+ int r;
|
|
+ int doclose = 0;
|
|
+
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL,
|
|
+ &server, NULL, NULL);
|
|
+ }
|
|
+ if (r == IMAP_MAILBOX_MOVED) return;
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ /* remote mailbox */
|
|
+
|
|
+ if (imapd_userisadmin) {
|
|
+ /* If they are an admin, they won't retain that privledge if we
|
|
+ * proxy for them, so we need to refer them -- even if they haven't
|
|
+ * told us they're able to handle it. */
|
|
+ imapd_refer(tag, server, name);
|
|
+ } else {
|
|
+ struct backend *s;
|
|
+
|
|
+ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox, imapd_in);
|
|
+ if (!s) r = IMAP_SERVER_UNAVAILABLE;
|
|
+
|
|
+ imapd_check(s, 0, 0);
|
|
+
|
|
+ if (!r) {
|
|
+ prot_printf(s->out, "%s Getquotaroot {%d+}\r\n%s\r\n",
|
|
+ tag, strlen(name), name);
|
|
+ if (s != backend_current) pipe_including_tag(s, tag, 0);
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!r) {
|
|
+ r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ doclose = 1;
|
|
+ if (!imapd_userisadmin && !(mailbox.myrights & ACL_READ)) {
|
|
+ r = (mailbox.myrights & ACL_LOOKUP) ?
|
|
+ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ prot_printf(imapd_out, "* QUOTAROOT ");
|
|
+ printastring(name);
|
|
+ if (mailbox.quota.root) {
|
|
+ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
|
|
+ mailbox.quota.root,
|
|
+ imapd_userid, mailboxname);
|
|
+ prot_printf(imapd_out, " ");
|
|
+ printastring(mailboxname);
|
|
+ r = quota_read(&mailbox.quota, NULL, 0);
|
|
+ if (!r) {
|
|
+ prot_printf(imapd_out, "\r\n* QUOTA ");
|
|
+ printastring(mailboxname);
|
|
+ prot_printf(imapd_out, " (");
|
|
+ if (mailbox.quota.limit >= 0) {
|
|
+ prot_printf(imapd_out, "STORAGE " UQUOTA_T_FMT " %d",
|
|
+ mailbox.quota.used/QUOTA_UNITS,
|
|
+ mailbox.quota.limit);
|
|
+ }
|
|
+ prot_putc(')', imapd_out);
|
|
+ }
|
|
+ }
|
|
+ prot_printf(imapd_out, "\r\n");
|
|
+ }
|
|
+
|
|
+ if (doclose) mailbox_close(&mailbox);
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse and perform a SETQUOTA command
|
|
+ * The command has been parsed up to the resource list
|
|
+ */
|
|
+void cmd_setquota(const char *tag, const char *quotaroot)
|
|
+{
|
|
+ int newquota = -1;
|
|
+ int badresource = 0;
|
|
+ int c;
|
|
+ int force = 0;
|
|
+ static struct buf arg;
|
|
+ char *p;
|
|
+ int r;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ int mbtype;
|
|
+ char *server_rock_tmp = NULL;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != '(') goto badlist;
|
|
+
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (c != ')' || arg.s[0] != '\0') {
|
|
+ for (;;) {
|
|
+ if (c != ' ') goto badlist;
|
|
+ if (strcasecmp(arg.s, "storage") != 0) badresource = 1;
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (c != ' ' && c != ')') goto badlist;
|
|
+ if (arg.s[0] == '\0') goto badlist;
|
|
+ newquota = 0;
|
|
+ for (p = arg.s; *p; p++) {
|
|
+ if (!isdigit((int) *p)) goto badlist;
|
|
+ newquota = newquota * 10 + *p - '0';
|
|
+ if (newquota < 0) goto badlist; /* overflow */
|
|
+ }
|
|
+ if (c == ')') break;
|
|
+ }
|
|
+ }
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c == '\r') c = prot_getc(imapd_in);
|
|
+ if (c != '\n') {
|
|
+ prot_printf(imapd_out, "%s BAD Unexpected extra arguments to SETQUOTA\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (badresource) r = IMAP_UNSUPPORTED_QUOTA;
|
|
+ else if (!imapd_userisadmin && !imapd_userisproxyadmin) {
|
|
+ /* need to allow proxies so that mailbox moves can set initial quota
|
|
+ * roots */
|
|
+ r = IMAP_PERMISSION_DENIED;
|
|
+ } else {
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, quotaroot,
|
|
+ imapd_userid, mailboxname);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(NULL, NULL, mailboxname, &mbtype, NULL, NULL,
|
|
+ &server_rock_tmp, NULL, NULL);
|
|
+ }
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ /* remote mailbox */
|
|
+ char quotarootbuf[MAX_MAILBOX_NAME + 3];
|
|
+ char *server_rock = xstrdup(server_rock_tmp);
|
|
+
|
|
+ snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname);
|
|
+
|
|
+ r = mboxlist_findall(&imapd_namespace, quotarootbuf,
|
|
+ imapd_userisadmin, imapd_userid,
|
|
+ imapd_authstate, quota_cb, server_rock);
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (!r) {
|
|
+ /* Do the referral */
|
|
+ imapd_refer(tag, server_rock, quotaroot);
|
|
+ free(server_rock);
|
|
+ } else {
|
|
+ if (server_rock) free(server_rock);
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+ if (!r) {
|
|
+ /* are we forcing the creation of a quotaroot by having a leading +? */
|
|
+ if (quotaroot[0] == '+') {
|
|
+ force = 1;
|
|
+ quotaroot++;
|
|
+ }
|
|
+
|
|
+ r = mboxlist_setquota(mailboxname, newquota, force);
|
|
+ }
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ sync_log_quota(mailboxname);
|
|
+ return;
|
|
+
|
|
+ badlist:
|
|
+ prot_printf(imapd_out, "%s BAD Invalid quota list in Setquota\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+}
|
|
+
|
|
+#ifdef HAVE_SSL
|
|
+/*
|
|
+ * this implements the STARTTLS command, as described in RFC 2595.
|
|
+ * one caveat: it assumes that no external layer is currently present.
|
|
+ * if a client executes this command, information about the external
|
|
+ * layer that was passed on the command line is disgarded. this should
|
|
+ * be fixed.
|
|
+ */
|
|
+/* imaps - whether this is an imaps transaction or not */
|
|
+void cmd_starttls(char *tag, int imaps)
|
|
+{
|
|
+ int result;
|
|
+ int *layerp;
|
|
+
|
|
+ char *auth_id;
|
|
+ sasl_ssf_t ssf;
|
|
+
|
|
+ /* SASL and openssl have different ideas about whether ssf is signed */
|
|
+ layerp = (int *) &ssf;
|
|
+
|
|
+ if (imapd_starttls_done == 1)
|
|
+ {
|
|
+ prot_printf(imapd_out, "%s NO TLS already active\r\n", tag);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ result=tls_init_serverengine("imap",
|
|
+ 5, /* depth to verify */
|
|
+ !imaps, /* can client auth? */
|
|
+ !imaps); /* TLS only? */
|
|
+
|
|
+ if (result == -1) {
|
|
+
|
|
+ syslog(LOG_ERR, "error initializing TLS");
|
|
+
|
|
+ if (imaps == 0) {
|
|
+ prot_printf(imapd_out, "%s NO Error initializing TLS\r\n", tag);
|
|
+ } else {
|
|
+ fatal("tls_init() failed", EC_CONFIG);
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (imaps == 0)
|
|
+ {
|
|
+ prot_printf(imapd_out, "%s OK Begin TLS negotiation now\r\n", tag);
|
|
+ /* must flush our buffers before starting tls */
|
|
+ prot_flush(imapd_out);
|
|
+ }
|
|
+
|
|
+ result=tls_start_servertls(0, /* read */
|
|
+ 1, /* write */
|
|
+ layerp,
|
|
+ &auth_id,
|
|
+ &tls_conn);
|
|
+
|
|
+ /* if error */
|
|
+ if (result==-1) {
|
|
+ if (imaps == 0) {
|
|
+ prot_printf(imapd_out, "%s NO Starttls negotiation failed\r\n",
|
|
+ tag);
|
|
+ syslog(LOG_NOTICE, "STARTTLS negotiation failed: %s",
|
|
+ imapd_clienthost);
|
|
+ return;
|
|
+ } else {
|
|
+ syslog(LOG_NOTICE, "imaps TLS negotiation failed: %s",
|
|
+ imapd_clienthost);
|
|
+ fatal("tls_start_servertls() failed", EC_TEMPFAIL);
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* tell SASL about the negotiated layer */
|
|
+ result = sasl_setprop(imapd_saslconn, SASL_SSF_EXTERNAL, &ssf);
|
|
+ if (result != SASL_OK) {
|
|
+ fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
|
|
+ }
|
|
+ saslprops.ssf = ssf;
|
|
+
|
|
+ result = sasl_setprop(imapd_saslconn, SASL_AUTH_EXTERNAL, auth_id);
|
|
+ if (result != SASL_OK) {
|
|
+ fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
|
|
+ }
|
|
+ if(saslprops.authid) {
|
|
+ free(saslprops.authid);
|
|
+ saslprops.authid = NULL;
|
|
+ }
|
|
+ if(auth_id)
|
|
+ saslprops.authid = xstrdup(auth_id);
|
|
+
|
|
+ /* tell the prot layer about our new layers */
|
|
+ prot_settls(imapd_in, tls_conn);
|
|
+ prot_settls(imapd_out, tls_conn);
|
|
+
|
|
+ imapd_starttls_done = 1;
|
|
+}
|
|
+#else
|
|
+void cmd_starttls(char *tag, int imaps)
|
|
+{
|
|
+ fatal("cmd_starttls() executed, but starttls isn't implemented!",
|
|
+ EC_SOFTWARE);
|
|
+}
|
|
+#endif /* HAVE_SSL */
|
|
+
|
|
+/*
|
|
+ * Parse and perform a STATUS command
|
|
+ * The command has been parsed up to the attribute list
|
|
+ */
|
|
+void cmd_status(char *tag, char *name)
|
|
+{
|
|
+ int c;
|
|
+ int statusitems = 0;
|
|
+ static struct buf arg;
|
|
+ struct mailbox mailbox;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ int mbtype;
|
|
+ char *server;
|
|
+ int r = 0;
|
|
+ int doclose = 0;
|
|
+
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(tag, name, mailboxname, &mbtype, NULL, NULL,
|
|
+ &server, NULL, NULL);
|
|
+ }
|
|
+ if (r == IMAP_MAILBOX_MOVED) {
|
|
+ /* Eat the argument */
|
|
+ eatline(imapd_in, prot_getc(imapd_in));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!r && (mbtype & MBTYPE_REMOTE)) {
|
|
+ /* remote mailbox */
|
|
+
|
|
+ if (supports_referrals
|
|
+ && config_getswitch(IMAPOPT_PROXYD_ALLOW_STATUS_REFERRAL)) {
|
|
+ imapd_refer(tag, server, name);
|
|
+ /* Eat the argument */
|
|
+ eatline(imapd_in, prot_getc(imapd_in));
|
|
+ }
|
|
+ else {
|
|
+ struct backend *s;
|
|
+
|
|
+ s = proxy_findserver(server, &protocol[PROTOCOL_IMAP],
|
|
+ proxy_userid, &backend_cached,
|
|
+ &backend_current, &backend_inbox, imapd_in);
|
|
+ if (!s) r = IMAP_SERVER_UNAVAILABLE;
|
|
+
|
|
+ imapd_check(s, 0, 0);
|
|
+
|
|
+ if (!r) {
|
|
+ prot_printf(s->out, "%s Status {%d+}\r\n%s ", tag,
|
|
+ strlen(name), name);
|
|
+ if (!pipe_command(s, 65536)) {
|
|
+ if (s != backend_current) pipe_including_tag(s, tag, 0);
|
|
+ }
|
|
+ } else {
|
|
+ eatline(imapd_in, prot_getc(imapd_in));
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* local mailbox */
|
|
+
|
|
+ /*
|
|
+ * Perform a full checkpoint of any open mailbox, in case we're
|
|
+ * doing a STATUS check of the current mailbox.
|
|
+ */
|
|
+ imapd_check(NULL, 0, 1);
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != '(') goto badlist;
|
|
+
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (arg.s[0] == '\0') goto badlist;
|
|
+ for (;;) {
|
|
+ lcase(arg.s);
|
|
+ if (!strcmp(arg.s, "messages")) {
|
|
+ statusitems |= STATUS_MESSAGES;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "recent")) {
|
|
+ statusitems |= STATUS_RECENT;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "uidnext")) {
|
|
+ statusitems |= STATUS_UIDNEXT;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "uidvalidity")) {
|
|
+ statusitems |= STATUS_UIDVALIDITY;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "unseen")) {
|
|
+ statusitems |= STATUS_UNSEEN;
|
|
+ }
|
|
+ else {
|
|
+ prot_printf(imapd_out, "%s BAD Invalid Status attribute %s\r\n",
|
|
+ tag, arg.s);
|
|
+ eatline(imapd_in, c);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (c == ' ') c = getword(imapd_in, &arg);
|
|
+ else break;
|
|
+ }
|
|
+
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing close parenthesis in Status\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c == '\r') c = prot_getc(imapd_in);
|
|
+ if (c != '\n') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Unexpected extra arguments to Status\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = mailbox_open_header(mailboxname, imapd_authstate, &mailbox);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ doclose = 1;
|
|
+ r = mailbox_open_index(&mailbox);
|
|
+ }
|
|
+ if (!r && !(mailbox.myrights & ACL_READ)) {
|
|
+ r = (imapd_userisadmin || (mailbox.myrights & ACL_LOOKUP)) ?
|
|
+ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = index_status(&mailbox, name, statusitems);
|
|
+ }
|
|
+
|
|
+ if (doclose) mailbox_close(&mailbox);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ return;
|
|
+
|
|
+ badlist:
|
|
+ prot_printf(imapd_out, "%s BAD Invalid status list in Status\r\n", tag);
|
|
+ eatline(imapd_in, c);
|
|
+}
|
|
+
|
|
+#ifdef ENABLE_X_NETSCAPE_HACK
|
|
+/*
|
|
+ * Reply to Netscape's crock with a crock of my own
|
|
+ */
|
|
+void cmd_netscrape(char *tag)
|
|
+{
|
|
+ const char *url;
|
|
+
|
|
+ url = config_getstring(IMAPOPT_NETSCAPEURL);
|
|
+
|
|
+ /* I only know of three things to reply with: */
|
|
+ prot_printf(imapd_out,
|
|
+ "* OK [NETSCAPE] Carnegie Mellon Cyrus IMAP\r\n"
|
|
+ "* VERSION %s\r\n",
|
|
+ CYRUS_VERSION);
|
|
+ if (url) prot_printf(imapd_out, "* ACCOUNT-URL %s\r\n", url);
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n",
|
|
+ tag, error_message(IMAP_OK_COMPLETED));
|
|
+}
|
|
+#endif /* ENABLE_X_NETSCAPE_HACK */
|
|
+
|
|
+/* Callback for cmd_namespace to be passed to mboxlist_findall.
|
|
+ * For each top-level mailbox found, print a bit of the response
|
|
+ * if it is a shared namespace. The rock is used as an integer in
|
|
+ * order to ensure the namespace response is correct on a server with
|
|
+ * no shared namespace.
|
|
+ */
|
|
+static int namespacedata(char *name,
|
|
+ int matchlen __attribute__((unused)),
|
|
+ int maycreate __attribute__((unused)),
|
|
+ void *rock)
|
|
+{
|
|
+ int* sawone = (int*) rock;
|
|
+
|
|
+ if (!name) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if ((!strncasecmp(name, "INBOX", 5) && (!name[5] || name[5] == '.'))) {
|
|
+ /* The user has a "personal" namespace. */
|
|
+ sawone[NAMESPACE_INBOX] = 1;
|
|
+ } else if (mboxname_isusermailbox(name, 0)) {
|
|
+ /* The user can see the "other users" namespace. */
|
|
+ sawone[NAMESPACE_USER] = 1;
|
|
+ } else {
|
|
+ /* The user can see the "shared" namespace. */
|
|
+ sawone[NAMESPACE_SHARED] = 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Print out a response to the NAMESPACE command defined by
|
|
+ * RFC 2342.
|
|
+ */
|
|
+void cmd_namespace(tag)
|
|
+ char* tag;
|
|
+{
|
|
+ int sawone[3] = {0, 0, 0};
|
|
+ char* pattern;
|
|
+
|
|
+ if (SLEEZY_NAMESPACE) {
|
|
+ char inboxname[MAX_MAILBOX_NAME+1];
|
|
+
|
|
+ if (strlen(imapd_userid) + 5 > MAX_MAILBOX_NAME)
|
|
+ sawone[NAMESPACE_INBOX] = 0;
|
|
+ else {
|
|
+ (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, "INBOX",
|
|
+ imapd_userid, inboxname);
|
|
+ sawone[NAMESPACE_INBOX] =
|
|
+ !mboxlist_lookup(inboxname, NULL, NULL);
|
|
+ }
|
|
+ sawone[NAMESPACE_USER] = 1;
|
|
+ sawone[NAMESPACE_SHARED] = 1;
|
|
+ } else {
|
|
+ pattern = xstrdup("%");
|
|
+ /* now find all the exciting toplevel namespaces -
|
|
+ * we're using internal names here
|
|
+ */
|
|
+ mboxlist_findall(NULL, pattern, imapd_userisadmin, imapd_userid,
|
|
+ imapd_authstate, namespacedata, (void*) sawone);
|
|
+ free(pattern);
|
|
+ }
|
|
+
|
|
+ prot_printf(imapd_out, "* NAMESPACE");
|
|
+ if (sawone[NAMESPACE_INBOX]) {
|
|
+ prot_printf(imapd_out, " ((\"%s\" \"%c\"))",
|
|
+ imapd_namespace.prefix[NAMESPACE_INBOX],
|
|
+ imapd_namespace.hier_sep);
|
|
+ } else {
|
|
+ prot_printf(imapd_out, " NIL");
|
|
+ }
|
|
+ if (sawone[NAMESPACE_USER]) {
|
|
+ prot_printf(imapd_out, " ((\"%s\" \"%c\"))",
|
|
+ imapd_namespace.prefix[NAMESPACE_USER],
|
|
+ imapd_namespace.hier_sep);
|
|
+ } else {
|
|
+ prot_printf(imapd_out, " NIL");
|
|
+ }
|
|
+ if (sawone[NAMESPACE_SHARED]) {
|
|
+ prot_printf(imapd_out, " ((\"%s\" \"%c\"))",
|
|
+ imapd_namespace.prefix[NAMESPACE_SHARED],
|
|
+ imapd_namespace.hier_sep);
|
|
+ } else {
|
|
+ prot_printf(imapd_out, " NIL");
|
|
+ }
|
|
+ prot_printf(imapd_out, "\r\n");
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse annotate fetch data.
|
|
+ *
|
|
+ * This is a generic routine which parses just the annotation data.
|
|
+ * Any surrounding command text must be parsed elsewhere, ie,
|
|
+ * GETANNOTATION, FETCH.
|
|
+ */
|
|
+
|
|
+int getannotatefetchdata(char *tag,
|
|
+ struct strlist **entries, struct strlist **attribs)
|
|
+{
|
|
+ int c;
|
|
+ static struct buf arg;
|
|
+
|
|
+ *entries = *attribs = NULL;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation entry\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+ else if (c == '(') {
|
|
+ /* entry list */
|
|
+ do {
|
|
+ c = getqstring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation entry\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ /* add the entry to the list */
|
|
+ appendstrlist(entries, arg.s);
|
|
+
|
|
+ } while (c == ' ');
|
|
+
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing close paren in annotation entry list \r\n",
|
|
+ tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+ else {
|
|
+ /* single entry -- add it to the list */
|
|
+ prot_ungetc(c, imapd_in);
|
|
+ c = getqstring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation entry\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ appendstrlist(entries, arg.s);
|
|
+ }
|
|
+
|
|
+ if (c != ' ' || (c = prot_getc(imapd_in)) == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation attribute(s)\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ if (c == '(') {
|
|
+ /* attrib list */
|
|
+ do {
|
|
+ c = getnstring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation attribute(s)\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ /* add the attrib to the list */
|
|
+ appendstrlist(attribs, arg.s);
|
|
+
|
|
+ } while (c == ' ');
|
|
+
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing close paren in "
|
|
+ "annotation attribute list\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+ else {
|
|
+ /* single attrib */
|
|
+ prot_ungetc(c, imapd_in);
|
|
+ c = getqstring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation attribute\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ appendstrlist(attribs, arg.s);
|
|
+ }
|
|
+
|
|
+ return c;
|
|
+
|
|
+ baddata:
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse annotate store data.
|
|
+ *
|
|
+ * This is a generic routine which parses just the annotation data.
|
|
+ * Any surrounding command text must be parsed elsewhere, ie,
|
|
+ * SETANNOTATION, STORE, APPEND.
|
|
+ */
|
|
+
|
|
+int getannotatestoredata(char *tag, struct entryattlist **entryatts)
|
|
+{
|
|
+ int c, islist = 0;
|
|
+ static struct buf entry, attrib, value;
|
|
+ struct attvaluelist *attvalues = NULL;
|
|
+
|
|
+ *entryatts = NULL;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation entry\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+ else if (c == '(') {
|
|
+ /* entry list */
|
|
+ islist = 1;
|
|
+ }
|
|
+ else {
|
|
+ /* single entry -- put the char back */
|
|
+ prot_ungetc(c, imapd_in);
|
|
+ }
|
|
+
|
|
+ do {
|
|
+ /* get entry */
|
|
+ c = getqstring(imapd_in, imapd_out, &entry);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation entry\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ /* parse att-value list */
|
|
+ if (c != ' ' || (c = prot_getc(imapd_in)) != '(') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation attribute-values list\r\n",
|
|
+ tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ do {
|
|
+ /* get attrib */
|
|
+ c = getqstring(imapd_in, imapd_out, &attrib);
|
|
+ if (c == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation attribute\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ /* get value */
|
|
+ if (c != ' ' ||
|
|
+ (c = getnstring(imapd_in, imapd_out, &value)) == EOF) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing annotation value\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ /* add the attrib-value pair to the list */
|
|
+ appendattvalue(&attvalues, attrib.s, value.s);
|
|
+
|
|
+ } while (c == ' ');
|
|
+
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing close paren in annotation "
|
|
+ "attribute-values list\r\n", tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ /* add the entry to the list */
|
|
+ appendentryatt(entryatts, entry.s, attvalues);
|
|
+ attvalues = NULL;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ } while (c == ' ');
|
|
+
|
|
+ if (islist) {
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing close paren in annotation entry list \r\n",
|
|
+ tag);
|
|
+ goto baddata;
|
|
+ }
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+
|
|
+ return c;
|
|
+
|
|
+ baddata:
|
|
+ if (attvalues) freeattvalues(attvalues);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Output an entry/attribute-value list response.
|
|
+ *
|
|
+ * This is a generic routine which outputs just the annotation data.
|
|
+ * Any surrounding response text must be output elsewhere, ie,
|
|
+ * GETANNOTATION, FETCH.
|
|
+ */
|
|
+void annotate_response(struct entryattlist *l)
|
|
+{
|
|
+ int islist; /* do we have more than one entry? */
|
|
+
|
|
+ if (!l) return;
|
|
+
|
|
+ islist = (l->next != NULL);
|
|
+
|
|
+ if (islist) prot_printf(imapd_out, "(");
|
|
+
|
|
+ while (l) {
|
|
+ prot_printf(imapd_out, "\"%s\"", l->entry);
|
|
+
|
|
+ /* do we have attributes? solicited vs. unsolicited */
|
|
+ if (l->attvalues) {
|
|
+ struct attvaluelist *av = l->attvalues;
|
|
+
|
|
+ prot_printf(imapd_out, " (");
|
|
+ while (av) {
|
|
+ prot_printf(imapd_out, "\"%s\" ", av->attrib);
|
|
+ if (!strcasecmp(av->value, "NIL"))
|
|
+ prot_printf(imapd_out, "NIL");
|
|
+ else
|
|
+ prot_printf(imapd_out, "\"%s\"", av->value);
|
|
+
|
|
+ if ((av = av->next) == NULL)
|
|
+ prot_printf(imapd_out, ")");
|
|
+ else
|
|
+ prot_printf(imapd_out, " ");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if ((l = l->next) != NULL)
|
|
+ prot_printf(imapd_out, " ");
|
|
+ }
|
|
+
|
|
+ if (islist) prot_printf(imapd_out, ")");
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a GETANNOTATION command
|
|
+ *
|
|
+ * The command has been parsed up to the entries
|
|
+ */
|
|
+void cmd_getannotation(char *tag, char *mboxpat)
|
|
+{
|
|
+ int c, r = 0;
|
|
+ struct strlist *entries = NULL, *attribs = NULL;
|
|
+
|
|
+ c = getannotatefetchdata(tag, &entries, &attribs);
|
|
+ if (c == EOF) {
|
|
+ eatline(imapd_in, c);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* check for CRLF */
|
|
+ if (c == '\r') c = prot_getc(imapd_in);
|
|
+ if (c != '\n') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Unexpected extra arguments to Getannotation\r\n",
|
|
+ tag);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+
|
|
+ r = annotatemore_fetch(mboxpat, entries, attribs, &imapd_namespace,
|
|
+ imapd_userisadmin || imapd_userisproxyadmin,
|
|
+ imapd_userid, imapd_authstate, imapd_out);
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n",
|
|
+ tag, error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+
|
|
+ freeargs:
|
|
+ if (entries) freestrlist(entries);
|
|
+ if (attribs) freestrlist(attribs);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Perform a SETANNOTATION command
|
|
+ *
|
|
+ * The command has been parsed up to the entry-att list
|
|
+ */
|
|
+void cmd_setannotation(char *tag, char *mboxpat)
|
|
+{
|
|
+ int c, r = 0;
|
|
+ struct entryattlist *entryatts = NULL;
|
|
+
|
|
+ c = getannotatestoredata(tag, &entryatts);
|
|
+ if (c == EOF) {
|
|
+ eatline(imapd_in, c);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* check for CRLF */
|
|
+ if (c == '\r') c = prot_getc(imapd_in);
|
|
+ if (c != '\n') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Unexpected extra arguments to Setannotation\r\n",
|
|
+ tag);
|
|
+ eatline(imapd_in, c);
|
|
+ goto freeargs;
|
|
+ }
|
|
+
|
|
+ r = annotatemore_store(mboxpat,
|
|
+ entryatts, &imapd_namespace, imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate);
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+
|
|
+ freeargs:
|
|
+ if (entryatts) freeentryatts(entryatts);
|
|
+ return;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse a search program
|
|
+ */
|
|
+int getsearchprogram(tag, searchargs, charset, parsecharset)
|
|
+char *tag;
|
|
+struct searchargs *searchargs;
|
|
+int *charset;
|
|
+int parsecharset;
|
|
+{
|
|
+ int c;
|
|
+
|
|
+ do {
|
|
+ c = getsearchcriteria(tag, searchargs, charset, parsecharset);
|
|
+ parsecharset = 0;
|
|
+ } while (c == ' ');
|
|
+ return c;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse a search criteria
|
|
+ */
|
|
+int getsearchcriteria(tag, searchargs, charset, parsecharset)
|
|
+char *tag;
|
|
+struct searchargs *searchargs;
|
|
+int *charset;
|
|
+int parsecharset;
|
|
+{
|
|
+ static struct buf criteria, arg;
|
|
+ struct searchargs *sub1, *sub2;
|
|
+ char *p, *str;
|
|
+ int c, flag;
|
|
+ unsigned size;
|
|
+ time_t start, end;
|
|
+
|
|
+ c = getword(imapd_in, &criteria);
|
|
+ lcase(criteria.s);
|
|
+ switch (criteria.s[0]) {
|
|
+ case '\0':
|
|
+ if (c != '(') goto badcri;
|
|
+ c = getsearchprogram(tag, searchargs, charset, 0);
|
|
+ if (c == EOF) return EOF;
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out, "%s BAD Missing required close paren in Search command\r\n",
|
|
+ tag);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+ }
|
|
+ c = prot_getc(imapd_in);
|
|
+ break;
|
|
+
|
|
+ case '0': case '1': case '2': case '3': case '4':
|
|
+ case '5': case '6': case '7': case '8': case '9':
|
|
+ case '*':
|
|
+ if (imparse_issequence(criteria.s)) {
|
|
+ appendstrlist(&searchargs->sequence, criteria.s);
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'a':
|
|
+ if (!strcmp(criteria.s, "answered")) {
|
|
+ searchargs->system_flags_set |= FLAG_ANSWERED;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "all")) {
|
|
+ break;
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'b':
|
|
+ if (!strcmp(criteria.s, "before")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getsearchdate(&start, &end);
|
|
+ if (c == EOF) goto baddate;
|
|
+ if (!searchargs->before || searchargs->before > start) {
|
|
+ searchargs->before = start;
|
|
+ }
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "bcc")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(&searchargs->bcc, str);
|
|
+ }
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "body")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(&searchargs->body, str);
|
|
+ }
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'c':
|
|
+ if (!strcmp(criteria.s, "cc")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(&searchargs->cc, str);
|
|
+ }
|
|
+ }
|
|
+ else if (parsecharset && !strcmp(criteria.s, "charset")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c != ' ') goto missingarg;
|
|
+ lcase(arg.s);
|
|
+ *charset = charset_lookupname(arg.s);
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'd':
|
|
+ if (!strcmp(criteria.s, "deleted")) {
|
|
+ searchargs->system_flags_set |= FLAG_DELETED;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "draft")) {
|
|
+ searchargs->system_flags_set |= FLAG_DRAFT;
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'f':
|
|
+ if (!strcmp(criteria.s, "flagged")) {
|
|
+ searchargs->system_flags_set |= FLAG_FLAGGED;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "from")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(&searchargs->from, str);
|
|
+ }
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'h':
|
|
+ if (!strcmp(criteria.s, "header")) {
|
|
+ struct strlist **patlist;
|
|
+
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c != ' ') goto missingarg;
|
|
+ lcase(arg.s);
|
|
+
|
|
+ /* some headers can be reduced to search terms */
|
|
+ if (!strcmp(arg.s, "bcc")) {
|
|
+ patlist = &searchargs->bcc;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "cc")) {
|
|
+ patlist = &searchargs->cc;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "to")) {
|
|
+ patlist = &searchargs->to;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "from")) {
|
|
+ patlist = &searchargs->from;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "subject")) {
|
|
+ patlist = &searchargs->subject;
|
|
+ }
|
|
+
|
|
+ /* we look message-id up in the envelope */
|
|
+ else if (!strcmp(arg.s, "message-id")) {
|
|
+ patlist = &searchargs->messageid;
|
|
+ }
|
|
+
|
|
+ /* all other headers we handle normally */
|
|
+ else {
|
|
+ if (searchargs->cache_atleast < BIT32_MAX) {
|
|
+ bit32 this_ver =
|
|
+ mailbox_cached_header(arg.s);
|
|
+ if(this_ver > searchargs->cache_atleast)
|
|
+ searchargs->cache_atleast = this_ver;
|
|
+ }
|
|
+ appendstrlist(&searchargs->header_name, arg.s);
|
|
+ patlist = &searchargs->header;
|
|
+ }
|
|
+
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(patlist, str);
|
|
+ }
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'k':
|
|
+ if (!strcmp(criteria.s, "keyword")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (!imparse_isatom(arg.s)) goto badflag;
|
|
+ lcase(arg.s);
|
|
+ for (flag=0; flag < MAX_USER_FLAGS; flag++) {
|
|
+ if (imapd_mailbox->flagname[flag] &&
|
|
+ !strcasecmp(imapd_mailbox->flagname[flag], arg.s)) break;
|
|
+ }
|
|
+ if (flag == MAX_USER_FLAGS) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ break;
|
|
+ }
|
|
+ searchargs->user_flags_set[flag/32] |= 1<<(flag&31);
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'l':
|
|
+ if (!strcmp(criteria.s, "larger")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getword(imapd_in, &arg);
|
|
+ size = 0;
|
|
+ for (p = arg.s; *p && isdigit((int) *p); p++) {
|
|
+ size = size * 10 + *p - '0';
|
|
+ /* if (size < 0) goto badnumber; */
|
|
+ }
|
|
+ if (!arg.s || *p) goto badnumber;
|
|
+ if (size > searchargs->larger) searchargs->larger = size;
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'n':
|
|
+ if (!strcmp(criteria.s, "not")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ sub1 = (struct searchargs *)xzmalloc(sizeof(struct searchargs));
|
|
+ c = getsearchcriteria(tag, sub1, charset, 0);
|
|
+ if (c == EOF) {
|
|
+ freesearchargs(sub1);
|
|
+ return EOF;
|
|
+ }
|
|
+
|
|
+ appendsearchargs(searchargs, sub1, (struct searchargs *)0);
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "new")) {
|
|
+ searchargs->flags |= (SEARCH_SEEN_UNSET|SEARCH_RECENT_SET);
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'o':
|
|
+ if (!strcmp(criteria.s, "or")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ sub1 = (struct searchargs *)xzmalloc(sizeof(struct searchargs));
|
|
+ c = getsearchcriteria(tag, sub1, charset, 0);
|
|
+ if (c == EOF) {
|
|
+ freesearchargs(sub1);
|
|
+ return EOF;
|
|
+ }
|
|
+ if (c != ' ') goto missingarg;
|
|
+ sub2 = (struct searchargs *)xzmalloc(sizeof(struct searchargs));
|
|
+ c = getsearchcriteria(tag, sub2, charset, 0);
|
|
+ if (c == EOF) {
|
|
+ freesearchargs(sub1);
|
|
+ freesearchargs(sub2);
|
|
+ return EOF;
|
|
+ }
|
|
+ appendsearchargs(searchargs, sub1, sub2);
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "old")) {
|
|
+ searchargs->flags |= SEARCH_RECENT_UNSET;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "on")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getsearchdate(&start, &end);
|
|
+ if (c == EOF) goto baddate;
|
|
+ if (!searchargs->before || searchargs->before > end) {
|
|
+ searchargs->before = end;
|
|
+ }
|
|
+ if (!searchargs->after || searchargs->after < start) {
|
|
+ searchargs->after = start;
|
|
+ }
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'r':
|
|
+ if (!strcmp(criteria.s, "recent")) {
|
|
+ searchargs->flags |= SEARCH_RECENT_SET;
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 's':
|
|
+ if (!strcmp(criteria.s, "seen")) {
|
|
+ searchargs->flags |= SEARCH_SEEN_SET;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "sentbefore")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getsearchdate(&start, &end);
|
|
+ if (c == EOF) goto baddate;
|
|
+ if (!searchargs->sentbefore || searchargs->sentbefore > start) {
|
|
+ searchargs->sentbefore = start;
|
|
+ }
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "senton")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getsearchdate(&start, &end);
|
|
+ if (c == EOF) goto baddate;
|
|
+ if (!searchargs->sentbefore || searchargs->sentbefore > end) {
|
|
+ searchargs->sentbefore = end;
|
|
+ }
|
|
+ if (!searchargs->sentafter || searchargs->sentafter < start) {
|
|
+ searchargs->sentafter = start;
|
|
+ }
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "sentsince")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getsearchdate(&start, &end);
|
|
+ if (c == EOF) goto baddate;
|
|
+ if (!searchargs->sentafter || searchargs->sentafter < start) {
|
|
+ searchargs->sentafter = start;
|
|
+ }
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "since")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getsearchdate(&start, &end);
|
|
+ if (c == EOF) goto baddate;
|
|
+ if (!searchargs->after || searchargs->after < start) {
|
|
+ searchargs->after = start;
|
|
+ }
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "smaller")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getword(imapd_in, &arg);
|
|
+ size = 0;
|
|
+ for (p = arg.s; *p && isdigit((int) *p); p++) {
|
|
+ size = size * 10 + *p - '0';
|
|
+ /* if (size < 0) goto badnumber; */
|
|
+ }
|
|
+ if (!arg.s || *p) goto badnumber;
|
|
+ if (size == 0) size = 1;
|
|
+ if (!searchargs->smaller || size < searchargs->smaller)
|
|
+ searchargs->smaller = size;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "subject")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(&searchargs->subject, str);
|
|
+ }
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 't':
|
|
+ if (!strcmp(criteria.s, "to")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(&searchargs->to, str);
|
|
+ }
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "text")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getastring(imapd_in, imapd_out, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ str = charset_convert(arg.s, *charset, NULL, 0);
|
|
+ if (strchr(str, EMPTY)) {
|
|
+ /* Force failure */
|
|
+ searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
|
|
+ }
|
|
+ else {
|
|
+ appendstrlistpat(&searchargs->text, str);
|
|
+ }
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ case 'u':
|
|
+ if (!strcmp(criteria.s, "uid")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (!imparse_issequence(arg.s)) goto badcri;
|
|
+ appendstrlist(&searchargs->uidsequence, arg.s);
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "unseen")) {
|
|
+ searchargs->flags |= SEARCH_SEEN_UNSET;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "unanswered")) {
|
|
+ searchargs->system_flags_unset |= FLAG_ANSWERED;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "undeleted")) {
|
|
+ searchargs->system_flags_unset |= FLAG_DELETED;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "undraft")) {
|
|
+ searchargs->system_flags_unset |= FLAG_DRAFT;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "unflagged")) {
|
|
+ searchargs->system_flags_unset |= FLAG_FLAGGED;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "unkeyword")) {
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (!imparse_isatom(arg.s)) goto badflag;
|
|
+ lcase(arg.s);
|
|
+ for (flag=0; flag < MAX_USER_FLAGS; flag++) {
|
|
+ if (imapd_mailbox->flagname[flag] &&
|
|
+ !strcasecmp(imapd_mailbox->flagname[flag], arg.s)) break;
|
|
+ }
|
|
+ if (flag != MAX_USER_FLAGS) {
|
|
+ searchargs->user_flags_unset[flag/32] |= 1<<(flag&31);
|
|
+ }
|
|
+ }
|
|
+ else goto badcri;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ badcri:
|
|
+ prot_printf(imapd_out, "%s BAD Invalid Search criteria\r\n", tag);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+ }
|
|
+
|
|
+ return c;
|
|
+
|
|
+ missingarg:
|
|
+ prot_printf(imapd_out, "%s BAD Missing required argument to Search %s\r\n",
|
|
+ tag, criteria.s);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+
|
|
+ badflag:
|
|
+ prot_printf(imapd_out, "%s BAD Invalid flag name %s in Search command\r\n",
|
|
+ tag, arg.s);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+
|
|
+ baddate:
|
|
+ prot_printf(imapd_out, "%s BAD Invalid date in Search command\r\n", tag);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+
|
|
+ badnumber:
|
|
+ prot_printf(imapd_out, "%s BAD Invalid number in Search command\r\n", tag);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+}
|
|
+
|
|
+void cmd_dump(char *tag, char *name, int uid_start)
|
|
+{
|
|
+ int r = 0;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ char *path, *mpath, *acl;
|
|
+
|
|
+ /* administrators only please */
|
|
+ if (!imapd_userisadmin) {
|
|
+ r = IMAP_PERMISSION_DENIED;
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(tag, name, mailboxname, NULL, &path, &mpath,
|
|
+ NULL, &acl, NULL);
|
|
+ }
|
|
+ if (r == IMAP_MAILBOX_MOVED) return;
|
|
+
|
|
+ if(!r) {
|
|
+ r = dump_mailbox(tag, mailboxname, path, mpath, acl, uid_start,
|
|
+ imapd_in, imapd_out, imapd_authstate);
|
|
+ }
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+}
|
|
+
|
|
+void cmd_undump(char *tag, char *name)
|
|
+{
|
|
+ int r = 0;
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ char *path, *mpath, *acl;
|
|
+
|
|
+ /* administrators only please */
|
|
+ if (!imapd_userisadmin) {
|
|
+ r = IMAP_PERMISSION_DENIED;
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
|
|
+ imapd_userid, mailboxname);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(tag, name, mailboxname, NULL, &path, &mpath,
|
|
+ NULL, &acl, NULL);
|
|
+ }
|
|
+ if (r == IMAP_MAILBOX_MOVED) return;
|
|
+
|
|
+ if(!r) {
|
|
+ /* save this stuff from additional mlookups */
|
|
+ char *safe_path = xstrdup(path);
|
|
+ char *safe_mpath = mpath ? xstrdup(mpath) : NULL;
|
|
+ char *safe_acl = xstrdup(acl);
|
|
+ r = undump_mailbox(mailboxname, safe_path, safe_mpath, safe_acl,
|
|
+ imapd_in, imapd_out, imapd_authstate);
|
|
+ free(safe_path);
|
|
+ if (safe_mpath) free(safe_mpath);
|
|
+ free(safe_acl);
|
|
+ }
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s%s\r\n",
|
|
+ tag,
|
|
+ (r == IMAP_MAILBOX_NONEXISTENT &&
|
|
+ mboxlist_createmailboxcheck(mailboxname, 0, 0,
|
|
+ imapd_userisadmin,
|
|
+ imapd_userid, imapd_authstate,
|
|
+ NULL, NULL) == 0)
|
|
+ ? "[TRYCREATE] " : "", error_message(r));
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+}
|
|
+
|
|
+static int getresult(struct protstream *p, char *tag)
|
|
+{
|
|
+ char buf[4096];
|
|
+ char *str = (char *) buf;
|
|
+
|
|
+ while(1) {
|
|
+ if (!prot_fgets(str, sizeof(buf), p)) {
|
|
+ return IMAP_SERVER_UNAVAILABLE;
|
|
+ }
|
|
+ if (!strncmp(str, tag, strlen(tag))) {
|
|
+ str += strlen(tag);
|
|
+ if(!*str) {
|
|
+ /* We got a tag, but no response */
|
|
+ return IMAP_SERVER_UNAVAILABLE;
|
|
+ }
|
|
+ str++;
|
|
+ if (!strncasecmp(str, "OK ", 3)) { return 0; }
|
|
+ if (!strncasecmp(str, "NO ", 3)) { return IMAP_REMOTE_DENIED; }
|
|
+ return IMAP_SERVER_UNAVAILABLE; /* huh? */
|
|
+ }
|
|
+ /* skip this line, we don't really care */
|
|
+ }
|
|
+}
|
|
+
|
|
+/* given 2 protstreams and a mailbox, gets the acl and then wipes it */
|
|
+static int trashacl(struct protstream *pin, struct protstream *pout,
|
|
+ char *mailbox)
|
|
+{
|
|
+ int i=0, j=0;
|
|
+ char tagbuf[128];
|
|
+ int c; /* getword() returns an int */
|
|
+ struct buf tag, cmd, tmp, user;
|
|
+ int r = 0;
|
|
+
|
|
+ memset(&tag, 0, sizeof(struct buf));
|
|
+ memset(&cmd, 0, sizeof(struct buf));
|
|
+ memset(&tmp, 0, sizeof(struct buf));
|
|
+ memset(&user, 0, sizeof(struct buf));
|
|
+
|
|
+ prot_printf(pout, "ACL0 GETACL {%d+}\r\n%s\r\n",
|
|
+ strlen(mailbox), mailbox);
|
|
+
|
|
+ while(1) {
|
|
+ c = getword(pin, &tag);
|
|
+ if (c == EOF) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ c = getword(pin, &cmd);
|
|
+ if (c == EOF) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if(c == '\r') {
|
|
+ c = prot_getc(pin);
|
|
+ if(c != '\n') {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ }
|
|
+ if(c == '\n') goto cleanup;
|
|
+
|
|
+ if (tag.s[0] == '*' && !strncmp(cmd.s, "ACL", 3)) {
|
|
+ while(c != '\n') {
|
|
+ /* An ACL response, we should send a DELETEACL command */
|
|
+ c = getastring(pin, pout, &tmp);
|
|
+ if (c == EOF) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ if(c == '\r') {
|
|
+ c = prot_getc(pin);
|
|
+ if(c != '\n') {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ }
|
|
+ if(c == '\n') goto cleanup;
|
|
+
|
|
+ c = getastring(pin, pout, &user);
|
|
+ if (c == EOF) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ goto cleanup;
|
|
+ }
|
|
+
|
|
+ snprintf(tagbuf, sizeof(tagbuf), "ACL%d", ++i);
|
|
+
|
|
+ prot_printf(pout, "%s DELETEACL {%d+}\r\n%s {%d+}\r\n%s\r\n",
|
|
+ tagbuf, strlen(mailbox), mailbox,
|
|
+ strlen(user.s), user.s);
|
|
+ if(c == '\r') {
|
|
+ c = prot_getc(pin);
|
|
+ if(c != '\n') {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ goto cleanup;
|
|
+ }
|
|
+ }
|
|
+ /* if the next character is \n, we'll exit the loop */
|
|
+ }
|
|
+ continue;
|
|
+ } else if (!strncmp(tag.s, "ACL0", 4)) {
|
|
+ /* end of this command */
|
|
+ if (!strcasecmp(cmd.s, "OK")) { break; }
|
|
+ if (!strcasecmp(cmd.s, "NO")) { r = IMAP_REMOTE_DENIED; break; }
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ cleanup:
|
|
+
|
|
+ /* Now cleanup after all the DELETEACL commands */
|
|
+ if(!r) {
|
|
+ while(j < i) {
|
|
+ c = getword(pin, &tag);
|
|
+ if (c == EOF) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ eatline(pin, c);
|
|
+
|
|
+ if(!strncmp("ACL", tag.s, 3)) {
|
|
+ j++;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if(r) eatline(pin, c);
|
|
+
|
|
+ freebuf(&user);
|
|
+ freebuf(&tmp);
|
|
+ freebuf(&cmd);
|
|
+ freebuf(&tag);
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int dumpacl(struct protstream *pin, struct protstream *pout,
|
|
+ char *mailbox, char *acl_in)
|
|
+{
|
|
+ int r = 0;
|
|
+ int c; /* getword() returns an int */
|
|
+ char tag[128];
|
|
+ int tagnum = 1;
|
|
+ char *rights, *nextid;
|
|
+ int mailboxlen = strlen(mailbox);
|
|
+ char *acl_safe = acl_in ? xstrdup(acl_in) : NULL;
|
|
+ char *acl = acl_safe;
|
|
+ struct buf inbuf;
|
|
+
|
|
+ memset(&inbuf, 0, sizeof(struct buf));
|
|
+
|
|
+ while (acl) {
|
|
+ rights = strchr(acl, '\t');
|
|
+ if (!rights) break;
|
|
+ *rights++ = '\0';
|
|
+
|
|
+ nextid = strchr(rights, '\t');
|
|
+ if (!nextid) break;
|
|
+ *nextid++ = '\0';
|
|
+
|
|
+ snprintf(tag, sizeof(tag), "SACL%d", tagnum++);
|
|
+
|
|
+ prot_printf(pout, "%s SETACL {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
|
|
+ tag,
|
|
+ mailboxlen, mailbox,
|
|
+ strlen(acl), acl,
|
|
+ strlen(rights), rights);
|
|
+
|
|
+ while(1) {
|
|
+ c = getword(pin, &inbuf);
|
|
+ if (c == EOF) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ break;
|
|
+ }
|
|
+ if(strncmp(tag, inbuf.s, strlen(tag))) {
|
|
+ eatline(pin, c);
|
|
+ continue;
|
|
+ } else {
|
|
+ /* this is our line */
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Are we OK? */
|
|
+
|
|
+ c = getword(pin, &inbuf);
|
|
+ if (c == EOF) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if(strncmp("OK", inbuf.s, 2)) {
|
|
+ r = IMAP_REMOTE_DENIED;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* Eat the line and get the next one */
|
|
+ eatline(pin, c);
|
|
+ acl = nextid;
|
|
+ }
|
|
+
|
|
+ freebuf(&inbuf);
|
|
+ if(acl_safe) free(acl_safe);
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int do_xfer_single(char *toserver, char *topart,
|
|
+ char *name, char *mailboxname,
|
|
+ int mbflags,
|
|
+ char *path, char *mpath, char *part, char *acl,
|
|
+ int prereserved,
|
|
+ mupdate_handle *h_in,
|
|
+ struct backend *be_in)
|
|
+{
|
|
+ int r = 0, rerr = 0;
|
|
+ char buf[MAX_PARTITION_LEN+HOSTNAME_SIZE+2];
|
|
+ struct backend *be = NULL;
|
|
+ mupdate_handle *mupdate_h = NULL;
|
|
+ int backout_mupdate = 0;
|
|
+ int backout_remotebox = 0;
|
|
+ int backout_remoteflag = 0;
|
|
+
|
|
+ /* Make sure we're given a sane value */
|
|
+ if(topart && !imparse_isatom(topart)) {
|
|
+ return IMAP_PARTITION_UNKNOWN;
|
|
+ }
|
|
+
|
|
+ if(!strcmp(toserver, config_servername)) {
|
|
+ return IMAP_BAD_SERVER;
|
|
+ }
|
|
+
|
|
+ /* Okay, we have the mailbox, now the order of steps is:
|
|
+ *
|
|
+ * 1) Connect to remote server.
|
|
+ * 2) LOCALCREATE on remote server
|
|
+ * 2.5) Set mailbox as REMOTE on local server
|
|
+ * 3) mupdate.DEACTIVATE(mailbox, remoteserver) xxx what partition?
|
|
+ * 4) undump mailbox from local to remote
|
|
+ * 5) Sync remote acl
|
|
+ * 6) mupdate.ACTIVATE(mailbox, remoteserver)
|
|
+ * ** MAILBOX NOW LIVING ON REMOTE SERVER
|
|
+ * 6.5) force remote server to push the final mupdate entry to ensure
|
|
+ * that the state of the world is correct (required if we do not
|
|
+ * know the remote partition, but worst case it will be caught
|
|
+ * when they next sync)
|
|
+ * 7) local delete of mailbox
|
|
+ * 8) remove local remote mailbox entry??????
|
|
+ */
|
|
+
|
|
+ /* Step 1: Connect to remote server */
|
|
+ if(!r && !be_in) {
|
|
+ /* Just authorize as the IMAP server, so pass "" as our authzid */
|
|
+ be = backend_connect(NULL, toserver, &protocol[PROTOCOL_IMAP],
|
|
+ "", NULL, NULL);
|
|
+ if(!be) r = IMAP_SERVER_UNAVAILABLE;
|
|
+ if(r) syslog(LOG_ERR,
|
|
+ "Could not move mailbox: %s, Backend connect failed",
|
|
+ mailboxname);
|
|
+ } else if(!r) {
|
|
+ be = be_in;
|
|
+ }
|
|
+
|
|
+ /* Step 1a: Connect to mupdate (as needed) */
|
|
+ if(h_in) {
|
|
+ mupdate_h = h_in;
|
|
+ } else if (config_mupdate_server) {
|
|
+ r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
|
|
+ if(r) {
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not move mailbox: %s, MUPDATE connect failed",
|
|
+ mailboxname);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ /* Step 2: LOCALCREATE on remote server */
|
|
+ if(!r) {
|
|
+ if(topart) {
|
|
+ /* need to send partition as an atom */
|
|
+ prot_printf(be->out, "LC1 LOCALCREATE {%d+}\r\n%s %s\r\n",
|
|
+ strlen(name), name, topart);
|
|
+ } else {
|
|
+ prot_printf(be->out, "LC1 LOCALCREATE {%d+}\r\n%s\r\n",
|
|
+ strlen(name), name);
|
|
+ }
|
|
+ r = getresult(be->in, "LC1");
|
|
+ if(r) syslog(LOG_ERR, "Could not move mailbox: %s, LOCALCREATE failed",
|
|
+ mailboxname);
|
|
+ else backout_remotebox = 1;
|
|
+ }
|
|
+
|
|
+ /* Step 2.5: Set mailbox as REMOTE on local server */
|
|
+ if(!r) {
|
|
+ snprintf(buf, sizeof(buf), "%s!%s", toserver, part);
|
|
+ r = mboxlist_update(mailboxname, mbflags|MBTYPE_MOVING, buf, acl, 1);
|
|
+ if(r) syslog(LOG_ERR, "Could not move mailbox: %s, " \
|
|
+ "mboxlist_update failed", mailboxname);
|
|
+ }
|
|
+
|
|
+ /* Step 3: mupdate.DEACTIVATE(mailbox, newserver) */
|
|
+ /* (only if mailbox has not been already deactivated by our caller) */
|
|
+ if(!r && mupdate_h && !prereserved) {
|
|
+ backout_remoteflag = 1;
|
|
+
|
|
+ /* Note we are making the reservation on OUR host so that recovery
|
|
+ * make sense */
|
|
+ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part);
|
|
+ r = mupdate_deactivate(mupdate_h, mailboxname, buf);
|
|
+ if(r) syslog(LOG_ERR,
|
|
+ "Could not move mailbox: %s, MUPDATE DEACTIVATE failed",
|
|
+ mailboxname);
|
|
+ }
|
|
+
|
|
+ /* Step 4: Dump local -> remote */
|
|
+ if(!r) {
|
|
+ backout_mupdate = 1;
|
|
+
|
|
+ prot_printf(be->out, "D01 UNDUMP {%d+}\r\n%s ", strlen(name), name);
|
|
+
|
|
+ r = dump_mailbox(NULL, mailboxname, path, mpath, acl,
|
|
+ 0, be->in, be->out, imapd_authstate);
|
|
+
|
|
+ if(r)
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not move mailbox: %s, dump_mailbox() failed",
|
|
+ mailboxname);
|
|
+ }
|
|
+
|
|
+ if(!r) {
|
|
+ r = getresult(be->in, "D01");
|
|
+ if(r) syslog(LOG_ERR, "Could not move mailbox: %s, UNDUMP failed",
|
|
+ mailboxname);
|
|
+ }
|
|
+
|
|
+ /* Step 5: Set ACL on remote */
|
|
+ if(!r) {
|
|
+ r = trashacl(be->in, be->out, name);
|
|
+ if(r) syslog(LOG_ERR, "Could not clear remote acl on %s",
|
|
+ mailboxname);
|
|
+ }
|
|
+ if(!r) {
|
|
+ r = dumpacl(be->in, be->out, name, acl);
|
|
+ if(r) syslog(LOG_ERR, "Could not set remote acl on %s",
|
|
+ mailboxname);
|
|
+ }
|
|
+
|
|
+ /* Step 6: mupdate.activate(mailbox, remote) */
|
|
+ /* We do this from the local server first so that recovery is easier */
|
|
+ if(!r && mupdate_h) {
|
|
+ /* Note the flag that we don't have a valid partiton at the moment */
|
|
+ snprintf(buf, sizeof(buf), "%s!MOVED", toserver);
|
|
+ r = mupdate_activate(mupdate_h, mailboxname, buf, acl);
|
|
+ }
|
|
+
|
|
+ /* MAILBOX NOW LIVES ON REMOTE */
|
|
+ if(!r) {
|
|
+ backout_remotebox = 0;
|
|
+ backout_mupdate = 0;
|
|
+ backout_remoteflag = 0;
|
|
+
|
|
+ /* 6.5) Kick remote server to correct mupdate entry */
|
|
+ /* Note that we don't really care if this succeeds or not */
|
|
+ if (mupdate_h) {
|
|
+ prot_printf(be->out, "MP1 MUPDATEPUSH {%d+}\r\n%s\r\n",
|
|
+ strlen(name), name);
|
|
+ rerr = getresult(be->in, "MP1");
|
|
+ if(rerr) {
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not trigger remote push to mupdate server" \
|
|
+ "during move of %s",
|
|
+ mailboxname);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* 7) local delete of mailbox
|
|
+ * & remove local "remote" mailboxlist entry */
|
|
+ if(!r) {
|
|
+ /* Note that we do not check the ACL, and we don't update MUPDATE */
|
|
+ /* note also that we need to remember to let proxyadmins do this */
|
|
+ r = mboxlist_deletemailbox(mailboxname,
|
|
+ imapd_userisadmin || imapd_userisproxyadmin,
|
|
+ imapd_userid, imapd_authstate, 0, 1, 0);
|
|
+ if(r) syslog(LOG_ERR,
|
|
+ "Could not delete local mailbox during move of %s",
|
|
+ mailboxname);
|
|
+
|
|
+ if (!r) {
|
|
+ /* Delete mailbox annotations */
|
|
+ annotatemore_delete(mailboxname);
|
|
+ }
|
|
+ }
|
|
+
|
|
+done:
|
|
+ if(r && mupdate_h && backout_mupdate) {
|
|
+ rerr = 0;
|
|
+ /* xxx if the mupdate server is what failed, then this won't
|
|
+ help any! */
|
|
+ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part);
|
|
+ rerr = mupdate_activate(mupdate_h, mailboxname, buf, acl);
|
|
+ if(rerr) {
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not back out mupdate during move of %s (%s)",
|
|
+ mailboxname, error_message(rerr));
|
|
+ }
|
|
+ }
|
|
+ if(r && backout_remotebox) {
|
|
+ rerr = 0;
|
|
+ prot_printf(be->out, "LD1 LOCALDELETE {%d+}\r\n%s\r\n",
|
|
+ strlen(name), name);
|
|
+ rerr = getresult(be->in, "LD1");
|
|
+ if(rerr) {
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not back out remote mailbox during move of %s (%s)",
|
|
+ name, error_message(rerr));
|
|
+ }
|
|
+ }
|
|
+ if(r && backout_remoteflag) {
|
|
+ rerr = 0;
|
|
+
|
|
+ rerr = mboxlist_update(mailboxname, mbflags, part, acl, 1);
|
|
+ if(rerr) syslog(LOG_ERR, "Could not unset remote flag on mailbox: %s",
|
|
+ mailboxname);
|
|
+ }
|
|
+
|
|
+ /* release the handles we got locally if necessary */
|
|
+ if(mupdate_h && !h_in)
|
|
+ mupdate_disconnect(&mupdate_h);
|
|
+ if(be && !be_in)
|
|
+ backend_disconnect(be);
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+struct xfer_user_rock
|
|
+{
|
|
+ char *toserver;
|
|
+ char *topart;
|
|
+ mupdate_handle *h;
|
|
+ struct backend *be;
|
|
+};
|
|
+
|
|
+static int xfer_user_cb(char *name,
|
|
+ int matchlen __attribute__((unused)),
|
|
+ int maycreate __attribute__((unused)),
|
|
+ void *rock)
|
|
+{
|
|
+ mupdate_handle *mupdate_h = ((struct xfer_user_rock *)rock)->h;
|
|
+ char *toserver = ((struct xfer_user_rock *)rock)->toserver;
|
|
+ char *topart = ((struct xfer_user_rock *)rock)->topart;
|
|
+ struct backend *be = ((struct xfer_user_rock *)rock)->be;
|
|
+ char externalname[MAX_MAILBOX_NAME+1];
|
|
+ int mbflags;
|
|
+ int r = 0;
|
|
+ char *inpath, *inmpath, *inpart, *inacl;
|
|
+ char *path = NULL, *mpath = NULL, *part = NULL, *acl = NULL;
|
|
+
|
|
+ if (!r) {
|
|
+ /* NOTE: NOT mlookup() because we don't want to issue a referral */
|
|
+ /* xxx but what happens if they are remote
|
|
+ * mailboxes? */
|
|
+ r = mboxlist_detail(name, &mbflags,
|
|
+ &inpath, &inmpath, &inpart, &inacl, NULL);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ path = xstrdup(inpath);
|
|
+ if (inmpath) mpath = xstrdup(inmpath);
|
|
+ part = xstrdup(inpart);
|
|
+ acl = xstrdup(inacl);
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = (*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
|
|
+ name,
|
|
+ imapd_userid,
|
|
+ externalname);
|
|
+ }
|
|
+
|
|
+ if(!r) {
|
|
+ r = do_xfer_single(toserver, topart, externalname, name, mbflags,
|
|
+ path, mpath, part, acl, 0, mupdate_h, be);
|
|
+ }
|
|
+
|
|
+ if(path) free(path);
|
|
+ if(mpath) free(mpath);
|
|
+ if(part) free(part);
|
|
+ if(acl) free(acl);
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+
|
|
+void cmd_xfer(char *tag, char *name, char *toserver, char *topart)
|
|
+{
|
|
+ int r = 0;
|
|
+ char buf[MAX_PARTITION_LEN+HOSTNAME_SIZE+2];
|
|
+ char mailboxname[MAX_MAILBOX_NAME+1];
|
|
+ int mbflags;
|
|
+ int moving_user = 0;
|
|
+ int backout_mupdate = 0;
|
|
+ mupdate_handle *mupdate_h = NULL;
|
|
+ char *inpath, *inmpath, *inpart, *inacl;
|
|
+ char *path = NULL, *mpath = NULL, *part = NULL, *acl = NULL;
|
|
+ char *p, *mbox = mailboxname;
|
|
+
|
|
+ /* administrators only please */
|
|
+ /* however, proxys can do this, if their authzid is an admin */
|
|
+ if (!imapd_userisadmin && !imapd_userisproxyadmin) {
|
|
+ r = IMAP_PERMISSION_DENIED;
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
|
|
+ name,
|
|
+ imapd_userid,
|
|
+ mailboxname);
|
|
+ }
|
|
+
|
|
+ /* NOTE: Since XFER can only be used by an admin, and we always connect
|
|
+ * to the destination backend as an admin, we take advantage of the fact
|
|
+ * that admins *always* use a consistent mailbox naming scheme.
|
|
+ * So, 'name' should be used in any command we send to a backend, and
|
|
+ * 'mailboxname' is the internal name to be used for mupdate and findall.
|
|
+ */
|
|
+
|
|
+ if (config_virtdomains && (p = strchr(mailboxname, '!'))) {
|
|
+ /* pointer to mailbox w/o domain prefix */
|
|
+ mbox = p + 1;
|
|
+ }
|
|
+
|
|
+ if(!strncmp(mbox, "user.", 5) && !strchr(mbox+5, '.')) {
|
|
+ if ((strlen(mbox+5) == (strlen(imapd_userid) - (mbox - mailboxname))) &&
|
|
+ !strncmp(mbox+5, imapd_userid, strlen(mbox+5))) {
|
|
+ /* don't move your own inbox, that could be troublesome */
|
|
+ r = IMAP_MAILBOX_NOTSUPPORTED;
|
|
+ } else if (!config_getswitch(IMAPOPT_ALLOWUSERMOVES)) {
|
|
+ /* not configured to allow user moves */
|
|
+ r = IMAP_MAILBOX_NOTSUPPORTED;
|
|
+ } else {
|
|
+ moving_user = 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!r) {
|
|
+ r = mlookup(tag, name, mailboxname, &mbflags,
|
|
+ &inpath, &inmpath, &inpart, &inacl, NULL);
|
|
+ }
|
|
+ if (r == IMAP_MAILBOX_MOVED) return;
|
|
+
|
|
+ if (!r) {
|
|
+ path = xstrdup(inpath);
|
|
+ if (inmpath) mpath = xstrdup(inmpath);
|
|
+ part = xstrdup(inpart);
|
|
+ acl = xstrdup(inacl);
|
|
+ }
|
|
+
|
|
+ /* if we are not moving a user, just move the one mailbox */
|
|
+ if(!r && !moving_user) {
|
|
+ r = do_xfer_single(toserver, topart, name, mailboxname, mbflags,
|
|
+ path, mpath, part, acl, 0, NULL, NULL);
|
|
+ } else if (!r) {
|
|
+ struct backend *be = NULL;
|
|
+
|
|
+ /* we need to reserve the users inbox - connect to mupdate */
|
|
+ if(!r && config_mupdate_server) {
|
|
+ r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
|
|
+ if(r) {
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not move mailbox: %s, MUPDATE connect failed",
|
|
+ mailboxname);
|
|
+ goto done;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Get a single connection to the remote backend */
|
|
+ be = backend_connect(NULL, toserver, &protocol[PROTOCOL_IMAP],
|
|
+ "", NULL, NULL);
|
|
+ if(!be) {
|
|
+ r = IMAP_SERVER_UNAVAILABLE;
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not move mailbox: %s, " \
|
|
+ "Initial backend connect failed",
|
|
+ mailboxname);
|
|
+ }
|
|
+
|
|
+ /* deactivate their inbox */
|
|
+ if(!r && mupdate_h) {
|
|
+ /* Note we are making the reservation on OUR host so that recovery
|
|
+ * make sense */
|
|
+ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part);
|
|
+ r = mupdate_deactivate(mupdate_h, mailboxname, buf);
|
|
+ if(r) syslog(LOG_ERR,
|
|
+ "Could deactivate mailbox: %s, during move",
|
|
+ mailboxname);
|
|
+ else backout_mupdate = 1;
|
|
+ }
|
|
+
|
|
+ /* If needed, set an uppermost quota root */
|
|
+ if(!r) {
|
|
+ struct quota quota;
|
|
+
|
|
+ quota.root = mailboxname;
|
|
+ r = quota_read("a, NULL, 0);
|
|
+
|
|
+ if(!r) {
|
|
+ /* note use of + to force the setting of a nonexistant
|
|
+ * quotaroot */
|
|
+ prot_printf(be->out, "Q01 SETQUOTA {%d+}\r\n" \
|
|
+ "+%s (STORAGE %d)\r\n",
|
|
+ strlen(name)+1, name, quota.limit);
|
|
+ r = getresult(be->in, "Q01");
|
|
+ if(r) syslog(LOG_ERR,
|
|
+ "Could not move mailbox: %s, " \
|
|
+ "failed setting initial quota root\r\n",
|
|
+ mailboxname);
|
|
+ }
|
|
+ else if (r == IMAP_QUOTAROOT_NONEXISTENT) r = 0;
|
|
+ }
|
|
+
|
|
+
|
|
+ /* recursively move all sub-mailboxes, using internal names */
|
|
+ if(!r) {
|
|
+ struct xfer_user_rock rock;
|
|
+
|
|
+ rock.toserver = toserver;
|
|
+ rock.topart = topart;
|
|
+ rock.h = mupdate_h;
|
|
+ rock.be = be;
|
|
+
|
|
+ snprintf(buf, sizeof(buf), "%s.*", mailboxname);
|
|
+ r = mboxlist_findall(NULL, buf, 1, imapd_userid,
|
|
+ imapd_authstate, xfer_user_cb,
|
|
+ &rock);
|
|
+ }
|
|
+
|
|
+ /* xxx how do you back out if one of the above moves fails? */
|
|
+
|
|
+ /* move this mailbox */
|
|
+ /* ...and seen file, and subs file, and sieve scripts... */
|
|
+ if(!r) {
|
|
+ r = do_xfer_single(toserver, topart, name, mailboxname, mbflags,
|
|
+ path, mpath, part, acl, 1, mupdate_h, be);
|
|
+ }
|
|
+
|
|
+ if(be) {
|
|
+ backend_disconnect(be);
|
|
+ free(be);
|
|
+ }
|
|
+
|
|
+ if(r && mupdate_h && backout_mupdate) {
|
|
+ int rerr = 0;
|
|
+ /* xxx if the mupdate server is what failed, then this won't
|
|
+ help any! */
|
|
+ snprintf(buf, sizeof(buf), "%s!%s", config_servername, part);
|
|
+ rerr = mupdate_activate(mupdate_h, mailboxname, buf, acl);
|
|
+ if(rerr) {
|
|
+ syslog(LOG_ERR,
|
|
+ "Could not back out mupdate during move of %s (%s)",
|
|
+ mailboxname, error_message(rerr));
|
|
+ }
|
|
+ } else if(!r) {
|
|
+ /* this was a successful user delete, and we need to delete
|
|
+ certain user meta-data (but not seen state!) */
|
|
+ user_deletedata(mailboxname+5, imapd_userid, imapd_authstate, 0);
|
|
+ }
|
|
+
|
|
+ if(!r && mupdate_h) {
|
|
+ mupdate_disconnect(&mupdate_h);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ done:
|
|
+ if(part) free(part);
|
|
+ if(path) free(path);
|
|
+ if(mpath) free(mpath);
|
|
+ if(acl) free(acl);
|
|
+
|
|
+ imapd_check(NULL, 0, 0);
|
|
+
|
|
+ if (r) {
|
|
+ prot_printf(imapd_out, "%s NO %s\r\n",
|
|
+ tag,
|
|
+ error_message(r));
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "%s OK %s\r\n", tag,
|
|
+ error_message(IMAP_OK_COMPLETED));
|
|
+ }
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Parse a "date", for SEARCH criteria
|
|
+ * The time_t's pointed to by 'start' and 'end' are set to the
|
|
+ * times of the start and end of the parsed date.
|
|
+ */
|
|
+int getsearchdate(start, end)
|
|
+time_t *start, *end;
|
|
+{
|
|
+ int c;
|
|
+ struct tm tm;
|
|
+ int quoted = 0;
|
|
+ char month[4];
|
|
+
|
|
+ memset(&tm, 0, sizeof tm);
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c == '\"') {
|
|
+ quoted++;
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+
|
|
+ /* Day of month */
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_mday = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (isdigit(c)) {
|
|
+ tm.tm_mday = tm.tm_mday * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+
|
|
+ if (c != '-') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ /* Month name */
|
|
+ if (!isalpha(c)) goto baddate;
|
|
+ month[0] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isalpha(c)) goto baddate;
|
|
+ month[1] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isalpha(c)) goto baddate;
|
|
+ month[2] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ month[3] = '\0';
|
|
+ lcase(month);
|
|
+
|
|
+ for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
|
|
+ if (!strcmp(month, monthname[tm.tm_mon])) break;
|
|
+ }
|
|
+ if (tm.tm_mon == 12) goto baddate;
|
|
+
|
|
+ if (c != '-') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ /* Year */
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_year = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_year = tm.tm_year * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (isdigit(c)) {
|
|
+ if (tm.tm_year < 19) goto baddate;
|
|
+ tm.tm_year -= 19;
|
|
+ tm.tm_year = tm.tm_year * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_year = tm.tm_year * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+
|
|
+ if (quoted) {
|
|
+ if (c != '\"') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+
|
|
+ tm.tm_isdst = -1;
|
|
+ *start = mktime(&tm);
|
|
+
|
|
+ tm.tm_sec = tm.tm_min = 59;
|
|
+ tm.tm_hour = 23;
|
|
+ tm.tm_isdst = -1;
|
|
+ *end = mktime(&tm);
|
|
+
|
|
+ return c;
|
|
+
|
|
+ baddate:
|
|
+ prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+}
|
|
+
|
|
+#define SORTGROWSIZE 10
|
|
+
|
|
+/*
|
|
+ * Parse sort criteria
|
|
+ */
|
|
+int getsortcriteria(char *tag, struct sortcrit **sortcrit)
|
|
+{
|
|
+ int c;
|
|
+ static struct buf criteria;
|
|
+ int nsort, n;
|
|
+
|
|
+ *sortcrit = NULL;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != '(') goto missingcrit;
|
|
+
|
|
+ c = getword(imapd_in, &criteria);
|
|
+ if (criteria.s[0] == '\0') goto missingcrit;
|
|
+
|
|
+ nsort = 0;
|
|
+ n = 0;
|
|
+ for (;;) {
|
|
+ if (n >= nsort - 1) { /* leave room for implicit criterion */
|
|
+ /* (Re)allocate an array for sort criteria */
|
|
+ nsort += SORTGROWSIZE;
|
|
+ *sortcrit =
|
|
+ (struct sortcrit *) xrealloc(*sortcrit,
|
|
+ nsort * sizeof(struct sortcrit));
|
|
+ /* Zero out the newly added sortcrit */
|
|
+ memset((*sortcrit)+n, 0, SORTGROWSIZE * sizeof(struct sortcrit));
|
|
+ }
|
|
+
|
|
+ lcase(criteria.s);
|
|
+ if (!strcmp(criteria.s, "reverse")) {
|
|
+ (*sortcrit)[n].flags |= SORT_REVERSE;
|
|
+ goto nextcrit;
|
|
+ }
|
|
+ else if (!strcmp(criteria.s, "arrival"))
|
|
+ (*sortcrit)[n].key = SORT_ARRIVAL;
|
|
+ else if (!strcmp(criteria.s, "cc"))
|
|
+ (*sortcrit)[n].key = SORT_CC;
|
|
+ else if (!strcmp(criteria.s, "date"))
|
|
+ (*sortcrit)[n].key = SORT_DATE;
|
|
+ else if (!strcmp(criteria.s, "from"))
|
|
+ (*sortcrit)[n].key = SORT_FROM;
|
|
+ else if (!strcmp(criteria.s, "size"))
|
|
+ (*sortcrit)[n].key = SORT_SIZE;
|
|
+ else if (!strcmp(criteria.s, "subject"))
|
|
+ (*sortcrit)[n].key = SORT_SUBJECT;
|
|
+ else if (!strcmp(criteria.s, "to"))
|
|
+ (*sortcrit)[n].key = SORT_TO;
|
|
+#if 0
|
|
+ else if (!strcmp(criteria.s, "annotation")) {
|
|
+ (*sortcrit)[n].key = SORT_ANNOTATION;
|
|
+ if (c != ' ') goto missingarg;
|
|
+ c = getstring(imapd_in, &arg);
|
|
+ if (c != ' ') goto missingarg;
|
|
+ (*sortcrit)[n].args.annot.entry = xstrdup(arg.s);
|
|
+ c = getstring(imapd_in, &arg);
|
|
+ if (c == EOF) goto missingarg;
|
|
+ (*sortcrit)[n].args.annot.attrib = xstrdup(arg.s);
|
|
+ }
|
|
+#endif
|
|
+ else {
|
|
+ prot_printf(imapd_out, "%s BAD Invalid Sort criterion %s\r\n",
|
|
+ tag, criteria.s);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+ }
|
|
+
|
|
+ n++;
|
|
+
|
|
+ nextcrit:
|
|
+ if (c == ' ') c = getword(imapd_in, &criteria);
|
|
+ else break;
|
|
+ }
|
|
+
|
|
+ if ((*sortcrit)[n].flags & SORT_REVERSE && !(*sortcrit)[n].key) {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing Sort criterion to reverse\r\n", tag);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+ }
|
|
+
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing close parenthesis in Sort\r\n", tag);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+ }
|
|
+
|
|
+ /* Terminate the list with the implicit sort criterion */
|
|
+ (*sortcrit)[n++].key = SORT_SEQUENCE;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ return c;
|
|
+
|
|
+ missingcrit:
|
|
+ prot_printf(imapd_out, "%s BAD Missing Sort criteria\r\n", tag);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+#if 0 /* For annotations stuff above */
|
|
+ missingarg:
|
|
+ prot_printf(imapd_out, "%s BAD Missing argument to Sort criterion %s\r\n",
|
|
+ tag, criteria.s);
|
|
+ if (c != EOF) prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+#endif
|
|
+}
|
|
+
|
|
+#ifdef ENABLE_LISTEXT
|
|
+/*
|
|
+ * Parse LIST options.
|
|
+ * The command has been parsed up to and including the opening '('.
|
|
+ */
|
|
+int getlistopts(char *tag, int *listopts)
|
|
+{
|
|
+ int c;
|
|
+ static struct buf arg;
|
|
+
|
|
+ *listopts = LIST_EXT;
|
|
+
|
|
+ for (;;) {
|
|
+ c = getword(imapd_in, &arg);
|
|
+ if (!arg.s[0]) break;
|
|
+
|
|
+ lcase(arg.s);
|
|
+ if (!strcmp(arg.s, "subscribed")) {
|
|
+ *listopts |= LIST_SUBSCRIBED;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "children")) {
|
|
+ *listopts |= LIST_CHILDREN;
|
|
+ }
|
|
+ else if (!strcmp(arg.s, "remote")) {
|
|
+ *listopts |= LIST_REMOTE;
|
|
+ }
|
|
+ else {
|
|
+ prot_printf(imapd_out, "%s BAD Invalid List option %s\r\n",
|
|
+ tag, arg.s);
|
|
+ return EOF;
|
|
+ }
|
|
+
|
|
+ if (c != ' ') break;
|
|
+ }
|
|
+
|
|
+ if (c != ')') {
|
|
+ prot_printf(imapd_out,
|
|
+ "%s BAD Missing close parenthesis in List\r\n", tag);
|
|
+ return EOF;
|
|
+ }
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ return c;
|
|
+}
|
|
+#endif /* ENABLE_LISTEXT */
|
|
+
|
|
+/*
|
|
+ * Parse a date_time, for the APPEND command
|
|
+ */
|
|
+int getdatetime(date)
|
|
+time_t *date;
|
|
+{
|
|
+ int c;
|
|
+ struct tm tm;
|
|
+ int old_format = 0;
|
|
+ char month[4], zone[4], *p;
|
|
+ time_t tmp_gmtime;
|
|
+ int zone_off;
|
|
+
|
|
+ memset(&tm, 0, sizeof tm);
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != '\"') goto baddate;
|
|
+
|
|
+ /* Day of month */
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c == ' ') c = '0';
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_mday = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (isdigit(c)) {
|
|
+ tm.tm_mday = tm.tm_mday * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if(tm.tm_mday <= 0 || tm.tm_mday > 31)
|
|
+ goto baddate;
|
|
+ }
|
|
+
|
|
+ if (c != '-') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ /* Month name */
|
|
+ if (!isalpha(c)) goto baddate;
|
|
+ month[0] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isalpha(c)) goto baddate;
|
|
+ month[1] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isalpha(c)) goto baddate;
|
|
+ month[2] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ month[3] = '\0';
|
|
+ lcase(month);
|
|
+
|
|
+ for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
|
|
+ if (!strcmp(month, monthname[tm.tm_mon])) break;
|
|
+ }
|
|
+ if (tm.tm_mon == 12) goto baddate;
|
|
+ /* xxx this doesn't quite work in leap years */
|
|
+ if (tm.tm_mday > max_monthdays[tm.tm_mon]) goto baddate;
|
|
+
|
|
+ if (c != '-') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ /* Year */
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_year = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_year = tm.tm_year * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (isdigit(c)) {
|
|
+ if (tm.tm_year < 19) goto baddate;
|
|
+ tm.tm_year -= 19;
|
|
+ tm.tm_year = tm.tm_year * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_year = tm.tm_year * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ }
|
|
+ else old_format++;
|
|
+
|
|
+ /* Hour */
|
|
+ if (c != ' ') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_hour = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_hour = tm.tm_hour * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (tm.tm_hour > 23) goto baddate;
|
|
+
|
|
+ /* Minute */
|
|
+ if (c != ':') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_min = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_min = tm.tm_min * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (tm.tm_min > 59) goto baddate;
|
|
+
|
|
+ /* Second */
|
|
+ if (c != ':') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_sec = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ tm.tm_sec = tm.tm_sec * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (tm.tm_min > 60) goto baddate;
|
|
+
|
|
+ /* Time zone */
|
|
+ if (old_format) {
|
|
+ if (c != '-') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ if (!isalpha(c)) goto baddate;
|
|
+ zone[0] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ if (c == '\"') {
|
|
+ /* Military (single-char) zones */
|
|
+ zone[1] = '\0';
|
|
+ lcase(zone);
|
|
+ if (zone[0] <= 'm') {
|
|
+ zone_off = (zone[0] - 'a' + 1)*60;
|
|
+ }
|
|
+ else if (zone[0] < 'z') {
|
|
+ zone_off = ('m' - zone[0])*60;
|
|
+ }
|
|
+ else zone_off = 0;
|
|
+ }
|
|
+ else {
|
|
+ /* UT (universal time) */
|
|
+ zone[1] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c == '\"') {
|
|
+ zone[2] = '\0';
|
|
+ lcase(zone);
|
|
+ if (!strcmp(zone, "ut")) goto baddate;
|
|
+ zone_off = 0;
|
|
+ }
|
|
+ else {
|
|
+ /* 3-char time zone */
|
|
+ zone[2] = c;
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != '\"') goto baddate;
|
|
+ zone[3] = '\0';
|
|
+ lcase(zone);
|
|
+ p = strchr("aecmpyhb", zone[0]);
|
|
+ if (c != '\"' || zone[2] != 't' || !p) goto baddate;
|
|
+ zone_off = (strlen(p) - 12)*60;
|
|
+ if (zone[1] == 'd') zone_off -= 60;
|
|
+ else if (zone[1] != 's') goto baddate;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ if (c != ' ') goto baddate;
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ if (c != '+' && c != '-') goto baddate;
|
|
+ zone[0] = c;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ zone_off = c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ zone_off = zone_off * 10 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ zone_off = zone_off * 6 + c - '0';
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (!isdigit(c)) goto baddate;
|
|
+ zone_off = zone_off * 10 + c - '0';
|
|
+
|
|
+ if (zone[0] == '-') zone_off = -zone_off;
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+ if (c != '\"') goto baddate;
|
|
+
|
|
+ }
|
|
+
|
|
+ c = prot_getc(imapd_in);
|
|
+
|
|
+ tm.tm_isdst = -1;
|
|
+
|
|
+ tmp_gmtime = mkgmtime(&tm);
|
|
+ if(tmp_gmtime == -1) goto baddate;
|
|
+
|
|
+ *date = tmp_gmtime - zone_off*60;
|
|
+
|
|
+ return c;
|
|
+
|
|
+ baddate:
|
|
+ prot_ungetc(c, imapd_in);
|
|
+ return EOF;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Print 's' as a quoted-string or literal (but not an atom)
|
|
+ */
|
|
+void
|
|
+printstring(s)
|
|
+const char *s;
|
|
+{
|
|
+ const char *p;
|
|
+ int len = 0;
|
|
+
|
|
+ /* Look for any non-QCHAR characters */
|
|
+ for (p = s; *p && len < 1024; p++) {
|
|
+ len++;
|
|
+ if (*p & 0x80 || *p == '\r' || *p == '\n'
|
|
+ || *p == '\"' || *p == '%' || *p == '\\') break;
|
|
+ }
|
|
+
|
|
+ /* if it's too long, literal it */
|
|
+ if (*p || len >= 1024) {
|
|
+ prot_printf(imapd_out, "{%u}\r\n%s", strlen(s), s);
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "\"%s\"", s);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Print 's' as an atom, quoted-string, or literal
|
|
+ */
|
|
+void
|
|
+printastring(s)
|
|
+const char *s;
|
|
+{
|
|
+ const char *p;
|
|
+ int len = 0;
|
|
+
|
|
+ if (imparse_isatom(s)) {
|
|
+ prot_printf(imapd_out, "%s", s);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Look for any non-QCHAR characters */
|
|
+ for (p = s; *p && len < 1024; p++) {
|
|
+ len++;
|
|
+ if (*p & 0x80 || *p == '\r' || *p == '\n'
|
|
+ || *p == '\"' || *p == '%' || *p == '\\') break;
|
|
+ }
|
|
+
|
|
+ /* if it's too long, literal it */
|
|
+ if (*p || len >= 1024) {
|
|
+ prot_printf(imapd_out, "{%u}\r\n%s", strlen(s), s);
|
|
+ } else {
|
|
+ prot_printf(imapd_out, "\"%s\"", s);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Append 'section', 'fields', 'trail' to the fieldlist 'l'.
|
|
+ */
|
|
+void
|
|
+appendfieldlist(struct fieldlist **l, char *section,
|
|
+ struct strlist *fields, char *trail,
|
|
+ void *d, size_t size)
|
|
+{
|
|
+ struct fieldlist **tail = l;
|
|
+
|
|
+ while (*tail) tail = &(*tail)->next;
|
|
+
|
|
+ *tail = (struct fieldlist *)xmalloc(sizeof(struct fieldlist));
|
|
+ (*tail)->section = xstrdup(section);
|
|
+ (*tail)->fields = fields;
|
|
+ (*tail)->trail = xstrdup(trail);
|
|
+ if(d && size) {
|
|
+ (*tail)->rock = xmalloc(size);
|
|
+ memcpy((*tail)->rock, d, size);
|
|
+ } else {
|
|
+ (*tail)->rock = NULL;
|
|
+ }
|
|
+ (*tail)->next = 0;
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * Free the fieldlist 'l'
|
|
+ */
|
|
+void freefieldlist(struct fieldlist *l)
|
|
+{
|
|
+ struct fieldlist *n;
|
|
+
|
|
+ while (l) {
|
|
+ n = l->next;
|
|
+ free(l->section);
|
|
+ freestrlist(l->fields);
|
|
+ free(l->trail);
|
|
+ if (l->rock) free(l->rock);
|
|
+ free((char *)l);
|
|
+ l = n;
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Append the searchargs 's1' and 's2' to the sublist of 's'
|
|
+ */
|
|
+void
|
|
+appendsearchargs(s, s1, s2)
|
|
+struct searchargs *s, *s1, *s2;
|
|
+{
|
|
+ struct searchsub **tail = &s->sublist;
|
|
+
|
|
+ while (*tail) tail = &(*tail)->next;
|
|
+
|
|
+ *tail = (struct searchsub *)xmalloc(sizeof(struct searchsub));
|
|
+ (*tail)->sub1 = s1;
|
|
+ (*tail)->sub2 = s2;
|
|
+ (*tail)->next = 0;
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * Free the searchargs 's'
|
|
+ */
|
|
+void
|
|
+freesearchargs(s)
|
|
+struct searchargs *s;
|
|
+{
|
|
+ struct searchsub *sub, *n;
|
|
+
|
|
+ if (!s) return;
|
|
+
|
|
+ freestrlist(s->sequence);
|
|
+ freestrlist(s->uidsequence);
|
|
+ freestrlist(s->from);
|
|
+ freestrlist(s->to);
|
|
+ freestrlist(s->cc);
|
|
+ freestrlist(s->bcc);
|
|
+ freestrlist(s->subject);
|
|
+ freestrlist(s->body);
|
|
+ freestrlist(s->text);
|
|
+ freestrlist(s->header_name);
|
|
+ freestrlist(s->header);
|
|
+
|
|
+ for (sub = s->sublist; sub; sub = n) {
|
|
+ n = sub->next;
|
|
+ freesearchargs(sub->sub1);
|
|
+ freesearchargs(sub->sub2);
|
|
+ free(sub);
|
|
+ }
|
|
+ free(s);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Free an array of sortcrit
|
|
+ */
|
|
+static void freesortcrit(struct sortcrit *s)
|
|
+{
|
|
+ int i = 0;
|
|
+
|
|
+ if (!s) return;
|
|
+ do {
|
|
+ switch (s[i].key) {
|
|
+ case SORT_ANNOTATION:
|
|
+ free(s[i].args.annot.entry);
|
|
+ free(s[i].args.annot.attrib);
|
|
+ break;
|
|
+ }
|
|
+ i++;
|
|
+ } while (s[i].key != SORT_SEQUENCE);
|
|
+ free(s);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Issue a MAILBOX untagged response
|
|
+ */
|
|
+static int mailboxdata(char *name,
|
|
+ int matchlen __attribute__((unused)),
|
|
+ int maycreate __attribute__((unused)),
|
|
+ void *rock __attribute__((unused)))
|
|
+{
|
|
+ char mboxname[MAX_MAILBOX_PATH+1];
|
|
+
|
|
+ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, name,
|
|
+ imapd_userid, mboxname);
|
|
+ prot_printf(imapd_out, "* MAILBOX %s\r\n", mboxname);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Issue a LIST or LSUB untagged response
|
|
+ */
|
|
+static void mstringdata(char *cmd, char *name, int matchlen, int maycreate,
|
|
+ int listopts)
|
|
+{
|
|
+ static char lastname[MAX_MAILBOX_PATH+1];
|
|
+ static int lastnamedelayed = 0;
|
|
+ static int lastnamenoinferiors = 0;
|
|
+ static int nonexistent = 0;
|
|
+ static int sawuser = 0;
|
|
+ int lastnamehassub = 0;
|
|
+ int c, mbtype;
|
|
+ char mboxname[MAX_MAILBOX_PATH+1];
|
|
+
|
|
+ /* We have to reset the sawuser flag before each list command.
|
|
+ * Handle it as a dirty hack.
|
|
+ */
|
|
+ if (cmd == NULL) {
|
|
+ sawuser = 0;
|
|
+ mstringdatacalls = 0;
|
|
+ return;
|
|
+ }
|
|
+ mstringdatacalls++;
|
|
+
|
|
+ if (lastnamedelayed) {
|
|
+ /* Check if lastname has children */
|
|
+ if (name && strncmp(lastname, name, strlen(lastname)) == 0 &&
|
|
+ name[strlen(lastname)] == '.') {
|
|
+ lastnamehassub = 1;
|
|
+ }
|
|
+ prot_printf(imapd_out, "* %s (", cmd);
|
|
+ if (nonexistent == IMAP_MAILBOX_RESERVED) {
|
|
+ /* LISTEXT wants \\PlaceHolder instead of \\Noselect */
|
|
+ if (listopts & LIST_EXT)
|
|
+ prot_printf(imapd_out, "\\PlaceHolder");
|
|
+ else
|
|
+ prot_printf(imapd_out, "\\Noselect");
|
|
+ } else if (nonexistent) {
|
|
+ prot_printf(imapd_out, "\\NonExistent");
|
|
+ }
|
|
+ if (lastnamenoinferiors) {
|
|
+ prot_printf(imapd_out, "%s\\Noinferiors", nonexistent ? " " : "");
|
|
+ }
|
|
+ else if ((listopts & LIST_CHILDREN) &&
|
|
+ /* we can't determine \HasNoChildren for subscriptions */
|
|
+ (lastnamehassub ||
|
|
+ !(listopts & (LIST_LSUB | LIST_SUBSCRIBED)))) {
|
|
+ prot_printf(imapd_out, "%s%s", nonexistent ? " " : "",
|
|
+ lastnamehassub ? "\\HasChildren" : "\\HasNoChildren");
|
|
+ }
|
|
+ prot_printf(imapd_out, ") \"%c\" ", imapd_namespace.hier_sep);
|
|
+
|
|
+ (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, lastname,
|
|
+ imapd_userid, mboxname);
|
|
+ printstring(mboxname);
|
|
+ prot_printf(imapd_out, "\r\n");
|
|
+ lastnamedelayed = lastnamenoinferiors = nonexistent = 0;
|
|
+ }
|
|
+
|
|
+ /* Special-case to flush any final state */
|
|
+ if (!name) {
|
|
+ lastname[0] = '\0';
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Suppress any output of a partial match */
|
|
+ if ((name[matchlen]
|
|
+ && strncmp(lastname, name, matchlen) == 0
|
|
+ && (lastname[matchlen] == '\0' || lastname[matchlen] == '.'))) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * We can get a partial match for "user" multiple times with
|
|
+ * other matches inbetween. Handle it as a special case
|
|
+ */
|
|
+ if (matchlen == 4 && strncasecmp(name, "user", 4) == 0) {
|
|
+ if (sawuser) return;
|
|
+ sawuser = 1;
|
|
+ }
|
|
+
|
|
+ strlcpy(lastname, name, sizeof(lastname));
|
|
+ lastname[matchlen] = '\0';
|
|
+ nonexistent = 0;
|
|
+
|
|
+ /* Now we need to see if this mailbox exists */
|
|
+ /* first convert "INBOX" to "user.<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$
|
|
+.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
|
|
+
|