Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
339 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/imap/ctl_cyrusdb.c b/imap/ctl_cyrusdb.c
index c4d9079e9..5cb9f1123 100644
--- a/imap/ctl_cyrusdb.c
+++ b/imap/ctl_cyrusdb.c
@@ -1,361 +1,354 @@
/* ctl_cyrusdb.c -- Program to perform operations common to all cyrus DBs
*
* Copyright (c) 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: ctl_cyrusdb.c,v 1.13 2002/05/23 20:01:05 rjs3 Exp $
+ * $Id: ctl_cyrusdb.c,v 1.14 2002/05/29 16:49:14 rjs3 Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <com_err.h>
#include <errno.h>
#include <time.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "util.h"
#include "imapconf.h"
#include "exitcodes.h"
#include "xmalloc.h"
#include "cyrusdb.h"
/* find out what things are using... */
#include "mboxlist.h"
#include "seen.h"
#include "duplicate.h"
#include "tls.h"
#define N(a) (sizeof(a) / sizeof(a[0]))
struct cyrusdb {
const char *name;
struct cyrusdb_backend *env;
int archive;
} dblist[] = {
{ FNAME_MBOXLIST, CONFIG_DB_MBOX, 1 },
{ FNAME_DELIVERDB, CONFIG_DB_DUPLICATE, 0 },
{ FNAME_TLSSESSIONS, CONFIG_DB_TLS, 0 },
{ NULL, NULL, 0 }
};
static int compdb(const void *v1, const void *v2)
{
struct cyrusdb *db1 = (struct cyrusdb *) v1;
struct cyrusdb *db2 = (struct cyrusdb *) v2;
return (db1->env - db2->env);
}
void fatal(const char *message, int code)
{
fprintf(stderr, "fatal error: %s\n", message);
exit(code);
}
void usage(void)
{
fprintf(stderr, "ctl_cyrusdb [-C <altconfig>] -c\n");
fprintf(stderr, "ctl_cyrusdb [-C <altconfig>] -r [-x]\n");
exit(-1);
}
/* Callback for use by recover_reserved */
static int fixmbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock __attribute__((unused)))
{
int mbtype;
int r;
char *path, *part, *acl;
/* Do an mboxlist_detail on the mailbox */
r = mboxlist_detail(name, &mbtype, &path, &part, &acl, NULL);
/* if it is MBTYPE_RESERVED, unset it & call mboxlist_delete */
if(!r && (mbtype & MBTYPE_RESERVE)) {
if(!r) {
- r = mboxlist_update(name, (mbtype & ~MBTYPE_RESERVE), part, acl);
+ r = mboxlist_deletemailbox(name, 1, NULL, NULL, 0, 0, 1);
if(r) {
- syslog(LOG_ERR, "could not unreserve mailbox '%s': %s",
+ /* log the error */
+ syslog(LOG_ERR,
+ "could not remove reserved mailbox '%s': %s",
name, error_message(r));
} else {
- r = mboxlist_deletemailbox(name, 1, NULL, NULL, 0, 0);
- if(r) {
- /* put it back, log the error */
- mboxlist_update(name, mbtype, part, acl);
- syslog(LOG_ERR,
- "could not remove reserved mailbox '%s': %s",
- name, error_message(r));
- } else {
- syslog(LOG_ERR,
- "removed reserved mailbox '%s'",
- name);
- }
+ syslog(LOG_ERR,
+ "removed reserved mailbox '%s'",
+ name);
}
}
}
return 0;
}
void recover_reserved()
{
char pattern[2] = { '*', '\0' };
mboxlist_init(0);
mboxlist_open(NULL);
/* build a list of mailboxes - we're using internal names here */
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, fixmbox, NULL);
mboxlist_close();
mboxlist_done();
}
int main(int argc, char *argv[])
{
extern char *optarg;
int opt, r, r2;
char *alt_config = NULL;
int flag = 0;
int reserve_flag = 1;
enum { RECOVER, CHECKPOINT, NONE } op = NONE;
char dirname[1024], backup1[1024], backup2[1024];
char *archive_files[N(dblist)];
char *msg = "";
int i, j, rotated = 0;
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
r = r2 = 0;
while ((opt = getopt(argc, argv, "C:rxc")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'r':
flag |= CYRUSDB_RECOVER;
msg = "recovering cyrus databases";
if (op == NONE) op = RECOVER;
else usage();
break;
case 'c':
msg = "checkpointing cyrus databases";
if (op == NONE) op = CHECKPOINT;
else usage();
break;
case 'x':
reserve_flag = 0;
break;
default:
usage();
break;
}
}
if (op == NONE) {
usage();
exit(1);
}
if(op != RECOVER && !reserve_flag) {
usage();
exit(1);
}
config_init(alt_config, "ctl_cyrusdb");
/* create the name of the db directory */
strcpy(dirname, config_dir);
strcat(dirname, FNAME_DBDIR);
/* create the names of the backup directories */
strcpy(backup1, dirname);
strcat(backup1, ".backup1/");
strcpy(backup2, dirname);
strcat(backup2, ".backup2/");
syslog(LOG_NOTICE, "%s", msg);
/* sort dbenvs */
qsort(dblist, N(dblist)-1, sizeof(struct cyrusdb), &compdb);
memset(archive_files, 0, N(dblist) * sizeof(char*));
for (i = 0, j = 0; dblist[i].name != NULL; i++) {
/* if we need to archive this db, add it to the list */
if (dblist[i].archive) {
archive_files[j] = (char*) xmalloc(strlen(config_dir) +
strlen(dblist[i].name) + 1);
strcpy(archive_files[j], config_dir);
strcat(archive_files[j++], dblist[i].name);
}
/* deal with each dbenv once */
if (dblist[i].env == dblist[i+1].env) continue;
r = (dblist[i].env)->init(dirname, flag);
if (r) {
syslog(LOG_ERR, "DBERROR: init %s: %s", dirname,
cyrusdb_strerror(r));
fprintf(stderr,
"ctl_cyrusdb: unable to init environment\n");
dblist[i].env = NULL;
/* stop here, but we need to close all existing ones */
break;
}
r2 = 0;
switch (op) {
case RECOVER:
break;
case CHECKPOINT:
r2 = (dblist[i].env)->sync();
if (r2) {
syslog(LOG_ERR, "DBERROR: sync %s: %s", dirname,
cyrusdb_strerror(r));
fprintf(stderr,
"ctl_cyrusdb: unable to sync environment\n");
}
/* ARCHIVE */
r2 = 0;
if (!rotated) {
/* rotate the backup directories -- ONE time only */
char *tail;
DIR *dirp;
struct dirent *dirent;
tail = backup2 + strlen(backup2);
/* remove db.backup2 */
dirp = opendir(backup2);
if (dirp) {
while ((dirent = readdir(dirp)) != NULL) {
if (dirent->d_name[0] == '.') continue;
strcpy(tail, dirent->d_name);
unlink(backup2);
}
closedir(dirp);
}
*tail = '\0';
r2 = rmdir(backup2);
/* move db.backup1 to db.backup2 */
if (r2 == 0 || errno == ENOENT)
r2 = rename(backup1, backup2);
/* make a new db.backup1 */
if (r2 == 0 || errno == ENOENT)
r2 = mkdir(backup1, 0755);
rotated = 1;
}
/* do the archive */
if (r2 == 0)
r2 = (dblist[i].env)->archive((const char**) archive_files,
backup1);
if (r2) {
syslog(LOG_ERR, "DBERROR: archive %s: %s", dirname,
cyrusdb_strerror(r));
fprintf(stderr,
"ctl_cyrusdb: unable to archive environment\n");
}
break;
default:
break;
}
/* free the archive_list */
while (j > 0) {
free(archive_files[--j]);
archive_files[j] = NULL;
}
r2 = (dblist[i].env)->done();
if (r2) {
syslog(LOG_ERR, "DBERROR: done: %s", cyrusdb_strerror(r));
}
}
if(op == RECOVER && reserve_flag)
recover_reserved();
syslog(LOG_NOTICE, "done %s", msg);
exit(r || r2);
}
diff --git a/imap/ctl_mboxlist.c b/imap/ctl_mboxlist.c
index d22502484..85d417fc5 100644
--- a/imap/ctl_mboxlist.c
+++ b/imap/ctl_mboxlist.c
@@ -1,695 +1,695 @@
/* ctl_mboxlist.c -- do DB related operations on mboxlist
*
* Copyright (c) 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: ctl_mboxlist.c,v 1.34 2002/05/13 20:32:03 rjs3 Exp $ */
+/* $Id: ctl_mboxlist.c,v 1.35 2002/05/29 16:49:14 rjs3 Exp $ */
/* currently doesn't catch signals; probably SHOULD */
#include <config.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <syslog.h>
#include <com_err.h>
#include <stdlib.h>
#include <string.h>
#include <sasl/sasl.h>
#include "exitcodes.h"
#include "mboxlist.h"
#include "imapconf.h"
#include "assert.h"
#include "xmalloc.h"
#include "imap_err.h"
#include "mupdate-client.h"
extern int optind;
extern char *optarg;
extern int errno;
enum mboxop { DUMP, M_POPULATE, RECOVER, CHECKPOINT, UNDUMP, NONE };
void fatal(const char *message, int code)
{
fprintf(stderr, "fatal error: %s\n", message);
exit(code);
}
struct dumprock {
enum mboxop op;
mupdate_handle *h;
};
static int dump_p(void *rockp __attribute__((unused)),
const char *key __attribute__((unused)),
int keylen __attribute__((unused)),
const char *data __attribute__((unused)),
int datalen __attribute__((unused)))
{
return 1;
}
struct mb_node
{
char mailbox[MAX_MAILBOX_NAME];
char server[MAX_MAILBOX_NAME];
char *acl;
struct mb_node *next;
};
static struct mb_node *act_head = NULL, **act_tail = &act_head;
static struct mb_node *del_head = NULL;
static struct mb_node *wipe_head = NULL, *unflag_head = NULL;
/* assume the local copy is authoritative and that it should just overwrite
* mupdate */
static int local_authoritative = 0;
static int warn_only = 0;
/* For each mailbox that this guy gets called for, check that
* it is a mailbox that:
* a) mupdate server thinks *we* host
* -> Because we were called, this is the case, provided we
* -> gave the prefix parameter to the remote.
* b) we do not actually host
*
* if that's the case, enqueue a delete
* otherwise, we both agree that it exists, but we still need
* to verify that its info is up to date.
*/
static int mupdate_list_cb(struct mupdate_mailboxdata *mdata,
const char *cmd, void *context)
{
int ret;
/* the server thinks we have it, do we think we have it? */
ret = mboxlist_lookup(mdata->mailbox, NULL, NULL, NULL);
if(ret) {
struct mb_node *next;
next = xzmalloc(sizeof(struct mb_node));
strcpy(next->mailbox, mdata->mailbox);
next->next = del_head;
del_head = next;
} else {
/* we both agree that it exists */
/* throw it onto the back of the activate queue */
/* we may or may not need to send an update */
struct mb_node *next;
next = xzmalloc(sizeof(struct mb_node));
strcpy(next->mailbox, mdata->mailbox);
strcpy(next->server, mdata->server);
if(!strncmp(cmd, "MAILBOX", 7))
next->acl = xstrdup(mdata->acl);
*act_tail = next;
act_tail = &(next->next);
}
return 0;
}
static int dump_cb(void *rockp,
const char *key, int keylen,
const char *data, int datalen)
{
struct dumprock *d = (struct dumprock *) rockp;
int r;
char *p;
char *name, *part, *acl;
int mbtype;
/* \0 terminate 'name' */
name = xstrndup(key, keylen);
/* Get mailbox type */
mbtype = strtol(data, &p, 10);
p = strchr(data, ' ');
if (p == NULL) {
abort();
}
p++;
acl = strchr(p, ' ');
if (acl == NULL) {
abort();
}
/* grab 'part', \0 terminate */
part = xstrndup(p, acl - p);
/* \0 terminate 'acl' */
p = acl + 1;
acl = xstrndup(p, datalen - (p - data));
switch (d->op) {
case DUMP:
printf("%s\t%s\t%s\n", name, part, acl);
break;
case M_POPULATE:
{
char *realpart = xmalloc(strlen(config_servername) + 1
+ strlen(part) + 1);
int skip_flag;
/* If it is marked MBTYPE_MOVING, and it DOES match the entry,
* we need to unmark it. If it does not match the entry in our
* list, then we assume that it successfully made the move and
* we delete it from the local disk */
/* realpart is 'hostname!partition' */
sprintf(realpart, "%s!%s", config_servername, part);
/* If they match, then we should check that we actually need
* to update it. If they *don't* match, then we believe that we
* need to send fresh data. There will be no point at which something
* is in the act_head list that we do not have locally, because that
* is a condition of being in the act_head list */
if(act_head && !strcmp(name, act_head->mailbox)) {
struct mb_node *tmp;
/* If this mailbox was moving, we want to unmark the movingness,
* since the MUPDATE server agreed that it lives here. */
/* (and later also force an mupdate push) */
if(mbtype & MBTYPE_MOVING) {
struct mb_node *next;
if(warn_only) {
printf("Remove remote flag on: %s\n", name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strcpy(next->mailbox, name);
next->next = unflag_head;
unflag_head = next;
}
/* No need to update mupdate NOW, we'll get it when we
* untag the mailbox */
skip_flag = 1;
} else if(act_head->acl &&
!strcmp(realpart, act_head->server) &&
!strcmp(acl, act_head->acl)) {
/* Do not update if location does match, and there is an acl,
* and the acl matches */
skip_flag = 1;
} else {
skip_flag = 0;
}
/* in any case, free the node. */
if(act_head->acl) free(act_head->acl);
tmp = act_head;
act_head = act_head->next;
free(tmp);
} else {
/* if they do not match, do an explicit MUPDATE find on the
* mailbox, and if it is living somewhere else, delete the local
* data, if it is NOT living somewhere else, recreate it in
* mupdate */
struct mupdate_mailboxdata *unused_mbdata;
/* if this is okay, we found it (so it is on another host, since
* it wasn't in our list in this position) */
if(!local_authoritative &&
!mupdate_find(d->h, name, &unused_mbdata)) {
/* since it lives on another server, schedule it for a wipe */
struct mb_node *next;
if(warn_only) {
printf("Remove Local Mailbox: %s\n", name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strcpy(next->mailbox, name);
next->next = wipe_head;
wipe_head = next;
}
skip_flag = 1;
} else {
/* Check that it isn't flagged moving */
if(mbtype & MBTYPE_MOVING) {
/* it's flagged moving, we'll fix it later (and
* push it then too) */
struct mb_node *next;
if(warn_only) {
printf("Remove remote flag on: %s\n", name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strcpy(next->mailbox, name);
next->next = unflag_head;
unflag_head = next;
}
/* No need to update mupdate now, we'll get it when we
* untag the mailbox */
skip_flag = 1;
} else {
/* we should just push the change to mupdate now */
skip_flag = 0;
}
}
}
if(skip_flag) {
free(realpart);
break;
}
if(warn_only) {
printf("Force Activate: %s\n", name);
free(realpart);
break;
}
r = mupdate_activate(d->h,name,realpart,acl);
free(realpart);
if(r == MUPDATE_NOCONN) {
fprintf(stderr, "permanant failure storing '%s'\n", name);
return IMAP_IOERROR;
} else if (r == MUPDATE_FAIL) {
fprintf(stderr,
"temporary failure storing '%s' (update continuing)",
name);
}
break;
}
default: /* yikes ! */
abort();
break;
}
free(name);
free(part);
free(acl);
return 0;
}
/* Resyncing with mupdate:
*
* If it is local and not present on mupdate at all, push to mupdate.
* If it is local and present on mupdate for another host, delete local mailbox
* If it is local and present on mupdate but with incorrect partition/acl,
* update mupdate.
* If it is not local and present on mupdate for this host, delete it from
* mupdate.
*/
void do_dump(enum mboxop op)
{
struct dumprock d;
int ret;
char buf[8192];
assert(op == DUMP || op == M_POPULATE);
d.op = op;
if(op == M_POPULATE) {
sasl_client_init(NULL);
ret = mupdate_connect(NULL, NULL, &(d.h), NULL);
if(ret) {
fprintf(stderr, "couldn't connect to mupdate server\n");
exit(1);
}
/* now we need a list of what the remote thinks we have
* To generate it, ask for a prefix of '<our hostname>!',
* (to ensure we get exactly our hostname) */
snprintf(buf, sizeof(buf), "%s!", config_servername);
ret = mupdate_list(d.h, mupdate_list_cb, buf, NULL);
if(ret) {
fprintf(stderr, "couldn't do LIST command on mupdate server\n");
exit(1);
}
/* Run pending mupdate deletes */
while(del_head) {
struct mb_node *me = del_head;
del_head = del_head->next;
if(warn_only) {
printf("Remove from MUPDATE: %s\n", me->mailbox);
} else {
ret = mupdate_delete(d.h, me->mailbox);
if(ret) {
fprintf(stderr,
"couldn't mupdate delete %s\n", me->mailbox);
exit(1);
}
}
free(me);
}
}
/* Dump Database */
CONFIG_DB_MBOX->foreach(mbdb, "", 0, &dump_p, &dump_cb, &d, NULL);
if(op == M_POPULATE) {
/* Remove MBTYPE_MOVING flags (unflag_head) */
while(unflag_head) {
struct mb_node *me = unflag_head;
int type;
char *part, *acl, *newpart;
unflag_head = unflag_head->next;
ret = mboxlist_detail(me->mailbox, &type, NULL, &part, &acl, NULL);
if(ret) {
fprintf(stderr,
"couldn't perform lookup to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
/* Reset the partition! */
newpart = strchr(part, '!');
if(!newpart) newpart = part;
else newpart++;
ret = mboxlist_update(me->mailbox, type & ~MBTYPE_MOVING,
newpart, acl);
if(ret) {
fprintf(stderr,
"couldn't perform update to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
/* force a push to mupdate */
snprintf(buf, sizeof(buf), "%s!%s", config_servername, part);
ret = mupdate_activate(d.h, me->mailbox, buf, acl);
if(ret) {
fprintf(stderr,
"couldn't perform mupdatepush to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
free(me);
}
/* Delete local mailboxes where needed (wipe_head) */
while(wipe_head) {
struct mb_node *me = wipe_head;
wipe_head = wipe_head->next;
- ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, 0, 1);
+ ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, 0, 1, 1);
if(ret) {
fprintf(stderr, "couldn't delete defunct mailbox %s\n",
me->mailbox);
exit(1);
}
free(me);
}
/* Done with mupdate */
mupdate_disconnect(&(d.h));
sasl_done();
}
return;
}
void do_undump(void)
{
int r = 0;
char buf[16384];
int line = 0;
char last_commit[MAX_MAILBOX_NAME];
char *key=NULL, *data=NULL;
int keylen, datalen;
const int PER_COMMIT = 1000;
int untilCommit = PER_COMMIT;
struct txn *tid = NULL;
last_commit[0] = '\0';
while (fgets(buf, sizeof(buf), stdin)) {
char *name, *partition, *acl;
char *p;
int tries = 0;
line++;
name = buf;
for (p = buf; *p && *p != '\t'; p++) ;
if (!*p) {
fprintf(stderr, "line %d: no partition found\n", line);
continue;
}
*p++ = '\0';
partition = p;
for (; *p && *p != '\t'; p++) ;
if (!*p) {
fprintf(stderr, "line %d: no acl found\n", line);
continue;
}
*p++ = '\0';
acl = p;
/* chop off the newline */
for (; *p && *p != '\r' && *p != '\n'; p++) ;
*p++ = '\0';
if (strlen(name) >= MAX_MAILBOX_NAME) {
fprintf(stderr, "line %d: mailbox name too long\n", line);
continue;
}
if (strlen(partition) >= MAX_PARTITION_LEN) {
fprintf(stderr, "line %d: partition name too long\n", line);
continue;
}
key = name; keylen = strlen(key);
data = mboxlist_makeentry(0, partition, acl); datalen = strlen(data);
tries = 0;
retry:
r = CONFIG_DB_MBOX->store(mbdb, key, keylen, data, datalen, &tid);
switch (r) {
case 0:
break;
case CYRUSDB_AGAIN:
if (tries++ < 5) {
fprintf(stderr, "warning: DB_LOCK_DEADLOCK; retrying\n");
goto retry;
}
fprintf(stderr, "error: too many deadlocks, aborting\n");
break;
default:
r = IMAP_IOERROR;
break;
}
free(data);
if(--untilCommit == 0) {
/* commit */
r = CONFIG_DB_MBOX->commit(mbdb, tid);
if(r) break;
tid = NULL;
untilCommit = PER_COMMIT;
strncpy(last_commit,key,MAX_MAILBOX_NAME);
}
if (r) break;
}
if(!r && tid) {
/* commit the last transaction */
r=CONFIG_DB_MBOX->commit(mbdb, tid);
}
if (r) {
if(tid) CONFIG_DB_MBOX->abort(mbdb, tid);
fprintf(stderr, "db error: %s\n", cyrusdb_strerror(r));
if(key) fprintf(stderr, "was processing mailbox: %s\n", key);
if(last_commit[0]) fprintf(stderr, "last commit was at: %s\n",
last_commit);
else fprintf(stderr, "no commits\n");
}
return;
}
void usage(void)
{
fprintf(stderr, "DUMP:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -d [-f filename]\n");
fprintf(stderr, "UNDUMP:\n");
fprintf(stderr,
" ctl_mboxlist [-C <alt_config>] -u [-f filename]"
" [< mboxlist.dump]\n");
fprintf(stderr, "MUPDATE populate:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -m [-a] [-w] [-f filename]\n");
exit(1);
}
int main(int argc, char *argv[])
{
char *mboxdb_fname = NULL;
int opt;
enum mboxop op = NONE;
char *alt_config = NULL;
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
while ((opt = getopt(argc, argv, "C:awmdurcf:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'r':
/* deprecated, but we still support it */
fprintf(stderr, "ctl_mboxlist -r is deprecated: "
"use ctl_cyrusdb -r instead\b");
syslog(LOG_WARNING, "ctl_mboxlist -r is deprecated: "
"use ctl_cyrusdb -r instead\b");
if (op == NONE) op = RECOVER;
else usage();
break;
case 'c':
/* deprecated, but we still support it */
fprintf(stderr, "ctl_mboxlist -c is deprecated: "
"use ctl_cyrusdb -c instead\b");
syslog(LOG_WARNING, "ctl_mboxlist -c is deprecated: "
"use ctl_cyrusdb -c instead\b");
if (op == NONE) op = CHECKPOINT;
else usage();
break;
case 'f':
if (!mboxdb_fname) {
mboxdb_fname = optarg;
} else {
usage();
}
break;
case 'd':
if (op == NONE) op = DUMP;
else usage();
break;
case 'u':
if (op == NONE) op = UNDUMP;
else usage();
break;
case 'm':
if (op == NONE) op = M_POPULATE;
else usage();
break;
case 'a':
local_authoritative = 1;
break;
case 'w':
warn_only = 1;
break;
default:
usage();
break;
}
}
if(op != M_POPULATE && (local_authoritative || warn_only)) usage();
config_init(alt_config, "ctl_mboxlist");
switch (op) {
case RECOVER:
syslog(LOG_NOTICE, "running mboxlist recovery");
mboxlist_init(MBOXLIST_RECOVER);
mboxlist_done();
syslog(LOG_NOTICE, "done running mboxlist recovery");
return 0;
case CHECKPOINT:
syslog(LOG_NOTICE, "checkpointing mboxlist");
mboxlist_init(MBOXLIST_SYNC);
mboxlist_done();
return 0;
case DUMP:
case M_POPULATE:
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
do_dump(op);
mboxlist_close();
mboxlist_done();
return 0;
case UNDUMP:
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
do_undump();
mboxlist_close();
mboxlist_done();
return 0;
default:
usage();
return 1;
}
return 0;
}
diff --git a/imap/imapd.c b/imap/imapd.c
index f76317446..02c2b3732 100644
--- a/imap/imapd.c
+++ b/imap/imapd.c
@@ -1,7368 +1,7369 @@
/*
* 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: imapd.c,v 1.392 2002/05/24 18:05:14 rjs3 Exp $ */
+/* $Id: imapd.c,v 1.393 2002/05/29 16:49:14 rjs3 Exp $ */
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <syslog.h>
#include <com_err.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 "imapconf.h"
#include "imap_err.h"
#include "imapd.h"
#include "imapurl.h"
#include "imparse.h"
#include "iptostring.h"
#include "mailbox.h"
#include "mboxname.h"
#include "mboxlist.h"
#include "mbdump.h"
#include "mkgmtime.h"
#include "mupdate-client.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;
extern int errno;
/* global state */
static char shutdownfilename[1024];
static int imaps = 0;
static sasl_ssf_t extprops_ssf = 0;
/* per-user/session state */
struct protstream *imapd_out = NULL;
struct protstream *imapd_in = NULL;
static char imapd_clienthost[250] = "[local]";
static int imapd_logfd = -1;
char *imapd_userid;
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? */
#ifdef HAVE_SSL
/* our tls connection, if any */
static SSL *tls_conn = NULL;
#endif /* HAVE_SSL */
/* current sub-user state */
static struct mailbox mboxstruct;
static struct mailbox *imapd_mailbox;
int imapd_exists;
/* current namespace */
static struct namespace imapd_namespace;
static const char *monthname[] = {
"jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec"
};
void shutdown_file(int fd);
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);
void cmd_noop(char *tag, char *cmd);
void cmd_capability(char *tag);
void cmd_append(char *tag, char *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(char *tag, char *msgno, char *data,
char *start, 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);
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(const 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 subscribed, char *reference, char *pattern);
void cmd_changesub(char *tag, char *namespace, char *name, int add);
void cmd_getacl(char *tag, char *name, int oldform);
void cmd_listrights(char *tag, char *name, char *identifier);
void cmd_myrights(char *tag, char *name, int oldform);
void cmd_setacl(char *tag, char *name, char *identifier, char *rights);
void cmd_getquota(char *tag, char *name);
void cmd_getquotaroot(char *tag, char *name);
void cmd_setquota(char *tag, char *quotaroot);
void cmd_status(char *tag, char *name);
void cmd_getuids(char *tag, char *startuid);
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 ENABLE_X_NETSCAPE_HACK
void cmd_netscrape(char* tag);
#endif
#ifdef ENABLE_ANNOTATEMORE
void cmd_getannotation(char* tag);
void cmd_setannotation(char* tag);
int getannotatefetchdata(char *tag,
struct strlist **entries, struct strlist **attribs);
int getannotatestoredata(char *tag, struct entryattlist **entryatts);
void annotate_response(struct entryattlist *l);
#endif /* ENABLE_ANNOTATEMORE */
#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 appendstrlist(struct strlist **l, char *s);
void appendstrlistpat(struct strlist **l, char *s);
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);
void appendattvalue(struct attvaluelist **l, char *attrib, char *value);
void freeattvalues(struct attvaluelist *l);
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);
static int hash_simple (const char *str);
/* 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};
/*
* acl_ok() checks to see if the the inbox for 'user' grants the 'a'
* right to 'authstate'. Returns 1 if so, 0 if not.
*/
/* Note that we do not determine if the mailbox is remote or not */
static int acl_ok(const char *user,
struct auth_state *authstate)
{
char *acl;
char inboxname[1024];
int r;
if (strchr(user, '.') || strlen(user)+6 >= sizeof(inboxname)) return 0;
strcpy(inboxname, "user.");
strcat(inboxname, user);
if (!authstate ||
mboxlist_lookup(inboxname, NULL, &acl, NULL)) {
r = 0; /* Failed so assume no proxy access */
}
else {
r = (cyrus_acl_myrights(authstate, acl) & ACL_ADMIN) != 0;
}
return r;
}
/* should we allow users to proxy? return SASL_OK if yes,
SASL_BADAUTH otherwise */
static int mysasl_authproc(sasl_conn_t *conn,
void *context __attribute__((unused)),
const char *requested_user, unsigned rlen,
const char *auth_identity, unsigned alen,
const char *def_realm __attribute__((unused)),
unsigned urlen __attribute__((unused)),
struct propctx *propctx __attribute__((unused)))
{
const char *val;
char *realm;
/* check if remote realm */
if ((realm = strchr(auth_identity, '@'))!=NULL) {
realm++;
val = config_getstring("loginrealms", "");
while (*val) {
if (!strncasecmp(val, realm, strlen(realm)) &&
(!val[strlen(realm)] || isspace((int) val[strlen(realm)]))) {
break;
}
/* not this realm, try next one */
while (*val && !isspace((int) *val)) val++;
while (*val && isspace((int) *val)) val++;
}
if (!*val) {
sasl_seterror(conn, 0, "cross-realm login %s denied",
auth_identity);
return SASL_BADAUTH;
}
}
imapd_authstate = auth_newstate(auth_identity, NULL);
/* ok, is auth_identity an admin? */
imapd_userisadmin = authisa(imapd_authstate, "imap", "admins");
if (alen != rlen || strncmp(auth_identity, requested_user, alen)) {
/* we want to authenticate as a different user; we'll allow this
if we're an admin or if we've allowed ACL proxy logins */
int use_acl = config_getswitch("loginuseacl", 0);
if (imapd_userisadmin ||
(use_acl && acl_ok(requested_user, imapd_authstate)) ||
authisa(imapd_authstate, "imap", "proxyservers")) {
/* proxy ok! */
imapd_userisadmin = 0; /* no longer admin */
auth_freestate(imapd_authstate);
imapd_authstate = auth_newstate(requested_user, NULL);
/* are we a proxy admin? */
imapd_userisproxyadmin =
authisa(imapd_authstate, "imap", "admins");
} else {
sasl_seterror(conn, 0, "user %s is not allowed to proxy",
auth_identity);
auth_freestate(imapd_authstate);
return SASL_BADAUTH;
}
}
return SASL_OK;
}
static const struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, &mysasl_authproc, NULL },
{ SASL_CB_CANON_USER, &mysasl_canon_user, NULL },
{ 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)
{
char url[MAX_MAILBOX_PATH];
if(!strcmp(imapd_userid, "anonymous")) {
imapurl_toURL(url, server, mailbox, "ANONYMOUS");
} else {
imapurl_toURL(url, server, mailbox, "*");
}
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. */
static int mlookup(const char *tag, const char *ext_name,
const char *name, int *flags, char **pathp, char **partp,
char **aclp, void *tid)
{
int r, mbtype;
char *remote, *acl;
r = mboxlist_detail(name, &mbtype, pathp, &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;
}
}
return r;
}
static void imapd_reset(void)
{
proc_cleanup();
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;
close(0);
close(1);
close(2);
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 (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;
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;
#ifdef HAVE_SSL
if (tls_conn) {
if (tls_reset_servertls(&tls_conn) == -1) {
fatal("tls_reset() failed", EC_TEMPFAIL);
}
tls_conn = NULL;
}
#endif
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 r;
int opt;
config_changeident("imapd");
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);
signals_add_handlers();
signal(SIGPIPE, SIG_IGN);
/* set the SASL allocation functions */
sasl_set_alloc((sasl_malloc_t *) &xmalloc,
(sasl_calloc_t *) &calloc,
(sasl_realloc_t *) &xrealloc,
(sasl_free_t *) &free);
/* load the SASL plugins */
if ((r = sasl_server_init(mysasl_cb, "Cyrus")) != SASL_OK) {
syslog(LOG_ERR, "SASL failed initializing: sasl_server_init(): %s",
sasl_errstring(r, NULL, NULL));
return EC_SOFTWARE;
}
#ifndef DELAY_SASL_CLIENT_INIT
if ((r = sasl_client_init(NULL)) != SASL_OK) {
syslog(LOG_ERR, "SASL failed initializing: sasl_client_init(): %s",
sasl_errstring(r, NULL, NULL));
return EC_SOFTWARE;
}
#endif
sprintf(shutdownfilename, "%s/msg/shutdown", config_dir);
/* open the mboxlist, we'll need it for real work */
mboxlist_init(0);
mboxlist_open(NULL);
mailbox_initialize();
/* 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, "C:sp:")) != EOF) {
switch (opt) {
case 'C': /* alt config file - handled by service::main() */
break;
case 's': /* imaps (do starttls right away) */
imaps = 1;
if (!tls_enabled("imap")) {
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;
}
}
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;
struct hostent *hp;
int timeout;
sasl_security_properties_t *secprops = NULL;
struct sockaddr_in imapd_localaddr, imapd_remoteaddr;
char localip[60], remoteip[60];
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
imapd_in = prot_new(0, 0);
imapd_out = prot_new(1, 1);
/* Find out name of client host */
salen = sizeof(imapd_remoteaddr);
if (getpeername(0, (struct sockaddr *)&imapd_remoteaddr, &salen) == 0 &&
imapd_remoteaddr.sin_family == AF_INET) {
hp = gethostbyaddr((char *)&imapd_remoteaddr.sin_addr,
sizeof(imapd_remoteaddr.sin_addr), AF_INET);
if (hp != NULL) {
strncpy(imapd_clienthost, hp->h_name, sizeof(imapd_clienthost)-30);
imapd_clienthost[sizeof(imapd_clienthost)-30] = '\0';
} else {
imapd_clienthost[0] = '\0';
}
strcat(imapd_clienthost, "[");
strcat(imapd_clienthost, inet_ntoa(imapd_remoteaddr.sin_addr));
strcat(imapd_clienthost, "]");
salen = sizeof(imapd_localaddr);
if (getsockname(0, (struct sockaddr *)&imapd_localaddr, &salen) == 0) {
if(iptostring((struct sockaddr *)&imapd_remoteaddr,
sizeof(struct sockaddr_in),
remoteip, 60) == 0
&& iptostring((struct sockaddr *)&imapd_localaddr,
sizeof(struct sockaddr_in),
localip, 60) == 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("timeout", 30);
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 if 'service_init()' was called but not 'service_main()' */
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);
}
/*
* Found a shutdown file: Spit out an untagged BYE and shut down
*/
void shutdown_file(fd)
int fd;
{
struct protstream *shutdown_in;
char buf[1024];
char *p;
shutdown_in = prot_new(fd, 0);
prot_fgets(buf, sizeof(buf), shutdown_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, "* BYE [ALERT] %s\r\n", p);
shut_down(0);
}
/*
* Cleanly shut down and exit
*/
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
proc_cleanup();
if (imapd_mailbox) {
index_closemailbox(imapd_mailbox);
mailbox_close(imapd_mailbox);
}
seen_done();
mboxlist_close();
mboxlist_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);
}
#ifdef HAVE_SSL
tls_shutdown_serverengine();
#endif
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);
}
shut_down(code);
}
/*
* Top-level command loop parsing
*/
void cmdloop()
{
int fd;
char motdfilename[1024];
int c;
int usinguid, havepartition, havenamespace, recursive, oldform;
static struct buf tag, cmd, arg1, arg2, arg3, arg4;
char *p;
const char *err;
prot_printf(imapd_out,
"* OK %s Cyrus IMAP4 %s server ready\r\n", config_servername,
CYRUS_VERSION);
sprintf(motdfilename, "%s/msg/motd", config_dir);
if ((fd = open(motdfilename, O_RDONLY, 0)) != -1) {
motd_file(fd);
close(fd);
}
for (;;) {
if ( !imapd_userisadmin && imapd_userid
&& (fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
shutdown_file(fd);
}
signals_poll();
/* Parse tag */
c = getword(imapd_in, &tag);
if (c == EOF) {
if ((err = prot_error(imapd_in))!=NULL) {
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);
}
/* 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")) {
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 == '\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);
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);
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) 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) 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) 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);
snmp_increment(DELETE_COUNT, 1);
}
else if (!strcmp(cmd.s, "Deleteacl")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (!strcasecmp(arg1.s, "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 == 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) 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) 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")) {
oldform = 0;
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (!strcasecmp(arg1.s, "mailbox")) {
oldform = 1;
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, oldform);
snmp_increment(GETACL_COUNT, 1);
}
#ifdef ENABLE_ANNOTATEMORE
else if (!strcmp(cmd.s, "Getannotation")) {
if (c != ' ') goto missingargs;
cmd_getannotation(tag.s);
snmp_increment(GETANNOTATION_COUNT, 1);
}
#endif
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);
}
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")) {
if (!idle_enabled()) {
/* we don't support idle */
goto badcmd;
}
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);
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 */
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, "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);
/* xxxx snmp_increment(DELETE_COUNT, 1); */
}
else goto badcmd;
break;
case 'M':
if (!strcmp(cmd.s, "Myrights")) {
oldform = 0;
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (!strcasecmp(arg1.s, "mailbox")) {
oldform = 1;
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, oldform);
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);
/* 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);
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);
snmp_increment(NAMESPACE_COUNT, 1);
}
else goto badcmd;
break;
case 'P':
if (!strcmp(cmd.s, "Partial")) {
if (!imapd_mailbox) 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);
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);
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);
/* xxx needed? */
/* snmp_increment(RECONSTRUCT_COUNT, 1); */
}
else if (!strcmp(cmd.s, "Rlist")) {
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")) {
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); */
} else goto badcmd;
break;
case 'S':
if (!strcmp(cmd.s, "Starttls")) {
if (!tls_enabled("imap")) {
/* 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) 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) 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 (!strcasecmp(arg1.s, "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;
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);
}
#ifdef ENABLE_ANNOTATEMORE
else if (!strcmp(cmd.s, "Setannotation")) {
if (c != ' ') goto missingargs;
cmd_setannotation(tag.s);
snmp_increment(SETANNOTATION_COUNT, 1);
}
#endif
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) 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) 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) 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) 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);*/
}
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 c;
struct buf passwdbuf;
char *passwd;
char *canon_user;
const char *reply = 0;
const char *val;
char buf[MAX_MAILBOX_PATH];
char *p;
int plaintextloginpause;
int r;
if (imapd_userid) {
eatline(imapd_in, ' ');
prot_printf(imapd_out, "%s BAD Already logged in\r\n", tag);
return;
}
canon_user = auth_canonifyid(user, 0);
if (!canon_user) {
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("allowplaintext", 1) == 0) &&
strcmp(canon_user, "anonymous") != 0) {
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 (!strcmp(canon_user, "anonymous")) {
if (config_getswitch("allowanonymouslogin", 0)) {
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) {
prot_printf(imapd_out, "%s NO Login failed: %s\r\n", tag, reply);
} else 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, hash_simple("LOGIN"),
VARIABLE_LISTEND);
freebuf(&passwdbuf);
return;
}
else {
imapd_userid = xstrdup(canon_user);
snmp_increment_args(AUTHENTICATION_YES, 1,
VARIABLE_AUTH, hash_simple("LOGIN"),
VARIABLE_LISTEND);
syslog(LOG_NOTICE, "login: %s %s plaintext%s %s", imapd_clienthost,
canon_user, imapd_starttls_done ? "+TLS" : "",
reply ? reply : "");
plaintextloginpause = config_getint("plaintextloginpause", 0);
if (plaintextloginpause != 0 && !imapd_starttls_done) {
/* Apply penalty only if not under layer */
sleep(plaintextloginpause);
}
}
imapd_authstate = auth_newstate(canon_user, (char *)0);
val = config_getstring("admins", "");
while (*val) {
for (p = (char *)val; *p && !isspace((int) *p); p++);
strlcpy(buf, val, p - val);
buf[p-val] = 0;
if (auth_memberof(imapd_authstate, buf)) {
imapd_userisadmin = 1;
break;
}
val = p;
while (*val && isspace((int) *val)) val++;
}
if (!reply) reply = "User logged in";
prot_printf(imapd_out, "%s OK %s\r\n", tag, reply);
/* Create telemetry log */
imapd_logfd = telemetry_log(imapd_userid, imapd_in, imapd_out);
/* Set namespace */
if ((r = mboxname_init_namespace(&imapd_namespace, imapd_userisadmin)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
/* Translate any separators in userid */
mboxname_hiersep_tointernal(&imapd_namespace, imapd_userid);
freebuf(&passwdbuf);
return;
}
static int hash_simple (const char *str)
{
int value = 0;
int i;
if (!str)
return 0;
for (i = 0; *str; i++)
{
value ^= (*str++ << ((i & 3)*8));
}
return value;
}
/*
* Perform an AUTHENTICATE command
*/
void
cmd_authenticate(char *tag,char *authtype)
{
int sasl_result;
static struct buf clientin;
int clientinlen=0;
const char *serverout;
unsigned int serveroutlen;
const int *ssfp;
char *ssfmsg=NULL;
const char *canon_user;
int r;
sasl_result = sasl_server_start(imapd_saslconn, authtype,
NULL, 0,
&serverout, &serveroutlen);
/* sasl_server_start will return SASL_OK or SASL_CONTINUE on success */
while (sasl_result == SASL_CONTINUE)
{
char c;
/* print the message to the user */
printauthready(imapd_out, serveroutlen, (unsigned char *)serverout);
c = prot_getc(imapd_in);
if(c == '*') {
eatline(imapd_in,c);
prot_printf(imapd_out,
"%s NO Client canceled authentication\r\n", tag);
reset_saslconn(&imapd_saslconn);
return;
} else {
prot_ungetc(c, imapd_in);
}
/* get string from user */
clientinlen = getbase64string(imapd_in, &clientin);
if (clientinlen == -1) {
reset_saslconn(&imapd_saslconn);
prot_printf(imapd_out, "%s BAD Invalid base64 string\r\n", tag);
return;
}
sasl_result = sasl_server_step(imapd_saslconn,
clientin.s,
clientinlen,
&serverout, &serveroutlen);
}
/* failed authentication */
if (sasl_result != SASL_OK)
{
syslog(LOG_NOTICE, "badlogin: %s %s [%s]",
imapd_clienthost, authtype, sasl_errdetail(imapd_saslconn));
snmp_increment_args(AUTHENTICATION_NO, 1,
VARIABLE_AUTH, hash_simple(authtype),
VARIABLE_LISTEND);
sleep(3);
reset_saslconn(&imapd_saslconn);
prot_printf(imapd_out, "%s NO Error authenticating\r\n", tag);
return;
}
/* successful authentication */
/* get the userid from SASL --- already canonicalized from
* mysasl_authproc()
*/
sasl_result = sasl_getprop(imapd_saslconn, SASL_USERNAME,
(const void **) &canon_user);
imapd_userid = xstrdup(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;
}
proc_register("imapd", imapd_clienthost, imapd_userid, (char *)0);
syslog(LOG_NOTICE, "login: %s %s %s%s %s", imapd_clienthost, imapd_userid,
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, 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);
/* Set namespace */
if ((r = mboxname_init_namespace(&imapd_namespace, imapd_userisadmin)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
/* Translate any separators in userid */
mboxname_hiersep_tointernal(&imapd_namespace, imapd_userid);
return;
}
/*
* Perform a NOOP command
*/
void
cmd_noop(char *tag, char *cmd __attribute__((unused)))
{
if (imapd_mailbox) {
index_check(imapd_mailbox, 0, 1);
}
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Parse and perform an ID command.
*
* the command has been parsed up to the parameter list.
*
* we only allow one ID in non-authenticated state from a given client.
* we only allow MAXIDFAILED consecutive failed IDs from a given client.
* we only record MAXIDLOG ID responses from a given client.
*/
void cmd_id(char *tag)
{
static int did_id = 0;
static int failed_id = 0;
static int logged_id = 0;
int error = 0;
int c = EOF, npair = 0;
static struct buf arg, field;
struct attvaluelist *params = 0;
/* check if we've already had an ID in non-authenticated state */
if (!imapd_userid && did_id) {
prot_printf(imapd_out,
"%s NO Only one Id allowed in non-authenticated state\r\n",
tag);
eatline(imapd_in, c);
return;
}
/* check if we've had too many failed IDs in a row */
if (failed_id >= MAXIDFAILED) {
prot_printf(imapd_out, "%s NO Too many (%u) invalid Id commands\r\n",
tag, failed_id);
eatline(imapd_in, c);
return;
}
/* ok, accept parameter list */
c = getword(imapd_in, &arg);
/* check for "NIL" or start of parameter list */
if (strcasecmp(arg.s, "NIL") && c != '(') {
prot_printf(imapd_out, "%s BAD Invalid parameter list in Id\r\n", tag);
eatline(imapd_in, c);
failed_id++;
return;
}
/* parse parameter list */
if (c == '(') {
for (;;) {
if (c == ')') {
/* end of string/value pairs */
break;
}
/* get field name */
c = getstring(imapd_in, imapd_out, &field);
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Invalid/missing field name in Id\r\n",
tag);
error = 1;
break;
}
/* get field value */
c = getnstring(imapd_in, imapd_out, &arg);
if (c != ' ' && c != ')') {
prot_printf(imapd_out,
"%s BAD Invalid/missing value in Id\r\n",
tag);
error = 1;
break;
}
/* ok, we're anal, but we'll still process the ID command */
if (strlen(field.s) > MAXIDFIELDLEN) {
prot_printf(imapd_out,
"%s BAD field longer than %u octets in Id\r\n",
tag, MAXIDFIELDLEN);
error = 1;
break;
}
if (strlen(arg.s) > MAXIDVALUELEN) {
prot_printf(imapd_out,
"%s BAD value longer than %u octets in Id\r\n",
tag, MAXIDVALUELEN);
error = 1;
break;
}
if (++npair > MAXIDPAIRS) {
prot_printf(imapd_out,
"%s BAD too many (%u) field-value pairs in ID\r\n",
tag, MAXIDPAIRS);
error = 1;
break;
}
/* ok, we're happy enough */
appendattvalue(&params, field.s, arg.s);
}
if (error || c != ')') {
/* erp! */
eatline(imapd_in, c);
freeattvalues(params);
failed_id++;
return;
}
c = prot_getc(imapd_in);
}
/* check for CRLF */
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to Id\r\n", tag);
eatline(imapd_in, c);
freeattvalues(params);
failed_id++;
return;
}
/* log the client's ID string.
eventually this should be a callback or something. */
if (npair && logged_id < MAXIDLOG) {
char logbuf[MAXIDLOGLEN + 1] = "";
struct attvaluelist *pptr;
for (pptr = params; pptr; pptr = pptr->next) {
/* should we check for and format literals here ??? */
snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
" \"%s\" ", pptr->attrib);
if (!strcmp(pptr->value, "NIL"))
snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
"NIL");
else
snprintf(logbuf + strlen(logbuf), MAXIDLOGLEN - strlen(logbuf),
"\"%s\"", pptr->value);
}
syslog(LOG_INFO, "client id:%s", logbuf);
logged_id++;
}
freeattvalues(params);
/* spit out our ID string.
eventually this might be configurable. */
if (config_getswitch("imapidresponse", 1)) {
id_response(imapd_out);
prot_printf(imapd_out, ")\r\n");
}
else
prot_printf(imapd_out, "* ID NIL\r\n");
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
failed_id = 0;
did_id = 1;
}
/*
* Append the 'attrib'/'value' pair to the attvaluelist 'l'.
*/
void appendattvalue(struct attvaluelist **l, char *attrib, char *value)
{
struct attvaluelist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct attvaluelist *)xmalloc(sizeof(struct attvaluelist));
(*tail)->attrib = xstrdup(attrib);
(*tail)->value = xstrdup(value);
(*tail)->next = 0;
}
/*
* Free the attvaluelist 'l'
*/
void freeattvalues(struct attvaluelist *l)
{
struct attvaluelist *n;
while (l) {
n = l->next;
free(l->attrib);
free(l->value);
l = n;
}
}
/*
* Perform an IDLE command
*/
void cmd_idle(char *tag)
{
int c;
static struct buf arg;
/* Setup for doing mailbox updates */
if (!idle_init(imapd_mailbox, 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, "+ go ahead\r\n");
prot_flush(imapd_out);
/* Get continuation data */
c = getword(imapd_in, &arg);
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);
}
}
/* Do any necessary cleanup */
idle_done(imapd_mailbox);
return;
}
/* Send unsolicited untagged responses to the client */
void idle_update(idle_flags_t flags)
{
int fd;
if ((flags & IDLE_MAILBOX) && imapd_mailbox)
index_check(imapd_mailbox, 0, 1);
if (flags & IDLE_ALERT) {
if (! imapd_userisadmin &&
(fd = open(shutdownfilename, O_RDONLY, 0)) != -1) {
shutdown_file(fd);
}
}
prot_flush(imapd_out);
}
/*
* Perform a CAPABILITY command
*/
void cmd_capability(char *tag)
{
const char *sasllist; /* the list of SASL mechanisms */
unsigned mechcount;
if (imapd_mailbox) {
index_check(imapd_mailbox, 0, 0);
}
prot_printf(imapd_out, "* CAPABILITY " CAPABILITY_STRING);
if (idle_enabled()) {
prot_printf(imapd_out, " IDLE");
}
if (tls_enabled("imap")) {
prot_printf(imapd_out, " STARTTLS");
}
if (!imapd_starttls_done && !config_getswitch("allowplaintext", 1)) {
prot_printf(imapd_out, " LOGINDISABLED");
}
if(config_mupdate_server) {
prot_printf(imapd_out, " MUPDATE=mupdate://%s/", config_mupdate_server);
}
/* add the SASL mechs */
if (sasl_listmech(imapd_saslconn, NULL,
"AUTH=", " AUTH=", "",
&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_ANNOTATEMORE
prot_printf(imapd_out, " ANNOTATEMORE");
#endif
#ifdef ENABLE_X_NETSCAPE_HACK
prot_printf(imapd_out, " X-NETSCAPE");
#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)
{
if (s[0] == '\\') {
lcase(s);
if (!strcmp(s, "\\seen")) 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);
}
}
#define FLAGGROW 10
void
cmd_append(char *tag, char *name)
{
int c;
char **flag = NULL;
int nflags = 0, flagalloc = 0;
static struct buf arg;
char *p;
time_t internaldate;
unsigned size = 0;
int sawdigit = 0;
int isnowait = 0;
int r;
char mailboxname[MAX_MAILBOX_NAME+1];
struct appendstate mailbox;
unsigned long uidvalidity;
unsigned long firstuid, num;
const char *parseerr = NULL;
/* Set up the append */
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = append_setup(&mailbox, mailboxname, MAILBOX_FORMAT_NORMAL,
imapd_userid, imapd_authstate, ACL_INSERT, size);
}
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;
}
c = ' '; /* just parsed a space */
/* we loop, to support MULTIAPPEND */
while (!r && c == ' ') {
/* Parse flags */
c = getword(imapd_in, &arg);
if (c == '(' && !arg.s[0]) {
nflags = 0;
do {
c = getword(imapd_in, &arg);
if (!nflags && !arg.s[0] && c == ')') break; /* empty list */
if (!isokflag(arg.s)) {
parseerr = "Invalid flag in Append command";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
if (nflags == flagalloc) {
flagalloc += FLAGGROW;
flag = (char **)xrealloc((char *)flag,
flagalloc * sizeof(char *));
}
flag[nflags++] = xstrdup(arg.s);
} 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(&internaldate);
if (c != ' ') {
parseerr = "Invalid date-time in Append command";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
c = getword(imapd_in, &arg);
} else {
internaldate = time(NULL);
}
if (arg.s[0] != '{') {
parseerr = "Missing required argument to Append command";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
/* Read size from literal */
size = 0;
for (p = arg.s + 1; *p && isdigit((int) *p); p++) {
sawdigit++;
size = size*10 + *p - '0';
}
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";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
if (size < 2) {
r = IMAP_MESSAGE_NOBLANKLINE;
goto done;
}
if (!isnowait) {
/* Tell client to send the message */
prot_printf(imapd_out, "+ go ahead\r\n");
prot_flush(imapd_out);
}
/* Perform the rest of the append */
if (!r) r = append_fromstream(&mailbox, imapd_in, size, internaldate,
(const char **) flag, nflags);
/* 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);
}
}
if (!r) {
r = append_commit(&mailbox, &uidvalidity, &firstuid, &num);
} else {
append_abort(&mailbox);
}
if (imapd_mailbox) {
index_check(imapd_mailbox, 0, 0);
}
if (r == IMAP_PROTOCOL_ERROR && parseerr) {
prot_printf(imapd_out, "%s BAD %s\r\n", tag, 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] " : "", error_message(r));
} else {
/* 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));
}
/* free memory */
while (nflags--) {
free(flag[nflags]);
}
if (flag) free((char *)flag);
}
/*
* 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;
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, NULL, NULL, NULL, NULL, NULL);
}
if (r == IMAP_MAILBOX_MOVED) 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 && chdir(mailbox.path)) {
syslog(LOG_ERR, "IOERROR: changing directory to %s: %m", mailbox.path);
r = IMAP_IOERROR;
}
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_DELETE);
}
if (imapd_mailbox->myrights & ACL_DELETE) {
/* Warn if mailbox is close to or over quota */
mailbox_read_quota(&imapd_mailbox->quota);
if (imapd_mailbox->quota.limit > 0) {
usage = ((double) imapd_mailbox->quota.used * 100.0) / (double)
(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("quotawarn", 90)) {
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");
}
}
}
prot_printf(imapd_out, "%s OK [READ-%s] %s\r\n", tag,
(imapd_mailbox->myrights & (ACL_WRITE|ACL_DELETE)) ?
"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(tag)
char *tag;
{
int r;
if (!(imapd_mailbox->myrights & ACL_DELETE)) r = 0;
else {
r = mailbox_expunge(imapd_mailbox, 1, (int (*)())0, (char *)0);
}
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(tag)
char* tag;
{
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 and perform a FETCH/UID FETCH command
* The command has been parsed up to and including
* the sequence
*/
void
cmd_fetch(tag, sequence, usinguid)
char *tag;
char *sequence;
int usinguid;
{
char *cmd = usinguid ? "UID Fetch" : "Fetch";
static struct buf fetchatt, fieldname;
int c, i;
int inlist = 0;
int fetchitems = 0;
struct fetchargs fetchargs;
struct strlist *newfields = 0;
char *p, *section;
int fetchedsomething;
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 (!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 (*p == 'P') {
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') {
fetchitems |= FETCH_UNCACHEDHEADER;
}
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 (!(fetchitems & FETCH_UNCACHEDHEADER)) {
for (i=0; i<mailbox_num_cache_header; i++) {
if (!strcasecmp(mailbox_cache_header_name[i],
fieldname.s)) break;
}
if (i == mailbox_num_cache_header) {
fetchitems |= FETCH_UNCACHEDHEADER;
}
}
} 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;
}
if (*p == '<' && isdigit((int) p[1])) {
p += 2;
while (isdigit((int) *p)) p++;
if (*p == '.' && p[1] >= '1' && p[1] <= '9') {
p += 2;
while (isdigit((int) *p)) p++;
}
else p--;
if (*p != '>') {
prot_printf(imapd_out, "%s BAD Invalid body partial\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
p++;
}
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);
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++;
if (*p == '<' && isdigit((int) p[1])) {
p += 2;
while (isdigit((int) *p)) p++;
if (*p == '.' && p[1] >= '1' && p[1] <= '9') {
p += 2;
while (isdigit((int) *p)) p++;
}
else p--;
if (*p != '>') {
prot_printf(imapd_out, "%s BAD Invalid body partial\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
p++;
}
if (*p) {
prot_printf(imapd_out, "%s BAD Junk after body section\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
appendstrlist(&fetchargs.bodysections, section);
}
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);;
appendstrlist(strlen(fetchatt.s) == 19 ?
&fetchargs.headers : &fetchargs.headers_not,
fieldname.s);
if (strlen(fetchatt.s) != 19) {
fetchitems |= FETCH_UNCACHEDHEADER;
}
if (!(fetchitems & FETCH_UNCACHEDHEADER)) {
for (i=0; i<mailbox_num_cache_header; i++) {
if (!strcmp(mailbox_cache_header_name[i],
fieldname.s)) break;
}
if (i == mailbox_num_cache_header) {
fetchitems |= FETCH_UNCACHEDHEADER;
}
}
} 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.headers && !fetchargs.headers_not) {
prot_printf(imapd_out, "%s BAD Missing required argument to %s\r\n", tag, cmd);
goto freeargs;
}
if (usinguid) {
fetchitems |= FETCH_UID;
index_check(imapd_mailbox, 1, 0);
}
fetchargs.fetchitems = fetchitems;
index_fetch(imapd_mailbox, sequence, usinguid, &fetchargs,
&fetchedsomething);
if (fetchedsomething || usinguid) {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
} else {
/* normal FETCH, nothing came back */
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(IMAP_NO_NOSUCHMSG));
}
freeargs:
freestrlist(newfields);
freestrlist(fetchargs.bodysections);
freefieldlist(fetchargs.fsections);
freestrlist(fetchargs.headers);
freestrlist(fetchargs.headers_not);
}
/*
* Perform a PARTIAL command
*/
void
cmd_partial(tag, msgno, data, start, count)
char *tag;
char *msgno;
char *data;
char *start;
char *count;
{
char *p;
struct fetchargs fetchargs;
char *section;
int fetchedsomething;
memset(&fetchargs, 0, sizeof(struct fetchargs));
for (p = msgno; *p; p++) {
if (!isdigit((int) *p)) break;
}
if (*p || !*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 (*p == 'p') {
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 = '\0';
appendstrlist(&fetchargs.bodysections, section);
}
else {
prot_printf(imapd_out, "%s BAD Invalid Partial item\r\n", tag);
freestrlist(fetchargs.bodysections);
return;
}
for (p = start; *p; p++) {
if (!isdigit((int) *p)) break;
fetchargs.start_octet = fetchargs.start_octet*10 + *p - '0';
}
if (*p || !fetchargs.start_octet) {
prot_printf(imapd_out, "%s BAD Invalid starting octet\r\n", tag);
freestrlist(fetchargs.bodysections);
return;
}
for (p = count; *p; p++) {
if (!isdigit((int) *p)) break;
fetchargs.octet_count = fetchargs.octet_count*10 + *p - '0';
}
if (*p || !*count) {
prot_printf(imapd_out, "%s BAD Invalid octet count\r\n", tag);
freestrlist(fetchargs.bodysections);
return;
}
index_fetch(imapd_mailbox, msgno, 0, &fetchargs, &fetchedsomething);
index_check(imapd_mailbox, 0, 0);
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(tag, sequence, operation, usinguid)
char *tag;
char *sequence;
char *operation;
int usinguid;
{
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;
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);
}
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 (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));
}
freeflags:
while (nflags--) {
free(flag[nflags]);
}
if (flag) free((char *)flag);
}
void
cmd_search(tag, usinguid)
char *tag;
int usinguid;
{
int c;
int charset = 0;
struct searchargs *searchargs;
clock_t start = clock();
char mytime[100];
int n;
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;
}
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);
sprintf(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(tag, usinguid)
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;
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;
}
n = index_sort(imapd_mailbox, sortcrit, searchargs, usinguid);
sprintf(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(tag, usinguid)
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;
/* 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;
}
n = index_thread(imapd_mailbox, alg, searchargs, usinguid);
sprintf(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(tag, sequence, name, usinguid)
char *tag;
char *sequence;
char *name;
int usinguid;
{
int r;
char mailboxname[MAX_MAILBOX_NAME+1];
char *copyuid;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = index_copy(imapd_mailbox, sequence, usinguid, mailboxname,
&copyuid);
}
index_check(imapd_mailbox, usinguid, 0);
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] " : "", 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 if (usinguid) {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
else {
/* normal COPY, message doesn't exist */
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(IMAP_NO_NOSUCHMSG));
}
}
}
/*
* Perform an EXPUNGE command
*/
void
cmd_expunge(tag, sequence)
char *tag;
char *sequence;
{
int r;
if (!(imapd_mailbox->myrights & ACL_DELETE)) r = IMAP_PERMISSION_DENIED;
else if (sequence) {
r = mailbox_expunge(imapd_mailbox, 1, index_expungeuidlist, sequence);
}
else {
r = mailbox_expunge(imapd_mailbox, 1, (mailbox_decideproc_t *)0,
(void *)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));
}
}
/*
* 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;
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) {
/* 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);
if (r == IMAP_PERMISSION_DENIED && !strcasecmp(name, "INBOX") &&
(autocreatequota = config_getint("autocreatequota", 0))) {
/* Auto create */
r = mboxlist_createmailbox(mailboxname, 0,
partition, 1, imapd_userid,
imapd_authstate, 0, 0);
if (!r && autocreatequota > 0) {
(void) mboxlist_setquota(mailboxname, autocreatequota, 0);
}
}
}
if (imapd_mailbox) {
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));
}
}
/* 'tmplist' is used for recursive means in cmd_delete() and cmd_rename() */
struct tmplist {
int alloc;
int num;
char mb[1][MAX_MAILBOX_NAME];
};
#define TMPLIST_INC 50
/* Callback for use by cmd_delete */
static int addmbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct tmplist **lptr = (struct tmplist **) rock;
struct tmplist *l = *lptr;
if (l->alloc == l->num) {
l->alloc += TMPLIST_INC;
l = xrealloc(l, sizeof(struct tmplist) +
l->alloc * MAX_MAILBOX_NAME * (sizeof(char)));
*lptr = l;
}
strcpy(l->mb[l->num++], name);
return 0;
}
/*
* Perform a DELETE command
*/
void cmd_delete(char *tag, char *name, int localonly)
{
int r;
char mailboxname[MAX_MAILBOX_NAME+1];
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mboxlist_deletemailbox(mailboxname, imapd_userisadmin,
imapd_userid, imapd_authstate, 1,
- localonly);
+ localonly, 0);
}
/* was it a top-level user mailbox? */
/* localonly deletes are only per-mailbox */
if (!r && !localonly &&
!strncmp(mailboxname, "user.", 5) && !strchr(mailboxname+5, '.')) {
struct tmplist *l = xmalloc(sizeof(struct tmplist));
char *p;
int r2, i;
l->alloc = 0;
l->num = 0;
p = mailboxname + strlen(mailboxname); /* end of mailboxname */
strcpy(p, ".*");
/* build a list of mailboxes - we're using internal names here */
mboxlist_findall(NULL, mailboxname, imapd_userisadmin, imapd_userid,
imapd_authstate, addmbox, &l);
/* foreach mailbox in list, remove it */
for (i = 0; i < l->num; i++) {
r2 = mboxlist_deletemailbox(l->mb[i], imapd_userisadmin,
- imapd_userid, imapd_authstate, 0, 0);
+ imapd_userid, imapd_authstate,
+ 0, 0, 0);
if (r2) {
prot_printf(imapd_out, "* NO delete %s: %s\r\n",
l->mb[i], error_message(r2));
}
}
free(l);
/* take care of deleting ACLs, subscriptions, seen state and quotas */
*p = '\0'; /* clip off pattern */
user_delete(mailboxname+5, imapd_userid, imapd_authstate, 1);
}
if (imapd_mailbox) {
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));
}
}
/*
* Perform a RENAME command
*/
void cmd_rename(const char *tag,
char *oldname, char *newname, char *partition)
{
int r = 0;
char oldmailboxname[MAX_MAILBOX_NAME+3];
char newmailboxname[MAX_MAILBOX_NAME+2];
int omlen, nmlen;
char *p;
int recursive_rename;
/* canonicalize names */
if (partition && !imapd_userisadmin) {
r = IMAP_PERMISSION_DENIED;
}
recursive_rename = 1;
/* if this is my inbox, don't do recursive renames */
if (!strcasecmp(oldname, "inbox")) {
recursive_rename = 0;
}
if (!r)
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, oldname,
imapd_userid, oldmailboxname);
if (!r)
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, newname,
imapd_userid, newmailboxname);
/* if we're renaming something inside of something else,
don't recursively rename stuff */
omlen = strlen(oldmailboxname);
nmlen = strlen(newmailboxname);
if (strlen(oldmailboxname) < strlen(newmailboxname)) {
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);
}
/* rename all mailboxes matching this */
if (!r && recursive_rename) {
struct tmplist *l = xmalloc(sizeof(struct tmplist));
int ol = omlen + 1;
int nl = nmlen + 1;
int i;
l->alloc = 0;
l->num = 0;
strcat(oldmailboxname, ".*");
strcat(newmailboxname, ".");
/* add submailboxes; we pretend we're an admin since we successfully
renamed the parent - we're using internal names here */
mboxlist_findall(NULL, oldmailboxname, 1, imapd_userid,
imapd_authstate, addmbox, &l);
/* foreach mailbox in list, rename it, pretending we're admin */
for (i = 0; i < l->num; i++) {
int r2 = 0;
if (nl + strlen(l->mb[i] + ol) > MAX_MAILBOX_NAME) {
/* this mailbox name is too long */
continue;
}
strcpy(newmailboxname + nl, l->mb[i] + ol);
r2 = mboxlist_renamemailbox(l->mb[i], newmailboxname,
partition,
1, imapd_userid, imapd_authstate);
if (r2) {
prot_printf(imapd_out, "* NO rename %s %s: %s\r\n",
l->mb[i], newmailboxname, error_message(r2));
if (RENAME_STOP_ON_ERROR) break;
}
}
free(l);
}
if (imapd_mailbox) {
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));
}
}
/*
* 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];
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, NULL, NULL, NULL, NULL, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
int pid;
/* Reconstruct it */
pid = fork();
if(pid == -1) {
r = IMAP_SYS_ERROR;
} else if(pid == 0) {
char buf[4096];
/* Child - exec reconstruct*/
syslog(LOG_NOTICE, "Reconstructing '%s' (%s) for user '%s'",
mailboxname, recursive ? "recursive" : "not recursive",
imapd_userid);
snprintf(buf, sizeof(buf), "%s/reconstruct", SERVICE_PATH);
fclose(stdin);
fclose(stdout);
fclose(stderr);
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];
/* Child - exec reconstruct*/
syslog(LOG_NOTICE,
"Regenerating quota roots starting with '%s' for user '%s'",
mailboxname, imapd_userid);
snprintf(buf, sizeof(buf), "%s/quota", SERVICE_PATH);
fclose(stdin);
fclose(stdout);
fclose(stderr);
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));
}
}
/*
* Perform a FIND command
*/
void
cmd_find(tag, namespace, pattern)
char *tag;
char *namespace;
char *pattern;
{
char *p;
lcase(namespace);
for (p = pattern; *p; p++) {
if (*p == '%') *p = '?';
}
/* Translate any separators in pattern */
mboxname_hiersep_tointernal(&imapd_namespace, pattern);
if (!strcmp(namespace, "mailboxes")) {
(*imapd_namespace.mboxlist_findsub)(&imapd_namespace, pattern,
imapd_userisadmin, imapd_userid,
imapd_authstate, mailboxdata,
NULL, 0);
}
else if (!strcmp(namespace, "all.mailboxes")) {
(*imapd_namespace.mboxlist_findall)(&imapd_namespace, pattern,
imapd_userisadmin, imapd_userid,
imapd_authstate, mailboxdata, NULL);
}
else if (!strcmp(namespace, "bboards")
|| !strcmp(namespace, "all.bboards")) {
;
}
else {
prot_printf(imapd_out, "%s BAD Invalid FIND subcommand\r\n", tag);
return;
}
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, char *pattern,
int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock);
int (*findsub)(struct namespace *namespace, 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("ignorereference", 0);
}
/* 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 {
/* 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);
/* Check to see if we should only list the personal namespace */
if (!strcmp(pattern, "*") && config_getint("foolstupidclients", 0)) {
if (buf) free(buf);
buf = strdup("INBOX*");
pattern = buf;
findsub = mboxlist_findsub;
findall = mboxlist_findall;
}
else {
findsub = imapd_namespace.mboxlist_findsub;
findall = imapd_namespace.mboxlist_findall;
}
if (listopts & LIST_LSUB || listopts & LIST_SUBSCRIBED) {
int force = config_getswitch("allowallsubscribe", 0);
(*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);
}
sprintf(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)
{
int r;
char mailboxname[MAX_MAILBOX_NAME+1];
int force = config_getswitch("allowallsubscribe", 0);
if (namespace) lcase(namespace);
if (!namespace || !strcmp(namespace, "mailbox")) {
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,
add ? "Subscribe" : "Unsubscribe");
return;
}
if (r) {
prot_printf(imapd_out, "%s NO %s: %s\r\n", tag,
add ? "Subscribe" : "Unsubscribe", error_message(r));
}
else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
}
/*
* Perform a GETACL command
*/
void
cmd_getacl(tag, name, oldform)
char *tag;
char *name;
int oldform;
{
char mailboxname[MAX_MAILBOX_NAME+1];
int r, access;
char *acl;
char *rights, *nextid;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, NULL, NULL, NULL, &acl, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
access = cyrus_acl_myrights(imapd_authstate, acl);
if (!(access & (ACL_READ|ACL_ADMIN)) &&
!imapd_userisadmin &&
!mboxname_userownsmailbox(imapd_userid, mailboxname)) {
r = (access&ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
if (oldform) {
while (acl) {
rights = strchr(acl, '\t');
if (!rights) break;
*rights++ = '\0';
nextid = strchr(rights, '\t');
if (!nextid) break;
*nextid++ = '\0';
prot_printf(imapd_out, "* ACL MAILBOX ");
printastring(name);
prot_printf(imapd_out, " ");
printastring(acl);
prot_printf(imapd_out, " ");
printastring(rights);
prot_printf(imapd_out, "\r\n");
acl = nextid;
}
}
else {
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, " ");
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 *canon_identifier;
int canonidlen = 0;
char *acl;
char *rightsdesc;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, 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;
}
}
if (!r) {
canon_identifier = auth_canonifyid(identifier, 0);
if (canon_identifier) canonidlen = strlen(canon_identifier);
if (!canon_identifier) {
rightsdesc = "\"\"";
}
else if (!strncmp(mailboxname, "user.", 5) &&
!strchr(canon_identifier, '.') &&
!strncmp(mailboxname+5, canon_identifier, canonidlen) &&
(mailboxname[5+canonidlen] == '\0' ||
mailboxname[5+canonidlen] == '.')) {
rightsdesc = "lca r s w i p d 0 1 2 3 4 5 6 7 8 9";
}
else {
rightsdesc = "\"\" l r s w i p c d a 0 1 2 3 4 5 6 7 8 9";
}
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(tag, name, oldform)
char *tag;
char *name;
int oldform;
{
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, &acl, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
rights = cyrus_acl_myrights(imapd_authstate, acl);
/* Add in implicit rights */
if (imapd_userisadmin ||
mboxname_userownsmailbox(imapd_userid, mailboxname)) {
rights |= ACL_LOOKUP|ACL_ADMIN;
}
if (!rights) {
r = IMAP_MAILBOX_NONEXISTENT;
}
}
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
prot_printf(imapd_out, "* MYRIGHTS ");
if (oldform) prot_printf(imapd_out, "MAILBOX ");
printastring(name);
prot_printf(imapd_out, " ");
printastring(cyrus_acl_masktostr(rights, str));
prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Perform a SETACL command
*/
void
cmd_setacl(tag, name, identifier, rights)
char *tag;
char *name;
char *identifier;
char *rights;
{
int r;
char mailboxname[MAX_MAILBOX_NAME+1];
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
/* is it remote? */
if (!r) {
r = mlookup(tag, name, mailboxname, NULL, NULL, NULL, NULL, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
r = mboxlist_setacl(mailboxname, identifier, rights,
imapd_userisadmin, imapd_userid, imapd_authstate);
}
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 GETQUOTA command
*/
void
cmd_getquota(tag, name)
char *tag;
char *name;
{
int r;
struct quota quota;
char buf[MAX_MAILBOX_PATH];
char mailboxname[MAX_MAILBOX_NAME+1];
quota.fd = -1;
if (!imapd_userisadmin) r = IMAP_PERMISSION_DENIED;
else {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
quota.root = mailboxname;
mailbox_hash_quota(buf, quota.root);
quota.fd = open(buf, O_RDWR, 0);
if (quota.fd == -1) {
r = IMAP_QUOTAROOT_NONEXISTENT;
}
else {
r = mailbox_read_quota(&quota);
}
}
}
if (!r) {
prot_printf(imapd_out, "* QUOTA ");
printastring(name);
prot_printf(imapd_out, " (");
if (quota.limit >= 0) {
prot_printf(imapd_out, "STORAGE %lu %d",
quota.used/QUOTA_UNITS, quota.limit);
}
prot_printf(imapd_out, ")\r\n");
}
if (quota.fd != -1) {
close(quota.fd);
}
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(tag, name)
char *tag;
char *name;
{
char mailboxname[MAX_MAILBOX_NAME+1];
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, NULL, NULL, NULL, NULL, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
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 = mailbox_read_quota(&mailbox.quota);
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 %lu %d",
mailbox.quota.used/QUOTA_UNITS,
mailbox.quota.limit);
}
prot_putc(')', imapd_out);
}
}
prot_printf(imapd_out, "\r\n");
}
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));
}
/*
* Parse and perform a SETQUOTA command
* The command has been parsed up to the resource list
*/
void
cmd_setquota(tag, quotaroot)
char *tag;
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];
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 (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 {
/* are we forcing the creation of a quotaroot by having a leading +? */
if(quotaroot[0] == '+') {
force = 1;
quotaroot++;
}
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, quotaroot,
imapd_userid, mailboxname);
if (!r) {
r = mboxlist_setquota(mailboxname, newquota, force);
}
}
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 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? */
0, /* require client to 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 failed\r\n", tag);
syslog(LOG_NOTICE, "STARTTLS failed: %s", imapd_clienthost);
return;
} else {
syslog(LOG_NOTICE, "imaps 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(tag, name)
char *tag;
char *name;
{
int c;
int statusitems = 0;
static struct buf arg;
struct mailbox mailbox;
char mailboxname[MAX_MAILBOX_NAME+1];
int r = 0;
int doclose = 0;
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;
}
/*
* Perform a full checkpoint of any open mailbox, in case we're
* doing a STATUS check of the current mailbox.
*/
if (imapd_mailbox) {
index_check(imapd_mailbox, 0, 1);
}
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, NULL, NULL, NULL, NULL, NULL);
}
if (r == IMAP_MAILBOX_MOVED) 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("netscapeurl", NULL);
/* 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 (!(strncmp(name, "INBOX.", 6))) {
/* The user has a "personal" namespace. */
sawone[NAMESPACE_INBOX] = 1;
} else if (!(strncmp(name, "user.", 5))) {
/* 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 {
sprintf(inboxname, "user.%s", imapd_userid);
sawone[NAMESPACE_INBOX] =
!mboxlist_lookup(inboxname, NULL, 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");
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
#ifdef ENABLE_ANNOTATEMORE
/*
* 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;
static struct buf entry, attrib, value;
struct attvaluelist *attvalues = NULL;
*entryatts = NULL;
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 == ' ');
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)
{
int c, r = 0;
struct strlist *entries = NULL, *attribs = NULL;
struct entryattlist *entryatts = 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(entries, attribs, &imapd_namespace,
imapd_userisadmin, imapd_userid,
imapd_authstate, &entryatts);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
}
else if (entryatts) {
prot_printf(imapd_out, "* ANNOTATION ");
annotate_response(entryatts);
prot_printf(imapd_out, "\r\n");
prot_printf(imapd_out, "%s OK %s\r\n",
tag, error_message(IMAP_OK_COMPLETED));
}
else
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(IMAP_NO_NOSUCHANNOTATION));
freeargs:
if (entries) freestrlist(entries);
if (attribs) freestrlist(attribs);
if (entryatts) freeentryatts(entryatts);
return;
}
/*
* Perform a SETANNOTATION command
*
* The command has been parsed up to the entry-att list
*/
void cmd_setannotation(char *tag)
{
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;
}
/* administrators only please */
if (!imapd_userisadmin) {
r = IMAP_PERMISSION_DENIED;
}
if (!r) {
r = annotatemore_store(entryatts, &imapd_namespace, imapd_userisadmin,
imapd_userid, 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));
}
freeargs:
if (entryatts) freeentryatts(entryatts);
return;
}
#endif /* ENABLE_ANNOTATEMORE */
/*
* 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 i, 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->flags & SEARCH_UNCACHEDHEADER)) {
for (i=0; i<mailbox_num_cache_header; i++) {
if (!strcmp(mailbox_cache_header_name[i], arg.s)) break;
}
if (i == mailbox_num_cache_header) {
searchargs->flags |= SEARCH_UNCACHEDHEADER;
}
}
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 (!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 (!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, *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, NULL, &acl, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
if(!r) {
r = dump_mailbox(tag, mailboxname, path, 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, *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, NULL, &acl, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
if(!r) {
/* save this stuff from additional mlookups */
char *safe_path = xstrdup(path);
char *safe_acl = xstrdup(acl);
r = undump_mailbox(mailboxname, safe_path, safe_acl,
imapd_in, imapd_out,
imapd_authstate);
free(safe_path);
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) + 1;
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];
char c;
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;
char c;
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 *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) reconstruct remote mailbox
* 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 = findserver(NULL, toserver, "");
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 {
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);
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 && !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(mailboxname),
mailboxname);
r = dump_mailbox(NULL, mailboxname, path, 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 4.5: Set ACL on remote */
if(!r) {
r = trashacl(be->in, be->out, mailboxname);
if(r) syslog(LOG_ERR, "Could not clear remote acl on %s",
mailboxname);
}
if(!r) {
r = dumpacl(be->in, be->out, mailboxname, 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) {
/* 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 */
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);
+ imapd_userid, imapd_authstate, 0, 1, 0);
if(r) syslog(LOG_ERR,
"Could not delete local mailbox during move of %s",
mailboxname);
}
done:
if(r && 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, name, buf, acl);
if(rerr) {
syslog(LOG_ERR,
"Could not back out mupdate during move of %s (%s)",
name, 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);
if(rerr) syslog(LOG_ERR, "Could not unset remote flag on mailbox: %s",
mailboxname);
}
/* release the handles we got locally if necessary */
if(!h_in)
mupdate_disconnect(&mupdate_h);
if(be && !be_in)
downserver(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];
int mbflags;
int r = 0;
char *inpath, *inpart, *inacl;
char *path = 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, &inpart, &inacl, NULL);
}
if (!r) {
path = xstrdup(inpath);
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, part, acl, 0, mupdate_h, be);
}
if(path) free(path);
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, *inpart, *inacl;
char *path = NULL, *part = NULL, *acl = NULL;
/* administrators only please */
/* however, proxys can do this, if their authzid is an admin */
if (!imapd_userisadmin && !imapd_userisproxyadmin) {
r = IMAP_PERMISSION_DENIED;
}
/* if we're not in a murder this [currently] makes no sense */
if (!config_mupdate_server) {
r = IMAP_SERVER_UNAVAILABLE;
}
if (!r) {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
name,
imapd_userid,
mailboxname);
}
if(!strncmp(mailboxname, "user.", 5) && !strchr(mailboxname+5, '.')) {
if (!strcmp(mailboxname+5, imapd_userid)) {
/* don't move your own inbox, that could be troublesome */
r = IMAP_MAILBOX_NOTSUPPORTED;
} else {
moving_user = 1;
}
}
if (!r) {
r = mlookup(tag, name, mailboxname, &mbflags,
&inpath, &inpart, &inacl, NULL);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
path = xstrdup(inpath);
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, part, acl, 0, NULL, NULL);
} else {
struct backend *be = NULL;
/* we need to reserve the users inbox - connect to mupdate */
if(!r) {
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 = findserver(NULL, toserver, "");
if(!be) {
r = IMAP_SERVER_UNAVAILABLE;
syslog(LOG_ERR,
"Could not move mailbox: %s, " \
"Initial backend connect failed",
mailboxname);
}
/* deactivate their inbox */
if(!r) {
/* 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 */
{
char buf[MAX_MAILBOX_PATH];
struct quota quota;
quota.fd = -1;
quota.root = mailboxname;
mailbox_hash_quota(buf,quota.root);
quota.fd = open(buf, O_RDWR, 0);
if(quota.fd != -1) {
r = mailbox_read_quota(&quota);
close(quota.fd);
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);
}
}
}
/* 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, part, acl, 1, mupdate_h, be);
}
if(be) {
downserver(be);
}
if(r && 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, name, buf, acl);
if(rerr) {
syslog(LOG_ERR,
"Could not back out mupdate during move of %s (%s)",
name, 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_delete(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(acl) free(acl);
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 = strdup(arg.s);
c = getstring(imapd_in, &arg);
if (c == EOF) goto missingarg;
(*sortcrit)[n].args.annot.attrib = strdup(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;
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 (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);
}
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;
*date = mkgmtime(&tm) - 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(l, section, fields, trail)
struct fieldlist **l;
char *section;
struct strlist *fields;
char *trail;
{
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);
(*tail)->next = 0;
}
/*
* Append 's' to the strlist 'l'.
*/
void
appendstrlist(l, s)
struct strlist **l;
char *s;
{
struct strlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct strlist *)xmalloc(sizeof(struct strlist));
(*tail)->s = xstrdup(s);
(*tail)->p = 0;
(*tail)->next = 0;
}
/*
* Append 's' to the strlist 'l', compiling it as a pattern.
* Caller must pass in memory that is freed when the strlist is freed.
*/
void
appendstrlistpat(l, s)
struct strlist **l;
char *s;
{
struct strlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct strlist *)xmalloc(sizeof(struct strlist));
(*tail)->s = s;
(*tail)->p = charset_compilepat(s);
(*tail)->next = 0;
}
/*
* Free the fieldlist 'l'
*/
void
freefieldlist(l)
struct fieldlist *l;
{
struct fieldlist *n;
while (l) {
n = l->next;
free(l->section);
freestrlist(l->fields);
free(l->trail);
free((char *)l);
l = n;
}
}
/*
* Free the strlist 'l'
*/
void
freestrlist(l)
struct strlist *l;
{
struct strlist *n;
while (l) {
n = l->next;
free(l->s);
if (l->p) charset_freepat(l->p);
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];
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) {
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;
}
strcpy(lastname, name);
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))
sprintf(mboxname, "user.%s%s", imapd_userid, lastname+5);
else
strcpy(mboxname, lastname);
/* Look it up */
nonexistent = mboxlist_detail(mboxname, &mbtype,
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, &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) {
sprintf(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));
}
}
diff --git a/imap/mboxlist.c b/imap/mboxlist.c
index 82f9e45e6..08db75664 100644
--- a/imap/mboxlist.c
+++ b/imap/mboxlist.c
@@ -1,2719 +1,2724 @@
/* mboxlist.c -- Mailbox list manipulation routines
*
* 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: mboxlist.c,v 1.193 2002/05/23 21:12:39 rjs3 Exp $
+ * $Id: mboxlist.c,v 1.194 2002/05/29 16:49:15 rjs3 Exp $
*/
#include <config.h>
#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 <sys/ipc.h>
#include <sys/msg.h>
extern int errno;
#include "acl.h"
#include "auth.h"
#include "glob.h"
#include "assert.h"
#include "imapconf.h"
#include "cyrusdb.h"
#include "util.h"
#include "mailbox.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "xmalloc.h"
#include "mboxname.h"
#include "mupdate-client.h"
#include "mboxlist.h"
#define DB CONFIG_DB_MBOX
#define SUBDB CONFIG_DB_SUBS
cyrus_acl_canonproc_t mboxlist_ensureOwnerRights;
struct db *mbdb;
static int mboxlist_dbopen = 0;
static int mboxlist_opensubs();
static void mboxlist_closesubs();
static int mboxlist_rmquota(const char *name, int matchlen, int maycreate,
void *rock);
static int mboxlist_changequota(const char *name, int matchlen, int maycreate,
void *rock);
#define FNAME_SUBSSUFFIX ".sub"
/*
* Convert a partition into a path
*/
static int mboxlist_getpath(const char *partition, const char *name,
char **pathp)
{
size_t partitionlen;
char optionbuf[MAX_MAILBOX_NAME+1];
static char pathresult[MAX_MAILBOX_PATH];
const char *root;
assert(partition && pathp);
partitionlen = strlen(partition);
if (partitionlen > sizeof(optionbuf)-11) {
return IMAP_PARTITION_UNKNOWN;
}
strcpy(optionbuf, "partition-");
strcat(optionbuf, partition);
root = config_getstring(optionbuf, (char *)0);
if (!root) {
return IMAP_PARTITION_UNKNOWN;
}
mailbox_hash_mbox(pathresult, root, name);
*pathp = pathresult;
return 0;
}
char *mboxlist_makeentry(int mbtype, const char *part, const char *acl)
{
char *mboxent = (char *) xmalloc(sizeof(char) *
(30 + strlen(acl) + strlen(part)));
sprintf(mboxent, "%d %s %s", mbtype, part, acl);
return mboxent;
}
static const int get_deleteright(void)
{
const char *r = config_getstring("deleteright", "c");
return cyrus_acl_strtomask(r);
}
/*
* Lookup 'name' in the mailbox list.
* The capitalization of 'name' is canonicalized to the way it appears
* in the mailbox list.
* If 'path' is non-nil, a pointer to the full pathname of the mailbox
* is placed in the char * pointed to by it. If 'acl' is non-nil, a pointer
* to the mailbox ACL is placed in the char * pointed to by it.
*/
static int mboxlist_mylookup(const char *name, int *typep,
char **pathp, char **partp,
char **aclp,
struct txn **tid, int wrlock)
{
int acllen;
static char partition[MAX_PARTITION_LEN];
static char *aclresult;
static int aclresultalloced;
int r;
const char *data;
char *p, *q;
int datalen;
int namelen;
int mbtype;
namelen = strlen(name);
if (namelen == 0) {
return IMAP_MAILBOX_NONEXISTENT;
}
if (wrlock) {
r = DB->fetchlock(mbdb, name, namelen, &data, &datalen, tid);
} else {
r = DB->fetch(mbdb, name, namelen, &data, &datalen, tid);
}
switch (r) {
case CYRUSDB_OK:
if (data == NULL) {
return IMAP_MAILBOX_NONEXISTENT;
break;
}
/* copy out interesting parts */
mbtype = strtol(data, &p, 10);
if (typep) *typep = mbtype;
if (*p == ' ') p++;
q = partition;
while (*p != ' ') { /* copy out partition name */
*q++ = *p++;
}
*q = '\0';
p++;
if (partp) {
*partp = partition;
}
/* construct pathname if requested */
if (pathp) {
if (mbtype & MBTYPE_REMOTE) {
*pathp = partition;
} else if (mbtype & MBTYPE_MOVING) {
char *part = strchr(partition, '!');
if(!part) return IMAP_SYS_ERROR;
else part++; /* skip the !, go to the beginning
of the partition name */
r = mboxlist_getpath(part, name, pathp);
if(r) return r;
} else {
r = mboxlist_getpath(partition, name, pathp);
if(r) return r;
}
}
/* the rest is ACL; return it if requested */
if (aclp) {
acllen = datalen - (p - data);
if (acllen >= aclresultalloced) {
aclresultalloced = acllen + 100;
aclresult = xrealloc(aclresult, aclresultalloced);
}
memcpy(aclresult, p, acllen);
aclresult[acllen] = '\0';
*aclp = aclresult;
}
break;
case CYRUSDB_AGAIN:
return IMAP_AGAIN;
break;
default:
syslog(LOG_ERR, "DBERROR: error fetching %s: %s",
name, cyrusdb_strerror(r));
return IMAP_IOERROR;
break;
}
return 0;
}
/*
* Lookup 'name' in the mailbox list.
* The capitalization of 'name' is canonicalized to the way it appears
* in the mailbox list.
* If 'path' is non-nil, a pointer to the full pathname of the mailbox
* is placed in the char * pointed to by it. If 'acl' is non-nil, a pointer
* to the mailbox ACL is placed in the char * pointed to by it.
*/
int mboxlist_lookup(const char *name, char **pathp, char **aclp,
void *tid __attribute__((unused)))
{
return mboxlist_mylookup(name, NULL, pathp, NULL, aclp, NULL, 0);
}
int mboxlist_detail(const char *name, int *typep, char **pathp, char **partp,
char **aclp, struct txn *tid __attribute__((unused)))
{
return mboxlist_mylookup(name, typep, pathp, partp, aclp, NULL, 0);
}
int mboxlist_findstage(const char *name, char *stagedir)
{
char optionbuf[MAX_MAILBOX_NAME+1];
const char *root;
char *partition;
int r;
assert(stagedir != NULL);
/* Find mailbox */
r = mboxlist_mylookup(name, NULL, NULL, &partition, NULL, NULL, 0);
switch (r) {
case 0:
break;
default:
return r;
break;
}
strcpy(optionbuf, "partition-");
strcpy(optionbuf + 10, partition);
root = config_getstring(optionbuf, (char *)0);
if (!root) {
return IMAP_PARTITION_UNKNOWN;
}
sprintf(stagedir, "%s/stage./", root);
return 0;
}
int mboxlist_update(char *name, int flags, const char *part, const char *acl)
{
int r = 0;
char *mboxent = NULL;
mboxent = mboxlist_makeentry(flags, part, acl);
r = DB->store(mbdb, name, strlen(name), mboxent, strlen(mboxent), NULL);
free(mboxent);
return r;
}
/*
* Check/set up for mailbox creation
*/
/* xxx shouldn't we be using mbtype or getting rid of it entirely? */
static int
mboxlist_mycreatemailboxcheck(char *name,
int new_mbtype __attribute__((unused)),
char *partition,
int isadmin, char *userid,
struct auth_state *auth_state,
char **newacl, char **newpartition,
int RMW, int localonly, int force_user_create,
struct txn **tid)
{
int r;
char *p;
char *acl, *path;
char *defaultacl, *identifier, *rights;
char parent[MAX_MAILBOX_NAME+1];
unsigned long parentlen;
char *parentname = NULL;
char *parentpartition = NULL;
char *parentacl = NULL;
unsigned long parentpartitionlen = 0;
unsigned long parentacllen = 0;
int mbtype;
/* Check for invalid name/partition */
if (partition && strlen(partition) > MAX_PARTITION_LEN) {
return IMAP_PARTITION_UNKNOWN;
}
r = mboxname_policycheck(name);
if (r) return r;
/* you must be a real admin to create a local-only mailbox */
if(!isadmin && localonly) return IMAP_PERMISSION_DENIED;
if(!isadmin && force_user_create) return IMAP_PERMISSION_DENIED;
/* User has admin rights over their own mailbox namespace */
if (mboxname_userownsmailbox(userid, name)) {
isadmin = 1;
}
/* Check to see if new mailbox exists */
r = mboxlist_mylookup(name, &mbtype, &path, NULL, &acl, tid, RMW);
switch (r) {
case 0:
if(mbtype & MBTYPE_RESERVE)
r = IMAP_MAILBOX_RESERVED;
else
r = IMAP_MAILBOX_EXISTS;
/* Lie about error if privacy demands */
if (!isadmin &&
!(cyrus_acl_myrights(auth_state, acl) & ACL_LOOKUP)) {
r = IMAP_PERMISSION_DENIED;
}
return r;
break;
case IMAP_MAILBOX_NONEXISTENT:
break;
default:
return r;
break;
}
/* Search for a parent */
strcpy(parent, name);
parentlen = 0;
while ((parentlen==0) && (p = strrchr(parent, '.'))) {
*p = '\0';
r = mboxlist_mylookup(parent, NULL, NULL, &parentpartition,
&parentacl, tid, 0);
switch (r) {
case 0:
parentlen = strlen(parent);
parentname = parent;
parentpartitionlen = strlen(parentpartition);
parentacllen = strlen(parentacl);
break;
case IMAP_MAILBOX_NONEXISTENT:
break;
default:
return r;
break;
}
}
if (parentlen != 0) {
/* check acl */
if (!isadmin &&
!(cyrus_acl_myrights(auth_state, parentacl) & ACL_CREATE)) {
return IMAP_PERMISSION_DENIED;
}
/* Copy partition, if not specified */
if (partition == NULL) {
partition = xmalloc(parentpartitionlen + 1);
memcpy(partition, parentpartition, parentpartitionlen);
partition[parentpartitionlen] = '\0';
} else {
partition = xstrdup(partition);
}
/* Copy ACL */
acl = xmalloc(parentacllen + 1);
memcpy(acl, parentacl, parentacllen);
acl[parentacllen] = '\0';
/* Canonicalize case of parent prefix */
strlcpy(name, parent, strlen(parent));
} else { /* parentlen == 0, no parent mailbox */
if (!isadmin) {
return IMAP_PERMISSION_DENIED;
}
acl = xstrdup("");
if (!strncmp(name, "user.", 5)) {
if (!force_user_create && strchr(name+5, '.')) {
/* Disallow creating user.X.* when no user.X */
free(acl);
return IMAP_PERMISSION_DENIED;
}
/* disallow wildcards in userids with inboxes. */
if (strchr(name, '*') || strchr(name, '%') || strchr(name, '?')) {
return IMAP_MAILBOX_BADNAME;
}
/*
* Users by default have all access to their personal mailbox(es),
* Nobody else starts with any access to same.
*/
identifier = xstrdup(name+5);
if (config_getswitch("unixhierarchysep", 0)) {
/*
* The mailboxname is now in the internal format,
* so we we need to change DOTCHARs back to '.'
* in the identifier in order to have the correct ACL.
*/
for (p = identifier; *p; p++) {
if (*p == DOTCHAR) *p = '.';
}
}
cyrus_acl_set(&acl, identifier, ACL_MODE_SET, ACL_ALL,
(cyrus_acl_canonproc_t *)0, (void *)0);
free(identifier);
} else {
defaultacl = identifier =
xstrdup(config_getstring("defaultacl", "anyone lrs"));
for (;;) {
while (*identifier && isspace((int) *identifier)) identifier++;
rights = identifier;
while (*rights && !isspace((int) *rights)) rights++;
if (!*rights) break;
*rights++ = '\0';
while (*rights && isspace((int) *rights)) rights++;
if (!*rights) break;
p = rights;
while (*p && !isspace((int) *p)) p++;
if (*p) *p++ = '\0';
cyrus_acl_set(&acl, identifier, ACL_MODE_SET, cyrus_acl_strtomask(rights),
(cyrus_acl_canonproc_t *)0, (void *)0);
identifier = p;
}
free(defaultacl);
}
if (!partition) {
partition = (char *)config_defpartition;
if (strlen(partition) > MAX_PARTITION_LEN) {
/* Configuration error */
fatal("name of default partition is too long", EC_CONFIG);
}
}
partition = xstrdup(partition);
}
if (newpartition) *newpartition = partition;
else free(partition);
if (newacl) *newacl = acl;
else free(acl);
return 0;
}
int
mboxlist_createmailboxcheck(char *name, int mbtype, char *partition,
int isadmin, char *userid,
struct auth_state *auth_state,
char **newacl, char **newpartition)
{
return mboxlist_mycreatemailboxcheck(name, mbtype, partition, isadmin,
userid, auth_state, newacl,
newpartition, 0, 0, 0, NULL);
}
/*
* Create a mailbox
*
* 1. start mailboxes transaction
* 2. verify ACL's to best of ability (CRASH: abort)
* 3. open mupdate connection if necessary
* 4. verify parent ACL's if need to
* 5. create mupdate entry and set as reserved (CRASH: mupdate inconsistant)
* 6. create on disk (CRASH: mupdate inconsistant, disk inconsistant)
* 8. commit local transaction (CRASH: mupdate inconsistant)
* 9. set mupdate entry as commited (CRASH: commited)
*
*/
int mboxlist_createmailbox(char *name, int mbtype, char *partition,
int isadmin, char *userid,
struct auth_state *auth_state,
int localonly, int forceuser)
{
int r;
char *acl = NULL;
const char *root = NULL;
char *newpartition = NULL;
struct txn *tid = NULL;
mupdate_handle *mupdate_h = NULL;
char *mboxent = NULL;
/* Must be atleast MAX_PARTITION_LEN + 30 for partition, need
* MAX_PARTITION_LEN + HOSTNAME_SIZE + 2 for mupdate location */
char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
int madereserved = 0; /* made reserved entry on mupdate server */
retry:
tid = NULL;
/* 2. verify ACL's to best of ability (CRASH: abort) */
r = mboxlist_mycreatemailboxcheck(name, mbtype, partition, isadmin,
userid, auth_state,
&acl, &newpartition, 1, localonly,
forceuser, &tid);
switch (r) {
case 0:
break;
case IMAP_AGAIN:
goto retry;
default:
goto done;
}
/* You can't explicitly create a MOVING or RESERVED mailbox */
if(mbtype & (MBTYPE_MOVING | MBTYPE_RESERVE)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
if (!(mbtype & MBTYPE_REMOTE)) {
/* Get partition's path */
sprintf(buf, "partition-%s", newpartition);
root = config_getstring(buf, (char *)0);
if (!root) {
r = IMAP_PARTITION_UNKNOWN;
goto done;
}
if (strlen(root)+strlen(name)+20 > MAX_MAILBOX_PATH) {
r = IMAP_MAILBOX_BADNAME;
goto done;
}
}
/* 4. Create mupdate reservation */
/* xxx this is network I/O being done while holding a mboxlist lock */
if (config_mupdate_server && !localonly) {
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(r) {
syslog(LOG_ERR,
"can not connect to mupdate server for reservation on '%s'",
name);
goto done;
}
sprintf(buf, "%s!%s", config_servername, newpartition);
/* reserve the mailbox in MUPDATE */
r = mupdate_reserve(mupdate_h, name, buf);
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't reserve mailbox entry for '%s'", name);
goto done;
}
}
madereserved = 1; /* so we can roll back on failure */
/* 5. add the new entry */
mboxent = mboxlist_makeentry(mbtype, newpartition, acl);
r = DB->store(mbdb, name, strlen(name), mboxent, strlen(mboxent),
&tid);
switch (r) {
case CYRUSDB_OK:
break;
case CYRUSDB_AGAIN:
goto retry;
break;
default:
syslog(LOG_ERR, "DBERROR: error updating database %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
done: /* ALL DATABASE OPERATIONS DONE; NEED TO DO FILESYSTEM OPERATIONS */
if (!r && !(mbtype & MBTYPE_REMOTE)) {
char mbbuf[MAX_MAILBOX_PATH];
/* Create new mailbox and move new mailbox list file into place */
mailbox_hash_mbox(mbbuf, root, name);
r = mailbox_create(name, mbbuf, acl, NULL,
((mbtype & MBTYPE_NETNEWS) ?
MAILBOX_FORMAT_NETNEWS :
MAILBOX_FORMAT_NORMAL),
NULL);
}
if (r) { /* CREATE failed */
int r2;
r2 = 0;
if (tid) r2 = DB->abort(mbdb, tid);
switch (r2) {
case 0:
break;
default:
syslog(LOG_ERR, "DBERROR: failed on abort: %s",
cyrusdb_strerror(r2));
}
/* delete mupdate entry if we made it */
if (madereserved == 1 && config_mupdate_server) {
r2 = mupdate_delete(mupdate_h, name);
if(r2 > 0) {
/* Disconnect, reconnect, and retry */
syslog(LOG_WARNING,
"MUPDATE: lost connection, retrying");
mupdate_disconnect(&mupdate_h);
r2 = mupdate_connect(config_mupdate_server, NULL,
&mupdate_h, NULL);
if(!r2) {
r2 = mupdate_delete(mupdate_h, name);
}
}
if(r2) {
syslog(LOG_ERR,
"MUPDATE: can't unreserve mailbox entry '%s'",
name);
}
}
} else { /* all is well */
switch (r = DB->commit(mbdb, tid)) {
case 0:
break;
default:
syslog(LOG_ERR, "DBERROR: failed on commit: %s",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
}
/* 9. set MUPDATE entry as commited (CRASH: commited) */
/* xxx maybe we should roll back if this fails? */
if (!r && config_mupdate_server && !localonly) {
/* commit the mailbox in MUPDATE */
sprintf(buf, "%s!%s", config_servername, newpartition);
r = mupdate_activate(mupdate_h, name, buf, acl);
if(r > 0) {
/* Disconnect, reconnect, and retry */
syslog(LOG_WARNING,
"MUPDATE: lost connection, retrying");
mupdate_disconnect(&mupdate_h);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(!r) {
r = mupdate_activate(mupdate_h, name, buf, acl);
}
}
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't commit mailbox entry for '%s'", name);
}
}
if(config_mupdate_server && mupdate_h) mupdate_disconnect(&mupdate_h);
if (acl) free(acl);
if (newpartition) free(newpartition);
if (mboxent) free(mboxent);
return r;
}
/* insert an entry for the proxy */
/* xxx rettid needs usage? */
int mboxlist_insertremote(const char *name, int mbtype,
const char *host, const char *acl,
void **rettid __attribute__((unused)))
{
char *mboxent;
int r = 0;
assert(name != NULL && host != NULL);
mboxent = mboxlist_makeentry(mbtype | MBTYPE_REMOTE, host, acl);
/* database put */
r = DB->store(mbdb, name, strlen(name), mboxent, strlen(mboxent), NULL);
switch (r) {
case CYRUSDB_OK:
break;
case CYRUSDB_AGAIN:
abort(); /* shouldn't happen ! */
break;
default:
syslog(LOG_ERR, "DBERROR: error updating database %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
break;
}
free(mboxent);
return r;
}
/*
* Delete a mailbox.
* Deleting the mailbox user.FOO may only be performed by an admin.
*
* 1. Begin transaction
* 2. Verify ACL's
* 3. remove from database
* 4. remove from disk
* 5. commit transaction
* 6. Open mupdate connection if necessary
* 7. delete from mupdate
*
*/
int mboxlist_deletemailbox(const char *name, int isadmin, char *userid,
struct auth_state *auth_state, int checkacl,
- int local_only)
+ int local_only, int force)
{
int r;
char *acl;
long access;
struct mailbox mailbox;
int deletequotaroot = 0;
char *path;
struct txn *tid = NULL;
int isremote = 0;
int mbtype;
int deleteright = get_deleteright();
mupdate_handle *mupdate_h = NULL;
+ if(!isadmin && force) return IMAP_PERMISSION_DENIED;
+
retry:
/* Check for request to delete a user:
user.<x> with no dots after it */
if (!strncmp(name, "user.", 5) && !strchr(name+5, '.')) {
/* Can't DELETE INBOX (your own inbox) */
if (userid && !strcmp(name+5, userid)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* Only admins may delete user */
if (!isadmin) { r = IMAP_PERMISSION_DENIED; goto done; }
}
r = mboxlist_mylookup(name, &mbtype, &path, NULL, &acl, NULL, 1);
switch (r) {
case 0:
break;
case IMAP_AGAIN:
goto retry;
break;
default:
goto done;
}
isremote = mbtype & MBTYPE_REMOTE;
/* are we reserved? (but for remote mailboxes this is okay, since
* we don't touch their data files at all) */
- if(!isremote && (mbtype & MBTYPE_RESERVE)) {
+ if(!isremote && (mbtype & MBTYPE_RESERVE) && !force) {
r = IMAP_MAILBOX_RESERVED;
goto done;
}
/* check if user has Delete right (we've already excluded non-admins
* from deleting a user mailbox) */
if(checkacl) {
access = cyrus_acl_myrights(auth_state, acl);
if(!(access & deleteright)) {
/* User has admin rights over their own mailbox namespace */
if (mboxname_userownsmailbox(userid, name)) {
isadmin = 1;
}
/* Lie about error if privacy demands */
r = (isadmin || (access & ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* Lock the mailbox if it isn't a remote mailbox */
if(!r && !isremote) {
r = mailbox_open_locked(name, path, acl, 0, &mailbox, 0);
- if(r) goto done;
+ if(r && !force) goto done;
}
/* delete entry */
r = DB->delete(mbdb, name, strlen(name), &tid, 0);
switch (r) {
case CYRUSDB_OK: /* success */
break;
case CYRUSDB_AGAIN:
goto retry;
default:
syslog(LOG_ERR, "DBERROR: error deleting %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
- goto done;
+ if(!force) goto done;
}
/* remove from mupdate - this can be weird if the commit below fails */
/* xxx this is network I/O being done while holding a mboxlist lock */
- if (!r && !isremote && !local_only && config_mupdate_server) {
+ if ((!r || force)
+ && !isremote && !local_only && config_mupdate_server) {
/* delete the mailbox in MUPDATE */
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(r) {
syslog(LOG_ERR,
"can not connect to mupdate server for delete of '%s'",
name);
goto done;
}
r = mupdate_delete(mupdate_h, name);
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't delete mailbox entry '%s'", name);
}
mupdate_disconnect(&mupdate_h);
}
/* commit db operations */
- if (!r) {
+ if (!r || force) {
r = DB->commit(mbdb, tid);
if (r) {
syslog(LOG_ERR, "DBERROR: failed on commit: %s",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
tid = NULL;
}
- if (r || isremote) goto done;
+ if ((r && !force) || isremote) goto done;
- if (!r) r = mailbox_delete(&mailbox, deletequotaroot);
+ if (!r || force) r = mailbox_delete(&mailbox, deletequotaroot);
/*
* See if we have to remove mailbox's quota root
*/
if (!r && mailbox.quota.root != NULL) {
/* xxx look for any other mailboxes in this quotaroot */
}
done:
- if(r && tid) {
+ if(r && tid && !force) {
/* Abort the transaction if it is still in progress */
DB->abort(mbdb, tid);
+ } else if(tid && force) {
+ DB->commit(mbdb, tid);
}
return r;
}
/*
* Rename/move a single mailbox (recursive renames are handled at a
* higher level)
*
* 1. get path & acl from mboxlist (with lock)
* 2. verify acls
* 3. open mupdate connection / reserve destination if needed
* 4. Reserve mailbox locally (MBTYPE_RESERVE)
* 5. lock original mailbox / unlock mboxlist
* 6. Copy mailbox from src->dest
* 7. Perform update on mboxlist
* 8. finish mupdate updates (activate new, delete old)
* 9. delete from disk
*
*/
int mboxlist_renamemailbox(char *oldname, char *newname, char *partition,
int isadmin, char *userid,
struct auth_state *auth_state)
{
int r;
long access;
int isusermbox = 0;
int partitionmove = 0;
int mbtype;
char *oldpath = NULL;
char newpath[MAX_MAILBOX_PATH];
int oldopen = 0;
struct mailbox oldmailbox;
struct mailbox newmailbox;
char *oldacl = NULL, *newacl = NULL;
const char *root = NULL;
struct txn *tid = NULL;
char *newpartition = NULL;
char *mboxent = NULL;
int deleteright = get_deleteright();
mupdate_handle *mupdate_h = NULL;
int madenew = 0;
retry:
/* 1. get path & acl from mboxlist */
r = mboxlist_mylookup(oldname, &mbtype, &oldpath, NULL, &oldacl, &tid, 1);
switch (r) {
case 0:
break;
case IMAP_AGAIN:
goto retry;
default:
goto done;
}
if(mbtype & MBTYPE_RESERVE) {
r = IMAP_MAILBOX_RESERVED;
goto done;
}
/* make a copy of the old ACL so it doesn't get overwritten
by another call to mboxlist_mylookup() */
newacl = xstrdup(oldacl);
/* 2. verify acls */
if (!strcmp(oldname, newname) && !(mbtype & MBTYPE_REMOTE)) {
/* Attempt to move mailbox across partition */
if (!isadmin) {
r = IMAP_PERMISSION_DENIED;
goto done;
} else if (!partition) {
r = IMAP_PARTITION_UNKNOWN;
goto done;
}
partitionmove = 1;
root = config_partitiondir(partition);
if (!root) {
r = IMAP_PARTITION_UNKNOWN;
goto done;
}
if (!strncmp(root, oldpath, strlen(root)) &&
oldpath[strlen(root)] == '/') {
/* partitions are the same or share common prefix */
r = IMAP_MAILBOX_EXISTS;
goto done;
}
} else if (!strncmp(oldname, "user.", 5) && !strchr(oldname+5, '.')) {
if (!strcmp(oldname+5, userid)) {
/* Special case of renaming inbox */
access = cyrus_acl_myrights(auth_state, oldacl);
if (!(access & deleteright)) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
isusermbox = 1;
} else {
/* Even admins can't rename users */
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
} else {
access = cyrus_acl_myrights(auth_state, oldacl);
if (!(access & deleteright) && !isadmin) {
r = (isadmin || (access & ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* We don't support renaming mailboxes in transit */
if(!r && (mbtype & MBTYPE_MOVING)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* Check ability to create new mailbox */
if (!partitionmove) {
if (!strncmp(newname, "user.", 5) && !strchr(newname+5, '.')) {
/* Even admins can't rename to user's inboxes */
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
r = mboxlist_mycreatemailboxcheck(newname, 0, partition, isadmin,
userid, auth_state, NULL,
&newpartition, 1, 0, 0, &tid);
switch (r) {
case 0:
break;
case IMAP_AGAIN:
goto retry;
break;
default: /* not allowed to create the new mailbox */
goto done;
break;
}
} else {
newpartition = xstrdup(partition);
}
if (!(mbtype & MBTYPE_REMOTE)) {
/* Get partition's path */
root = config_partitiondir(newpartition);
if (!root) {
r = IMAP_PARTITION_UNKNOWN;
goto done;
}
}
/* 3. Open mupdate connection and reserve new name (if needed) */
if(!r && config_mupdate_server) {
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(r) {
syslog(LOG_ERR,
"can not connect to mupdate server for rename of '%s'",
newname);
goto done;
}
if (!partitionmove) {
/* Reserve new name in MUPDATE */
char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
sprintf(buf, "%s!%s", config_servername, newpartition);
r = mupdate_reserve(mupdate_h, newname, buf);
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't reserve mailbox entry for '%s'",
newname);
goto done;
}
madenew = 1;
}
}
/* 4. mark as reserved in the DB */
if(!r && !partitionmove) {
mboxent = mboxlist_makeentry(mbtype | MBTYPE_RESERVE,
newpartition, newacl);
r = DB->store(mbdb, newname, strlen(newname),
mboxent, strlen(mboxent), &tid);
free(mboxent);
}
/* 4. unlock mboxlist, Lock oldname/oldpath */
if(r) {
syslog(LOG_ERR, "Could not lock mailbox %s during rename", oldname);
goto done;
} else {
DB->commit(mbdb, tid);
tid = NULL;
}
if(!r) {
r = mailbox_open_locked(oldname, oldpath, oldacl, auth_state,
&oldmailbox, 0);
oldopen = 1;
}
/* 5. Copy mailbox */
if (!r && !(mbtype & MBTYPE_REMOTE)) {
/* Rename the actual mailbox */
assert(root != NULL); /* from above */
mailbox_hash_mbox(newpath, root, newname);
r = mailbox_rename_copy(&oldmailbox, newname, newpath,
NULL, NULL, &newmailbox);
if (r) {
goto done;
} else {
r = mailbox_open_locked(newname, newpath, newacl, auth_state,
&newmailbox, 0);
if(r) {
syslog(LOG_ERR, "error locking new mailbox");
goto done;
}
}
}
if (!isusermbox) {
/* 4. Delete entry from berkeley db */
r = DB->delete(mbdb, oldname, strlen(oldname), &tid, 0);
switch (r) {
case 0: /* success */
break;
case CYRUSDB_AGAIN:
goto retry;
break;
default:
syslog(LOG_ERR, "DBERROR: error deleting %s: %s",
oldname, cyrusdb_strerror(r));
r = IMAP_IOERROR;
mailbox_close(&newmailbox);
goto done;
break;
}
}
/* create new entry */
mboxent = mboxlist_makeentry(mbtype, newpartition, newacl);
/* put it into the db */
r = DB->store(mbdb, newname, strlen(newname),
mboxent, strlen(mboxent), &tid);
switch (r) {
case 0:
break;
case CYRUSDB_AGAIN:
goto retry;
default:
syslog(LOG_ERR, "DBERROR: error renaming %s: %s",
newname, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
r = mailbox_rename_finish(&oldmailbox,&newmailbox,isusermbox);
done:
if (r != 0) {
int r2 = 0;
if (tid) r2 = DB->abort(mbdb, tid);
if (r2) {
syslog(LOG_ERR, "DBERROR: can't abort: %s", cyrusdb_strerror(r2));
}
/* unroll mupdate operations if necessary */
if (madenew && config_mupdate_server) {
r2 = mupdate_delete(mupdate_h, newname);
if(r2 > 0) {
/* Disconnect, reconnect, and retry */
syslog(LOG_WARNING,
"MUPDATE: lost connection, retrying");
mupdate_disconnect(&mupdate_h);
r2 = mupdate_connect(config_mupdate_server, NULL,
&mupdate_h, NULL);
if(!r2) {
r2 = mupdate_delete(mupdate_h, newname);
}
}
if(r2) {
syslog(LOG_ERR,
"MUPDATE: can't unreserve mailbox entry '%s'",
newname);
}
}
} else {
/* commit now */
switch (r = DB->commit(mbdb, tid)) {
case 0:
break;
default:
syslog(LOG_ERR, "DBERROR: failed on commit: %s",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
break;
}
}
if (!r && config_mupdate_server) {
/* commit the mailbox in MUPDATE */
/* This is okay even if we are moving partitions */
char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
sprintf(buf, "%s!%s", config_servername, newpartition);
r = mupdate_activate(mupdate_h, newname, buf, newacl);
if(r > 0) {
/* Disconnect, reconnect, and retry */
syslog(LOG_WARNING,
"MUPDATE: lost connection, retrying");
mupdate_disconnect(&mupdate_h);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(!r) {
r = mupdate_activate(mupdate_h, newname, buf, newacl);
}
}
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't commit mailbox entry for '%s'",
newname);
}
}
if (!r && !partitionmove && config_mupdate_server) {
/* delete the old mailbox in MUPDATE */
r = mupdate_delete(mupdate_h, oldname);
if(r > 0) {
/* Disconnect, reconnect, and retry */
syslog(LOG_WARNING,
"MUPDATE: lost connection, retrying");
mupdate_disconnect(&mupdate_h);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(!r) {
r = mupdate_delete(mupdate_h, oldname);
}
}
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't delete mailbox entry '%s'", oldname);
}
}
if(oldopen) mailbox_close(&oldmailbox);
if(config_mupdate_server) mupdate_disconnect(&mupdate_h);
/* free memory */
if (newacl) free(newacl); /* we're done with the new ACL */
if (newpartition) free(newpartition);
if (mboxent) free(mboxent);
return r;
}
/*
* Change the ACL for mailbox 'name' so that 'identifier' has the
* rights enumerated in the string 'rights'. If 'rights' is the null
* pointer, removes the ACL entry for 'identifier'. 'isadmin' is
* nonzero if user is a mailbox admin. 'userid' is the user's login id.
*
*
* 1. Start transaction
* 2. Check rights
* 3. Open mupdate connection if necessary
* 4. Set db entry
* 5. Change on disk
* 6. Commit transaction
* 7. Change mupdate entry
*
* xxx i'm not happy with this function. the "goto done" bit has made it
* more confusing, not less. we should probably just nuke the gotos
* and it'll be more clear or make "goto done" really jump to the
* very end. -leg
*/
int mboxlist_setacl(char *name, char *identifier, char *rights,
int isadmin, char *userid,
struct auth_state *auth_state)
{
int useridlen = strlen(userid);
int r;
int access;
int mode = ACL_MODE_SET;
int isusermbox = 0;
struct mailbox mailbox;
int mailbox_open = 0;
char *acl, *newacl = NULL;
char *partition, *path;
char *mboxent = NULL;
int mbtype;
struct txn *tid = NULL;
if (!strncmp(name, "user.", 5) &&
!strchr(userid, '.') &&
!strncmp(name+5, userid, useridlen) &&
(name[5+useridlen] == '\0' || name[5+useridlen] == '.')) {
isusermbox = 1;
}
retry:
/* lookup the mailbox to make sure it exists and get its acl */
r = mboxlist_mylookup(name, &mbtype, &path, &partition, &acl, &tid, 1);
switch (r) {
case 0:
break;
case IMAP_AGAIN:
goto retry;
default:
goto done;
}
/* Can't do this to an in-transit or reserved mailbox */
if(mbtype & (MBTYPE_MOVING | MBTYPE_RESERVE)) {
r = IMAP_MAILBOX_NOTSUPPORTED;
goto done;
}
/* if it is not a remote mailbox, we need to unlock the mailbox list,
* lock the mailbox, and re-lock the mailboxes list */
/* we must do this to obey our locking rules */
if (!(mbtype & MBTYPE_REMOTE)) {
DB->abort(mbdb, tid);
tid = NULL;
/* open & lock mailbox header */
r = mailbox_open_header_path(name, path, acl, NULL, &mailbox, 0);
if (!r) {
mailbox_open = 1;
r = mailbox_lock_header(&mailbox);
}
if(!r) {
relock_retry:
/* lookup the mailbox to make sure it exists and get its acl */
r = mboxlist_mylookup(name, &mbtype, &path,
&partition, &acl, &tid, 1);
switch (r) {
case 0:
break;
case IMAP_AGAIN:
goto relock_retry;
default:
goto done;
}
} else goto done;
}
if (!r && !isadmin && !isusermbox) {
access = cyrus_acl_myrights(auth_state, acl);
if (!(access & ACL_ADMIN)) {
r = (access & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
goto done;
}
}
/* Make change to ACL */
newacl = xstrdup(acl);
if (rights) {
mode = ACL_MODE_SET;
if (*rights == '+') {
rights++;
mode = ACL_MODE_ADD;
}
else if (*rights == '-') {
rights++;
mode = ACL_MODE_REMOVE;
}
if (cyrus_acl_set(&newacl, identifier, mode, cyrus_acl_strtomask(rights),
isusermbox ? mboxlist_ensureOwnerRights : 0,
(void *)userid))
{
r = IMAP_INVALID_IDENTIFIER;
goto done;
}
}
else {
if (cyrus_acl_remove(&newacl, identifier,
isusermbox ? mboxlist_ensureOwnerRights : 0,
(void *)userid)) {
r = IMAP_INVALID_IDENTIFIER;
goto done;
}
}
/* ok, make the change */
mboxent = mboxlist_makeentry(mbtype, partition, newacl);
r = DB->store(mbdb, name, strlen(name), mboxent, strlen(mboxent), &tid);
switch (r) {
case 0:
break;
case CYRUSDB_AGAIN:
goto retry;
default:
syslog(LOG_ERR, "DBERROR: error updating acl %s: %s",
name, cyrusdb_strerror(r));
r = IMAP_IOERROR;
goto done;
}
/* Do change to mailbox header file; we already have
it locked from above */
if (!r && !(mbtype & MBTYPE_REMOTE)) {
if(mailbox.acl) free(mailbox.acl);
mailbox.acl = xstrdup(newacl);
r = mailbox_write_header(&mailbox);
}
done:
if (mailbox_open) {
mailbox_close(&mailbox);
}
if (mboxent) free(mboxent);
if (r) {
int r2;
if (tid && (r2 = DB->abort(mbdb, tid)) != 0) {
syslog(LOG_ERR, "DBERROR: error aborting txn: %s",
cyrusdb_strerror(r2));
r2 = IMAP_IOERROR;
}
} else {
/* commit now */
switch (r = DB->commit(mbdb, tid)) {
case 0:
break;
default:
syslog(LOG_ERR, "DBERROR: failed on commit: %s",
cyrusdb_strerror(r));
r = IMAP_IOERROR;
}
}
/* 7. Change mupdate entry */
if (!r && config_mupdate_server) {
mupdate_handle *mupdate_h = NULL;
/* commit the update to MUPDATE */
char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
sprintf(buf, "%s!%s", config_servername, partition);
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
if(r) {
syslog(LOG_ERR,
"can not connect to mupdate server for reservation on '%s'",
name);
} else {
r = mupdate_activate(mupdate_h, name, buf, newacl);
if(r) {
syslog(LOG_ERR,
"MUPDATE: can't update mailbox entry for '%s'",
name);
}
}
mupdate_disconnect(&mupdate_h);
}
if (newacl) free(newacl);
return r;
}
struct find_rock {
struct glob *g;
struct namespace *namespace;
int find_namespace;
int inboxoffset;
const char *inboxcase;
const char *usermboxname;
int usermboxnamelen;
int checkmboxlist;
int checkshared;
int isadmin;
struct auth_state *auth_state;
int (*proc)(char *, int, int, void *rock);
void *procrock;
};
/* return non-zero if we like this one */
static int find_p(void *rockp,
const char *key, int keylen,
const char *data, int datalen)
{
struct find_rock *rock = (struct find_rock *) rockp;
long minmatch;
struct glob *g = rock->g;
long matchlen;
minmatch = 0;
if (rock->inboxoffset) {
char namebuf[MAX_MAILBOX_NAME+1];
memcpy(namebuf, key, keylen);
namebuf[keylen] = '\0';
if (rock->inboxoffset) {
namebuf[rock->inboxoffset] = rock->inboxcase[0];
namebuf[rock->inboxoffset+1] = rock->inboxcase[1];
namebuf[rock->inboxoffset+2] = rock->inboxcase[2];
namebuf[rock->inboxoffset+3] = rock->inboxcase[3];
namebuf[rock->inboxoffset+4] = rock->inboxcase[4];
}
matchlen = glob_test(g, namebuf+rock->inboxoffset,
keylen-rock->inboxoffset, &minmatch);
} else {
matchlen = glob_test(g, key, keylen, &minmatch);
}
if (matchlen == -1) return 0;
if (rock->find_namespace != NAMESPACE_INBOX &&
rock->usermboxname &&
keylen >= rock->usermboxnamelen &&
(keylen == rock->usermboxnamelen ||
key[rock->usermboxnamelen] == '.') &&
!strncmp(key, rock->usermboxname, rock->usermboxnamelen)) {
/* this would've been output with the inbox stuff, so skip it */
return 0;
}
if (rock->find_namespace == NAMESPACE_SHARED &&
rock->namespace && rock->namespace->isalt &&
!strncmp(key, "user", 4) &&
(key[4] == '\0' || key[4] == '.')) {
/* this would've been output with the user stuff, so skip it */
return 0;
}
/* check acl */
if (!rock->isadmin) {
/* check the acls */
const char *p, *acl;
int rights;
int acllen;
static char *aclbuf = NULL;
static int aclbufsz = 0;
p = strchr(data, ' ');
if (!p) {
syslog(LOG_ERR, "%s: can't find partition", key);
return 0;
}
p++;
acl = strchr(p, ' ');
if (!acl) {
syslog(LOG_ERR, "%s: can't find acl", key);
return 0;
}
acl++;
acllen = datalen - (acl - data);
if (acllen >= aclbufsz) {
aclbufsz = acllen + 500;
aclbuf = xrealloc(aclbuf, aclbufsz);
}
memcpy(aclbuf, acl, acllen);
aclbuf[acllen] = '\0';
rights = cyrus_acl_myrights(rock->auth_state, aclbuf);
if (!(rights & ACL_LOOKUP)) {
return 0;
}
}
/* if we get here, close enough for us to spend the time
acting interested */
return 1;
}
static int find_cb(void *rockp,
const char *key, int keylen,
const char *data, int datalen)
{
char namebuf[MAX_MAILBOX_NAME+1];
struct find_rock *rock = (struct find_rock *) rockp;
int r = 0;
long minmatch;
struct glob *g = rock->g;
/* foreach match, do this test */
minmatch = 0;
while (minmatch >= 0) {
long matchlen;
memcpy(namebuf, key, keylen);
namebuf[keylen] = '\0';
if (rock->find_namespace != NAMESPACE_INBOX &&
rock->usermboxname &&
!strncmp(namebuf, rock->usermboxname, rock->usermboxnamelen)
&& (keylen == rock->usermboxnamelen ||
namebuf[rock->usermboxnamelen] == '.')) {
/* this would've been output with the inbox stuff, so skip it */
return 0;
}
/* make sure it's in the mailboxes db */
if (rock->checkmboxlist) {
r = mboxlist_lookup(namebuf, NULL, NULL, NULL);
} else {
r = 0; /* don't bother checking */
}
if (!r && rock->inboxoffset) {
namebuf[rock->inboxoffset] = rock->inboxcase[0];
namebuf[rock->inboxoffset+1] = rock->inboxcase[1];
namebuf[rock->inboxoffset+2] = rock->inboxcase[2];
namebuf[rock->inboxoffset+3] = rock->inboxcase[3];
namebuf[rock->inboxoffset+4] = rock->inboxcase[4];
}
matchlen = glob_test(g, namebuf+rock->inboxoffset,
keylen-rock->inboxoffset, &minmatch);
if (matchlen == -1) {
r = 0;
break;
}
switch (r) {
case 0:
/* found the entry; output it */
if (rock->find_namespace == NAMESPACE_SHARED &&
rock->checkshared && rock->namespace) {
/* special case: LIST "" % -- output prefix only */
r = (*rock->proc)(rock->namespace->prefix[NAMESPACE_SHARED],
strlen(rock->namespace->prefix[NAMESPACE_SHARED])-1,
1, rock->procrock);
/* short-circuit the foreach - one mailbox is sufficient */
r = CYRUSDB_DONE;
}
else {
r = (*rock->proc)(namebuf+rock->inboxoffset, matchlen,
1, rock->procrock);
}
break;
case IMAP_MAILBOX_NONEXISTENT:
/* didn't find the entry */
r = 0;
break;
default:
break;
}
if (r) break;
}
return r;
}
/*
* Find all mailboxes that match 'pattern'.
* 'isadmin' is nonzero if user is a mailbox admin. 'userid'
* is the user's login id. For each matching mailbox, calls
* 'proc' with the name of the mailbox. If 'proc' ever returns
* a nonzero value, mboxlist_findall immediately stops searching
* and returns that value. 'rock' is passed along as an argument to proc in
* case it wants some persistant storage or extra data.
*/
/* Find all mailboxes that match 'pattern'. */
int mboxlist_findall(struct namespace *namespace __attribute__((unused)),
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(), void *rock)
{
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_NAME+1];
int usermboxnamelen = 0;
const char *data;
int datalen;
int r = 0;
char *p;
int prefixlen;
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = NULL;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = isadmin;
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = 0; /* don't duplicate work */
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
/* Build usermboxname */
if (userid && !strchr(userid, '.') &&
strlen(userid)+5 < MAX_MAILBOX_NAME) {
strcpy(usermboxname, "user.");
strcat(usermboxname, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = NULL;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = DB->fetch(mbdb, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (!r && data) {
r = (*proc)(cbrock.inboxcase, 5, 1, rock);
}
}
else if (!strncmp(pattern, usermboxname, usermboxnamelen) &&
GLOB_TEST(cbrock.g, usermboxname) != -1) {
r = DB->fetch(mbdb, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (!r && data) {
r = (*proc)(usermboxname, usermboxnamelen, 1, rock);
}
}
strcpy(usermboxname+usermboxnamelen, ".");
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
if (r) goto done;
/* Find fixed-string pattern prefix */
for (p = pattern; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?') break;
}
prefixlen = p - pattern;
*p = '\0';
/*
* If user.X.* or INBOX.* can match pattern,
* search for those mailboxes next
*/
if (userid &&
(!strncmp(usermboxname, pattern, usermboxnamelen-1) ||
!strncasecmp("inbox.", pattern, prefixlen < 6 ? prefixlen : 6))) {
if (!strncmp(usermboxname, pattern, usermboxnamelen-1)) {
cbrock.inboxoffset = 0;
}
else {
cbrock.inboxoffset = strlen(userid);
}
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
r = DB->foreach(mbdb,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
}
if(!r) {
cbrock.find_namespace = NAMESPACE_USER;
cbrock.inboxoffset = 0;
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/* search for all remaining mailboxes.
just bother looking at the ones that have the same pattern
prefix. */
r = DB->foreach(mbdb,
pattern, prefixlen,
&find_p, &find_cb, &cbrock,
NULL);
}
done:
glob_free(&cbrock.g);
return r;
}
int mboxlist_findall_alt(struct namespace *namespace,
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock)
{
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_NAME+1], patbuf[MAX_MAILBOX_NAME+1];
int usermboxnamelen = 0;
const char *data;
int datalen;
int r = 0;
char *p;
int prefixlen, len;
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = namespace;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = isadmin;
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = 0; /* don't duplicate work */
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
/* Build usermboxname */
if (userid && !strchr(userid, '.') &&
strlen(userid)+5 < MAX_MAILBOX_NAME) {
strcpy(usermboxname, "user.");
strcat(usermboxname, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = 0;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = DB->fetch(mbdb, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (!r && data) {
r = (*proc)(cbrock.inboxcase, 5, 0, rock);
}
}
strcpy(usermboxname+usermboxnamelen, ".");
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
if (r) goto done;
glob_free(&cbrock.g);
/* Find fixed-string pattern prefix */
for (p = pattern; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?') break;
}
prefixlen = p - pattern;
/*
* Personal (INBOX) namespace
*
* Append pattern to "INBOX.", search for those mailboxes next
*/
if (userid) {
strcpy(patbuf, "INBOX.");
strcat(patbuf, pattern);
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.inboxoffset = strlen(userid);
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
DB->foreach(mbdb,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
glob_free(&cbrock.g);
}
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/*
* Other Users namespace
*
* If "Other Users*" can match pattern, search for those mailboxes next
*/
len = strlen(namespace->prefix[NAMESPACE_USER])-1;
if (!strncmp(namespace->prefix[NAMESPACE_USER], pattern,
prefixlen < len ? prefixlen : len)) {
if (prefixlen < len)
cbrock.g = glob_init(pattern+prefixlen, GLOB_HIERARCHY);
else {
strcpy(patbuf, "user");
strcat(patbuf, pattern+len);
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY);
}
cbrock.find_namespace = NAMESPACE_USER;
cbrock.inboxoffset = 0;
/* iterate through prefixes matching usermboxname */
DB->foreach(mbdb,
"user", 4,
&find_p, &find_cb, &cbrock,
NULL);
glob_free(&cbrock.g);
}
/*
* Shared namespace
*
* search for all remaining mailboxes.
* just bother looking at the ones that have the same pattern prefix.
*/
len = strlen(namespace->prefix[NAMESPACE_SHARED]);
if (!strncmp(namespace->prefix[NAMESPACE_SHARED], pattern,
prefixlen < len - 1 ? prefixlen : len - 1)) {
cbrock.find_namespace = NAMESPACE_SHARED;
cbrock.inboxoffset = 0;
if (prefixlen < len) {
/* Find pattern which matches shared namespace prefix */
for (p = pattern+prefixlen; *p; p++) {
if (*p == '%') continue;
else if (*p == '.') p++;
break;
}
if (!*p) {
/* special case: LIST "" % -- see if we have a shared mbox */
cbrock.g = glob_init("*", GLOB_HIERARCHY);
cbrock.checkshared = 1;
}
else {
cbrock.g = glob_init(p, GLOB_HIERARCHY);
}
DB->foreach(mbdb,
"", 0,
&find_p, &find_cb, &cbrock,
NULL);
}
else if (pattern[len-1] == '.') {
strcpy(patbuf, "");
strcat(patbuf, pattern+len);
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY);
pattern[prefixlen] = '\0';
DB->foreach(mbdb,
pattern+len, prefixlen-len,
&find_p, &find_cb, &cbrock,
NULL);
}
}
done:
glob_free(&cbrock.g);
return r;
}
/*
* Set the quota on or create a quota root
*/
int mboxlist_setquota(const char *root, int newquota, int force)
{
char quota_path[MAX_MAILBOX_PATH];
char pattern[MAX_MAILBOX_PATH];
struct quota quota;
int have_mailbox = 1;
int r, t;
if (!root[0] || root[0] == '.' || strchr(root, '/')
|| strchr(root, '*') || strchr(root, '%') || strchr(root, '?')) {
return IMAP_MAILBOX_BADNAME;
}
memset(&quota, 0, sizeof(struct quota));
quota.root = (char *) root;
mailbox_hash_quota(quota_path, root);
if ((quota.fd = open(quota_path, O_RDWR, 0)) != -1) {
/* Just lock and change it */
r = mailbox_lock_quota(&quota);
quota.limit = newquota;
if (!r) r = mailbox_write_quota(&quota);
if (quota.fd != -1) {
close(quota.fd);
}
return r;
}
/*
* Have to create a new quota root
*/
/* look for a top-level mailbox in the proposed quotaroot */
r = mboxlist_detail(quota.root, &t, NULL, NULL, NULL, NULL);
if (r) {
/* are we going to force the create anyway? */
if(!force) return r;
else {
have_mailbox = 0;
t = 0;
}
}
if(t & (MBTYPE_REMOTE | MBTYPE_MOVING)) {
/* Can't set quota on a remote mailbox */
return IMAP_MAILBOX_NOTSUPPORTED;
}
/* perhaps create .NEW, lock, check if it got recreated, move in place */
quota.lock_count = 1;
quota.used = 0;
quota.limit = newquota;
r = mailbox_write_quota(&quota);
if (r) {
return r;
}
strcpy(pattern, quota.root);
strcat(pattern, ".*");
/* top level mailbox */
if(have_mailbox)
mboxlist_changequota(quota.root, 0, 0, &quota);
/* submailboxes - we're using internal names here */
mboxlist_findall(NULL, pattern, 1, 0, 0, mboxlist_changequota, &quota);
r = mailbox_write_quota(&quota);
if (quota.fd != -1) {
close(quota.fd);
}
return r;
}
/*
* Remove a quota root
*/
int mboxlist_unsetquota(const char *root)
{
char quota_path[MAX_MAILBOX_PATH];
char pattern[MAX_MAILBOX_PATH];
int fd;
int r=0;
if (!root[0] || root[0] == '.' || strchr(root, '/')
|| strchr(root, '*') || strchr(root, '%') || strchr(root, '?')) {
return IMAP_MAILBOX_BADNAME;
}
mailbox_hash_quota(quota_path, root);
if ((fd = open(quota_path, O_RDWR, 0)) == -1) {
/* already unset */
return 0;
}
close(fd);
/*
* Have to remove it from all affected mailboxes
*/
strcpy(pattern, root);
strcat(pattern, ".*");
/* top level mailbox */
mboxlist_rmquota(root, 0, 0, (void *)root);
/* submailboxes - we're using internal names here */
mboxlist_findall(NULL, pattern, 1, 0, 0, mboxlist_rmquota, (void *)root);
if(unlink(quota_path) == -1) {
syslog(LOG_ERR, "could not unlink %s (%m)", quota_path);
r = IMAP_SYS_ERROR;
}
return r;
}
/*
* Retrieve internal information, for reconstructing mailboxes file
*/
void mboxlist_getinternalstuff(const char **listfnamep,
const char **newlistfnamep,
const char **basep,
unsigned long * sizep)
{
printf("yikes! don't reconstruct me!\n");
abort();
}
/*
* ACL access canonicalization routine which ensures that 'owner'
* retains lookup, administer, and create rights over a mailbox.
*/
int mboxlist_ensureOwnerRights(rock, identifier, access)
void *rock;
const char *identifier;
int access;
{
char *owner = (char *)rock;
if (strcmp(identifier, owner) != 0) return access;
return access|ACL_LOOKUP|ACL_ADMIN|ACL_CREATE;
}
/*
* Helper function to remove the quota root for 'name'
*/
static int mboxlist_rmquota(const char *name, int matchlen, int maycreate,
void *rock)
{
int r;
struct mailbox mailbox;
const char *oldroot = (const char *) rock;
assert(rock != NULL);
r = mailbox_open_header(name, 0, &mailbox);
if (r) goto error_noclose;
r = mailbox_lock_header(&mailbox);
if (r) goto error;
r = mailbox_open_index(&mailbox);
if (r) goto error;
r = mailbox_lock_index(&mailbox);
if (r) goto error;
if (mailbox.quota.root) {
if (strlen(mailbox.quota.root) != strlen(oldroot)
|| strcmp(mailbox.quota.root, oldroot)) {
/* Part of a different quota root */
mailbox_close(&mailbox);
return 0;
}
/* Need to clear the quota root */
free(mailbox.quota.root);
mailbox.quota.root = NULL;
r = mailbox_write_header(&mailbox);
if(r) goto error;
}
mailbox_close(&mailbox);
return 0;
error:
mailbox_close(&mailbox);
error_noclose:
syslog(LOG_ERR, "LOSTQUOTA: unable to remove quota root %s for %s: %s",
oldroot, name, error_message(r));
return 0;
}
/*
* Helper function to change the quota root for 'name' to that pointed
* to by the static global struct pointer 'mboxlist_newquota'.
*/
static int mboxlist_changequota(const char *name, int matchlen, int maycreate,
void *rock)
{
int r;
struct mailbox mailbox;
struct quota *mboxlist_newquota = (struct quota *) rock;
assert(rock != NULL);
r = mailbox_open_header(name, 0, &mailbox);
if (r) goto error_noclose;
r = mailbox_lock_header(&mailbox);
if (r) goto error;
r = mailbox_open_index(&mailbox);
if (r) goto error;
r = mailbox_lock_index(&mailbox);
if (r) goto error;
if (mailbox.quota.root) {
if (strlen(mailbox.quota.root) >= strlen(mboxlist_newquota->root)) {
/* Part of a child quota root */
mailbox_close(&mailbox);
return 0;
}
r = mailbox_lock_quota(&mailbox.quota);
if (r) goto error;
if (mailbox.quota.used >= mailbox.quota_mailbox_used) {
mailbox.quota.used -= mailbox.quota_mailbox_used;
}
else {
mailbox.quota.used = 0;
}
r = mailbox_write_quota(&mailbox.quota);
if (r) {
syslog(LOG_ERR,
"LOSTQUOTA: unable to record free of %lu bytes in quota %s",
mailbox.quota_mailbox_used, mailbox.quota.root);
}
mailbox_unlock_quota(&mailbox.quota);
free(mailbox.quota.root);
}
mailbox.quota.root = xstrdup(mboxlist_newquota->root);
r = mailbox_write_header(&mailbox);
if (r) goto error;
mboxlist_newquota->used += mailbox.quota_mailbox_used;
mailbox_close(&mailbox);
return 0;
error:
mailbox_close(&mailbox);
error_noclose:
syslog(LOG_ERR, "LOSTQUOTA: unable to change quota root for %s to %s: %s",
name, mboxlist_newquota->root, error_message(r));
/* Note, we're a callback, and it's not a huge tragedy if we
* fail, so we don't ever return a failure */
return 0;
}
void mboxlist_init(int myflags)
{
int r;
char dbdir[1024];
int flags = 0;
/* create the name of the db file */
strcpy(dbdir, config_dir);
strcat(dbdir, FNAME_DBDIR);
if (myflags & MBOXLIST_RECOVER) flags |= CYRUSDB_RECOVER;
r = DB->init(dbdir, flags);
if (r != CYRUSDB_OK) {
fatal("can't initialize mboxlist environment", EC_TEMPFAIL);
}
if (myflags & MBOXLIST_SYNC) {
r = DB->sync();
}
if(config_mupdate_server) {
/* We're going to need SASL */
r = sasl_client_init(NULL);
if(r != 0) {
syslog(LOG_ERR, "could not initialize SASL library");
fatal("could not initialize SASL library", EC_TEMPFAIL);
}
}
}
void mboxlist_open(char *fname)
{
int ret;
char *tofree = NULL;
/* create db file name */
if (!fname) {
fname = xmalloc(strlen(config_dir)+sizeof(FNAME_MBOXLIST));
tofree = fname;
strcpy(fname, config_dir);
strcat(fname, FNAME_MBOXLIST);
}
ret = DB->open(fname, &mbdb);
if (ret != 0) {
syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
cyrusdb_strerror(ret));
/* Exiting TEMPFAIL because Sendmail thinks this
EC_OSFILE == permanent failure. */
fatal("can't read mailboxes file", EC_TEMPFAIL);
}
if (tofree) free(tofree);
mboxlist_dbopen = 1;
}
void mboxlist_close(void)
{
int r;
if (mboxlist_dbopen) {
r = DB->close(mbdb);
if (r) {
syslog(LOG_ERR, "DBERROR: error closing mailboxes: %s",
cyrusdb_strerror(r));
}
mboxlist_dbopen = 0;
}
}
void mboxlist_done(void)
{
int r;
r = DB->done();
if (r) {
syslog(LOG_ERR, "DBERROR: error exiting application: %s",
cyrusdb_strerror(r));
}
}
/* hash the userid to a file containing the subscriptions for that user */
char *mboxlist_hash_usersubs(const char *userid)
{
char *fname = xmalloc(strlen(config_dir) + sizeof(FNAME_USERDIR) +
strlen(userid) + sizeof(FNAME_SUBSSUFFIX) + 10);
char c;
c = (char) dir_hash_c(userid);
sprintf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid,
FNAME_SUBSSUFFIX);
return fname;
}
/*
* Open the subscription list for 'userid'.
*
* On success, returns zero.
* On failure, returns an error code.
*/
static int
mboxlist_opensubs(const char *userid,
struct db **ret)
{
int r = 0;
char *subsfname;
/* Build subscription list filename */
subsfname = mboxlist_hash_usersubs(userid);
r = SUBDB->open(subsfname, ret);
if (r != CYRUSDB_OK) {
r = IMAP_IOERROR;
}
free(subsfname);
return r;
}
/*
* Close a subscription file
*/
static void mboxlist_closesubs(struct db *sub)
{
SUBDB->close(sub);
}
/*
* Find subscribed mailboxes that match 'pattern'.
* 'isadmin' is nonzero if user is a mailbox admin. 'userid'
* is the user's login id. For each matching mailbox, calls
* 'proc' with the name of the mailbox.
*/
int mboxlist_findsub(struct namespace *namespace __attribute__((unused)),
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state,
int (*proc)(), void *rock, int force)
{
struct db *subs = NULL;
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_NAME+1];
int usermboxnamelen = 0;
const char *data;
int datalen;
int r = 0;
char *p;
int prefixlen;
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = NULL;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = 1; /* user can always see their subs */
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = !force;
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
/* open the subscription file that contains the mailboxes the
user is subscribed to */
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
goto done;
}
/* Build usermboxname */
if (userid && !strchr(userid, '.') &&
strlen(userid)+5 < MAX_MAILBOX_NAME) {
strcpy(usermboxname, "user.");
strcat(usermboxname, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = 0;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = SUBDB->fetch(subs, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (!r && data) {
r = (*proc)(cbrock.inboxcase, 5, 1, rock);
}
}
else if (!strncmp(pattern, usermboxname, usermboxnamelen) &&
GLOB_TEST(cbrock.g, usermboxname) != -1) {
r = SUBDB->fetch(subs, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (!r && data) {
r = (*proc)(usermboxname, usermboxnamelen, 1, rock);
}
}
strcpy(usermboxname+usermboxnamelen, ".");
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
if (r) goto done;
/* Find fixed-string pattern prefix */
for (p = pattern; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?') break;
}
prefixlen = p - pattern;
*p = '\0';
/*
* If user.X.* or INBOX.* can match pattern,
* search for those mailboxes next
*/
if (userid &&
(!strncmp(usermboxname, pattern, usermboxnamelen-1) ||
!strncasecmp("inbox.", pattern, prefixlen < 6 ? prefixlen : 6))) {
if (!strncmp(usermboxname, pattern, usermboxnamelen-1)) {
cbrock.inboxoffset = 0;
}
else {
cbrock.inboxoffset = strlen(userid);
}
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
SUBDB->foreach(subs,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
cbrock.find_namespace = NAMESPACE_USER;
cbrock.inboxoffset = 0;
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/* search for all remaining mailboxes.
just bother looking at the ones that have the same pattern prefix. */
SUBDB->foreach(subs, pattern, prefixlen,
&find_p, &find_cb, &cbrock, NULL);
done:
if (subs) mboxlist_closesubs(subs);
glob_free(&cbrock.g);
return r;
}
int mboxlist_findsub_alt(struct namespace *namespace,
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state,
int (*proc)(), void *rock, int force)
{
struct db *subs = NULL;
struct find_rock cbrock;
char usermboxname[MAX_MAILBOX_NAME+1], patbuf[MAX_MAILBOX_NAME+1];
int usermboxnamelen = 0;
const char *data;
int datalen;
int r = 0;
char *p;
int prefixlen, len;
cbrock.g = glob_init(pattern, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.namespace = namespace;
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.isadmin = 1; /* user can always see their subs */
cbrock.auth_state = auth_state;
cbrock.checkmboxlist = !force;
cbrock.checkshared = 0;
cbrock.proc = proc;
cbrock.procrock = rock;
/* open the subscription file that contains the mailboxes the
user is subscribed to */
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
goto done;
}
/* Build usermboxname */
if (userid && !strchr(userid, '.') &&
strlen(userid)+5 < MAX_MAILBOX_NAME) {
strcpy(usermboxname, "user.");
strcat(usermboxname, userid);
usermboxnamelen = strlen(usermboxname);
}
else {
userid = 0;
}
/* Check for INBOX first of all */
if (userid) {
if (GLOB_TEST(cbrock.g, "INBOX") != -1) {
r = SUBDB->fetch(subs, usermboxname, usermboxnamelen,
&data, &datalen, NULL);
if (!r && data) {
r = (*proc)(cbrock.inboxcase, 5, 0, rock);
}
}
strcpy(usermboxname+usermboxnamelen, ".");
usermboxnamelen++;
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
if (r) goto done;
glob_free(&cbrock.g);
/* Find fixed-string pattern prefix */
for (p = pattern; *p; p++) {
if (*p == '*' || *p == '%' || *p == '?') break;
}
prefixlen = p - pattern;
/*
* Personal (INBOX) namespace
*
* Append pattern to "INBOX.", search for those subscriptions next
*/
if (userid) {
strcpy(patbuf, "INBOX.");
strcat(patbuf, pattern);
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY|GLOB_INBOXCASE);
cbrock.inboxcase = glob_inboxcase(cbrock.g);
cbrock.inboxoffset = strlen(userid);
cbrock.find_namespace = NAMESPACE_INBOX;
/* iterate through prefixes matching usermboxname */
SUBDB->foreach(subs,
usermboxname, usermboxnamelen,
&find_p, &find_cb, &cbrock,
NULL);
glob_free(&cbrock.g);
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
} else {
cbrock.usermboxname = NULL;
cbrock.usermboxnamelen = 0;
}
if (usermboxnamelen) {
usermboxname[--usermboxnamelen] = '\0';
cbrock.usermboxname = usermboxname;
cbrock.usermboxnamelen = usermboxnamelen;
}
/*
* Other Users namespace
*
* If "Other Users*" can match pattern, search for those subscriptions next
*/
len = strlen(namespace->prefix[NAMESPACE_USER])-1;
if (!strncmp(namespace->prefix[NAMESPACE_USER], pattern,
prefixlen < len ? prefixlen : len)) {
if (prefixlen < len)
cbrock.g = glob_init(pattern+prefixlen, GLOB_HIERARCHY);
else {
strcpy(patbuf, "user");
strcat(patbuf, pattern+len);
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY);
}
cbrock.find_namespace = NAMESPACE_USER;
cbrock.inboxoffset = 0;
/* iterate through prefixes matching usermboxname */
SUBDB->foreach(subs,
"user", 4,
&find_p, &find_cb, &cbrock,
NULL);
glob_free(&cbrock.g);
}
/*
* Shared namespace
*
* search for all remaining subscriptions.
* just bother looking at the ones that have the same pattern prefix.
*/
len = strlen(namespace->prefix[NAMESPACE_SHARED]);
if (!strncmp(namespace->prefix[NAMESPACE_SHARED], pattern,
prefixlen < len - 1 ? prefixlen : len - 1)) {
cbrock.find_namespace = NAMESPACE_SHARED;
cbrock.inboxoffset = 0;
if (prefixlen < len) {
/* Find pattern which matches shared namespace prefix */
for (p = pattern+prefixlen; *p; p++) {
if (*p == '%') continue;
else if (*p == '.') p++;
break;
}
if (!*p) {
/* special case: LSUB "" % -- see if we have a shared mbox */
cbrock.g = glob_init("*", GLOB_HIERARCHY);
cbrock.checkshared = 1;
}
else {
cbrock.g = glob_init(p, GLOB_HIERARCHY);
}
SUBDB->foreach(subs,
"", 0,
&find_p, &find_cb, &cbrock,
NULL);
}
else if (pattern[len-1] == '.') {
strcpy(patbuf, "");
strcat(patbuf, pattern+len);
cbrock.g = glob_init(patbuf, GLOB_HIERARCHY);
pattern[prefixlen] = '\0';
SUBDB->foreach(subs,
pattern+len, prefixlen-len,
&find_p, &find_cb, &cbrock,
NULL);
}
}
done:
if (subs) mboxlist_closesubs(subs);
glob_free(&cbrock.g);
return r;
}
/*
* Change 'user's subscription status for mailbox 'name'.
* Subscribes if 'add' is nonzero, unsubscribes otherwise.
* if 'force' is set, force the subscription through even if
* we don't know about 'name'.
*/
int mboxlist_changesub(const char *name, const char *userid,
struct auth_state *auth_state, int add, int force)
{
int r;
char *acl;
struct db *subs;
if ((r = mboxlist_opensubs(userid, &subs)) != 0) {
return r;
}
if (add && !force) {
/* Ensure mailbox exists and can be either seen or read by user */
if ((r = mboxlist_lookup(name, NULL, &acl, NULL))!=0) {
mboxlist_closesubs(subs);
return r;
}
if ((cyrus_acl_myrights(auth_state, acl) & (ACL_READ|ACL_LOOKUP)) == 0) {
mboxlist_closesubs(subs);
return IMAP_MAILBOX_NONEXISTENT;
}
}
if (add) {
r = SUBDB->store(subs, name, strlen(name), "", 0, NULL);
} else {
r = SUBDB->delete(subs, name, strlen(name), NULL, 0);
/* if it didn't exist, that's ok */
if (r == CYRUSDB_EXISTS) r = CYRUSDB_OK;
}
switch (r) {
case CYRUSDB_OK:
r = 0;
break;
default:
r = IMAP_IOERROR;
break;
}
mboxlist_closesubs(subs);
return r;
}
diff --git a/imap/mboxlist.h b/imap/mboxlist.h
index 1d33e2086..f85b30ddd 100644
--- a/imap/mboxlist.h
+++ b/imap/mboxlist.h
@@ -1,190 +1,192 @@
/* mboxlist.h -- Mailbox list manipulation routines
*
*
* Copyright (c) 1999-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: mboxlist.h,v 1.32 2002/05/23 21:12:39 rjs3 Exp $
+ * $Id: mboxlist.h,v 1.33 2002/05/29 16:49:16 rjs3 Exp $
*/
#ifndef INCLUDED_MBOXLIST_H
#define INCLUDED_MBOXLIST_H
#include "config.h"
#include "cyrusdb.h"
#include "mailbox.h"
#include "auth.h"
#include "mboxname.h"
extern struct db *mbdb;
/*
* Maximum length of partition name. [config.c has a limit of 70]
*/
#define MAX_PARTITION_LEN 64
/* flags for types of mailboxes */
#define MBTYPE_REMOTE 0x01 /* Not on this server (part is remote host) */
#define MBTYPE_RESERVE 0x02 /* Reserved [mupdate/imapd] /
Rename Target [imapd] (part is normal, but
you are not allowed to create this mailbox,
even though it doesn't actually exist */
#define MBTYPE_NETNEWS 0x04 /* Netnews Mailbox */
#define MBTYPE_MOVING 0x08 /* Mailbox in mid-transfer (part is remotehost!localpart) */
/* master name of the mailboxes file */
#define FNAME_MBOXLIST "/mailboxes.db"
#define HOSTNAME_SIZE 512
/* each mailbox has the following data */
struct mbox_entry {
char name[MAX_MAILBOX_NAME];
int mbtype;
char partition[MAX_PARTITION_LEN];
/* holds remote machine for REMOTE mailboxes */
char acls[1];
};
/* Lookup 'name' in the mailbox list. */
int mboxlist_lookup(const char *name, char **pathp, char **aclp, void *tid);
/* Lookup 'name' and get more detail */
int mboxlist_detail(const char *name, int *typep, char **pathp, char **partp,
char **aclp, struct txn *tid);
/* insert a stub entry */
int mboxlist_insertremote(const char *name, int mbtype, const char *host,
const char *acl, void **rettid);
/* Update a mailbox's entry */
int mboxlist_update(char *name, int flags, const char *part, const char *acl);
/* check user's ability to create mailbox */
int mboxlist_createmailboxcheck(char *name, int mbtype, char *partition,
int isadmin, char *userid,
struct auth_state *auth_state,
char **newacl, char **newpartition);
/* create mailbox */
/* localonly creates the local mailbox without touching mupdate */
/* forceuser allows the creation of user.x.<name> without a user.x */
int mboxlist_createmailbox(char *name, int mbtype, char *partition,
int isadmin, char *userid,
struct auth_state *auth_state,
int localonly, int forceuser);
/* Delete a mailbox. */
/* setting local_only disables any communication with the mupdate server
* and deletes the mailbox from the filesystem regardless of if it is
* MBTYPE_REMOTE or not */
+/* force ignores errors and just tries to wipe the mailbox off the face of
+ * the planet */
int mboxlist_deletemailbox(const char *name, int isadmin, char *userid,
struct auth_state *auth_state, int checkacl,
- int local_only);
+ int local_only, int force);
/* Rename/move a mailbox (hierarchical) */
int mboxlist_renamemailbox(char *oldname, char *newname, char *partition,
int isadmin, char *userid,
struct auth_state *auth_state);
/* change ACL */
int mboxlist_setacl(char *name, char *identifier, char *rights, int isadmin,
char *userid, struct auth_state *auth_state);
/* Find all mailboxes that match 'pattern'. */
int mboxlist_findall(struct namespace *namespace,
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(), void *rock);
int mboxlist_findall_std(struct namespace *namespace,
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock);
int mboxlist_findall_alt(struct namespace *namespace, char *pattern,
int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock);
/* Find subscribed mailboxes that match 'pattern'. */
int mboxlist_findsub(struct namespace *namespace,
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(), void *rock,
int force);
int mboxlist_findsub_std(struct namespace *namespace,
char *pattern, int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock, int force);
int mboxlist_findsub_alt(struct namespace *namespace, char *pattern,
int isadmin, char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock, int force);
/* given a mailbox 'name', where should we stage messages for it?
'stagedir' should be MAX_MAILBOX_PATH. */
int mboxlist_findstage(const char *name, char *stagedir);
/* Change 'user's subscription status for mailbox 'name'. */
int mboxlist_changesub(const char *name, const char *userid,
struct auth_state *auth_state, int add, int force);
/* get name a file containing subscriptions for 'userid' */
char *mboxlist_hash_usersubs(const char *userid);
/* set or create quota root */
int mboxlist_setquota(const char *root, int newquota, int force);
int mboxlist_unsetquota(const char *root);
/* returns a malloc() string that is the representation in the mailboxes
file. for ctl_mboxlist */
char *mboxlist_makeentry(int mbtype, const char *part, const char *acl);
/* open the mailboxes db */
void mboxlist_open(char *name);
/* close the database */
void mboxlist_close(void);
/* initialize database structures */
#define MBOXLIST_RECOVER 0x01
#define MBOXLIST_SYNC 0x02
void mboxlist_init(int flags);
/* done with database stuff */
void mboxlist_done(void);
#endif
diff --git a/imap/mupdate.c b/imap/mupdate.c
index 149e0a3dc..7050dbbc6 100644
--- a/imap/mupdate.c
+++ b/imap/mupdate.c
@@ -1,1630 +1,1630 @@
/* mupdate.c -- cyrus murder database master
*
- * $Id: mupdate.c,v 1.58 2002/05/24 16:07:23 ken3 Exp $
+ * $Id: mupdate.c,v 1.59 2002/05/29 16:49:16 rjs3 Exp $
* Copyright (c) 2001 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.
*/
#include <config.h>
#include <sys/time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <assert.h>
#include <syslog.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "mupdate.h"
#include "mupdate-client.h"
#include "xmalloc.h"
#include "iptostring.h"
#include "mailbox.h"
#include "mboxlist.h"
#include "exitcodes.h"
#include "prot.h"
#include "imapconf.h"
#include "version.h"
#include "mpool.h"
static int masterp = 0;
enum {
poll_interval = 1,
update_wait = 5
};
struct pending {
struct pending *next;
char mailbox[MAX_MAILBOX_NAME];
};
struct conn {
int fd;
struct protstream *pin;
struct protstream *pout;
sasl_conn_t *saslconn;
char *userid;
const char *clienthost;
struct
{
char *ipremoteport;
char *iplocalport;
} saslprops;
/* pending changes to send, in reverse order */
const char *streaming; /* tag */
pthread_mutex_t m;
pthread_cond_t cond;
struct pending *plist;
struct conn *updatelist_next;
struct prot_waitevent *ev; /* invoked every 'update_wait' seconds
to send out updates */
/* Prefix for list commands */
const char *list_prefix;
size_t list_prefix_len;
struct conn *next;
};
int ready_for_connections = 0;
pthread_cond_t ready_for_connections_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t ready_for_connections_mutex = PTHREAD_MUTEX_INITIALIZER;
int synced = 0;
pthread_cond_t synced_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t synced_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t connlist_mutex = PTHREAD_MUTEX_INITIALIZER;
struct conn *connlist = NULL;
/* ---- database access ---- */
pthread_mutex_t mailboxes_mutex = PTHREAD_MUTEX_INITIALIZER;
struct conn *updatelist = NULL;
/* --- prototypes --- */
void cmd_authenticate(struct conn *C,
const char *tag, const char *mech,
const char *clientstart);
void cmd_set(struct conn *C,
const char *tag, const char *mailbox,
const char *server, const char *acl, enum settype t);
void cmd_find(struct conn *C, const char *tag, const char *mailbox,
int dook, int send_delete);
void cmd_list(struct conn *C, const char *tag, const char *host_prefix);
void cmd_startupdate(struct conn *C, const char *tag);
void shut_down(int code);
static int reset_saslconn(struct conn *c);
void database_init();
void sendupdates(struct conn *C, int flushnow);
/* --- prototypes in mupdate-client.c */
void *mupdate_client_start(void *rock);
/* --- mutex wrapper functions for SASL */
void *my_mutex_new(void)
{
pthread_mutex_t *ret = (pthread_mutex_t *)xmalloc(sizeof(pthread_mutex_t));
pthread_mutex_init(ret, NULL);
return ret;
}
int my_mutex_destroy(pthread_mutex_t *m)
{
if(!m) return SASL_BADPARAM;
if(pthread_mutex_destroy(m)) return SASL_FAIL;
free(m);
return SASL_OK;
}
/* end mutex wrapper functions */
static struct conn *conn_new(int fd)
{
struct conn *C = xzmalloc(sizeof(struct conn));
C->fd = fd;
pthread_mutex_lock(&connlist_mutex); /* LOCK */
C->next = connlist;
connlist = C;
pthread_mutex_unlock(&connlist_mutex); /* UNLOCK */
return C;
}
static void conn_free(struct conn *C)
{
if (C->streaming) { /* remove from updatelist */
struct conn *upc;
pthread_mutex_lock(&mailboxes_mutex);
if (C == updatelist) {
/* first thing in updatelist */
updatelist = C->updatelist_next;
} else {
/* find in update list */
for (upc = updatelist; upc->updatelist_next != NULL;
upc = upc->updatelist_next) {
if (upc->updatelist_next == C) break;
}
/* must find it ! */
assert(upc->updatelist_next == C);
upc->updatelist_next = C->updatelist_next;
}
pthread_mutex_unlock(&mailboxes_mutex);
}
pthread_mutex_lock(&connlist_mutex); /* LOCK */
/* remove from connlist */
if (C == connlist) {
connlist = connlist->next;
} else {
struct conn *t;
for (t = connlist; t->next != NULL; t = t->next) {
if (t->next == C) break;
}
assert(t != NULL);
t->next = C->next;
}
pthread_mutex_unlock(&connlist_mutex); /* UNLOCK */
if (C->ev) prot_removewaitevent(C->pin, C->ev);
if (C->pin) prot_free(C->pin);
if (C->pout) prot_free(C->pout);
close(C->fd);
if (C->saslconn) sasl_dispose(&C->saslconn);
/* free update list */
free(C);
}
/* should we allow users to proxy? return SASL_OK if yes,
SASL_BADAUTH otherwise */
static int mysasl_authproc(sasl_conn_t *conn,
void *context __attribute__((unused)),
const char *requested_user __attribute__((unused)),
unsigned rlen __attribute__((unused)),
const char *auth_identity, unsigned alen,
const char *def_realm __attribute__((unused)),
unsigned urlen __attribute__((unused)),
struct propctx *propctx __attribute__((unused)))
{
const char *val;
char *realm;
int allowed=0;
struct auth_state *authstate;
char auth_id_buf[4096];
if(alen > sizeof(auth_id_buf)-1) return SASL_BUFOVER;
memcpy(auth_id_buf, auth_identity, alen);
auth_id_buf[alen] = '\0';
/* check if remote realm */
if ((realm = strchr(auth_id_buf, '@'))!=NULL) {
realm++;
val = config_getstring("loginrealms", "");
while (*val) {
if (!strncasecmp(val, realm, strlen(realm)) &&
(!val[strlen(realm)] || isspace((int) val[strlen(realm)]))) {
break;
}
/* not this realm, try next one */
while (*val && !isspace((int) *val)) val++;
while (*val && isspace((int) *val)) val++;
}
if (!*val) {
sasl_seterror(conn, 0, "cross-realm login %s denied",
auth_id_buf);
return SASL_BADAUTH;
}
}
/* ok, is auth_identity an admin?
* for now only admins can do mupdate from another machine
*/
authstate = auth_newstate(auth_id_buf, NULL);
allowed = authisa(authstate, "mupdate", "admins");
auth_freestate(authstate);
if (!allowed) {
sasl_seterror(conn, 0, "only admins may authenticate");
return SASL_BADAUTH;
}
return SASL_OK;
}
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, &mysasl_authproc, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
/*
* 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 __attribute__((unused)))
{
int r;
int opt;
config_changeident("mupdate");
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
/* set signal handlers */
signals_set_shutdown(&shut_down);
signals_add_handlers();
signal(SIGPIPE, SIG_IGN);
/* set the SASL allocation functions */
sasl_set_alloc((sasl_malloc_t *) &xmalloc,
(sasl_calloc_t *) &calloc,
(sasl_realloc_t *) &xrealloc,
(sasl_free_t *) &free);
/* set the SASL mutex functions */
sasl_set_mutex((sasl_mutex_alloc_t *) &my_mutex_new,
(sasl_mutex_lock_t *) &pthread_mutex_lock,
(sasl_mutex_unlock_t *) &pthread_mutex_unlock,
(sasl_mutex_free_t *) &my_mutex_destroy);
/* load the SASL plugins */
if ((r = sasl_server_init(mysasl_cb, "Cyrus")) != SASL_OK) {
syslog(LOG_ERR, "SASL failed initializing: sasl_server_init(): %s",
sasl_errstring(r, NULL, NULL));
return EC_SOFTWARE;
}
if ((r = sasl_client_init(NULL)) != SASL_OK) {
syslog(LOG_ERR, "SASL failed initializing: sasl_client_init(): %s",
sasl_errstring(r, NULL, NULL));
return EC_SOFTWARE;
}
/* see if we're the master or a slave */
while ((opt = getopt(argc, argv, "C:m")) != EOF) {
switch (opt) {
case 'C':
break;
case 'm':
masterp = 1;
break;
default:
break;
}
}
/* Slave Skiplist Fast Resync? */
if(!masterp && config_getswitch("mupdate_slave_fast_resync",0)) {
char fname[4096];
if(snprintf(fname,4096,"%s%s",config_dir,FNAME_MBOXLIST) == -1)
fatal("mboxlist database filename too large",EC_TEMPFAIL);
putenv("CYRUS_SKIPLIST_UNSAFE=1");
unlink(fname);
}
database_init();
if (!masterp) {
pthread_t t;
r = pthread_create(&t, NULL, &mupdate_client_start, NULL);
if(r == 0) {
pthread_detach(t);
} else {
syslog(LOG_ERR, "could not start client thread");
return EC_SOFTWARE;
}
/* Wait until they sync the database */
pthread_mutex_lock(&synced_mutex);
if(!synced)
pthread_cond_wait(&synced_cond, &synced_mutex);
pthread_mutex_unlock(&synced_mutex);
} else {
mupdate_ready();
}
return 0;
}
/* called if 'service_init()' was called but not 'service_main()' */
void service_abort(int error)
{
exit(error);
}
void fatal(const char *s, int code)
{
syslog(LOG_ERR, "%s", s);
exit(code);
}
#define CHECKNEWLINE(c, ch) do { if ((ch) == '\r') (ch)=prot_getc((c)->pin); \
if ((ch) != '\n') goto extraargs; } while (0)
void cmdloop(struct conn *c)
{
struct buf tag, cmd, arg1, arg2, arg3;
const char *mechs;
int ret;
unsigned int mechcount;
char slavebuf[4096];
syslog(LOG_DEBUG, "starting cmdloop() on fd %d", c->fd);
/* zero out struct bufs */
/* Note that since they live on the stack for the duration of the
* connection we don't need to fill up the struct conn with them */
memset(&tag, 0, sizeof(struct buf));
memset(&cmd, 0, sizeof(struct buf));
memset(&arg1, 0, sizeof(struct buf));
memset(&arg2, 0, sizeof(struct buf));
memset(&arg3, 0, sizeof(struct buf));
ret=sasl_listmech(c->saslconn, NULL, "* AUTH \"", "\" \"", "\"",
&mechs, NULL, &mechcount);
/* AUTH banner is mandatory */
if(!masterp) {
const char *mupdate_server = config_getstring("mupdate_server", NULL);
if(!mupdate_server)
fatal("mupdate server was not specified for slave", EC_TEMPFAIL);
snprintf(slavebuf, sizeof(slavebuf), "mupdate://%s", mupdate_server);
}
prot_printf(c->pout,
"%s\r\n* OK MUPDATE \"%s\" \"Cyrus Murder\" \"%s\" \"%s\"\r\n",
(ret == SASL_OK && mechcount > 0) ? mechs : "* AUTH",
config_servername,
CYRUS_VERSION, masterp ? "(master)" : slavebuf);
for (;;) {
int ch;
char *p;
signals_poll();
ch = getword(c->pin, &tag);
if (ch == EOF && errno == EAGAIN) {
/* streaming and no input from client */
continue;
}
if (ch == EOF) {
const char *err;
if ((err = prot_error(c->pin)) != NULL) {
syslog(LOG_WARNING, "%s, closing connection", err);
prot_printf(c->pout, "* BYE \"%s\"\r\n", err);
}
goto done;
}
/* send out any updates we have pending */
if (c->streaming) {
sendupdates(c, 0); /* don't flush pout though */
}
if (ch != ' ') {
prot_printf(c->pout, "%s BAD \"Need command\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
}
/* parse command name */
ch = getword(c->pin, &cmd);
if (!cmd.s[0]) {
prot_printf(c->pout, "%s BAD \"Null command\"\r\n", tag.s);
eatline(c->pin, ch);
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);
}
switch (cmd.s[0]) {
case 'A':
if (!strcmp(cmd.s, "Authenticate")) {
int opt = 0;
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
if (ch == ' ') {
ch = getstring(c->pin, c->pout, &arg2);
opt = 1;
}
CHECKNEWLINE(c, ch);
if (c->userid) {
prot_printf(c->pout,
"%s BAD \"already authenticated\"\r\n",
tag.s);
continue;
}
cmd_authenticate(c, tag.s, arg1.s, opt ? arg2.s : NULL);
}
else if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Activate")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg2);
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg3);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
if (!masterp) goto masteronly;
cmd_set(c, tag.s, arg1.s, arg2.s, arg3.s, SET_ACTIVE);
}
else goto badcmd;
break;
case 'D':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Deactivate")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg2);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
if (!masterp) goto masteronly;
cmd_set(c, tag.s, arg1.s, arg2.s, NULL, SET_DEACTIVATE);
}
else if (!strcmp(cmd.s, "Delete")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
if (!masterp) goto masteronly;
cmd_set(c, tag.s, arg1.s, NULL, NULL, SET_DELETE);
}
else goto badcmd;
break;
case 'F':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Find")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
cmd_find(c, tag.s, arg1.s, 1, 0);
}
else goto badcmd;
break;
case 'L':
if (!strcmp(cmd.s, "Logout")) {
CHECKNEWLINE(c, ch);
prot_printf(c->pout, "%s OK \"bye-bye\"\r\n", tag.s);
goto done;
}
else if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "List")) {
int opt = 0;
if (ch == ' ') {
/* Optional partition/host prefix parameter */
ch = getstring(c->pin, c->pout, &arg1);
opt = 1;
}
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
cmd_list(c, tag.s, opt ? arg1.s : NULL);
prot_printf(c->pout, "%s OK \"list complete\"\r\n", tag.s);
}
else goto badcmd;
break;
case 'N':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Noop")) {
CHECKNEWLINE(c, ch);
prot_printf(c->pout, "%s OK \"Noop done\"\r\n", tag.s);
}
else goto badcmd;
break;
case 'R':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Reserve")) {
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg1);
if (ch != ' ') goto missingargs;
ch = getstring(c->pin, c->pout, &arg2);
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
if (!masterp) goto masteronly;
cmd_set(c, tag.s, arg1.s, arg2.s, NULL, SET_RESERVE);
}
else goto badcmd;
break;
case 'U':
if (!c->userid) goto nologin;
else if (!strcmp(cmd.s, "Update")) {
CHECKNEWLINE(c, ch);
if (c->streaming) goto notwhenstreaming;
cmd_startupdate(c, tag.s);
}
else goto badcmd;
break;
default:
badcmd:
prot_printf(c->pout, "%s BAD \"Unrecognized command\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
extraargs:
prot_printf(c->pout, "%s BAD \"Extra arguments\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
missingargs:
prot_printf(c->pout, "%s BAD \"Missing arguments\"\r\n", tag.s);
eatline(c->pin, ch);
continue;
notwhenstreaming:
prot_printf(c->pout, "%s BAD \"not legal when streaming\"\r\n",
tag.s);
continue;
masteronly:
prot_printf(c->pout,
"%s BAD \"read-only session\"\r\n",
tag.s);
continue;
}
continue;
nologin:
prot_printf(c->pout, "%s BAD Please login first\r\n", tag.s);
eatline(c->pin, ch);
continue;
}
done:
prot_flush(c->pout);
/* free struct bufs */
freebuf(&tag);
freebuf(&cmd);
freebuf(&arg1);
freebuf(&arg2);
freebuf(&arg3);
syslog(LOG_DEBUG, "ending cmdloop() on fd %d", c->fd);
}
void *start(void *rock)
{
struct conn *c = (struct conn *) rock;
struct sockaddr_in localaddr, remoteaddr;
int haveaddr = 0;
int salen;
int secflags, plaintext_result;
sasl_security_properties_t *secprops = NULL;
char localip[60], remoteip[60];
char clienthost[250];
struct hostent *hp;
pthread_mutex_lock(&ready_for_connections_mutex);
/* are we ready to take connections? */
while(!ready_for_connections) {
pthread_cond_wait(&ready_for_connections_cond,
&ready_for_connections_mutex);
}
pthread_mutex_unlock(&ready_for_connections_mutex);
c->pin = prot_new(c->fd, 0);
c->pout = prot_new(c->fd, 1);
c->clienthost = clienthost;
prot_setflushonread(c->pin, c->pout);
prot_settimeout(c->pin, 180*60);
/* Find out name of client host */
salen = sizeof(remoteaddr);
if (getpeername(c->fd, (struct sockaddr *)&remoteaddr, &salen) == 0 &&
remoteaddr.sin_family == AF_INET) {
hp = gethostbyaddr((char *)&remoteaddr.sin_addr,
sizeof(remoteaddr.sin_addr), AF_INET);
if (hp != NULL) {
strncpy(clienthost, hp->h_name, sizeof(clienthost)-30);
clienthost[sizeof(clienthost)-30] = '\0';
} else {
clienthost[0] = '\0';
}
strcat(clienthost, "[");
strcat(clienthost, inet_ntoa(remoteaddr.sin_addr));
strcat(clienthost, "]");
salen = sizeof(localaddr);
if (getsockname(c->fd, (struct sockaddr *)&localaddr, &salen) == 0
&& iptostring((struct sockaddr *)&remoteaddr,
sizeof(struct sockaddr_in), remoteip, 60) == 0
&& iptostring((struct sockaddr *)&localaddr,
sizeof(struct sockaddr_in), localip, 60) == 0) {
haveaddr = 1;
}
}
if(haveaddr) {
c->saslprops.ipremoteport = remoteip;
c->saslprops.iplocalport = localip;
}
/* create sasl connection */
if (sasl_server_new("mupdate",
config_servername, NULL,
(haveaddr ? localip : NULL),
(haveaddr ? remoteip : NULL),
NULL, 0,
&c->saslconn) != SASL_OK) {
fatal("SASL failed initializing: sasl_server_new()", EC_TEMPFAIL);
}
/* set my allowable security properties */
secflags = SASL_SEC_NOANONYMOUS;
plaintext_result = config_getswitch("allowplaintext",1);
if (!config_getswitch("mupdate_allowplaintext", plaintext_result)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
sasl_setprop(c->saslconn, SASL_SEC_PROPS, secprops);
cmdloop(c);
close(c->fd);
conn_free(c);
return NULL;
}
/*
* run for each accepted connection
*/
int service_main_fd(int fd,
int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
/* spawn off a thread to handle this connection */
pthread_t t;
struct conn *c = conn_new(fd);
int r;
r = pthread_create(&t, NULL, &start, c);
if (r == 0) {
pthread_detach(t);
}
return 0;
}
/* read from disk database must be unlocked. */
void database_init()
{
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
mboxlist_init(0);
mboxlist_open(NULL);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
}
/* log change to database. database must be locked. */
void database_log(const struct mbent *mb)
{
switch (mb->t) {
case SET_ACTIVE:
mboxlist_insertremote(mb->mailbox, 0, mb->server, mb->acl, NULL);
break;
case SET_RESERVE:
mboxlist_insertremote(mb->mailbox, MBTYPE_RESERVE, mb->server,
"", NULL);
break;
case SET_DELETE:
- mboxlist_deletemailbox(mb->mailbox, 1, "", NULL, 0, 0);
+ mboxlist_deletemailbox(mb->mailbox, 1, "", NULL, 0, 0, 0);
break;
case SET_DEACTIVATE:
/* SET_DEACTIVATE is not a real value that an actual
mailbox can have! */
abort();
}
}
/* lookup in database. database must be locked */
/* This could probabally be more efficient and avoid some copies */
/* passing in a NULL pool implies that we should use regular xmalloc,
* a non-null pool implies we should use the mpool functionality */
struct mbent *database_lookup(const char *name, struct mpool *pool)
{
char *path, *acl;
int type;
struct mbent *out;
if(!name) return NULL;
if(mboxlist_detail(name, &type, &path, NULL, &acl, NULL))
return NULL;
if(type & MBTYPE_RESERVE) {
if(!pool) out = xmalloc(sizeof(struct mbent) + 1);
else out = mpool_malloc(pool, sizeof(struct mbent) + 1);
out->t = SET_RESERVE;
out->acl[0] = '\0';
} else {
if(!pool) out = xmalloc(sizeof(struct mbent) + strlen(acl));
else out = mpool_malloc(pool, sizeof(struct mbent) + strlen(acl));
out->t = SET_ACTIVE;
strcpy(out->acl, acl);
}
out->mailbox = (pool) ? mpool_strdup(pool, name) : xstrdup(name);
out->server = (pool) ? mpool_strdup(pool, path) : xstrdup(path);
return out;
}
void cmd_authenticate(struct conn *C,
const char *tag, const char *mech,
const char *clientstart)
{
int r;
char *in = NULL;
const char *out = NULL;
unsigned int inlen = 0, outlen = 0;
if(clientstart && clientstart[0]) {
unsigned len = strlen(clientstart);
in = xmalloc(len);
r = sasl_decode64(clientstart, len, in, len, &inlen);
if(r != SASL_OK) {
prot_printf(C->pout, "%s NO \"cannot base64 decode\"\r\n",tag);
free(in);
return;
}
}
r = sasl_server_start(C->saslconn, mech, in, inlen, &out, &outlen);
free(in); in=NULL;
if(r == SASL_NOMECH) {
prot_printf(C->pout,
"%s NO \"unknown authentication mechanism\"\r\n",tag);
return;
}
while(r == SASL_CONTINUE) {
char buf[4096];
char inbase64[4096];
char *p;
unsigned len;
if(out) {
r = sasl_encode64(out, outlen,
inbase64, sizeof(inbase64), NULL);
if(r != SASL_OK) break;
/* send out */
prot_printf(C->pout, "%s\r\n", inbase64);
prot_flush(C->pout);
}
/* read a line */
if(!prot_fgets(buf, sizeof(buf)-1, C->pin))
return;
p = buf + strlen(buf) - 1;
if(p >= buf && *p == '\n') *p-- = '\0';
if(p >= buf && *p == '\r') *p-- = '\0';
if(buf[0] == '*') {
prot_printf(C->pout,
"%s NO \"client canceled authentication\"\r\n",
tag);
reset_saslconn(C);
return;
}
len = strlen(buf);
in = xmalloc(len+1);
r = sasl_decode64(buf, len, in, len, &inlen);
if(r != SASL_OK) {
prot_printf(C->pout, "%s NO \"cannot base64 decode\"\r\n",tag);
free(in);
reset_saslconn(C);
return;
}
r = sasl_server_step(C->saslconn, in, inlen,
&out, &outlen);
free(in); in=NULL;
}
if(r != SASL_OK) {
sleep(3);
syslog(LOG_ERR, "badlogin: %s %s %s",
C->clienthost,
mech, sasl_errdetail(C->saslconn));
prot_printf(C->pout, "%s NO \"%s\"\r\n", tag,
sasl_errstring((r == SASL_NOUSER ? SASL_BADAUTH : r),
NULL, NULL));
reset_saslconn(C);
return;
}
/* Successful Authentication */
r = sasl_getprop(C->saslconn, SASL_USERNAME, (const void **)&C->userid);
if(r != SASL_OK) {
prot_printf(C->pout, "%s NO \"SASL Error\"\r\n", tag);
reset_saslconn(C);
return;
}
syslog(LOG_NOTICE, "login: %s from %s",
C->userid, C->clienthost);
prot_printf(C->pout, "%s OK \"Authenticated\"\r\n", tag);
prot_setsasl(C->pin, C->saslconn);
prot_setsasl(C->pout, C->saslconn);
return;
}
void cmd_set(struct conn *C,
const char *tag, const char *mailbox,
const char *server, const char *acl, enum settype t)
{
struct mbent *m;
struct conn *upc;
syslog(LOG_DEBUG, "cmd_set(fd:%d, %s)", C->fd, mailbox);
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
m = database_lookup(mailbox, NULL);
if (m && t == SET_RESERVE) {
/* failed; mailbox already exists */
prot_printf(C->pout, "%s NO \"mailbox already exists\"\r\n", tag);
goto done;
}
if ((!m || m->t != SET_ACTIVE) && t == SET_DEACTIVATE) {
/* failed; mailbox not currently active */
prot_printf(C->pout, "%s NO \"mailbox not currently active\"\r\n",
tag);
goto done;
} else if (t == SET_DEACTIVATE) {
t = SET_RESERVE;
}
if (t == SET_DELETE) {
if (!m) {
/* failed; mailbox doesn't exist */
prot_printf(C->pout, "%s NO \"mailbox doesn't exist\"\r\n", tag);
goto done;
}
/* do the deletion */
m->t = SET_DELETE;
} else {
if (m && (!acl || strlen(acl) < strlen(m->acl))) {
/* change what's already there */
free(m->server);
m->server = xstrdup(server);
if (acl) strcpy(m->acl, acl);
else m->acl[0] = '\0';
m->t = t;
} else {
struct mbent *newm;
/* allocate new mailbox */
if (acl) {
newm = xrealloc(m, sizeof(struct mbent) + strlen(acl));
} else {
newm = xrealloc(m, sizeof(struct mbent) + 1);
}
newm->mailbox = xstrdup(mailbox);
newm->server = xstrdup(server);
if (acl) {
strcpy(newm->acl, acl);
} else {
newm->acl[0] = '\0';
}
newm->t = t;
/* re-scope */
m = newm;
}
}
/* write to disk */
database_log(m);
/* post pending changes */
for (upc = updatelist; upc != NULL; upc = upc->updatelist_next) {
/* for each connection, add to pending list */
struct pending *p = (struct pending *) xmalloc(sizeof(struct pending));
strcpy(p->mailbox, mailbox);
pthread_mutex_lock(&upc->m);
p->next = upc->plist;
upc->plist = p;
pthread_cond_signal(&upc->cond);
pthread_mutex_unlock(&upc->m);
}
prot_printf(C->pout, "%s OK \"done\"\r\n", tag);
done:
free_mbent(m);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
}
void cmd_find(struct conn *C, const char *tag, const char *mailbox, int dook,
int send_delete)
{
struct mbent *m;
syslog(LOG_DEBUG, "cmd_find(fd:%d, %s)", C->fd, mailbox);
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
m = database_lookup(mailbox, NULL);
if (m && m->t == SET_ACTIVE) {
prot_printf(C->pout, "%s MAILBOX {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server,
strlen(m->acl), m->acl);
} else if (m && m->t == SET_RESERVE) {
prot_printf(C->pout, "%s RESERVE {%d+}\r\n%s {%d+}\r\n%s\r\n",
tag,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server);
} else if (send_delete) {
/* not found, if needed, send a delete */
prot_printf(C->pout, "%s DELETE {%d+}\r\n%s\r\n",
tag, strlen(mailbox), mailbox);
}
free_mbent(m);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
if (dook) {
prot_printf(C->pout, "%s OK \"Search completed\"\r\n", tag);
}
}
/* Callback for cmd_startupdate to be passed to mboxlist_findall. */
/* Requires that C->streaming be set to the tag to respond with */
static int sendupdate(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct conn *C = (struct conn *)rock;
struct mbent *m;
if(!C) return -1;
m = database_lookup(name, NULL);
if(!m) return -1;
if(!C->list_prefix ||
!strncmp(m->server, C->list_prefix, C->list_prefix_len)) {
/* Either there is not a prefix to test, or we matched it */
switch (m->t) {
case SET_ACTIVE:
prot_printf(C->pout,
"%s MAILBOX {%d+}\r\n%s {%d+}\r\n%s {%d+}\r\n%s\r\n",
C->streaming,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server,
strlen(m->acl), m->acl);
break;
case SET_RESERVE:
prot_printf(C->pout, "%s RESERVE {%d+}\r\n%s {%d+}\r\n%s\r\n",
C->streaming,
strlen(m->mailbox), m->mailbox,
strlen(m->server), m->server);
break;
case SET_DELETE:
/* deleted item in the list !?! */
case SET_DEACTIVATE:
/* SET_DEACTIVATE is not a real value! */
abort();
}
}
free_mbent(m);
return 0;
}
void cmd_list(struct conn *C, const char *tag, const char *host_prefix)
{
char pattern[2] = {'*','\0'};
/* indicate interest in updates */
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
/* since this isn't valid when streaming, just use the same callback */
C->streaming = tag;
C->list_prefix = host_prefix;
if(C->list_prefix) C->list_prefix_len = strlen(C->list_prefix);
else C->list_prefix_len = 0;
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, sendupdate, (void*)C);
C->streaming = NULL;
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
}
/*
* we've registered this connection for streaming, and every X seconds
* this will be invoked. note that we always send out updates as soon
* as we get a noop: that resets this counter back */
struct prot_waitevent *sendupdates_evt(struct protstream *s __attribute__((unused)),
struct prot_waitevent *ev,
void *rock)
{
struct conn *C = (struct conn *) rock;
sendupdates(C, 1);
/* 'sendupdates()' will update when we next trigger */
return ev;
}
void cmd_startupdate(struct conn *C, const char *tag)
{
char pattern[2] = {'*','\0'};
/* initialize my condition variable */
pthread_cond_init(&C->cond, NULL);
/* indicate interest in updates */
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
C->updatelist_next = updatelist;
updatelist = C;
C->streaming = xstrdup(tag);
/* dump initial list */
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, sendupdate, (void*)C);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
prot_printf(C->pout, "%s OK \"streaming starts\"\r\n", tag);
/* schedule our first update */
C->ev = prot_addwaitevent(C->pin, time(NULL) + update_wait,
sendupdates_evt, C);
}
/* send out any pending updates.
if 'flushnow' is set, flush the output buffer */
void sendupdates(struct conn *C, int flushnow)
{
struct pending *p, *q;
pthread_mutex_lock(&C->m);
/* just grab the update list and release the lock */
p = C->plist;
C->plist = NULL;
pthread_mutex_unlock(&C->m);
while (p != NULL) {
/* send update */
q = p;
p = p->next;
/* notify just like a FIND - except enable sending of DELETE
* notifications */
cmd_find(C, C->streaming, q->mailbox, 0, 1);
free(q);
}
/* reschedule event for 'update_wait' seconds */
C->ev->mark = time(NULL) + update_wait;
if (flushnow) {
prot_flush(C->pout);
}
}
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
exit(code);
}
/* Reset the given sasl_conn_t to a sane state */
static int reset_saslconn(struct conn *c)
{
int ret, secflags, plaintext_result;
sasl_security_properties_t *secprops = NULL;
sasl_dispose(&c->saslconn);
/* do initialization typical of service_main */
ret = sasl_server_new("mupdate", config_servername,
NULL, NULL, NULL,
NULL, 0, &c->saslconn);
if(ret != SASL_OK) return ret;
if(c->saslprops.ipremoteport)
ret = sasl_setprop(c->saslconn, SASL_IPREMOTEPORT,
c->saslprops.ipremoteport);
if(ret != SASL_OK) return ret;
if(c->saslprops.iplocalport)
ret = sasl_setprop(c->saslconn, SASL_IPLOCALPORT,
c->saslprops.iplocalport);
if(ret != SASL_OK) return ret;
secflags = SASL_SEC_NOANONYMOUS;
plaintext_result = config_getswitch("allowplaintext",1);
if (!config_getswitch("mupdate_allowplaintext", plaintext_result)) {
secflags |= SASL_SEC_NOPLAINTEXT;
}
secprops = mysasl_secprops(secflags);
ret = sasl_setprop(c->saslconn, SASL_SEC_PROPS, secprops);
if(ret != SASL_OK) return ret;
/* end of service_main initialization excepting SSF */
return SASL_OK;
}
int cmd_change(struct mupdate_mailboxdata *mdata,
const char *rock, void *context __attribute__((unused)))
{
struct mbent *m = NULL;
struct conn *upc = NULL;
enum settype t = -1;
int ret = 0;
if(!mdata || !rock || !mdata->mailbox) return 1;
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
if(!strncmp(rock, "DELETE", 6)) {
m = database_lookup(mdata->mailbox, NULL);
if(!m) {
syslog(LOG_DEBUG, "attempt to delete unknown mailbox %s",
mdata->mailbox);
/* Mailbox doesn't exist - this isn't as fatal as you might
* think. */
/* ret = -1; */
goto done;
}
m->t = t = SET_DELETE;
} else {
m = database_lookup(mdata->mailbox, NULL);
if (m && (!mdata->acl || strlen(mdata->acl) < strlen(m->acl))) {
/* change what's already there */
free(m->server);
m->server = xstrdup(mdata->server);
if (mdata->acl) strcpy(m->acl, mdata->acl);
else m->acl[0] = '\0';
if(!strncmp(rock, "MAILBOX", 6)) {
m->t = t = SET_ACTIVE;
} else if(!strncmp(rock, "RESERVE", 7)) {
m->t = t = SET_RESERVE;
} else {
syslog(LOG_DEBUG,
"bad mupdate command in cmd_change: %s", rock);
ret = 1;
goto done;
}
} else {
struct mbent *newm;
if(m) {
free(m->mailbox);
free(m->server);
}
/* allocate new mailbox */
if (mdata->acl) {
newm = xrealloc(m, sizeof(struct mbent) + strlen(mdata->acl));
} else {
newm = xrealloc(m, sizeof(struct mbent) + 1);
}
newm->mailbox = xstrdup(mdata->mailbox);
newm->server = xstrdup(mdata->server);
if (mdata->acl) {
strcpy(newm->acl, mdata->acl);
} else {
newm->acl[0] = '\0';
}
if(!strncmp(rock, "MAILBOX", 6)) {
newm->t = t = SET_ACTIVE;
} else if(!strncmp(rock, "RESERVE", 7)) {
newm->t = t = SET_RESERVE;
} else {
syslog(LOG_DEBUG,
"bad mupdate command in cmd_change: %s", rock);
ret = 1;
goto done;
}
/* Bring it back into scope */
m = newm;
}
}
/* write to disk */
database_log(m);
/* post pending changes to anyone we are talking to */
for (upc = updatelist; upc != NULL; upc = upc->updatelist_next) {
/* for each connection, add to pending list */
struct pending *p = (struct pending *) xmalloc(sizeof(struct pending));
strcpy(p->mailbox, mdata->mailbox);
pthread_mutex_lock(&upc->m);
p->next = upc->plist;
upc->plist = p;
pthread_cond_signal(&upc->cond);
pthread_mutex_unlock(&upc->m);
}
done:
free_mbent(m);
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
return ret;
}
struct sync_rock
{
struct mpool *pool;
struct mbent_queue *boxes;
};
/* Read a series of MAILBOX and RESERVE commands and tack them onto a
* queue */
int cmd_resync(struct mupdate_mailboxdata *mdata,
const char *rock, void *context)
{
struct sync_rock *r = (struct sync_rock *)context;
struct mbent_queue *remote_boxes = r->boxes;
struct mbent *newm = NULL;
if(!mdata || !rock || !mdata->mailbox || !remote_boxes) return 1;
/* allocate new mailbox */
if (mdata->acl) {
newm = mpool_malloc(r->pool,sizeof(struct mbent) + strlen(mdata->acl));
} else {
newm = mpool_malloc(r->pool,sizeof(struct mbent) + 1);
}
newm->mailbox = mpool_strdup(r->pool, mdata->mailbox);
newm->server = mpool_strdup(r->pool, mdata->server);
if (mdata->acl) {
strcpy(newm->acl, mdata->acl);
} else {
newm->acl[0] = '\0';
}
if(!strncmp(rock, "MAILBOX", 6)) {
newm->t = SET_ACTIVE;
} else if(!strncmp(rock, "RESERVE", 7)) {
newm->t = SET_RESERVE;
} else {
syslog(LOG_NOTICE,
"bad mupdate command in cmd_resync: %s", rock);
return 1;
}
/* Insert onto queue */
newm->next = NULL;
*(remote_boxes->tail) = newm;
remote_boxes->tail = &(newm->next);
return 0;
}
/* Callback for mupdate_synchronize to be passed to mboxlist_findall. */
static int sync_findall_cb(char *name,
int matchlen __attribute((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct sync_rock *r = (struct sync_rock *)rock;
struct mbent_queue *local_boxes = (struct mbent_queue *)r->boxes;
struct mbent *m;
if(!local_boxes) return 1;
m = database_lookup(name, r->pool);
/* If it doesn't exist, fine... */
if(!m) return 0;
m->next = NULL;
*(local_boxes->tail) = m;
local_boxes->tail = &(m->next);
return 0;
}
int mupdate_synchronize(mupdate_handle *handle)
{
struct mbent_queue local_boxes;
struct mbent_queue remote_boxes;
struct mbent *l,*r;
struct mpool *pool;
struct sync_rock rock;
char pattern[] = { '*', '\0' };
if(!handle || !handle->saslcompleted) return 1;
pool = new_mpool(131072); /* Arbitrary, but large (128k) */
rock.pool = pool;
/* ask for updates and set nonblocking */
prot_printf(handle->pout, "U01 UPDATE\r\n");
/* Note that this prevents other people from running an UPDATE against
* us for the duration. this is a GOOD THING */
pthread_mutex_lock(&mailboxes_mutex); /* LOCK */
syslog(LOG_NOTICE,
"synchronizing mailbox list with master mupdate server");
local_boxes.head = NULL;
local_boxes.tail = &(local_boxes.head);
remote_boxes.head = NULL;
remote_boxes.tail = &(remote_boxes.head);
rock.boxes = &remote_boxes;
/* If there is a fatal error, die, other errors ignore */
if (mupdate_scarf(handle, cmd_resync, &rock, 1, NULL) != 0) {
struct mbent *p=remote_boxes.head, *p_next=NULL;
while(p) {
p_next = p->next;
p = p_next;
}
pthread_mutex_unlock(&mailboxes_mutex);
free_mpool(pool);
return 1;
}
/* Make socket nonblocking now */
prot_NONBLOCK(handle->pin);
rock.boxes = &local_boxes;
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, sync_findall_cb, (void*)&rock);
/* Traverse both lists, compare the names */
/* If they match, ensure that server and acl are correct, if so,
move on, if not, fix them */
/* If the local is before the next remote, delete it */
/* If the next remote is before theis local, insert it and try again */
for(l = local_boxes.head, r = remote_boxes.head; l && r;
l = local_boxes.head, r = remote_boxes.head)
{
int ret = strcmp(l->mailbox, r->mailbox);
if(!ret) {
/* Match */
if(l->t != r->t ||
strcmp(l->server, r->server) ||
strcmp(l->acl,r->acl)) {
/* Something didn't match, replace it */
mboxlist_insertremote(r->mailbox,
(r->t == SET_RESERVE ?
MBTYPE_RESERVE : 0),
r->server, r->acl, NULL);
}
/* Okay, dump these two */
local_boxes.head = l->next;
remote_boxes.head = r->next;
} else if (ret < 0) {
/* Local without corresponding remote, delete it */
- mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0);
+ mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0, 0);
local_boxes.head = l->next;
} else /* (ret > 0) */ {
/* Remote without corresponding local, insert it */
mboxlist_insertremote(r->mailbox,
(r->t == SET_RESERVE ?
MBTYPE_RESERVE : 0),
r->server, r->acl, NULL);
remote_boxes.head = r->next;
}
}
if(l && !r) {
/* we have more deletes to do */
while(l) {
- mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0);
+ mboxlist_deletemailbox(l->mailbox, 1, "", NULL, 0, 0, 0);
local_boxes.head = l->next;
l = local_boxes.head;
}
} else if (r && !l) {
/* we have more inserts to do */
while(r) {
mboxlist_insertremote(r->mailbox,
(r->t == SET_RESERVE ?
MBTYPE_RESERVE : 0),
r->server, r->acl, NULL);
remote_boxes.head = r->next;
r = remote_boxes.head;
}
}
/* All up to date! */
syslog(LOG_NOTICE, "mailbox list synchronization complete");
pthread_mutex_unlock(&mailboxes_mutex); /* UNLOCK */
free_mpool(pool);
return 0;
}
void mupdate_signal_db_synced(void)
{
pthread_mutex_lock(&synced_mutex);
synced = 1;
pthread_cond_broadcast(&synced_cond);
pthread_mutex_unlock(&synced_mutex);
}
void mupdate_ready(void)
{
if(ready_for_connections) {
syslog(LOG_CRIT, "mupdate_ready called when already ready");
fatal("mupdate_ready called when already ready", EC_TEMPFAIL);
}
pthread_mutex_lock(&ready_for_connections_mutex);
ready_for_connections = 1;
pthread_cond_broadcast(&ready_for_connections_cond);
pthread_mutex_unlock(&ready_for_connections_mutex);
}
void mupdate_unready(void)
{
pthread_mutex_lock(&ready_for_connections_mutex);
syslog(LOG_NOTICE, "unready for connections");
/* close all connections to us */
while(connlist) conn_free(connlist);
ready_for_connections = 0;
pthread_mutex_unlock(&ready_for_connections_mutex);
}
/* Used to free malloc'd mbent's */
void free_mbent(struct mbent *p)
{
if(!p) return;
free(p->server);
free(p->mailbox);
free(p);
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 4, 8:00 AM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18823109
Default Alt Text
(339 KB)

Event Timeline