Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
570 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/imap/imapd.c b/imap/imapd.c
index e6be721b5..fa857a7fb 100644
--- a/imap/imapd.c
+++ b/imap/imapd.c
@@ -1,12908 +1,12875 @@
/*
* Copyright (c) 1994-2008 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 legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@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>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <syslog.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sasl/sasl.h>
#ifdef HAVE_SSL
#include <openssl/hmac.h>
#include <openssl/rand.h>
#endif /* HAVE_SSL */
#include "acl.h"
#include "annotate.h"
#include "append.h"
#include "auth.h"
#ifdef USE_AUTOCREATE
#include "autocreate.h"
#endif // USE_AUTOCREATE
#include "assert.h"
#include "backend.h"
#include "bsearch.h"
#include "charset.h"
#include "dlist.h"
#include "exitcodes.h"
#include "idle.h"
#include "global.h"
#include "times.h"
#include "imap/imap_err.h"
#include "proxy.h"
#include "imap_proxy.h"
#include "imapd.h"
#include "imapurl.h"
#include "imparse.h"
#include "index.h"
#include "mailbox.h"
#include "message.h"
#include "mboxevent.h"
#include "mboxkey.h"
#include "mboxlist.h"
#include "mboxname.h"
#include "mbdump.h"
#include "mupdate-client.h"
#include "partlist.h"
#include "proc.h"
#include "quota.h"
#include "seen.h"
#include "statuscache.h"
#include "sync_log.h"
#include "telemetry.h"
#include "tls.h"
#include "user.h"
#include "userdeny.h"
#include "util.h"
#include "version.h"
#include "xmalloc.h"
#include "xstrlcat.h"
#include "xstrlcpy.h"
#include "ptrarray.h"
#include "xstats.h"
#include "imap/pushstats.h" /* SNMP interface */
#include "iostat.h"
extern int optind;
extern char *optarg;
/* global state */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
static int imaps = 0;
static sasl_ssf_t extprops_ssf = 0;
static int nosaslpasswdcheck = 0;
/* PROXY STUFF */
/* we want a list of our outgoing connections here and which one we're
currently piping */
static const int ultraparanoid = 1; /* should we kick after every operation? */
unsigned int proxy_cmdcnt;
static int referral_kick = 0; /* kick after next command recieved, for
referrals that are likely to change the
mailbox list */
/* all subscription commands go to the backend server containing the
user's inbox */
struct backend *backend_inbox = NULL;
/* the current server most commands go to */
struct backend *backend_current = NULL;
/* our cached connections */
struct backend **backend_cached = NULL;
/* are we doing virtdomains with multiple IPs? */
static int disable_referrals;
/* has the client issued an RLIST, RLSUB, or LIST (REMOTE)? */
static int supports_referrals;
/* end PROXY STUFF */
/* per-user/session state */
static int imapd_timeout;
struct protstream *imapd_out = NULL;
struct protstream *imapd_in = NULL;
static struct protgroup *protin = NULL;
static const char *imapd_clienthost = "[local]";
static int imapd_logfd = -1;
char *imapd_userid = NULL, *proxy_userid = NULL;
static char *imapd_magicplus = NULL;
struct auth_state *imapd_authstate = 0;
static int imapd_userisadmin = 0;
static int imapd_userisproxyadmin = 0;
unsigned imapd_client_capa = 0;
static sasl_conn_t *imapd_saslconn; /* the sasl connection context */
static int imapd_starttls_done = 0; /* have we done a successful starttls? */
static void *imapd_tls_comp = NULL; /* TLS compression method, if any */
static int imapd_compress_done = 0; /* have we done a successful compress? */
static const char *plaintextloginalert = NULL;
static int ignorequota = 0;
static struct id_data {
struct attvaluelist *params;
int did_id;
} imapd_id;
#ifdef HAVE_SSL
/* our tls connection, if any */
static SSL *tls_conn = NULL;
#endif /* HAVE_SSL */
/* stage(s) for APPEND */
struct appendstage {
struct stagemsg *stage;
FILE *f;
strarray_t flags;
time_t internaldate;
int binary;
struct entryattlist *annotations;
};
static ptrarray_t stages = PTRARRAY_INITIALIZER;
/* the sasl proxy policy context */
static struct proxy_context imapd_proxyctx = {
1, 1, &imapd_authstate, &imapd_userisadmin, &imapd_userisproxyadmin
};
/* current sub-user state */
static struct index_state *imapd_index;
/* current namespace */
struct namespace imapd_namespace;
/* track if we're idling */
static int idling = 0;
static const struct mbox_name_attribute {
int flag;
const char *id;
} mbox_name_attributes[] = {
/* from RFC 3501 */
{ MBOX_ATTRIBUTE_NOINFERIORS, "\\Noinferiors" },
{ MBOX_ATTRIBUTE_NOSELECT, "\\Noselect" },
{ MBOX_ATTRIBUTE_MARKED, "\\Marked" },
{ MBOX_ATTRIBUTE_UNMARKED, "\\Unmarked" },
/* from draft-ietf-imapext-list-extensions-18.txt */
{ MBOX_ATTRIBUTE_NONEXISTENT, "\\NonExistent" },
{ MBOX_ATTRIBUTE_SUBSCRIBED, "\\Subscribed" },
{ MBOX_ATTRIBUTE_REMOTE, "\\Remote" },
{ MBOX_ATTRIBUTE_HASCHILDREN, "\\HasChildren" },
{ MBOX_ATTRIBUTE_HASNOCHILDREN, "\\HasNoChildren" },
{ 0, NULL }
};
/*
* These bitmasks define how List selection options can be combined:
* list_select_mod_opts may only be used if at least one list_select_base_opt
* is also present.
* For example, (RECURSIVEMATCH) and (RECURSIVEMATCH REMOTE) are invalid, but
* (RECURSIVEMATCH SUBSCRIBED) is ok.
*/
static const int list_select_base_opts = LIST_SEL_SUBSCRIBED;
static const int list_select_mod_opts = LIST_SEL_RECURSIVEMATCH;
/* structure that list_data passes its callbacks */
struct list_rock {
struct listargs *listargs;
char *last_name;
int last_attributes;
};
/* Information about one mailbox name that LIST returns */
struct list_entry {
const char *name;
int attributes; /* bitmap of MBOX_ATTRIBUTE_* */
};
/* structure that list_data_recursivematch passes its callbacks */
struct list_rock_recursivematch {
struct listargs *listargs;
struct hash_table table; /* maps mailbox names to attributes (int *) */
int count; /* # of entries in table */
struct list_entry *array;
};
/* CAPABILITIES are defined here, not including TLS/SASL ones,
and those that are configurable */
enum {
CAPA_PREAUTH = 0x1,
CAPA_POSTAUTH = 0x2
};
struct capa_struct {
const char *str;
int mask;
};
static struct capa_struct base_capabilities[] = {
/* pre-auth capabilities */
{ "IMAP4rev1", 3 },
{ "LITERAL+", 3 },
{ "ID", 3 },
{ "ENABLE", 3 },
/* post-auth capabilities */
{ "ACL", 2 },
{ "RIGHTS=kxten", 2 },
{ "QUOTA", 2 },
{ "MAILBOX-REFERRALS", 2 },
{ "NAMESPACE", 2 },
{ "UIDPLUS", 2 },
{ "NO_ATOMIC_RENAME", 2 },
{ "UNSELECT", 2 },
{ "CHILDREN", 2 },
{ "MULTIAPPEND", 2 },
{ "BINARY", 2 },
{ "CATENATE", 2 },
{ "CONDSTORE", 2 },
{ "ESEARCH", 2 },
{ "SORT", 2 },
{ "SORT=MODSEQ", 2 },
{ "SORT=DISPLAY", 2 },
{ "SORT=UID", 2 }, /* not standard */
{ "THREAD=ORDEREDSUBJECT", 2 },
{ "THREAD=REFERENCES", 2 },
{ "ANNOTATEMORE", 2 },
{ "ANNOTATE-EXPERIMENT-1", 2 },
{ "METADATA", 2 },
{ "LIST-EXTENDED", 2 },
{ "LIST-STATUS", 2 },
{ "LIST-MYRIGHTS", 2 }, /* not standard */
{ "WITHIN", 2 },
{ "QRESYNC", 2 },
{ "SCAN", 2 },
{ "XLIST", 2 },
{ "XMOVE", 2 },
{ "MOVE", 2 }, /* draft */
{ "SPECIAL-USE", 2 },
{ "CREATE-SPECIAL-USE", 2 },
{ "DIGEST=SHA1", 2 },
#ifdef HAVE_SSL
{ "URLAUTH", 2 },
{ "URLAUTH=BINARY", 2 },
#endif
#ifdef ENABLE_X_NETSCAPE_HACK
{ "X-NETSCAPE", 2 },
#endif
/* keep this to mark the end of the list */
{ 0, 0 }
};
static void motd_file(void);
void shut_down(int code);
void fatal(const char *s, int code);
static void cmdloop(void);
static void cmd_login(char *tag, char *user);
static void cmd_authenticate(char *tag, char *authtype, char *resp);
static void cmd_noop(char *tag, char *cmd);
static void capa_response(int flags);
static void cmd_capability(char *tag);
static void cmd_append(char *tag, char *name, const char *cur_name);
static void cmd_select(char *tag, char *cmd, char *name);
static void cmd_close(char *tag, char *cmd);
static int parse_fetch_args(const char *tag, const char *cmd,
int allow_vanished,
struct fetchargs *fa);
static void cmd_fetch(char *tag, char *sequence, int usinguid);
static void cmd_store(char *tag, char *sequence, int usinguid);
static void cmd_search(char *tag, int usinguid);
static void cmd_sort(char *tag, int usinguid);
static void cmd_thread(char *tag, int usinguid);
static void cmd_copy(char *tag, char *sequence, char *name, int usinguid, int ismove);
static void cmd_expunge(char *tag, char *sequence);
static void cmd_create(char *tag, char *name, struct dlist *extargs, int localonly);
static void cmd_delete(char *tag, char *name, int localonly, int force);
static void cmd_dump(char *tag, char *name, int uid_start);
static void cmd_undump(char *tag, char *name);
static void cmd_xfer(const char *tag, const char *name,
const char *toserver, const char *topart);
static void cmd_rename(char *tag, char *oldname, char *newname, char *partition);
static void cmd_reconstruct(const char *tag, const char *name, int recursive);
static void getlistargs(char *tag, struct listargs *listargs);
static void cmd_list(char *tag, struct listargs *listargs);
static void cmd_changesub(char *tag, char *namespace, char *name, int add);
static void cmd_getacl(const char *tag, const char *name);
static void cmd_listrights(char *tag, char *name, char *identifier);
static void cmd_myrights(const char *tag, const char *name);
static void cmd_setacl(char *tag, const char *name,
const char *identifier, const char *rights);
static void cmd_getquota(const char *tag, const char *name);
static void cmd_getquotaroot(const char *tag, const char *name);
static void cmd_setquota(const char *tag, const char *quotaroot);
static void cmd_status(char *tag, char *name);
static void cmd_namespace(char* tag);
static void cmd_mupdatepush(char *tag, char *name);
static void cmd_id(char* tag);
static void cmd_idle(char* tag);
static void cmd_starttls(char *tag, int imaps);
static void cmd_xconvsort(char *tag, int updates);
static void cmd_xconvmultisort(char *tag);
static void cmd_xconvmeta(const char *tag);
static void cmd_xconvfetch(const char *tag);
static int do_xconvfetch(struct dlist *cidlist,
modseq_t ifchangedsince,
struct fetchargs *fetchargs);
static void cmd_xsnippets(char *tag);
static void cmd_xstats(char *tag, int c);
#ifdef HAVE_SSL
static void cmd_urlfetch(char *tag);
static void cmd_genurlauth(char *tag);
static void cmd_resetkey(char *tag, char *mailbox, char *mechanism);
#endif
#ifdef HAVE_ZLIB
static void cmd_compress(char *tag, char *alg);
#endif
#ifdef ENABLE_X_NETSCAPE_HACK
void cmd_netscrape(char* tag);
#endif
static void cmd_getannotation(const char* tag, char *mboxpat);
static void cmd_getmetadata(const char* tag);
static void cmd_setannotation(const char* tag, char *mboxpat);
static void cmd_setmetadata(const char* tag, char *mboxpat);
static void cmd_xrunannotator(const char *tag, const char *sequence,
int usinguid);
static void cmd_xwarmup(const char *tag);
static void cmd_enable(char* tag);
static int parsecreateargs(struct dlist **extargs);
static int parse_annotate_fetch_data(const char *tag,
int permessage_flag,
strarray_t *entries,
strarray_t *attribs);
static int parse_metadata_string_or_list(const char *tag,
strarray_t *sa,
int *is_list);
static int parse_annotate_store_data(const char *tag,
int permessage_flag,
struct entryattlist **entryatts);
static int parse_metadata_store_data(const char *tag,
struct entryattlist **entryatts);
static int getlistselopts(char *tag, struct listargs *args);
static int getlistretopts(char *tag, struct listargs *args);
static int get_snippetargs(struct snippetargs **sap);
static void free_snippetargs(struct snippetargs **sap);
static int getsortcriteria(char *tag, struct sortcrit **sortcrit);
static char *sortcrit_as_string(const struct sortcrit *sortcrit);
static int getdatetime(time_t *date);
static int parse_windowargs(const char *tag, struct windowargs **, int);
static void free_windowargs(struct windowargs *wa);
static void appendfieldlist(struct fieldlist **l, char *section,
strarray_t *fields, char *trail,
void *d, size_t size);
static void freefieldlist(struct fieldlist *l);
void freestrlist(struct strlist *l);
static int set_haschildren(char *name, int matchlen, int maycreate,
int *attributes);
static void list_response(const char *name, int attributes,
struct listargs *listargs);
static int set_subscribed(char *name, int matchlen, int maycreate,
void *rock);
static char *canonical_list_pattern(const char *reference,
const char *pattern);
static void canonical_list_patterns(const char *reference,
strarray_t *patterns);
static int list_cb(char *name, int matchlen, int maycreate,
struct list_rock *rock);
static int subscribed_cb(const char *name, int matchlen, int maycreate,
struct list_rock *rock);
static void list_data(struct listargs *listargs);
static int list_data_remote(char *tag, struct listargs *listargs);
static void clear_id();
extern int saslserver(sasl_conn_t *conn, const char *mech,
const char *init_resp, const char *resp_prefix,
const char *continuation, const char *empty_resp,
struct protstream *pin, struct protstream *pout,
int *sasl_result, char **success_data);
/* Enable the resetting of a sasl_conn_t */
static int reset_saslconn(sasl_conn_t **conn);
static struct
{
char *ipremoteport;
char *iplocalport;
sasl_ssf_t ssf;
char *authid;
} saslprops = {NULL,NULL,0,NULL};
static int imapd_canon_user(sasl_conn_t *conn, void *context,
const char *user, unsigned ulen,
unsigned flags, const char *user_realm,
char *out, unsigned out_max, unsigned *out_ulen)
{
char userbuf[MAX_MAILBOX_BUFFER], *p;
size_t n;
int r;
if (!ulen) ulen = strlen(user);
if (config_getswitch(IMAPOPT_IMAPMAGICPLUS)) {
/* make a working copy of the auth[z]id */
if (ulen >= MAX_MAILBOX_BUFFER) {
sasl_seterror(conn, 0, "buffer overflow while canonicalizing");
return SASL_BUFOVER;
}
memcpy(userbuf, user, ulen);
userbuf[ulen] = '\0';
user = userbuf;
/* See if we're using the magic plus
(currently we don't support anything after '+') */
if ((p = strchr(userbuf, '+')) &&
(n = config_virtdomains ? strcspn(p, "@") : strlen(p)) == 1) {
if (flags & SASL_CU_AUTHZID) {
/* make a copy of the magic plus */
if (imapd_magicplus) free(imapd_magicplus);
imapd_magicplus = xstrndup(p, n);
}
/* strip the magic plus from the auth[z]id */
memmove(p, p+n, strlen(p+n)+1);
ulen -= n;
}
}
r = mysasl_canon_user(conn, context, user, ulen, flags, user_realm,
out, out_max, out_ulen);
if (!r && imapd_magicplus && flags == SASL_CU_AUTHZID) {
/* If we're only doing the authzid, put back the magic plus
in case its used in the challenge/response calculation */
n = strlen(imapd_magicplus);
if (*out_ulen + n > out_max) {
sasl_seterror(conn, 0, "buffer overflow while canonicalizing");
r = SASL_BUFOVER;
}
else {
p = (config_virtdomains && (p = strchr(out, '@'))) ?
p : out + *out_ulen;
memmove(p+n, p, strlen(p)+1);
memcpy(p, imapd_magicplus, n);
*out_ulen += n;
}
}
return r;
}
static int imapd_proxy_policy(sasl_conn_t *conn,
void *context,
const char *requested_user, unsigned rlen,
const char *auth_identity, unsigned alen,
const char *def_realm,
unsigned urlen,
struct propctx *propctx)
{
char userbuf[MAX_MAILBOX_BUFFER];
if (config_getswitch(IMAPOPT_IMAPMAGICPLUS)) {
size_t n;
char *p;
/* make a working copy of the authzid */
if (!rlen) rlen = strlen(requested_user);
if (rlen >= MAX_MAILBOX_BUFFER) {
sasl_seterror(conn, 0, "buffer overflow while proxying");
return SASL_BUFOVER;
}
memcpy(userbuf, requested_user, rlen);
userbuf[rlen] = '\0';
requested_user = userbuf;
/* See if we're using the magic plus */
if ((p = strchr(userbuf, '+'))) {
n = config_virtdomains ? strcspn(p, "@") : strlen(p);
/* strip the magic plus from the authzid */
memmove(p, p+n, strlen(p+n)+1);
rlen -= n;
}
}
return mysasl_proxy_policy(conn, context, requested_user, rlen,
auth_identity, alen, def_realm, urlen, propctx);
}
static int imapd_sasl_log(void *context __attribute__((unused)),
int level, const char *message)
{
int syslog_level = LOG_INFO;
switch (level) {
case SASL_LOG_ERR:
case SASL_LOG_FAIL:
syslog_level = LOG_ERR;
break;
case SASL_LOG_WARN:
syslog_level = LOG_WARNING;
break;
case SASL_LOG_DEBUG:
case SASL_LOG_TRACE:
case SASL_LOG_PASS:
syslog_level = LOG_DEBUG;
break;
}
syslog(syslog_level, "SASL %s", message);
return SASL_OK;
}
static const struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, (mysasl_cb_ft *) &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, (mysasl_cb_ft *) &imapd_proxy_policy, (void*) &imapd_proxyctx },
{ SASL_CB_CANON_USER, (mysasl_cb_ft *) &imapd_canon_user, (void*) &disable_referrals },
{ SASL_CB_LOG, (mysasl_cb_ft *) &imapd_sasl_log, 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)
{
struct imapurl imapurl;
char url[MAX_MAILBOX_PATH+1];
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = server;
imapurl.mailbox = mailbox;
imapurl.auth = !strcmp(imapd_userid, "anonymous") ? "anonymous" : "*";
imapurl_toURL(url, &imapurl);
prot_printf(imapd_out, "%s NO [REFERRAL %s] Remote mailbox.\r\n",
tag, url);
}
/* wrapper for mboxlist_lookup that will force a referral if we are remote
* returns IMAP_SERVER_UNAVAILABLE if we don't have a place to send the client
* (that'd be a bug).
* returns IMAP_MAILBOX_MOVED if we referred the client */
/* ext_name is the external name of the mailbox */
/* you can avoid referring the client by setting tag or ext_name to NULL. */
static int mlookup(const char *tag, const char *ext_name,
const char *name, mbentry_t **mbentryptr)
{
int r;
mbentry_t *mbentry = NULL;
r = mboxlist_lookup(name, &mbentry, NULL);
if ((r == IMAP_MAILBOX_NONEXISTENT || (!r && (mbentry->mbtype & MBTYPE_RESERVE))) &&
config_mupdate_server) {
/* It is not currently active, make sure we have the most recent
* copy of the database */
kick_mupdate();
mboxlist_entry_free(&mbentry);
r = mboxlist_lookup(name, &mbentry, NULL);
}
if (r) goto done;
if (mbentry->mbtype & MBTYPE_RESERVE) {
r = IMAP_MAILBOX_RESERVED;
}
else if (mbentry->mbtype & MBTYPE_DELETED) {
r = IMAP_MAILBOX_NONEXISTENT;
}
else if (mbentry->mbtype & MBTYPE_MOVING) {
/* do we have rights on the mailbox? */
if (!imapd_userisadmin &&
(!mbentry->acl || !(cyrus_acl_myrights(imapd_authstate, mbentry->acl) & ACL_LOOKUP))) {
r = IMAP_MAILBOX_NONEXISTENT;
} else if (tag && ext_name && mbentry->server) {
imapd_refer(tag, mbentry->server, ext_name);
r = IMAP_MAILBOX_MOVED;
} else if (config_mupdate_server) {
r = IMAP_SERVER_UNAVAILABLE;
} else {
r = IMAP_MAILBOX_NOTSUPPORTED;
}
}
done:
if (r) mboxlist_entry_free(&mbentry);
else if (mbentryptr) *mbentryptr = mbentry;
else mboxlist_entry_free(&mbentry); /* we don't actually want it! */
return r;
}
static void imapd_reset(void)
{
int i;
int bytes_in = 0;
int bytes_out = 0;
proc_cleanup();
/* close backend connections */
i = 0;
while (backend_cached && backend_cached[i]) {
proxy_downserver(backend_cached[i]);
if (backend_cached[i]->last_result.s) {
free(backend_cached[i]->last_result.s);
}
free(backend_cached[i]);
i++;
}
if (backend_cached) free(backend_cached);
backend_cached = NULL;
backend_inbox = backend_current = NULL;
proxy_cmdcnt = 0;
disable_referrals = 0;
supports_referrals = 0;
if (imapd_index) index_close(&imapd_index);
if (imapd_in) {
/* Flush the incoming buffer */
prot_NONBLOCK(imapd_in);
prot_fill(imapd_in);
bytes_in = prot_bytes_in(imapd_in);
prot_free(imapd_in);
}
if (imapd_out) {
/* Flush the outgoing buffer */
prot_flush(imapd_out);
bytes_out = prot_bytes_out(imapd_out);
prot_free(imapd_out);
}
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>",
session_id(), bytes_in, bytes_out);
imapd_in = imapd_out = NULL;
if (protin) protgroup_reset(protin);
#ifdef HAVE_SSL
if (tls_conn) {
if (tls_reset_servertls(&tls_conn) == -1) {
fatal("tls_reset() failed", EC_TEMPFAIL);
}
tls_conn = NULL;
}
#endif
cyrus_reset_stdio();
imapd_clienthost = "[local]";
if (imapd_logfd != -1) {
close(imapd_logfd);
imapd_logfd = -1;
}
if (imapd_userid != NULL) {
free(imapd_userid);
imapd_userid = NULL;
}
if (proxy_userid != NULL) {
free(proxy_userid);
proxy_userid = NULL;
}
if (imapd_magicplus != NULL) {
free(imapd_magicplus);
imapd_magicplus = NULL;
}
if (imapd_authstate) {
auth_freestate(imapd_authstate);
imapd_authstate = NULL;
}
imapd_userisadmin = 0;
imapd_userisproxyadmin = 0;
imapd_client_capa = 0;
if (imapd_saslconn) {
sasl_dispose(&imapd_saslconn);
free(imapd_saslconn);
imapd_saslconn = NULL;
}
imapd_compress_done = 0;
imapd_tls_comp = NULL;
imapd_starttls_done = 0;
plaintextloginalert = NULL;
if(saslprops.iplocalport) {
free(saslprops.iplocalport);
saslprops.iplocalport = NULL;
}
if(saslprops.ipremoteport) {
free(saslprops.ipremoteport);
saslprops.ipremoteport = NULL;
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
saslprops.ssf = 0;
clear_id();
}
/*
* 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 opt;
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
setproctitle_init(argc, argv, envp);
/* set signal handlers */
signals_set_shutdown(&shut_down);
signal(SIGPIPE, SIG_IGN);
/* load the SASL plugins */
global_sasl_init(1, 1, mysasl_cb);
/* open the mboxlist, we'll need it for real work */
mboxlist_init(0);
mboxlist_open(NULL);
/* open the quota db, we'll need it for real work */
quotadb_init(0);
quotadb_open(NULL);
/* open the user deny db */
denydb_init(0);
denydb_open(0);
/* setup for sending IMAP IDLE notifications */
idle_init();
/* setup for mailbox event notifications */
mboxevent_init();
search_attr_init();
/* 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, "Np:sq")) != EOF) {
switch (opt) {
case 's': /* imaps (do starttls right away) */
imaps = 1;
if (!tls_enabled()) {
syslog(LOG_ERR, "imaps: required OpenSSL options not present");
fatal("imaps: required OpenSSL options not present",
EC_CONFIG);
}
break;
case 'p': /* external protection */
extprops_ssf = atoi(optarg);
break;
case 'N': /* bypass SASL password check. Not recommended unless
* you know what you're doing! */
nosaslpasswdcheck = 1;
break;
case 'q': /* don't enforce quotas */
ignorequota = 1;
break;
default:
break;
}
}
/* Initialize the annotatemore extention */
if (config_mupdate_server)
annotate_init(annotate_fetch_proxy, annotate_store_proxy);
else
annotate_init(NULL, NULL);
annotatemore_open();
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
statuscache_open();
}
/* Create a protgroup for input from the client and selected backend */
protin = protgroup_new(2);
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
{
sasl_security_properties_t *secprops = NULL;
const char *localip, *remoteip;
struct mboxevent *mboxevent = NULL;
struct io_count *io_count_start = NULL;
struct io_count *io_count_stop = NULL;
if (config_iolog) {
io_count_start = xmalloc (sizeof (struct io_count));
io_count_stop = xmalloc (sizeof (struct io_count));
read_io_count(io_count_start);
}
session_new_id();
signals_poll();
#ifdef ID_SAVE_CMDLINE
/* get command line args for use in ID before getopt mangles them */
id_getcmdline(argc, argv);
#endif
sync_log_init();
imapd_in = prot_new(0, 0);
imapd_out = prot_new(1, 1);
protgroup_insert(protin, imapd_in);
/* Find out name of client host */
imapd_clienthost = get_clienthost(0, &localip, &remoteip);
/* 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);
}
secprops = mysasl_secprops(0);
if (sasl_setprop(imapd_saslconn, SASL_SEC_PROPS, secprops) != SASL_OK)
fatal("Failed to set SASL property", EC_TEMPFAIL);
if (sasl_setprop(imapd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf) != SASL_OK)
fatal("Failed to set SASL property", EC_TEMPFAIL);
if (localip && remoteip) {
sasl_setprop(imapd_saslconn, SASL_IPREMOTEPORT, remoteip);
saslprops.ipremoteport = xstrdup(remoteip);
sasl_setprop(imapd_saslconn, SASL_IPLOCALPORT, localip);
saslprops.iplocalport = xstrdup(localip);
}
proc_register(config_ident, imapd_clienthost, NULL, NULL, NULL);
/* Set inactivity timer */
imapd_timeout = config_getint(IMAPOPT_TIMEOUT);
if (imapd_timeout < 30) imapd_timeout = 30;
imapd_timeout *= 60;
prot_settimeout(imapd_in, imapd_timeout);
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);
/* Setup a default namespace until replaced after authentication. */
mboxname_init_namespace(&imapd_namespace, /*isadmin*/1);
mboxevent_setnamespace(&imapd_namespace);
cmdloop();
/* LOGOUT executed */
prot_flush(imapd_out);
snmp_increment(ACTIVE_CONNECTIONS, -1);
/* send a Logout event notification */
if ((mboxevent = mboxevent_new(EVENT_LOGOUT))) {
mboxevent_set_access(mboxevent, saslprops.iplocalport,
saslprops.ipremoteport, imapd_userid, NULL, 1);
mboxevent_notify(mboxevent);
mboxevent_free(&mboxevent);
}
/* cleanup */
imapd_reset();
if (config_iolog) {
read_io_count(io_count_stop);
syslog(LOG_INFO,
"IMAP session stats : I/O read : %d bytes : I/O write : %d bytes",
io_count_stop->io_read_count - io_count_start->io_read_count,
io_count_stop->io_write_count - io_count_start->io_write_count);
free (io_count_start);
free (io_count_stop);
}
return 0;
}
/* Called by service API to shut down the service */
void service_abort(int error)
{
shut_down(error);
}
/*
* Try to find a motd file; if found spit out message as an [ALERT]
*/
static void motd_file(void)
{
char *filename = NULL;
int fd = -1;
struct protstream *motd_in = NULL;
char buf[MAX_MAILBOX_PATH+1];
char *p;
filename = strconcat(config_dir, "/msg/motd", (char *)NULL);
fd = open(filename, O_RDONLY, 0);
if (fd < 0)
goto out;
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);
out:
if (motd_in)
prot_free(motd_in);
if (fd >= 0)
close(fd);
free(filename);
}
/*
* Cleanly shut down and exit
*/
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
int i;
int bytes_in = 0;
int bytes_out = 0;
in_shutdown = 1;
proc_cleanup();
i = 0;
while (backend_cached && backend_cached[i]) {
proxy_downserver(backend_cached[i]);
if (backend_cached[i]->last_result.s) {
free(backend_cached[i]->last_result.s);
}
free(backend_cached[i]);
i++;
}
if (backend_cached) free(backend_cached);
if (idling)
idle_stop(index_mboxname(imapd_index));
if (imapd_index) index_close(&imapd_index);
sync_log_done();
seen_done();
mboxkey_done();
mboxlist_close();
mboxlist_done();
quotadb_close();
quotadb_done();
denydb_close();
denydb_done();
annotatemore_close();
annotate_done();
idle_done();
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
statuscache_close();
statuscache_done();
}
partlist_local_done();
if (imapd_in) {
/* Flush the incoming buffer */
prot_NONBLOCK(imapd_in);
prot_fill(imapd_in);
bytes_in = prot_bytes_in(imapd_in);
prot_free(imapd_in);
}
if (imapd_out) {
/* Flush the outgoing buffer */
prot_flush(imapd_out);
bytes_out = prot_bytes_out(imapd_out);
prot_free(imapd_out);
/* one less active connection */
snmp_increment(ACTIVE_CONNECTIONS, -1);
}
if (config_auditlog)
syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>",
session_id(), bytes_in, bytes_out);
if (protin) protgroup_free(protin);
#ifdef HAVE_SSL
tls_shutdown_serverengine();
#endif
cyrus_done();
exit(code);
}
EXPORTED void fatal(const char *s, int code)
{
static int recurse_code = 0;
if (recurse_code) {
/* We were called recursively. Just give up */
proc_cleanup();
snmp_increment(ACTIVE_CONNECTIONS, -1);
exit(recurse_code);
}
recurse_code = code;
if (imapd_out) {
prot_printf(imapd_out, "* BYE Fatal error: %s\r\n", s);
prot_flush(imapd_out);
}
if (stages.count) {
/* Cleanup the stage(s) */
struct appendstage *curstage;
while ((curstage = ptrarray_pop(&stages))) {
if (curstage->f != NULL) fclose(curstage->f);
append_removestage(curstage->stage);
strarray_fini(&curstage->flags);
freeentryatts(curstage->annotations);
free(curstage);
}
ptrarray_fini(&stages);
}
syslog(LOG_ERR, "Fatal error: %s", s);
abort();
shut_down(code);
}
/*
* Check the currently selected mailbox for updates.
*
* 'be' is the backend (if any) that we just proxied a command to.
*/
static void imapd_check(struct backend *be, int usinguid)
{
if (backend_current && backend_current != be) {
/* remote mailbox */
char mytag[128];
proxy_gentag(mytag, sizeof(mytag));
prot_printf(backend_current->out, "%s Noop\r\n", mytag);
pipe_until_tag(backend_current, mytag, 0);
}
else {
/* local mailbox */
index_check(imapd_index, usinguid, 0);
}
}
/*
* Top-level command loop parsing
*/
static void cmdloop(void)
{
int c;
int usinguid, havepartition, havenamespace, recursive;
static struct buf tag, cmd, arg1, arg2, arg3;
char *p, shut[MAX_MAILBOX_PATH+1], cmdname[100];
const char *err;
const char * commandmintimer;
double commandmintimerd = 0.0;
prot_printf(imapd_out, "* OK [CAPABILITY ");
capa_response(CAPA_PREAUTH);
prot_printf(imapd_out, "]");
if (config_serverinfo) prot_printf(imapd_out, " %s", config_servername);
if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
prot_printf(imapd_out, " Cyrus IMAP%s %s",
config_mupdate_server ? " Murder" : "", cyrus_version());
}
prot_printf(imapd_out, " server ready\r\n");
motd_file();
/* Get command timer logging paramater. This string
* is a time in seconds. Any command that takes >=
* this time to execute is logged */
commandmintimer = config_getstring(IMAPOPT_COMMANDMINTIMER);
cmdtime_settimer(commandmintimer ? 1 : 0);
if (commandmintimer) {
commandmintimerd = atof(commandmintimer);
}
for (;;) {
/* Release any held index */
index_release(imapd_index);
/* Flush any buffered output */
prot_flush(imapd_out);
if (backend_current) prot_flush(backend_current->out);
/* command no longer running */
proc_register(config_ident, imapd_clienthost, imapd_userid, index_mboxname(imapd_index), NULL);
/* Check for shutdown file */
if ( !imapd_userisadmin && imapd_userid &&
(shutdown_file(shut, sizeof(shut)) ||
userdeny(imapd_userid, config_ident, shut, sizeof(shut)))) {
for (p = shut; *p == '['; p++); /* can't have [ be first char */
prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p);
telemetry_rusage(imapd_userid);
shut_down(0);
}
signals_poll();
if (!proxy_check_input(protin, imapd_in, imapd_out,
backend_current ? backend_current->in : NULL,
NULL, 0)) {
/* No input from client */
continue;
}
/* Parse tag */
c = getword(imapd_in, &tag);
if (c == EOF) {
if ((err = prot_error(imapd_in))!=NULL
&& strcmp(err, PROT_EOF_STRING)) {
syslog(LOG_WARNING, "%s, closing connection", err);
prot_printf(imapd_out, "* BYE %s\r\n", err);
}
return;
}
if (c != ' ' || !imparse_isatom(tag.s) || (tag.s[0] == '*' && !tag.s[1])) {
prot_printf(imapd_out, "* BAD Invalid tag\r\n");
eatline(imapd_in, c);
continue;
}
/* Parse command name */
c = getword(imapd_in, &cmd);
if (!cmd.s[0]) {
prot_printf(imapd_out, "%s BAD Null command\r\n", tag.s);
eatline(imapd_in, c);
continue;
}
lcase(cmd.s);
xstrncpy(cmdname, cmd.s, 99);
cmd.s[0] = toupper((unsigned char) cmd.s[0]);
if (config_getswitch(IMAPOPT_CHATTY))
syslog(LOG_NOTICE, "command: %s %s", tag.s, cmd.s);
proc_register(config_ident, imapd_clienthost, imapd_userid, index_mboxname(imapd_index), cmd.s);
/* if we need to force a kick, do so */
if (referral_kick) {
kick_mupdate();
referral_kick = 0;
}
if (plaintextloginalert) {
prot_printf(imapd_out, "* OK [ALERT] %s\r\n",
plaintextloginalert);
plaintextloginalert = NULL;
}
/* Only Authenticate/Enable/Login/Logout/Noop/Capability/Id/Starttls
allowed when not logged in */
if (!imapd_userid && !strchr("AELNCIS", cmd.s[0])) goto nologin;
/* Start command timer */
cmdtime_starttimer();
/* note that about half the commands (the common ones that don't
hit the mailboxes file) now close the mailboxes file just in
case it was open. */
switch (cmd.s[0]) {
case 'A':
if (!strcmp(cmd.s, "Authenticate")) {
int haveinitresp = 0;
if (c != ' ') goto missingargs;
c = getword(imapd_in, &arg1);
if (!imparse_isatom(arg1.s)) {
prot_printf(imapd_out, "%s BAD Invalid authenticate mechanism\r\n", tag.s);
eatline(imapd_in, c);
continue;
}
if (c == ' ') {
haveinitresp = 1;
c = getword(imapd_in, &arg2);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
if (imapd_userid) {
prot_printf(imapd_out, "%s BAD Already authenticated\r\n", tag.s);
continue;
}
cmd_authenticate(tag.s, arg1.s, haveinitresp ? arg2.s : NULL);
snmp_increment(AUTHENTICATE_COUNT, 1);
}
else if (!imapd_userid) goto nologin;
else if (!strcmp(cmd.s, "Append")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_append(tag.s, arg1.s, NULL);
snmp_increment(APPEND_COUNT, 1);
}
else goto badcmd;
break;
case '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;
#ifdef HAVE_ZLIB
else if (!strcmp(cmd.s, "Compress")) {
if (c != ' ') goto missingargs;
c = getword(imapd_in, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_compress(tag.s, arg1.s);
snmp_increment(COMPRESS_COUNT, 1);
}
#endif /* HAVE_ZLIB */
else if (!strcmp(cmd.s, "Check")) {
if (!imapd_index && !backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_noop(tag.s, cmd.s);
snmp_increment(CHECK_COUNT, 1);
}
else if (!strcmp(cmd.s, "Copy")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
copy:
c = getword(imapd_in, &arg1);
if (c == '\r') goto missingargs;
if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
c = getastring(imapd_in, imapd_out, &arg2);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_copy(tag.s, arg1.s, arg2.s, usinguid, /*ismove*/0);
snmp_increment(COPY_COUNT, 1);
}
else if (!strcmp(cmd.s, "Create")) {
struct dlist *extargs = NULL;
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == ' ') {
c = parsecreateargs(&extargs);
if (c == EOF) goto badpartition;
}
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_create(tag.s, arg1.s, extargs, 0);
dlist_free(&extargs);
snmp_increment(CREATE_COUNT, 1);
}
else if (!strcmp(cmd.s, "Close")) {
if (!imapd_index && !backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_close(tag.s, cmd.s);
snmp_increment(CLOSE_COUNT, 1);
}
else goto badcmd;
break;
case 'D':
if (!strcmp(cmd.s, "Delete")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_delete(tag.s, arg1.s, 0, 0);
snmp_increment(DELETE_COUNT, 1);
}
else if (!strcmp(cmd.s, "Deleteacl")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg2);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_setacl(tag.s, arg1.s, arg2.s, NULL);
snmp_increment(DELETEACL_COUNT, 1);
}
else if (!strcmp(cmd.s, "Dump")) {
int uid_start = 0;
if(c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if(c == ' ') {
c = getastring(imapd_in, imapd_out, &arg2);
if(!imparse_isnumber(arg2.s)) goto extraargs;
uid_start = atoi(arg2.s);
}
if(c == '\r') c = prot_getc(imapd_in);
if(c != '\n') goto extraargs;
cmd_dump(tag.s, arg1.s, uid_start);
/* snmp_increment(DUMP_COUNT, 1);*/
}
else goto badcmd;
break;
case 'E':
if (!imapd_userid) goto nologin;
else if (!strcmp(cmd.s, "Enable")) {
if (c != ' ') goto missingargs;
cmd_enable(tag.s);
}
else if (!strcmp(cmd.s, "Expunge")) {
if (!imapd_index && !backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_expunge(tag.s, 0);
snmp_increment(EXPUNGE_COUNT, 1);
}
else if (!strcmp(cmd.s, "Examine")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
prot_ungetc(c, imapd_in);
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_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
fetch:
c = getword(imapd_in, &arg1);
if (c == '\r') goto missingargs;
if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
cmd_fetch(tag.s, arg1.s, usinguid);
snmp_increment(FETCH_COUNT, 1);
}
else goto badcmd;
break;
case 'G':
if (!strcmp(cmd.s, "Getacl")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_getacl(tag.s, arg1.s);
snmp_increment(GETACL_COUNT, 1);
}
else if (!strcmp(cmd.s, "Getannotation")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_getannotation(tag.s, arg1.s);
snmp_increment(GETANNOTATION_COUNT, 1);
}
else if (!strcmp(cmd.s, "Getmetadata")) {
if (c != ' ') goto missingargs;
cmd_getmetadata(tag.s);
snmp_increment(GETANNOTATION_COUNT, 1);
}
else if (!strcmp(cmd.s, "Getquota")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_getquota(tag.s, arg1.s);
snmp_increment(GETQUOTA_COUNT, 1);
}
else if (!strcmp(cmd.s, "Getquotaroot")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_getquotaroot(tag.s, arg1.s);
snmp_increment(GETQUOTAROOT_COUNT, 1);
}
#ifdef HAVE_SSL
else if (!strcmp(cmd.s, "Genurlauth")) {
if (c != ' ') goto missingargs;
cmd_genurlauth(tag.s);
/* snmp_increment(GENURLAUTH_COUNT, 1);*/
}
#endif
else goto badcmd;
break;
case 'I':
if (!strcmp(cmd.s, "Id")) {
if (c != ' ') goto missingargs;
cmd_id(tag.s);
snmp_increment(ID_COUNT, 1);
}
else if (!imapd_userid) goto nologin;
else if (!strcmp(cmd.s, "Idle") && idle_enabled()) {
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_idle(tag.s);
snmp_increment(IDLE_COUNT, 1);
}
else goto badcmd;
break;
case 'L':
if (!strcmp(cmd.s, "Login")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if(c != ' ') goto missingargs;
cmd_login(tag.s, arg1.s);
snmp_increment(LOGIN_COUNT, 1);
}
else if (!strcmp(cmd.s, "Logout")) {
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
snmp_increment(LOGOUT_COUNT, 1);
/* force any responses from our selected backend */
if (backend_current) imapd_check(NULL, 0);
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));
if (imapd_userid && *imapd_userid) {
// Translate the name to external
mboxname_hiersep_toexternal(&imapd_namespace, imapd_userid, config_virtdomains ? strcspn(imapd_userid, "@") : 0);
telemetry_rusage(imapd_userid);
mboxname_hiersep_tointernal(&imapd_namespace, imapd_userid, config_virtdomains ? strcspn(imapd_userid, "@") : 0);
}
return;
}
else if (!imapd_userid) goto nologin;
else if (!strcmp(cmd.s, "List")) {
struct listargs listargs;
if (c != ' ') goto missingargs;
memset(&listargs, 0, sizeof(struct listargs));
listargs.ret = LIST_RET_CHILDREN;
getlistargs(tag.s, &listargs);
if (listargs.pat.count) cmd_list(tag.s, &listargs);
snmp_increment(LIST_COUNT, 1);
}
else if (!strcmp(cmd.s, "Lsub")) {
struct listargs listargs;
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;
memset(&listargs, 0, sizeof(struct listargs));
listargs.cmd = LIST_CMD_LSUB;
listargs.sel = LIST_SEL_SUBSCRIBED;
listargs.ref = arg1.s;
strarray_append(&listargs.pat, arg2.s);
cmd_list(tag.s, &listargs);
snmp_increment(LSUB_COUNT, 1);
}
else if (!strcmp(cmd.s, "Listrights")) {
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg2);
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_listrights(tag.s, arg1.s, arg2.s);
snmp_increment(LISTRIGHTS_COUNT, 1);
}
else if (!strcmp(cmd.s, "Localappend")) {
/* create a local-only mailbox */
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg2);
if (c != ' ') goto missingargs;
cmd_append(tag.s, arg1.s, *arg2.s ? arg2.s : NULL);
snmp_increment(APPEND_COUNT, 1);
}
else if (!strcmp(cmd.s, "Localcreate")) {
/* create a local-only mailbox */
struct dlist *extargs = NULL;
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == ' ') {
c = parsecreateargs(&extargs);
if (c == EOF) goto badpartition;
}
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_create(tag.s, arg1.s, extargs, 1);
dlist_free(&extargs);
/* xxxx snmp_increment(CREATE_COUNT, 1); */
}
else if (!strcmp(cmd.s, "Localdelete")) {
/* delete a mailbox locally only */
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_delete(tag.s, arg1.s, 1, 1);
/* xxxx snmp_increment(DELETE_COUNT, 1); */
}
else goto badcmd;
break;
case 'M':
if (!strcmp(cmd.s, "Myrights")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_myrights(tag.s, arg1.s);
/* xxxx snmp_increment(MYRIGHTS_COUNT, 1); */
}
else if (!strcmp(cmd.s, "Mupdatepush")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if(c == EOF) goto missingargs;
if(c == '\r') c = prot_getc(imapd_in);
if(c != '\n') goto extraargs;
cmd_mupdatepush(tag.s, arg1.s);
/* xxxx snmp_increment(MUPDATEPUSH_COUNT, 1); */
}
else if (!strcmp(cmd.s, "Move")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
move:
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, /*ismove*/1);
snmp_increment(COPY_COUNT, 1);
} else goto badcmd;
break;
case 'N':
if (!strcmp(cmd.s, "Noop")) {
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_noop(tag.s, cmd.s);
/* xxxx snmp_increment(NOOP_COUNT, 1); */
}
#ifdef ENABLE_X_NETSCAPE_HACK
else if (!strcmp(cmd.s, "Netscape")) {
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_netscrape(tag.s);
}
#endif
else if (!imapd_userid) goto nologin;
else if (!strcmp(cmd.s, "Namespace")) {
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_namespace(tag.s);
/* xxxx snmp_increment(NAMESPACE_COUNT, 1); */
}
else goto badcmd;
break;
case 'R':
if (!strcmp(cmd.s, "Rename")) {
havepartition = 0;
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg2);
if (c == EOF) goto missingargs;
if (c == ' ') {
havepartition = 1;
c = getword(imapd_in, &arg3);
if (!imparse_isatom(arg3.s)) goto badpartition;
}
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_rename(tag.s, arg1.s, arg2.s, havepartition ? arg3.s : 0);
/* xxxx snmp_increment(RENAME_COUNT, 1); */
} else if(!strcmp(cmd.s, "Reconstruct")) {
recursive = 0;
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if(c == ' ') {
/* Optional RECURSEIVE argument */
c = getword(imapd_in, &arg2);
if(!imparse_isatom(arg2.s))
goto extraargs;
else if(!strcasecmp(arg2.s, "RECURSIVE"))
recursive = 1;
else
goto extraargs;
}
if(c == '\r') c = prot_getc(imapd_in);
if(c != '\n') goto extraargs;
cmd_reconstruct(tag.s, arg1.s, recursive);
/* snmp_increment(RECONSTRUCT_COUNT, 1); */
}
else if (!strcmp(cmd.s, "Rlist")) {
struct listargs listargs;
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;
memset(&listargs, 0, sizeof(struct listargs));
listargs.sel = LIST_SEL_REMOTE;
listargs.ret = LIST_RET_CHILDREN;
listargs.ref = arg1.s;
strarray_append(&listargs.pat, arg2.s);
cmd_list(tag.s, &listargs);
/* snmp_increment(LIST_COUNT, 1); */
}
else if (!strcmp(cmd.s, "Rlsub")) {
struct listargs listargs;
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;
memset(&listargs, 0, sizeof(struct listargs));
listargs.cmd = LIST_CMD_LSUB;
listargs.sel = LIST_SEL_REMOTE | LIST_SEL_SUBSCRIBED;
listargs.ref = arg1.s;
strarray_append(&listargs.pat, arg2.s);
cmd_list(tag.s, &listargs);
/* snmp_increment(LSUB_COUNT, 1); */
}
#ifdef HAVE_SSL
else if (!strcmp(cmd.s, "Resetkey")) {
int have_mbox = 0, have_mech = 0;
if (c == ' ') {
have_mbox = 1;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == EOF) goto missingargs;
if (c == ' ') {
have_mech = 1;
c = getword(imapd_in, &arg2);
}
}
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_resetkey(tag.s, have_mbox ? arg1.s : 0,
have_mech ? arg2.s : 0);
/* snmp_increment(RESETKEY_COUNT, 1);*/
}
#endif
else goto badcmd;
break;
case 'S':
if (!strcmp(cmd.s, "Starttls")) {
if (!tls_enabled()) {
/* we don't support starttls */
goto badcmd;
}
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
/* XXX discard any input pipelined after STARTTLS */
prot_flush(imapd_in);
/* 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;
}
/* if we've already done COMPRESS fail */
if (imapd_compress_done == 1) {
prot_printf(imapd_out,
"%s BAD Can't Starttls after Compress\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_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
store:
c = getword(imapd_in, &arg1);
if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence;
cmd_store(tag.s, arg1.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;
prot_ungetc(c, imapd_in);
cmd_select(tag.s, cmd.s, arg1.s);
snmp_increment(SELECT_COUNT, 1);
}
else if (!strcmp(cmd.s, "Search")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
search:
cmd_search(tag.s, usinguid);
snmp_increment(SEARCH_COUNT, 1);
}
else if (!strcmp(cmd.s, "Subscribe")) {
if (c != ' ') goto missingargs;
havenamespace = 0;
c = getastring(imapd_in, imapd_out, &arg1);
if (c == ' ') {
havenamespace = 1;
c = getastring(imapd_in, imapd_out, &arg2);
}
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
if (havenamespace) {
cmd_changesub(tag.s, arg1.s, arg2.s, 1);
}
else {
cmd_changesub(tag.s, (char *)0, arg1.s, 1);
}
snmp_increment(SUBSCRIBE_COUNT, 1);
}
else if (!strcmp(cmd.s, "Setacl")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg2);
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg3);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_setacl(tag.s, arg1.s, arg2.s, arg3.s);
snmp_increment(SETACL_COUNT, 1);
}
else if (!strcmp(cmd.s, "Setannotation")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_setannotation(tag.s, arg1.s);
snmp_increment(SETANNOTATION_COUNT, 1);
}
else if (!strcmp(cmd.s, "Setmetadata")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_setmetadata(tag.s, arg1.s);
snmp_increment(SETANNOTATION_COUNT, 1);
}
else if (!strcmp(cmd.s, "Setquota")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_setquota(tag.s, arg1.s);
snmp_increment(SETQUOTA_COUNT, 1);
}
else if (!strcmp(cmd.s, "Sort")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
sort:
cmd_sort(tag.s, usinguid);
snmp_increment(SORT_COUNT, 1);
}
else if (!strcmp(cmd.s, "Status")) {
if (c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') goto missingargs;
cmd_status(tag.s, arg1.s);
snmp_increment(STATUS_COUNT, 1);
}
else if (!strcmp(cmd.s, "Scan")) {
struct listargs listargs;
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 == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
memset(&listargs, 0, sizeof(struct listargs));
listargs.ref = arg1.s;
strarray_append(&listargs.pat, arg2.s);
listargs.scan = arg3.s;
cmd_list(tag.s, &listargs);
snmp_increment(SCAN_COUNT, 1);
}
else goto badcmd;
break;
case 'T':
if (!strcmp(cmd.s, "Thread")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
thread:
cmd_thread(tag.s, usinguid);
snmp_increment(THREAD_COUNT, 1);
}
else goto badcmd;
break;
case 'U':
if (!strcmp(cmd.s, "Uid")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 1;
if (c != ' ') goto missingargs;
c = getword(imapd_in, &arg1);
if (c != ' ') goto missingargs;
lcase(arg1.s);
xstrncpy(cmdname, arg1.s, 99);
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, "move")) {
goto move;
}
else if (!strcmp(arg1.s, "xmove")) {
goto move;
}
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 if (!strcmp(arg1.s, "xrunannotator")) {
goto xrunannotator;
}
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_index && !backend_current) goto nomailbox;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto extraargs;
cmd_close(tag.s, cmd.s);
snmp_increment(UNSELECT_COUNT, 1);
}
else if (!strcmp(cmd.s, "Undump")) {
if(c != ' ') goto missingargs;
c = getastring(imapd_in, imapd_out, &arg1);
/* we want to get a list at this point */
if(c != ' ') goto missingargs;
cmd_undump(tag.s, arg1.s);
/* snmp_increment(UNDUMP_COUNT, 1);*/
}
#ifdef HAVE_SSL
else if (!strcmp(cmd.s, "Urlfetch")) {
if (c != ' ') goto missingargs;
cmd_urlfetch(tag.s);
/* snmp_increment(URLFETCH_COUNT, 1);*/
}
#endif
else goto badcmd;
break;
case 'X':
if (!strcmp(cmd.s, "Xconvfetch")) {
cmd_xconvfetch(tag.s);
// snmp_increment(XCONVFETCH_COUNT, 1);
}
else if (!strcmp(cmd.s, "Xconvmultisort")) {
if (c != ' ') goto missingargs;
if (!imapd_index && !backend_current) goto nomailbox;
cmd_xconvmultisort(tag.s);
// snmp_increment(XCONVMULTISORT_COUNT, 1);
}
else if (!strcmp(cmd.s, "Xconvsort")) {
if (c != ' ') goto missingargs;
if (!imapd_index && !backend_current) goto nomailbox;
cmd_xconvsort(tag.s, 0);
// snmp_increment(XCONVSORT_COUNT, 1);
}
else if (!strcmp(cmd.s, "Xconvupdates")) {
if (c != ' ') goto missingargs;
if (!imapd_index && !backend_current) goto nomailbox;
cmd_xconvsort(tag.s, 1);
// snmp_increment(XCONVUPDATES_COUNT, 1);
}
else 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 if (!strcmp(cmd.s, "Xconvmeta")) {
cmd_xconvmeta(tag.s);
}
else if (!strcmp(cmd.s, "Xlist")) {
struct listargs listargs;
if (c != ' ') goto missingargs;
memset(&listargs, 0, sizeof(struct listargs));
listargs.cmd = LIST_CMD_XLIST;
listargs.ret = LIST_RET_CHILDREN | LIST_RET_SPECIALUSE;
getlistargs(tag.s, &listargs);
if (listargs.pat.count) cmd_list(tag.s, &listargs);
snmp_increment(LIST_COUNT, 1);
}
else if (!strcmp(cmd.s, "Xmove")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
goto move;
}
else if (!strcmp(cmd.s, "Xrunannotator")) {
if (!imapd_index && !backend_current) goto nomailbox;
usinguid = 0;
if (c != ' ') goto missingargs;
xrunannotator:
c = getword(imapd_in, &arg1);
if (!arg1.len || !imparse_issequence(arg1.s)) goto badsequence;
cmd_xrunannotator(tag.s, arg1.s, usinguid);
// snmp_increment(XRUNANNOTATOR_COUNT, 1);
}
else if (!strcmp(cmd.s, "Xsnippets")) {
if (c != ' ') goto missingargs;
if (!imapd_index && !backend_current) goto nomailbox;
cmd_xsnippets(tag.s);
// snmp_increment(XSNIPPETS_COUNT, 1);
}
else if (!strcmp(cmd.s, "Xstats")) {
cmd_xstats(tag.s, c);
}
else if (!strcmp(cmd.s, "Xwarmup")) {
/* XWARMUP doesn't need a mailbox to be selected */
if (c != ' ') goto missingargs;
cmd_xwarmup(tag.s);
// snmp_increment(XWARMUP_COUNT, 1);
}
else goto badcmd;
break;
default:
badcmd:
prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag.s);
eatline(imapd_in, c);
}
/* End command timer - don't log "idle" commands */
if (commandmintimer && strcmp("idle", cmdname)) {
double cmdtime, nettime;
const char *mboxname = index_mboxname(imapd_index);
if (!mboxname) mboxname = "<none>";
cmdtime_endtimer(&cmdtime, &nettime);
if (cmdtime >= commandmintimerd) {
syslog(LOG_NOTICE, "cmdtimer: '%s' '%s' '%s' '%f' '%f' '%f'",
imapd_userid ? imapd_userid : "<none>", cmdname, mboxname,
cmdtime, nettime, cmdtime + nettime);
/* XXX - this would explode horribly if ptr is pointing into zbuf */
syslog(LOG_NOTICE, "buf: %.*s", (int)(imapd_in->ptr - imapd_in->buf), imapd_in->buf);
}
}
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;
}
}
#ifdef USE_AUTOCREATE
/*
* Autocreate Inbox and subfolders upon login
*/
static void autocreate_inbox(void)
{
if (imapd_userisadmin) return;
if (imapd_userisproxyadmin) return;
if (config_getint(IMAPOPT_AUTOCREATE_QUOTA)) {
char *inboxname = mboxname_user_mbox(imapd_userid, NULL);
int r = mboxlist_lookup(inboxname, NULL, NULL);
free(inboxname);
if (r != IMAP_MAILBOX_NONEXISTENT) return;
autocreate_user(&imapd_namespace, imapd_userid);
}
}
#endif // USE_AUTOCREATE
static void authentication_success(void)
{
int r;
struct mboxevent *mboxevent;
/* authstate already created by mysasl_proxy_policy() */
imapd_userisadmin = global_authisa(imapd_authstate, IMAPOPT_ADMINS);
/* Create telemetry log */
imapd_logfd = telemetry_log(imapd_userid, imapd_in, imapd_out, 0);
/* Set namespace */
r = mboxname_init_namespace(&imapd_namespace,
imapd_userisadmin || imapd_userisproxyadmin);
mboxevent_setnamespace(&imapd_namespace);
if (r) {
syslog(LOG_ERR, "%s", error_message(r));
fatal(error_message(r), EC_CONFIG);
}
/* Make a copy of the external userid for use in proxying */
proxy_userid = xstrdup(imapd_userid);
/* Translate any separators in userid */
mboxname_hiersep_tointernal(&imapd_namespace, imapd_userid,
config_virtdomains ?
strcspn(imapd_userid, "@") : 0);
/* send a Login event notification */
if ((mboxevent = mboxevent_new(EVENT_LOGIN))) {
mboxevent_set_access(mboxevent, saslprops.iplocalport,
saslprops.ipremoteport, imapd_userid, NULL, 1);
mboxevent_notify(mboxevent);
mboxevent_free(&mboxevent);
}
#ifdef USE_AUTOCREATE
autocreate_inbox();
#endif // USE_AUTOCREATE
}
static int checklimits(const char *tag)
{
struct proc_limits limits;
limits.procname = "imapd";
limits.clienthost = imapd_clienthost;
limits.userid = imapd_userid;
if (proc_checklimits(&limits)) {
const char *sep = "";
prot_printf(imapd_out, "%s NO Too many open connections (", tag);
if (limits.maxhost) {
prot_printf(imapd_out, "%s%d of %d from %s", sep,
limits.host, limits.maxhost, imapd_clienthost);
sep = ", ";
}
if (limits.maxuser) {
prot_printf(imapd_out, "%s%d of %d for %s", sep,
limits.user, limits.maxuser, imapd_userid);
}
prot_printf(imapd_out, ")\r\n");
free(imapd_userid);
imapd_userid = NULL;
auth_freestate(imapd_authstate);
imapd_authstate = NULL;
return 1;
}
return 0;
}
/*
* Perform a LOGIN command
*/
static void cmd_login(char *tag, char *user)
{
char userbuf[MAX_MAILBOX_BUFFER];
char replybuf[MAX_MAILBOX_BUFFER];
unsigned userlen;
const char *canon_user = userbuf;
const void *val;
char c;
struct buf passwdbuf;
char *passwd;
const char *reply = NULL;
int r;
int failedloginpause;
if (imapd_userid) {
eatline(imapd_in, ' ');
prot_printf(imapd_out, "%s BAD Already logged in\r\n", tag);
return;
}
r = imapd_canon_user(imapd_saslconn, NULL, user, 0,
SASL_CU_AUTHID | SASL_CU_AUTHZID, NULL,
userbuf, sizeof(userbuf), &userlen);
if (r) {
eatline(imapd_in, ' ');
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 && (extprops_ssf < 2) &&
!config_getswitch(IMAPOPT_ALLOWPLAINTEXT) &&
!is_userid_anonymous(canon_user)) {
eatline(imapd_in, ' ');
prot_printf(imapd_out, "%s NO Login only available under a layer\r\n",
tag);
return;
}
memset(&passwdbuf,0,sizeof(struct buf));
c = getastring(imapd_in, imapd_out, &passwdbuf);
if(c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
buf_free(&passwdbuf);
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to LOGIN\r\n",
tag);
eatline(imapd_in, c);
return;
}
passwd = passwdbuf.s;
if (is_userid_anonymous(canon_user)) {
if (config_getswitch(IMAPOPT_ALLOWANONYMOUSLOGIN)) {
passwd = beautify_string(passwd);
if (strlen(passwd) > 500) passwd[500] = '\0';
syslog(LOG_NOTICE, "login: %s anonymous %s",
imapd_clienthost, passwd);
reply = "Anonymous access granted";
imapd_userid = xstrdup("anonymous");
}
else {
syslog(LOG_NOTICE, "badlogin: %s anonymous login refused",
imapd_clienthost);
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(IMAP_ANONYMOUS_NOT_PERMITTED));
buf_free(&passwdbuf);
return;
}
}
else if ( nosaslpasswdcheck ) {
snprintf(replybuf, sizeof(replybuf),
"User logged in SESSIONID=<%s>", session_id());
reply = replybuf;
imapd_userid = xstrdup(canon_user);
imapd_authstate = auth_newstate(canon_user);
syslog(LOG_NOTICE, "login: %s %s%s nopassword%s %s", imapd_clienthost,
imapd_userid, imapd_magicplus ? imapd_magicplus : "",
imapd_starttls_done ? "+TLS" : "", reply);
}
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));
failedloginpause = config_getint(IMAPOPT_FAILEDLOGINPAUSE);
if (failedloginpause != 0) {
sleep(failedloginpause);
}
/* Don't allow user probing */
if (r == SASL_NOUSER) r = SASL_BADAUTH;
if ((reply = sasl_errstring(r, NULL, NULL)) != NULL) {
prot_printf(imapd_out, "%s NO Login failed: %s\r\n", tag, reply);
} else {
prot_printf(imapd_out, "%s NO Login failed: %d\r\n", tag, r);
}
snmp_increment_args(AUTHENTICATION_NO, 1,
VARIABLE_AUTH, 0 /* hash_simple("LOGIN") */,
VARIABLE_LISTEND);
buf_free(&passwdbuf);
return;
}
else {
r = sasl_getprop(imapd_saslconn, SASL_USERNAME, &val);
if(r != SASL_OK) {
if ((reply = sasl_errstring(r, NULL, NULL)) != NULL) {
prot_printf(imapd_out, "%s NO Login failed: %s\r\n",
tag, reply);
} else {
prot_printf(imapd_out, "%s NO Login failed: %d\r\n", tag, r);
}
snmp_increment_args(AUTHENTICATION_NO, 1,
VARIABLE_AUTH, 0 /* hash_simple("LOGIN") */,
VARIABLE_LISTEND);
buf_free(&passwdbuf);
return;
}
snprintf(replybuf, sizeof(replybuf),
"User logged in SESSIONID=<%s>", session_id());
reply = replybuf;
imapd_userid = xstrdup((const char *) val);
snmp_increment_args(AUTHENTICATION_YES, 1,
VARIABLE_AUTH, 0 /*hash_simple("LOGIN") */,
VARIABLE_LISTEND);
syslog(LOG_NOTICE, "login: %s %s%s plaintext%s %s", imapd_clienthost,
imapd_userid, imapd_magicplus ? imapd_magicplus : "",
imapd_starttls_done ? "+TLS" : "",
reply ? reply : "");
/* Apply penalty only if not under layer */
if (!imapd_starttls_done) {
int plaintextloginpause = config_getint(IMAPOPT_PLAINTEXTLOGINPAUSE);
if (plaintextloginpause) {
sleep(plaintextloginpause);
}
/* Fetch plaintext login nag message */
plaintextloginalert = config_getstring(IMAPOPT_PLAINTEXTLOGINALERT);
}
}
buf_free(&passwdbuf);
if (checklimits(tag)) return;
prot_printf(imapd_out, "%s OK [CAPABILITY ", tag);
capa_response(CAPA_PREAUTH|CAPA_POSTAUTH);
prot_printf(imapd_out, "] %s\r\n", reply);
authentication_success();
}
/*
* Perform an AUTHENTICATE command
*/
static void cmd_authenticate(char *tag, char *authtype, char *resp)
{
int sasl_result;
const void *val;
const char *ssfmsg = NULL;
char replybuf[MAX_MAILBOX_BUFFER];
const char *reply = NULL;
const char *canon_user;
int r;
int failedloginpause;
r = saslserver(imapd_saslconn, authtype, resp, "", "+ ", "",
imapd_in, imapd_out, &sasl_result, NULL);
if (r) {
const char *errorstring = NULL;
switch (r) {
case IMAP_SASL_CANCEL:
prot_printf(imapd_out,
"%s BAD Client canceled authentication\r\n", tag);
break;
case IMAP_SASL_PROTERR:
errorstring = prot_error(imapd_in);
prot_printf(imapd_out,
"%s NO Error reading client response: %s\r\n",
tag, errorstring ? errorstring : "");
break;
default:
/* failed authentication */
syslog(LOG_NOTICE, "badlogin: %s %s [%s]",
imapd_clienthost, authtype, sasl_errdetail(imapd_saslconn));
snmp_increment_args(AUTHENTICATION_NO, 1,
VARIABLE_AUTH, 0, /* hash_simple(authtype) */
VARIABLE_LISTEND);
failedloginpause = config_getint(IMAPOPT_FAILEDLOGINPAUSE);
if (failedloginpause != 0) {
sleep(failedloginpause);
}
/* Don't allow user probing */
if (sasl_result == SASL_NOUSER) sasl_result = SASL_BADAUTH;
errorstring = sasl_errstring(sasl_result, NULL, NULL);
if (errorstring) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, errorstring);
} else {
prot_printf(imapd_out, "%s NO Error authenticating\r\n", tag);
}
}
reset_saslconn(&imapd_saslconn);
return;
}
/* successful authentication */
/* get the userid from SASL --- already canonicalized from
* mysasl_proxy_policy()
*/
sasl_result = sasl_getprop(imapd_saslconn, SASL_USERNAME, &val);
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;
}
canon_user = (const char *) val;
/* If we're proxying, the authzid may contain a magic plus,
so re-canonify it */
if (config_getswitch(IMAPOPT_IMAPMAGICPLUS) && strchr(canon_user, '+')) {
char userbuf[MAX_MAILBOX_BUFFER];
unsigned userlen;
sasl_result = imapd_canon_user(imapd_saslconn, NULL, canon_user, 0,
SASL_CU_AUTHID | SASL_CU_AUTHZID,
NULL, userbuf, sizeof(userbuf), &userlen);
if (sasl_result != SASL_OK) {
prot_printf(imapd_out,
"%s NO SASL canonification error %d\r\n",
tag, sasl_result);
reset_saslconn(&imapd_saslconn);
return;
}
imapd_userid = xstrdup(userbuf);
} else {
imapd_userid = xstrdup(canon_user);
}
snprintf(replybuf, sizeof(replybuf),
"User logged in SESSIONID=<%s>", session_id());
reply = replybuf;
syslog(LOG_NOTICE, "login: %s %s%s %s%s %s", imapd_clienthost,
imapd_userid, imapd_magicplus ? imapd_magicplus : "",
authtype, imapd_starttls_done ? "+TLS" : "", reply);
sasl_getprop(imapd_saslconn, SASL_SSF, &val);
saslprops.ssf = *((sasl_ssf_t *) val);
/* 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(saslprops.ssf) {
case 0: ssfmsg = "tls protection"; break;
case 1: ssfmsg = "tls plus integrity protection"; break;
default: ssfmsg = "tls plus privacy protection"; break;
}
} else {
switch(saslprops.ssf) {
case 0: ssfmsg = "no protection"; break;
case 1: ssfmsg = "integrity protection"; break;
default: ssfmsg = "privacy protection"; break;
}
}
snmp_increment_args(AUTHENTICATION_YES, 1,
VARIABLE_AUTH, 0, /* hash_simple(authtype) */
VARIABLE_LISTEND);
if (checklimits(tag)) {
reset_saslconn(&imapd_saslconn);
return;
}
if (!saslprops.ssf) {
prot_printf(imapd_out, "%s OK [CAPABILITY ", tag);
capa_response(CAPA_PREAUTH|CAPA_POSTAUTH);
prot_printf(imapd_out, "] Success (%s) SESSIONID=<%s>\r\n",
ssfmsg, session_id());
} else {
prot_printf(imapd_out, "%s OK Success (%s) SESSIONID=<%s>\r\n",
tag, ssfmsg, session_id());
}
prot_setsasl(imapd_in, imapd_saslconn);
prot_setsasl(imapd_out, imapd_saslconn);
authentication_success();
}
/*
* Perform a NOOP command
*/
static void cmd_noop(char *tag, char *cmd)
{
if (backend_current) {
/* remote mailbox */
prot_printf(backend_current->out, "%s %s\r\n", tag, cmd);
pipe_including_tag(backend_current, tag, 0);
return;
}
index_check(imapd_index, 1, 0);
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
static void clear_id() {
if (imapd_id.params) {
freeattvalues(imapd_id.params);
}
memset(&imapd_id, 0, sizeof(struct id_data));
}
/*
* 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.
*/
static void cmd_id(char *tag)
{
int c = EOF, npair = 0;
static struct buf arg, field;
/* check if we've already had an ID in non-authenticated state */
if (!imapd_userid && imapd_id.did_id) {
prot_printf(imapd_out,
"%s NO Only one Id allowed in non-authenticated state\r\n",
tag);
eatline(imapd_in, c);
return;
}
clear_id();
/* 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);
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);
eatline(imapd_in, c);
return;
}
/* 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);
eatline(imapd_in, c);
return;
}
/* 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);
eatline(imapd_in, c);
return;
}
if (arg.len > MAXIDVALUELEN) {
prot_printf(imapd_out,
"%s BAD value longer than %u octets in Id\r\n",
tag, MAXIDVALUELEN);
eatline(imapd_in, c);
return;
}
if (++npair > MAXIDPAIRS) {
prot_printf(imapd_out,
"%s BAD too many (%u) field-value pairs in ID\r\n",
tag, MAXIDPAIRS);
eatline(imapd_in, c);
return;
}
/* ok, we're happy enough */
appendattvalue(&imapd_id.params, field.s, &arg);
}
if (c != ')') {
/* erp! */
prot_printf(imapd_out, "%s BAD trailing junk\r\n", tag);
eatline(imapd_in, c);
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);
return;
}
/* log the client's ID string.
eventually this should be a callback or something. */
if (npair) {
struct buf logbuf = BUF_INITIALIZER;
struct attvaluelist *pptr;
for (pptr = imapd_id.params; pptr; pptr = pptr->next) {
const char *val = buf_cstring(&pptr->value);
/* should we check for and format literals here ??? */
buf_printf(&logbuf, " \"%s\" ", pptr->attrib);
if (!val || !strcmp(val, "NIL"))
buf_printf(&logbuf, "NIL");
else
buf_printf(&logbuf, "\"%s\"", val);
}
syslog(LOG_INFO, "client id:%s", buf_cstring(&logbuf));
buf_free(&logbuf);
}
/* spit out our ID string.
eventually this might be configurable. */
if (config_getswitch(IMAPOPT_IMAPIDRESPONSE) &&
(imapd_authstate || (config_serverinfo == IMAP_ENUM_SERVERINFO_ON))) {
id_response(imapd_out);
prot_printf(imapd_out, ")\r\n");
}
else
prot_printf(imapd_out, "* ID NIL\r\n");
imapd_check(NULL, 0);
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
imapd_id.did_id = 1;
}
/*
* Perform an IDLE command
*/
static void cmd_idle(char *tag)
{
int c = EOF;
int flags;
static struct buf arg;
static int idle_period = -1;
if (!backend_current) { /* Local mailbox */
/* Tell client we are idling and waiting for end of command */
prot_printf(imapd_out, "+ idling\r\n");
prot_flush(imapd_out);
/* Start doing mailbox updates */
index_check(imapd_index, 1, 0);
idle_start(index_mboxname(imapd_index));
/* use this flag so if getc causes a shutdown due to
* connection abort we tell idled about it */
idling = 1;
index_release(imapd_index);
while ((flags = idle_wait(imapd_in->fd))) {
if (flags & IDLE_INPUT) {
/* Get continuation data */
c = getword(imapd_in, &arg);
break;
}
/* Send unsolicited untagged responses to the client */
if (flags & IDLE_MAILBOX)
index_check(imapd_index, 1, 0);
if (flags & IDLE_ALERT) {
char shut[MAX_MAILBOX_PATH+1];
if (! imapd_userisadmin &&
(shutdown_file(shut, sizeof(shut)) ||
(imapd_userid &&
userdeny(imapd_userid, config_ident, shut, sizeof(shut))))) {
char *p;
for (p = shut; *p == '['; p++); /* can't have [ be first char */
prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p);
shut_down(0);
}
}
index_release(imapd_index);
prot_flush(imapd_out);
}
/* Stop updates and do any necessary cleanup */
idling = 0;
idle_stop(index_mboxname(imapd_index));
}
else { /* Remote mailbox */
int done = 0, shutdown = 0;
char buf[2048];
/* get polling period */
if (idle_period == -1) {
idle_period = config_getint(IMAPOPT_IMAPIDLEPOLL);
}
if (CAPA(backend_current, CAPA_IDLE)) {
/* Start IDLE on backend */
prot_printf(backend_current->out, "%s IDLE\r\n", tag);
if (!prot_fgets(buf, sizeof(buf), backend_current->in)) {
/* If we received nothing from the backend, fail */
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(IMAP_SERVER_UNAVAILABLE));
return;
}
if (buf[0] != '+') {
/* If we received anything but a continuation response,
spit out what we received and quit */
prot_write(imapd_out, buf, strlen(buf));
return;
}
}
/* Tell client we are idling and waiting for end of command */
prot_printf(imapd_out, "+ idling\r\n");
prot_flush(imapd_out);
/* Pipe updates to client while waiting for end of command */
while (!done) {
/* Flush any buffered output */
prot_flush(imapd_out);
/* Check for shutdown file */
if (!imapd_userisadmin &&
(shutdown_file(buf, sizeof(buf)) ||
(imapd_userid &&
userdeny(imapd_userid, config_ident, buf, sizeof(buf))))) {
shutdown = done = 1;
goto done;
}
done = proxy_check_input(protin, imapd_in, imapd_out,
backend_current->in, NULL, idle_period);
/* If not running IDLE on backend, poll the mailbox for updates */
if (!CAPA(backend_current, CAPA_IDLE)) {
imapd_check(NULL, 0);
}
}
/* Get continuation data */
c = getword(imapd_in, &arg);
done:
if (CAPA(backend_current, CAPA_IDLE)) {
/* Either the client timed out, or ended the command.
In either case we're done, so terminate IDLE on backend */
prot_printf(backend_current->out, "Done\r\n");
pipe_until_tag(backend_current, tag, 0);
}
if (shutdown) {
char *p;
for (p = buf; *p == '['; p++); /* can't have [ be first char */
prot_printf(imapd_out, "* BYE [ALERT] %s\r\n", p);
shut_down(0);
}
}
imapd_check(NULL, 1);
if (c != EOF) {
if (!strcasecmp(arg.s, "Done") &&
(c = (c == '\r') ? prot_getc(imapd_in) : c) == '\n') {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
else {
prot_printf(imapd_out,
"%s BAD Invalid Idle continuation\r\n", tag);
eatline(imapd_in, c);
}
}
}
static void capa_response(int flags)
{
const char *sasllist; /* the list of SASL mechanisms */
int mechcount;
int need_space = 0;
int i;
for (i = 0; base_capabilities[i].str; i++) {
/* Filter capabilities if requested */
if (capa_is_disabled(base_capabilities[i].str))
continue;
/* Don't show "MAILBOX-REFERRALS" if disabled by config */
if (config_getswitch(IMAPOPT_PROXYD_DISABLE_MAILBOX_REFERRALS) &&
!strcmp(base_capabilities[i].str, "MAILBOX-REFERRALS"))
continue;
/* Don't show if they're not shown at this level of login */
if (!(base_capabilities[i].mask & flags))
continue;
/* print the capability */
if (need_space) prot_putc(' ', imapd_out);
else need_space = 1;
prot_printf(imapd_out, "%s", base_capabilities[i].str);
}
if (config_mupdate_server) {
prot_printf(imapd_out, " MUPDATE=mupdate://%s/", config_mupdate_server);
}
if (tls_enabled() && !imapd_starttls_done && !imapd_authstate) {
prot_printf(imapd_out, " STARTTLS");
}
if (imapd_authstate ||
(!imapd_starttls_done && (extprops_ssf < 2) &&
!config_getswitch(IMAPOPT_ALLOWPLAINTEXT))) {
prot_printf(imapd_out, " LOGINDISABLED");
}
/* add the SASL mechs */
if ((!imapd_authstate || saslprops.ssf) &&
sasl_listmech(imapd_saslconn, NULL,
"AUTH=", " AUTH=",
!imapd_authstate ? " SASL-IR" : "", &sasllist,
NULL, &mechcount) == SASL_OK && mechcount > 0) {
prot_printf(imapd_out, " %s", sasllist);
} else {
/* else don't show anything */
}
if (!(flags & CAPA_POSTAUTH)) return;
if (config_getswitch(IMAPOPT_CONVERSATIONS))
prot_printf(imapd_out, " XCONVERSATIONS");
#ifdef HAVE_ZLIB
if (!imapd_compress_done && !imapd_tls_comp) {
prot_printf(imapd_out, " COMPRESS=DEFLATE");
}
#endif // HAVE_ZLIB
for (i = 0 ; i < QUOTA_NUMRESOURCES ; i++)
prot_printf(imapd_out, " X-QUOTA=%s", quota_names[i]);
if (idle_enabled()) {
prot_printf(imapd_out, " IDLE");
}
}
/*
* Perform a CAPABILITY command
*/
static void cmd_capability(char *tag)
{
imapd_check(NULL, 0);
prot_printf(imapd_out, "* CAPABILITY ");
capa_response(CAPA_PREAUTH|CAPA_POSTAUTH);
prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Parse and perform an APPEND command.
* The command has been parsed up to and including
* the mailbox name.
*/
static int isokflag(char *s, int *isseen)
{
if (s[0] == '\\') {
lcase(s);
if (!strcmp(s, "\\seen")) {
*isseen = 1;
return 1;
}
if (!strcmp(s, "\\answered")) return 1;
if (!strcmp(s, "\\flagged")) return 1;
if (!strcmp(s, "\\draft")) return 1;
if (!strcmp(s, "\\deleted")) return 1;
/* uh oh, system flag i don't recognize */
return 0;
} else {
/* valid user flag? */
return imparse_isatom(s);
}
}
static int getliteralsize(const char *p, int c,
unsigned *size, int *binary, const char **parseerr)
{
int isnowait = 0;
uint32_t num;
/* Check for literal8 */
if (*p == '~') {
p++;
*binary = 1;
}
/* check for start of literal */
if (*p != '{') {
*parseerr = "Missing required argument to Append command";
return IMAP_PROTOCOL_ERROR;
}
/* Read size from literal */
if (parseuint32(p+1, &p, &num)) {
*parseerr = "Literal size not a number";
return IMAP_PROTOCOL_ERROR;
}
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') {
*parseerr = "Invalid literal in Append command";
return IMAP_PROTOCOL_ERROR;
}
if (!isnowait) {
/* Tell client to send the message */
prot_printf(imapd_out, "+ go ahead\r\n");
prot_flush(imapd_out);
}
*size = num;
return 0;
}
static int catenate_text(FILE *f, unsigned *totalsize, int *binary,
const char **parseerr)
{
int c;
static struct buf arg;
unsigned size = 0;
char buf[4096+1];
unsigned n;
int r;
c = getword(imapd_in, &arg);
/* Read size from literal */
r = getliteralsize(arg.s, c, &size, binary, parseerr);
if (r) return r;
if (*totalsize > UINT_MAX - size) r = IMAP_MESSAGE_TOO_LARGE;
/* Catenate message part to stage */
while (size) {
n = prot_read(imapd_in, buf, size > 4096 ? 4096 : size);
if (!n) {
syslog(LOG_ERR,
"IOERROR: reading message: unexpected end of file");
return IMAP_IOERROR;
}
buf[n] = '\0';
if (!*binary && (n != strlen(buf))) r = IMAP_MESSAGE_CONTAINSNULL;
size -= n;
if (r) continue;
/* XXX do we want to try and validate the message like
we do in message_copy_strict()? */
if (f) fwrite(buf, n, 1, f);
}
*totalsize += size;
return r;
}
static int catenate_url(const char *s, const char *cur_name, FILE *f,
unsigned *totalsize, const char **parseerr)
{
struct imapurl url;
char mailboxname[MAX_MAILBOX_BUFFER];
struct index_state *state;
uint32_t msgno;
int r = 0, doclose = 0;
unsigned long size = 0;
r = imapurl_fromURL(&url, s);
if (r) {
*parseerr = "Improperly specified URL";
r = IMAP_BADURL;
} else if (url.server) {
*parseerr = "Only relative URLs are supported";
r = IMAP_BADURL;
#if 0
} else if (url.server && strcmp(url.server, config_servername)) {
*parseerr = "Cannot catenate messages from another server";
r = IMAP_BADURL;
#endif
} else if (!url.mailbox && !imapd_index && !cur_name) {
*parseerr = "No mailbox is selected or specified";
r = IMAP_BADURL;
} else if (url.mailbox || (url.mailbox = cur_name)) {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
url.mailbox,
imapd_userid, mailboxname);
if (!r) {
mbentry_t *mbentry = NULL;
/* lookup the location of the mailbox */
r = mlookup(NULL, NULL, mailboxname, &mbentry);
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
struct backend *be;
be = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (be) {
r = proxy_catenate_url(be, &url, f, &size, parseerr);
if (*totalsize > UINT_MAX - size)
r = IMAP_MESSAGE_TOO_LARGE;
else
*totalsize += size;
}
else
r = IMAP_SERVER_UNAVAILABLE;
free(url.freeme);
mboxlist_entry_free(&mbentry);
return r;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (!r) {
struct index_init init;
memset(&init, 0, sizeof(init));
init.qresync = imapd_client_capa & CAPA_QRESYNC;
init.userid = imapd_userid;
init.authstate = imapd_authstate;
init.out = imapd_out;
r = index_open(mailboxname, &init, &state);
if (init.vanishedlist) seqset_free(init.vanishedlist);
}
if (!r) doclose = 1;
if (!r && !(state->myrights & ACL_READ))
r = (imapd_userisadmin || (state->myrights & ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
if (r) {
*parseerr = error_message(r);
r = IMAP_BADURL;
}
} else {
state = imapd_index;
}
if (r) {
/* nothing to do, handled up top */
} else if (url.uidvalidity &&
(state->mailbox->i.uidvalidity != url.uidvalidity)) {
*parseerr = "Uidvalidity of mailbox has changed";
r = IMAP_BADURL;
} else if (!url.uid || !(msgno = index_finduid(state, url.uid)) ||
(index_getuid(state, msgno) != url.uid)) {
*parseerr = "No such message in mailbox";
r = IMAP_BADURL;
} else {
/* Catenate message part to stage */
struct protstream *s = prot_new(fileno(f), 1);
r = index_urlfetch(state, msgno, 0, url.section,
url.start_octet, url.octet_count, s, &size);
if (r == IMAP_BADURL)
*parseerr = "No such message part";
else if (!r) {
if (*totalsize > UINT_MAX - size)
r = IMAP_MESSAGE_TOO_LARGE;
else
*totalsize += size;
}
prot_flush(s);
prot_free(s);
/* XXX do we want to try and validate the message like
we do in message_copy_strict()? */
}
free(url.freeme);
if (doclose) index_close(&state);
return r;
}
static int append_catenate(FILE *f, const char *cur_name, unsigned *totalsize,
int *binary, const char **parseerr, const char **url)
{
int c, r = 0;
static struct buf arg;
do {
c = getword(imapd_in, &arg);
if (c != ' ') {
*parseerr = "Missing message part data in Append command";
return IMAP_PROTOCOL_ERROR;
}
if (!strcasecmp(arg.s, "TEXT")) {
int r1 = catenate_text(f, totalsize, binary, parseerr);
if (r1) return r1;
/* if we see a SP, we're trying to catenate more than one part */
/* Parse newline terminating command */
c = prot_getc(imapd_in);
}
else if (!strcasecmp(arg.s, "URL")) {
c = getastring(imapd_in, imapd_out, &arg);
if (c != ' ' && c != ')') {
*parseerr = "Missing URL in Append command";
return IMAP_PROTOCOL_ERROR;
}
if (!r) {
r = catenate_url(arg.s, cur_name, f, totalsize, parseerr);
if (r) {
*url = arg.s;
return r;
}
}
}
else {
*parseerr = "Invalid message part type in Append command";
return IMAP_PROTOCOL_ERROR;
}
fflush(f);
} while (c == ' ');
if (c != ')') {
*parseerr = "Missing space or ) after catenate list in Append command";
return IMAP_PROTOCOL_ERROR;
}
if (ferror(f) || fsync(fileno(f))) {
syslog(LOG_ERR, "IOERROR: writing message: %m");
return IMAP_IOERROR;
}
return r;
}
/* If an APPEND is proxied from another server,
* 'cur_name' is the name of the currently selected mailbox (if any)
* in case we have to resolve relative URLs
*/
static void cmd_append(char *tag, char *name, const char *cur_name)
{
int c;
static struct buf arg;
time_t now = time(NULL);
quota_t qdiffs[QUOTA_NUMRESOURCES] = QUOTA_DIFFS_INITIALIZER;
unsigned size;
int sync_seen = 0;
int r;
int i;
char mailboxname[MAX_MAILBOX_BUFFER];
struct appendstate appendstate;
unsigned long uidvalidity = 0;
long doappenduid = 0;
const char *parseerr = NULL, *url = NULL;
struct appendstage *curstage;
mbentry_t *mbentry = NULL;
/* See if we can append */
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
struct backend *s = NULL;
if (supports_referrals) {
imapd_refer(tag, mbentry->server, name);
/* Eat the argument */
eatline(imapd_in, prot_getc(imapd_in));
mboxlist_entry_free(&mbentry);
return;
}
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
mboxlist_entry_free(&mbentry);
imapd_check(s, 0);
if (!r) {
int is_active = 1;
s->context = (void*) &is_active;
if (imapd_index) {
const char *mboxname = index_mboxname(imapd_index);
prot_printf(s->out, "%s Localappend {" SIZE_T_FMT "+}\r\n%s"
" {" SIZE_T_FMT "+}\r\n%s ",
tag, strlen(name), name,
strlen(mboxname), mboxname);
} else {
prot_printf(s->out, "%s Localappend {" SIZE_T_FMT "+}\r\n%s"
" \"\" ", tag, strlen(name), name);
}
if (!(r = pipe_command(s, 16384))) {
pipe_including_tag(s, tag, 0);
}
s->context = NULL;
} else {
eatline(imapd_in, prot_getc(imapd_in));
}
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
prot_error(imapd_in) ? prot_error(imapd_in) :
error_message(r));
}
return;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (!r) {
qdiffs[QUOTA_MESSAGE] = 1;
r = append_check(mailboxname, imapd_authstate, ACL_INSERT, ignorequota ? NULL : qdiffs);
}
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,
NULL, NULL, 0) == 0)
? "[TRYCREATE] " : "", error_message(r));
return;
}
c = ' '; /* just parsed a space */
/* we loop, to support MULTIAPPEND */
while (!r && c == ' ') {
curstage = xzmalloc(sizeof(*curstage));
ptrarray_push(&stages, curstage);
/* now parsing "append-opts" in the ABNF */
/* Parse flags */
c = getword(imapd_in, &arg);
if (c == '(' && !arg.s[0]) {
strarray_init(&curstage->flags);
do {
c = getword(imapd_in, &arg);
if (!curstage->flags.count && !arg.s[0] && c == ')') break; /* empty list */
if (!isokflag(arg.s, &sync_seen)) {
parseerr = "Invalid flag in Append command";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
strarray_append(&curstage->flags, 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(&(curstage->internaldate));
if (c != ' ') {
parseerr = "Invalid date-time in Append command";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
c = getword(imapd_in, &arg);
}
/* try to parse a sequence of "append-ext" */
for (;;) {
if (!strcasecmp(arg.s, "ANNOTATION")) {
/* RFC5257 */
if (c != ' ') {
parseerr = "Missing annotation data in Append command";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
c = parse_annotate_store_data(tag,
/*permessage_flag*/1,
&curstage->annotations);
if (c == EOF) {
eatline(imapd_in, c);
goto cleanup;
}
qdiffs[QUOTA_ANNOTSTORAGE] += sizeentryatts(curstage->annotations);
c = getword(imapd_in, &arg);
}
else
break; /* not a known extension keyword */
}
/* Stage the message */
curstage->f = append_newstage(mailboxname, now, stages.count, &(curstage->stage));
if (!curstage->f) {
r = IMAP_IOERROR;
goto done;
}
/* now parsing "append-data" in the ABNF */
if (!strcasecmp(arg.s, "CATENATE")) {
if (c != ' ' || (c = prot_getc(imapd_in) != '(')) {
parseerr = "Missing message part(s) in Append command";
r = IMAP_PROTOCOL_ERROR;
goto done;
}
/* Catenate the message part(s) to stage */
size = 0;
r = append_catenate(curstage->f, cur_name, &size,
&(curstage->binary), &parseerr, &url);
if (r) goto done;
}
else {
/* Read size from literal */
r = getliteralsize(arg.s, c, &size, &(curstage->binary), &parseerr);
if (!r && size == 0) r = IMAP_ZERO_LENGTH_LITERAL;
if (r) goto done;
/* Copy message to stage */
r = message_copy_strict(imapd_in, curstage->f, size, curstage->binary);
}
qdiffs[QUOTA_STORAGE] += size;
/* If this is a non-BINARY message, close the stage file.
* Otherwise, leave it open so we can encode the binary parts.
*
* XXX For BINARY MULTIAPPEND, we may have to close the stage files
* anyways to avoid too many open files.
*/
if (!curstage->binary) {
fclose(curstage->f);
curstage->f = NULL;
}
/* if we see a SP, we're trying to append more than one message */
/* Parse newline terminating command */
c = prot_getc(imapd_in);
}
done:
if (r) {
eatline(imapd_in, c);
} else {
/* we should be looking at the end of the line */
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
parseerr = "junk after literal";
r = IMAP_PROTOCOL_ERROR;
eatline(imapd_in, c);
}
}
/* Append from the stage(s) */
if (!r) {
qdiffs[QUOTA_MESSAGE] = stages.count;
r = append_setup(&appendstate, mailboxname,
imapd_userid, imapd_authstate, ACL_INSERT,
ignorequota ? NULL : qdiffs, &imapd_namespace,
(imapd_userisadmin || imapd_userisproxyadmin),
EVENT_MESSAGE_APPEND);
}
if (!r) {
struct body *body;
doappenduid = (appendstate.myrights & ACL_READ);
uidvalidity = append_uidvalidity(&appendstate);
for (i = 0; !r && i < stages.count ; i++) {
curstage = stages.data[i];
body = NULL;
if (curstage->binary) {
r = message_parse_binary_file(curstage->f, &body);
fclose(curstage->f);
curstage->f = NULL;
}
if (!r) {
r = append_fromstage(&appendstate, &body, curstage->stage,
curstage->internaldate,
&curstage->flags, 0,
curstage->annotations);
}
if (body) {
/* Note: either the calls to message_parse_binary_file()
* or append_fromstage() above, may create a body. */
message_free_body(body);
free(body);
body = NULL;
}
}
if (!r) {
r = append_commit(&appendstate);
} else {
append_abort(&appendstate);
}
}
imapd_check(NULL, 1);
if (r == IMAP_PROTOCOL_ERROR && parseerr) {
prot_printf(imapd_out, "%s BAD %s\r\n", tag, parseerr);
} else if (r == IMAP_BADURL) {
prot_printf(imapd_out, "%s NO [BADURL \"%s\"] %s\r\n",
tag, url, parseerr);
} else if (r) {
prot_printf(imapd_out, "%s NO %s%s\r\n",
tag,
(r == IMAP_MAILBOX_NONEXISTENT &&
mboxlist_createmailboxcheck(mailboxname, 0, 0,
imapd_userisadmin,
imapd_userid, imapd_authstate,
NULL, NULL, 0) == 0)
? "[TRYCREATE] " : r == IMAP_MESSAGE_TOO_LARGE
? "[TOOBIG]" : "", error_message(r));
} else if (doappenduid) {
/* is this a space seperated list or sequence list? */
prot_printf(imapd_out, "%s OK [APPENDUID %lu ", tag, uidvalidity);
if (appendstate.nummsg == 1) {
prot_printf(imapd_out, "%u", appendstate.baseuid);
} else {
prot_printf(imapd_out, "%u:%u", appendstate.baseuid,
appendstate.baseuid + appendstate.nummsg - 1);
}
prot_printf(imapd_out, "] %s\r\n", error_message(IMAP_OK_COMPLETED));
} else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
cleanup:
/* Cleanup the stage(s) */
while ((curstage = ptrarray_pop(&stages))) {
if (curstage->f != NULL) fclose(curstage->f);
append_removestage(curstage->stage);
strarray_fini(&curstage->flags);
freeentryatts(curstage->annotations);
free(curstage);
}
ptrarray_fini(&stages);
}
/*
* Warn if mailbox is close to or over any quota resource.
*
* Warn if the following possibilities occur:
* - quotawarnkb not set + quotawarn hit
* - quotawarnkb set larger than mailbox + quotawarn hit
* - quotawarnkb set + hit + quotawarn hit
* - quotawarnmsg not set + quotawarn hit
* - quotawarnmsg set larger than mailbox + quotawarn hit
* - quotawarnmsg set + hit + quotawarn hit
*/
static void warn_about_quota(const char *quotaroot)
{
time_t now = time(NULL);
struct quota q;
int res;
int r;
int thresholds[QUOTA_NUMRESOURCES];
int pc_threshold = config_getint(IMAPOPT_QUOTAWARN);
int pc_usage;
struct buf msg = BUF_INITIALIZER;
static char lastqr[MAX_MAILBOX_PATH+1] = "";
static time_t nextalert = 0;
if (!quotaroot || !*quotaroot)
return; /* no quota, nothing to do */
/* rate limit checks and warnings to every 10 min */
if (!strcmp(quotaroot, lastqr) && now < nextalert)
return;
strlcpy(lastqr, quotaroot, sizeof(lastqr));
nextalert = now + 600;
quota_init(&q, quotaroot);
r = quota_read(&q, NULL, 0);
if (r)
goto out; /* failed to read */
memset(thresholds, 0, sizeof(thresholds));
thresholds[QUOTA_STORAGE] = config_getint(IMAPOPT_QUOTAWARNKB);
thresholds[QUOTA_MESSAGE] = config_getint(IMAPOPT_QUOTAWARNMSG);
thresholds[QUOTA_ANNOTSTORAGE] = config_getint(IMAPOPT_QUOTAWARNKB);
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (q.limits[res] < 0)
continue; /* this resource is unlimited */
buf_reset(&msg);
if (thresholds[res] <= 0 ||
thresholds[res] >= q.limits[res] ||
q.useds[res] > ((quota_t) (q.limits[res] - thresholds[res])) * quota_units[res]) {
pc_usage = (int)(((double) q.useds[res] * 100.0) /
(double) ((quota_t) q.limits[res] * quota_units[res]));
if (q.useds[res] > (quota_t) q.limits[res] * quota_units[res])
buf_printf(&msg, error_message(IMAP_NO_OVERQUOTA),
quota_names[res]);
else if (pc_usage > pc_threshold)
buf_printf(&msg, error_message(IMAP_NO_CLOSEQUOTA),
pc_usage, quota_names[res]);
}
if (msg.len)
prot_printf(imapd_out, "* NO [ALERT] %s\r\n", buf_cstring(&msg));
}
buf_reset(&msg);
out:
quota_free(&q);
}
/*
* Perform a SELECT/EXAMINE/BBOARD command
*/
static void cmd_select(char *tag, char *cmd, char *name)
{
int c;
char mailboxname[MAX_MAILBOX_BUFFER];
int r = 0;
int doclose = 0;
mbentry_t *mbentry = NULL;
struct backend *backend_next = NULL;
struct index_init init;
int wasopen = 0;
struct vanished_params *v = &init.vanished;
memset(&init, 0, sizeof(struct index_init));
c = prot_getc(imapd_in);
if (c == ' ') {
static struct buf arg, parm1, parm2;
c = prot_getc(imapd_in);
if (c != '(') goto badlist;
c = getword(imapd_in, &arg);
if (arg.s[0] == '\0') goto badlist;
for (;;) {
ucase(arg.s);
if (!strcmp(arg.s, "CONDSTORE")) {
imapd_client_capa |= CAPA_CONDSTORE;
}
else if ((imapd_client_capa & CAPA_QRESYNC) &&
!strcmp(arg.s, "QRESYNC")) {
char *p;
if (c != ' ') goto badqresync;
c = prot_getc(imapd_in);
if (c != '(') goto badqresync;
c = getastring(imapd_in, imapd_out, &arg);
v->uidvalidity = strtoul(arg.s, &p, 10);
if (*p || !v->uidvalidity || v->uidvalidity == ULONG_MAX) goto badqresync;
if (c != ' ') goto badqresync;
c = getmodseq(imapd_in, &v->modseq);
if (c == EOF) goto badqresync;
if (c == ' ') {
c = prot_getc(imapd_in);
if (c != '(') {
/* optional UID sequence */
prot_ungetc(c, imapd_in);
c = getword(imapd_in, &arg);
if (!imparse_issequence(arg.s)) goto badqresync;
v->sequence = arg.s;
if (c == ' ') {
c = prot_getc(imapd_in);
if (c != '(') goto badqresync;
}
}
if (c == '(') {
/* optional sequence match data */
c = getword(imapd_in, &parm1);
if (!imparse_issequence(parm1.s)) goto badqresync;
v->match_seq = parm1.s;
if (c != ' ') goto badqresync;
c = getword(imapd_in, &parm2);
if (!imparse_issequence(parm2.s)) goto badqresync;
v->match_uid = parm2.s;
if (c != ')') goto badqresync;
c = prot_getc(imapd_in);
}
}
if (c != ')') goto badqresync;
c = prot_getc(imapd_in);
}
else if (!strcmp(arg.s, "ANNOTATE")) {
/*
* RFC5257 requires us to parse this keyword, which
* indicates that the client wants unsolicited
* ANNOTATION responses in this session, but we don't
* actually have to do anything with it, so we won't.
*/
;
}
else {
prot_printf(imapd_out, "%s BAD Invalid %s modifier %s\r\n",
tag, cmd, 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 %s\r\n", tag, cmd);
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 %s\r\n", tag, cmd);
eatline(imapd_in, c);
return;
}
if (imapd_index) {
index_close(&imapd_index);
wasopen = 1;
}
if (backend_current) {
/* remove backend_current from the protgroup */
protgroup_delete(protin, backend_current->in);
wasopen = 1;
}
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
char mytag[128];
if (supports_referrals) {
imapd_refer(tag, mbentry->server, name);
mboxlist_entry_free(&mbentry);
return;
}
backend_next = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox,
imapd_in);
if (!backend_next) r = IMAP_SERVER_UNAVAILABLE;
if (backend_current && backend_current != backend_next) {
/* switching servers; flush old server output */
proxy_gentag(mytag, sizeof(mytag));
prot_printf(backend_current->out, "%s Unselect\r\n", mytag);
/* do not fatal() here, because we don't really care about this
* server anymore anyway */
pipe_until_tag(backend_current, mytag, 1);
}
backend_current = backend_next;
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
mboxlist_entry_free(&mbentry);
return;
}
if (imapd_client_capa) {
/* Enable client capabilities on new backend */
proxy_gentag(mytag, sizeof(mytag));
prot_printf(backend_current->out, "%s Enable", mytag);
if (imapd_client_capa & CAPA_QRESYNC)
prot_printf(backend_current->out, " Qresync");
else if (imapd_client_capa & CAPA_CONDSTORE)
prot_printf(backend_current->out, " Condstore");
prot_printf(backend_current->out, "\r\n");
pipe_until_tag(backend_current, mytag, 0);
}
/* Send SELECT command to backend */
prot_printf(backend_current->out, "%s %s {" SIZE_T_FMT "+}\r\n%s",
tag, cmd, strlen(name), name);
if (v->uidvalidity) {
prot_printf(backend_current->out, " (QRESYNC (%lu " MODSEQ_FMT,
v->uidvalidity, v->modseq);
if (v->sequence) {
prot_printf(backend_current->out, " %s", v->sequence);
}
if (v->match_seq && v->match_uid) {
prot_printf(backend_current->out, " (%s %s)",
v->match_seq, v->match_uid);
}
prot_printf(backend_current->out, "))");
}
prot_printf(backend_current->out, "\r\n");
switch (pipe_including_tag(backend_current, tag, 0)) {
case PROXY_OK:
syslog(LOG_DEBUG, "open: user %s opened %s on %s",
imapd_userid, name, mbentry->server);
/* add backend_current to the protgroup */
protgroup_insert(protin, backend_current->in);
break;
default:
syslog(LOG_DEBUG, "open: user %s failed to open %s", imapd_userid,
name);
/* not successfully selected */
backend_current = NULL;
break;
}
mboxlist_entry_free(&mbentry);
return;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (backend_current) {
char mytag[128];
/* switching servers; flush old server output */
proxy_gentag(mytag, sizeof(mytag));
prot_printf(backend_current->out, "%s Unselect\r\n", mytag);
/* do not fatal() here, because we don't really care about this
* server anymore anyway */
pipe_until_tag(backend_current, mytag, 1);
}
backend_current = NULL;
if (wasopen) prot_printf(imapd_out, "* OK [CLOSED] Ok\r\n");
init.qresync = imapd_client_capa & CAPA_QRESYNC;
init.userid = imapd_userid;
init.authstate = imapd_authstate;
init.out = imapd_out;
init.examine_mode = cmd[0] == 'E';
init.select = 1;
r = index_open(mailboxname, &init, &imapd_index);
if (!r) doclose = 1;
if (!r && !index_hasrights(imapd_index, ACL_READ)) {
r = (imapd_userisadmin || index_hasrights(imapd_index, ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
if (init.vanishedlist) seqset_free(init.vanishedlist);
init.vanishedlist = NULL;
if (doclose) index_close(&imapd_index);
return;
}
if (index_hasrights(imapd_index, ACL_EXPUNGE))
warn_about_quota(imapd_index->mailbox->quotaroot);
index_select(imapd_index, &init);
if (init.vanishedlist) seqset_free(init.vanishedlist);
init.vanishedlist = NULL;
prot_printf(imapd_out, "%s OK [READ-%s] %s\r\n", tag,
index_hasrights(imapd_index, ACL_READ_WRITE) ?
"WRITE" : "ONLY", error_message(IMAP_OK_COMPLETED));
syslog(LOG_DEBUG, "open: user %s opened %s", imapd_userid, name);
return;
badlist:
prot_printf(imapd_out, "%s BAD Invalid modifier list in %s\r\n", tag, cmd);
eatline(imapd_in, c);
return;
badqresync:
prot_printf(imapd_out, "%s BAD Invalid QRESYNC parameter list in %s\r\n",
tag, cmd);
eatline(imapd_in, c);
return;
}
/*
* Perform a CLOSE/UNSELECT command
*/
static void cmd_close(char *tag, char *cmd)
{
if (backend_current) {
/* remote mailbox */
prot_printf(backend_current->out, "%s %s\r\n", tag, cmd);
/* xxx do we want this to say OK if the connection is gone?
* saying NO is clearly wrong, hense the fatal request. */
pipe_including_tag(backend_current, tag, 0);
/* remove backend_current from the protgroup */
protgroup_delete(protin, backend_current->in);
backend_current = NULL;
return;
}
/* local mailbox */
if ((cmd[0] == 'C') && index_hasrights(imapd_index, ACL_EXPUNGE)) {
index_expunge(imapd_index, NULL, 1);
/* don't tell changes here */
}
index_close(&imapd_index);
/* http://www.rfc-editor.org/errata_search.php?rfc=5162
* Errata ID: 1808 - don't send HIGHESTMODSEQ to a close
* command, because it can lose synchronisation */
prot_printf(imapd_out, "%s OK %s\r\n",
tag, error_message(IMAP_OK_COMPLETED));
}
/*
* Append to the section list.
*/
static void section_list_append(struct section **l,
const char *name,
const struct octetinfo *oi)
{
struct section **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = xzmalloc(sizeof(struct section));
(*tail)->name = xstrdup(name);
(*tail)->octetinfo = *oi;
(*tail)->next = NULL;
}
static void section_list_free(struct section *l)
{
struct section *n;
while (l) {
n = l->next;
free(l->name);
free(l);
l = n;
}
}
/*
* Parse the syntax for a partial fetch:
* "<" number "." nz-number ">"
*/
#define PARSE_PARTIAL(start_octet, octet_count) \
(start_octet) = (octet_count) = 0; \
if (*p == '<' && Uisdigit(p[1])) { \
(start_octet) = p[1] - '0'; \
p += 2; \
while (Uisdigit((int) *p)) { \
(start_octet) = \
(start_octet) * 10 + *p++ - '0'; \
} \
\
if (*p == '.' && p[1] >= '1' && p[1] <= '9') { \
(octet_count) = p[1] - '0'; \
p[0] = '>'; p[1] = '\0'; /* clip off the octet count \
(its not used in the reply) */ \
p += 2; \
while (Uisdigit(*p)) { \
(octet_count) = \
(octet_count) * 10 + *p++ - '0'; \
} \
} \
else p--; \
\
if (*p != '>') { \
prot_printf(imapd_out, \
"%s BAD Invalid body partial\r\n", tag); \
eatline(imapd_in, c); \
goto freeargs; \
} \
p++; \
}
static int parse_fetch_args(const char *tag, const char *cmd,
int allow_vanished,
struct fetchargs *fa)
{
static struct buf fetchatt, fieldname;
int c;
int inlist = 0;
char *p, *section;
struct octetinfo oi;
strarray_t *newfields = strarray_new();
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")) {
fa->fetchitems |= FETCH_ALL;
}
else if (!strcmp(fetchatt.s, "ANNOTATION")) {
fa->fetchitems |= FETCH_ANNOTATION;
if (c != ' ')
goto badannotation;
c = prot_getc(imapd_in);
if (c != '(')
goto badannotation;
c = parse_annotate_fetch_data(tag,
/*permessage_flag*/1,
&fa->entries,
&fa->attribs);
if (c == EOF) {
eatline(imapd_in, c);
goto freeargs;
}
if (c != ')') {
badannotation:
prot_printf(imapd_out, "%s BAD invalid Annotation\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
c = prot_getc(imapd_in);
}
else goto badatt;
break;
case 'B':
if (!strncmp(fetchatt.s, "BINARY[", 7) ||
!strncmp(fetchatt.s, "BINARY.PEEK[", 12) ||
!strncmp(fetchatt.s, "BINARY.SIZE[", 12)) {
int binsize = 0;
p = section = fetchatt.s + 7;
if (!strncmp(p, "PEEK[", 5)) {
p = section += 5;
}
else if (!strncmp(p, "SIZE[", 5)) {
p = section += 5;
binsize = 1;
}
else {
fa->fetchitems |= FETCH_SETSEEN;
}
while (Uisdigit(*p) || *p == '.') {
if (*p == '.' && !Uisdigit(p[-1])) break;
/* Part number cannot begin with '0' */
if (*p == '0' && !Uisdigit(p[-1])) break;
p++;
}
if (*p != ']') {
prot_printf(imapd_out, "%s BAD Invalid binary section\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
p++;
if (!binsize) PARSE_PARTIAL(oi.start_octet, oi.octet_count);
if (*p) {
prot_printf(imapd_out, "%s BAD Junk after binary section\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
if (binsize)
section_list_append(&fa->sizesections, section, &oi);
else
section_list_append(&fa->binsections, section, &oi);
}
else if (!strcmp(fetchatt.s, "BODY")) {
fa->fetchitems |= FETCH_BODY;
}
else if (!strcmp(fetchatt.s, "BODYSTRUCTURE")) {
fa->fetchitems |= FETCH_BODYSTRUCTURE;
}
else if (!strncmp(fetchatt.s, "BODY[", 5) ||
!strncmp(fetchatt.s, "BODY.PEEK[", 10)) {
p = section = fetchatt.s + 5;
if (!strncmp(p, "PEEK[", 5)) {
p = section += 5;
}
else {
fa->fetchitems |= FETCH_SETSEEN;
}
while (Uisdigit(*p) || *p == '.') {
if (*p == '.' && !Uisdigit(p[-1])) break;
/* Obsolete section 0 can only occur before close brace */
if (*p == '0' && !Uisdigit(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') {
fa->cache_atleast = BIT32_MAX;
}
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Missing required argument to %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
c = prot_getc(imapd_in);
if (c != '(') {
prot_printf(imapd_out, "%s BAD Missing required open parenthesis in %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
do {
c = getastring(imapd_in, imapd_out, &fieldname);
for (p = fieldname.s; *p; p++) {
if (*p <= ' ' || *p & 0x80 || *p == ':') break;
}
if (*p || !*fieldname.s) {
prot_printf(imapd_out, "%s BAD Invalid field-name in %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
strarray_append(newfields, fieldname.s);
if (fa->cache_atleast < BIT32_MAX) {
bit32 this_ver =
mailbox_cached_header(fieldname.s);
if(this_ver > fa->cache_atleast)
fa->cache_atleast = this_ver;
}
} while (c == ' ');
if (c != ')') {
prot_printf(imapd_out, "%s BAD Missing required close parenthesis in %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
/* Grab/parse the ]<x.y> part */
c = getword(imapd_in, &fieldname);
p = fieldname.s;
if (*p++ != ']') {
prot_printf(imapd_out, "%s BAD Missing required close bracket after %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
PARSE_PARTIAL(oi.start_octet, oi.octet_count);
if (*p) {
prot_printf(imapd_out, "%s BAD Junk after body section\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
appendfieldlist(&fa->fsections,
section, newfields, fieldname.s,
&oi, sizeof(oi));
/* old 'newfields' is managed by the fieldlist now */
newfields = strarray_new();
break;
}
switch (*p) {
case 'H':
if (p != section && p[-1] != '.') break;
if (!strncmp(p, "HEADER]", 7)) p += 6;
break;
case 'M':
if (!strncmp(p-1, ".MIME]", 6)) p += 4;
break;
case 'T':
if (p != section && p[-1] != '.') break;
if (!strncmp(p, "TEXT]", 5)) p += 4;
break;
}
if (*p != ']') {
prot_printf(imapd_out, "%s BAD Invalid body section\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
p++;
PARSE_PARTIAL(oi.start_octet, oi.octet_count);
if (*p) {
prot_printf(imapd_out, "%s BAD Junk after body section\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
section_list_append(&fa->bodysections, section, &oi);
}
else goto badatt;
break;
case 'C':
if (!strcmp(fetchatt.s, "CID") &&
config_getswitch(IMAPOPT_CONVERSATIONS)) {
fa->fetchitems |= FETCH_CID;
}
else goto badatt;
break;
case 'D':
if (!strcmp(fetchatt.s, "DIGEST.SHA1")) {
fa->fetchitems |= FETCH_GUID;
}
else goto badatt;
break;
case 'E':
if (!strcmp(fetchatt.s, "ENVELOPE")) {
fa->fetchitems |= FETCH_ENVELOPE;
}
else goto badatt;
break;
case 'F':
if (!inlist && !strcmp(fetchatt.s, "FAST")) {
fa->fetchitems |= FETCH_FAST;
}
else if (!inlist && !strcmp(fetchatt.s, "FULL")) {
fa->fetchitems |= FETCH_FULL;
}
else if (!strcmp(fetchatt.s, "FLAGS")) {
fa->fetchitems |= FETCH_FLAGS;
}
else if (!strcmp(fetchatt.s, "FOLDER")) {
fa->fetchitems |= FETCH_FOLDER;
}
else goto badatt;
break;
case 'I':
if (!strcmp(fetchatt.s, "INTERNALDATE")) {
fa->fetchitems |= FETCH_INTERNALDATE;
}
else goto badatt;
break;
case 'M':
if (!strcmp(fetchatt.s, "MODSEQ")) {
fa->fetchitems |= FETCH_MODSEQ;
}
else goto badatt;
break;
case 'R':
if (!strcmp(fetchatt.s, "RFC822")) {
fa->fetchitems |= FETCH_RFC822|FETCH_SETSEEN;
}
else if (!strcmp(fetchatt.s, "RFC822.HEADER")) {
fa->fetchitems |= FETCH_HEADER;
}
else if (!strcmp(fetchatt.s, "RFC822.PEEK")) {
fa->fetchitems |= FETCH_RFC822;
}
else if (!strcmp(fetchatt.s, "RFC822.SIZE")) {
fa->fetchitems |= FETCH_SIZE;
}
else if (!strcmp(fetchatt.s, "RFC822.TEXT")) {
fa->fetchitems |= FETCH_TEXT|FETCH_SETSEEN;
}
else if (!strcmp(fetchatt.s, "RFC822.SHA1")) {
fa->fetchitems |= FETCH_SHA1;
}
else if (!strcmp(fetchatt.s, "RFC822.FILESIZE")) {
fa->fetchitems |= FETCH_FILESIZE;
}
else if (!strcmp(fetchatt.s, "RFC822.TEXT.PEEK")) {
fa->fetchitems |= FETCH_TEXT;
}
else if (!strcmp(fetchatt.s, "RFC822.HEADER.LINES") ||
!strcmp(fetchatt.s, "RFC822.HEADER.LINES.NOT")) {
if (c != ' ') {
prot_printf(imapd_out, "%s BAD Missing required argument to %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
c = prot_getc(imapd_in);
if (c != '(') {
prot_printf(imapd_out, "%s BAD Missing required open parenthesis in %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
do {
c = getastring(imapd_in, imapd_out, &fieldname);
for (p = fieldname.s; *p; p++) {
if (*p <= ' ' || *p & 0x80 || *p == ':') break;
}
if (*p || !*fieldname.s) {
prot_printf(imapd_out, "%s BAD Invalid field-name in %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
lcase(fieldname.s);;
/* 19 is magic number -- length of
* "RFC822.HEADERS.NOT" */
strarray_append(strlen(fetchatt.s) == 19 ?
&fa->headers : &fa->headers_not,
fieldname.s);
if (strlen(fetchatt.s) != 19) {
fa->cache_atleast = BIT32_MAX;
}
if (fa->cache_atleast < BIT32_MAX) {
bit32 this_ver =
mailbox_cached_header(fieldname.s);
if(this_ver > fa->cache_atleast)
fa->cache_atleast = this_ver;
}
} while (c == ' ');
if (c != ')') {
prot_printf(imapd_out, "%s BAD Missing required close parenthesis in %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
c = prot_getc(imapd_in);
}
else goto badatt;
break;
case 'U':
if (!strcmp(fetchatt.s, "UID")) {
fa->fetchitems |= FETCH_UID;
}
else if (!strcmp(fetchatt.s, "UIDVALIDITY")) {
fa->fetchitems |= FETCH_UIDVALIDITY;
}
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 == ' ') {
/* Grab/parse the modifier(s) */
c = prot_getc(imapd_in);
if (c != '(') {
prot_printf(imapd_out,
"%s BAD Missing required open parenthesis in %s modifiers\r\n",
tag, cmd);
eatline(imapd_in, c);
goto freeargs;
}
do {
c = getword(imapd_in, &fetchatt);
ucase(fetchatt.s);
if (!strcmp(fetchatt.s, "CHANGEDSINCE")) {
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 = getmodseq(imapd_in, &fa->changedsince);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Invalid argument to %s %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
fa->fetchitems |= FETCH_MODSEQ;
}
else if (allow_vanished &&
!strcmp(fetchatt.s, "VANISHED")) {
fa->vanished = 1;
}
else {
prot_printf(imapd_out, "%s BAD Invalid %s modifier %s\r\n",
tag, cmd, fetchatt.s);
eatline(imapd_in, c);
goto freeargs;
}
} while (c == ' ');
if (c != ')') {
prot_printf(imapd_out, "%s BAD Missing close parenthesis in %s\r\n",
tag, cmd);
eatline(imapd_in, c);
goto freeargs;
}
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 %s\r\n", tag, cmd);
eatline(imapd_in, c);
goto freeargs;
}
if (!fa->fetchitems && !fa->bodysections && !fa->fsections &&
!fa->binsections && !fa->sizesections &&
!fa->headers.count && !fa->headers_not.count) {
prot_printf(imapd_out, "%s BAD Missing required argument to %s\r\n", tag, cmd);
goto freeargs;
}
if (fa->vanished && !fa->changedsince) {
prot_printf(imapd_out, "%s BAD Missing required argument to %s\r\n", tag, cmd);
goto freeargs;
}
if (fa->fetchitems & FETCH_MODSEQ) {
if (!(imapd_client_capa & CAPA_CONDSTORE)) {
imapd_client_capa |= CAPA_CONDSTORE;
if (imapd_index)
prot_printf(imapd_out, "* OK [HIGHESTMODSEQ " MODSEQ_FMT "] \r\n",
index_highestmodseq(imapd_index));
}
}
if (fa->fetchitems & (FETCH_ANNOTATION|FETCH_FOLDER)) {
fa->namespace = &imapd_namespace;
fa->userid = imapd_userid;
}
if (fa->fetchitems & FETCH_ANNOTATION) {
fa->isadmin = imapd_userisadmin || imapd_userisproxyadmin;
fa->authstate = imapd_authstate;
}
strarray_free(newfields);
return 0;
freeargs:
strarray_free(newfields);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
static void fetchargs_fini (struct fetchargs *fa)
{
section_list_free(fa->binsections);
section_list_free(fa->sizesections);
section_list_free(fa->bodysections);
freefieldlist(fa->fsections);
strarray_fini(&fa->headers);
strarray_fini(&fa->headers_not);
strarray_fini(&fa->entries);
strarray_fini(&fa->attribs);
memset(fa, 0, sizeof(struct fetchargs));
}
/*
* Parse and perform a FETCH/UID FETCH command
* The command has been parsed up to and including
* the sequence
*/
static void cmd_fetch(char *tag, char *sequence, int usinguid)
{
const char *cmd = usinguid ? "UID Fetch" : "Fetch";
struct fetchargs fetchargs;
int fetchedsomething, r;
clock_t start = clock();
char mytime[100];
if (backend_current) {
/* remote mailbox */
prot_printf(backend_current->out, "%s %s %s ", tag, cmd, sequence);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
/* local mailbox */
memset(&fetchargs, 0, sizeof(struct fetchargs));
r = parse_fetch_args(tag, cmd,
(usinguid && (imapd_client_capa & CAPA_QRESYNC)),
&fetchargs);
if (r)
goto freeargs;
if (usinguid)
fetchargs.fetchitems |= FETCH_UID;
r = index_fetch(imapd_index, sequence, usinguid, &fetchargs,
&fetchedsomething);
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
if (r) {
prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag,
error_message(r), mytime);
} else if (fetchedsomething || usinguid) {
prot_printf(imapd_out, "%s OK %s (%s sec)\r\n", tag,
error_message(IMAP_OK_COMPLETED), mytime);
} else {
/* normal FETCH, nothing came back */
prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag,
error_message(IMAP_NO_NOSUCHMSG), mytime);
}
freeargs:
fetchargs_fini(&fetchargs);
}
static void do_one_xconvmeta(struct conversations_state *state,
conversation_id_t cid,
conversation_t *conv,
struct dlist *itemlist)
{
struct dlist *item = dlist_newpklist(NULL, "");
struct dlist *fl;
assert(conv);
assert(itemlist);
for (fl = itemlist->head; fl; fl = fl->next) {
const char *key = dlist_cstring(fl);
/* xxx - parse to a fetchitems? */
if (!strcasecmp(key, "MODSEQ"))
dlist_setnum64(item, "MODSEQ", conv->modseq);
else if (!strcasecmp(key, "EXISTS"))
dlist_setnum32(item, "EXISTS", conv->exists);
else if (!strcasecmp(key, "UNSEEN"))
dlist_setnum32(item, "UNSEEN", conv->unseen);
else if (!strcasecmp(key, "SIZE"))
dlist_setnum32(item, "SIZE", conv->size);
else if (!strcasecmp(key, "COUNT")) {
struct dlist *flist = dlist_newlist(item, "COUNT");
fl = fl->next;
if (dlist_isatomlist(fl)) {
struct dlist *tmp;
for (tmp = fl->head; tmp; tmp = tmp->next) {
const char *lookup = dlist_cstring(tmp);
int i = strarray_find_case(state->counted_flags, lookup, 0);
if (i >= 0) {
dlist_setflag(flist, "FLAG", lookup);
dlist_setnum32(flist, "COUNT", conv->counts[i]);
}
}
}
}
else if (!strcasecmp(key, "SENDERS")) {
conv_sender_t *sender;
struct dlist *slist = dlist_newlist(item, "SENDERS");
for (sender = conv->senders; sender; sender = sender->next) {
struct dlist *sli = dlist_newlist(slist, "");
dlist_setatom(sli, "NAME", sender->name);
dlist_setatom(sli, "ROUTE", sender->route);
dlist_setatom(sli, "MAILBOX", sender->mailbox);
dlist_setatom(sli, "DOMAIN", sender->domain);
}
}
else if (!strcasecmp(key, "FOLDEREXISTS")) {
struct dlist *flist = dlist_newlist(item, "FOLDEREXISTS");
conv_folder_t *folder;
fl = fl->next;
if (dlist_isatomlist(fl)) {
struct dlist *tmp;
for (tmp = fl->head; tmp; tmp = tmp->next) {
const char *fname = dlist_cstring(tmp);
char intname[MAX_MAILBOX_NAME];
/* ugly city */
if ((*imapd_namespace.mboxname_tointernal)(&imapd_namespace, fname,
imapd_userid, intname))
continue;
folder = conversation_find_folder(state, conv, intname);
dlist_setatom(flist, "MBOXNAME", fname);
/* ok if it's not there */
dlist_setnum32(flist, "EXISTS", folder ? folder->exists : 0);
}
}
}
else {
dlist_setatom(item, key, NULL); /* add a NIL response */
}
}
prot_printf(imapd_out, "* XCONVMETA %s ", conversation_id_encode(cid));
dlist_print(item, 0, imapd_out);
prot_printf(imapd_out, "\r\n");
dlist_free(&item);
}
static void do_xconvmeta(const char *tag,
struct conversations_state *state,
struct dlist *cidlist,
struct dlist *itemlist)
{
conversation_id_t cid;
struct dlist *dl;
int r;
for (dl = cidlist->head; dl; dl = dl->next) {
const char *cidstr = dlist_cstring(dl);
conversation_t *conv = NULL;
if (!conversation_id_decode(&cid, cidstr) || !cid) {
prot_printf(imapd_out, "%s BAD Invalid CID %s\r\n", tag, cidstr);
return;
}
r = conversation_load(state, cid, &conv);
if (r) {
prot_printf(imapd_out, "%s BAD Failed to read %s\r\n", tag, cidstr);
conversation_free(conv);
return;
}
if (conv && conv->exists)
do_one_xconvmeta(state, cid, conv, itemlist);
conversation_free(conv);
}
prot_printf(imapd_out, "%s OK Completed\r\n", tag);
}
/*
* Parse and perform a XCONVMETA command.
*/
void cmd_xconvmeta(const char *tag)
{
int r;
char c = ' ';
struct conversations_state *state = NULL;
struct dlist *cidlist = NULL;
struct dlist *itemlist = NULL;
if (backend_current) {
/* remote mailbox */
prot_printf(backend_current->out, "%s XCONVMETA ", tag);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
if (!config_getswitch(IMAPOPT_CONVERSATIONS)) {
prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag);
eatline(imapd_in, c);
goto done;
}
c = dlist_parse_asatomlist(&cidlist, 0, imapd_in);
if (c != ' ') {
prot_printf(imapd_out, "%s BAD Failed to parse CID list\r\n", tag);
eatline(imapd_in, c);
goto done;
}
c = dlist_parse_asatomlist(&itemlist, 0, imapd_in);
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out, "%s BAD Failed to parse item list\r\n", tag);
eatline(imapd_in, c);
goto done;
}
r = conversations_open_user(imapd_userid, &state);
if (r) {
prot_printf(imapd_out, "%s BAD failed to open db: %s\r\n",
tag, error_message(r));
goto done;
}
do_xconvmeta(tag, state, cidlist, itemlist);
done:
dlist_free(&itemlist);
dlist_free(&cidlist);
conversations_commit(&state);
}
/*
* Parse and perform a XCONVFETCH command.
*/
void cmd_xconvfetch(const char *tag)
{
int c = ' ';
struct fetchargs fetchargs;
int r;
clock_t start = clock();
modseq_t ifchangedsince = 0;
char mytime[100];
struct dlist *cidlist = NULL;
struct dlist *item;
if (backend_current) {
/* remote mailbox */
prot_printf(backend_current->out, "%s XCONVFETCH ", tag);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
if (!config_getswitch(IMAPOPT_CONVERSATIONS)) {
prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag);
eatline(imapd_in, c);
return;
}
/* local mailbox */
memset(&fetchargs, 0, sizeof(struct fetchargs));
c = dlist_parse_asatomlist(&cidlist, 0, imapd_in);
if (c != ' ')
goto syntax_error;
/* check CIDs */
for (item = cidlist->head; item; item = item->next) {
if (!dlist_ishex64(item)) {
prot_printf(imapd_out, "%s BAD Invalid CID\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
}
c = getmodseq(imapd_in, &ifchangedsince);
if (c != ' ')
goto syntax_error;
r = parse_fetch_args(tag, "Xconvfetch", 0, &fetchargs);
if (r)
goto freeargs;
fetchargs.fetchitems |= (FETCH_UIDVALIDITY|FETCH_FOLDER);
fetchargs.namespace = &imapd_namespace;
fetchargs.userid = imapd_userid;
r = do_xconvfetch(cidlist, ifchangedsince, &fetchargs);
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
if (r) {
prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag,
error_message(r), mytime);
} else {
prot_printf(imapd_out, "%s OK Completed (%s sec)\r\n",
tag, mytime);
}
freeargs:
dlist_free(&cidlist);
fetchargs_fini(&fetchargs);
return;
syntax_error:
prot_printf(imapd_out, "%s BAD Syntax error\r\n", tag);
eatline(imapd_in, c);
dlist_free(&cidlist);
fetchargs_fini(&fetchargs);
}
static int xconvfetch_lookup(struct conversations_state *statep,
conversation_id_t cid,
modseq_t ifchangedsince,
hash_table *wanted_cids,
strarray_t *folder_list)
{
const char *key = conversation_id_encode(cid);
conversation_t *conv = NULL;
conv_folder_t *folder;
int r;
r = conversation_load(statep, cid, &conv);
if (r) return r;
if (!conv)
goto out;
if (!conv->exists)
goto out;
/* output the metadata for this conversation */
{
struct dlist *dl = dlist_newlist(NULL, "");
dlist_setatom(dl, "", "MODSEQ");
do_one_xconvmeta(statep, cid, conv, dl);
dlist_free(&dl);
}
if (ifchangedsince >= conv->modseq)
goto out;
hash_insert(key, (void *)1, wanted_cids);
for (folder = conv->folders; folder; folder = folder->next) {
/* no contents */
if (!folder->exists)
continue;
/* finally, something worth looking at */
strarray_add(folder_list, strarray_nth(statep->folder_names, folder->number));
}
out:
conversation_free(conv);
return 0;
}
static int do_xconvfetch(struct dlist *cidlist,
modseq_t ifchangedsince,
struct fetchargs *fetchargs)
{
struct conversations_state *state = NULL;
int r = 0;
struct index_state *index_state = NULL;
struct dlist *dl;
hash_table wanted_cids = HASH_TABLE_INITIALIZER;
strarray_t folder_list = STRARRAY_INITIALIZER;
struct index_init init;
int i;
r = conversations_open_user(imapd_userid, &state);
if (r) goto out;
construct_hash_table(&wanted_cids, 1024, 0);
for (dl = cidlist->head; dl; dl = dl->next) {
r = xconvfetch_lookup(state, dlist_num(dl), ifchangedsince,
&wanted_cids, &folder_list);
if (r) goto out;
}
/* unchanged, woot */
if (!folder_list.count)
goto out;
fetchargs->cidhash = &wanted_cids;
memset(&init, 0, sizeof(struct index_init));
init.userid = imapd_userid;
init.authstate = imapd_authstate;
init.out = imapd_out;
for (i = 0; i < folder_list.count; i++) {
const char *mboxname = folder_list.data[i];
r = index_open(mboxname, &init, &index_state);
if (r == IMAP_MAILBOX_NONEXISTENT)
continue;
if (r)
goto out;
index_checkflags(index_state, 0, 0);
/* make sure \Deleted messages are expunged. Will also lock the
* mailbox state and read any new information */
r = index_expunge(index_state, NULL, 1);
if (r) goto out;
index_fetchresponses(index_state, NULL, /*usinguid*/1,
fetchargs, NULL);
index_close(&index_state);
}
r = 0;
out:
index_close(&index_state);
conversations_commit(&state);
free_hash_table(&wanted_cids, NULL);
strarray_fini(&folder_list);
return r;
}
#undef PARSE_PARTIAL /* cleanup */
/*
* Parse and perform a STORE/UID STORE command
* The command has been parsed up to and including
* the sequence
*/
static void cmd_store(char *tag, char *sequence, int usinguid)
{
const char *cmd = usinguid ? "UID Store" : "Store";
struct storeargs storeargs;
static struct buf operation, flagname;
int len, c;
int flagsparsed = 0, inlist = 0;
char *modified = NULL;
int r;
if (backend_current) {
/* remote mailbox */
prot_printf(backend_current->out, "%s %s %s ",
tag, cmd, sequence);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
/* local mailbox */
memset(&storeargs, 0, sizeof storeargs);
storeargs.unchangedsince = ~0ULL;
storeargs.usinguid = usinguid;
strarray_init(&storeargs.flags);
c = prot_getc(imapd_in);
if (c == '(') {
/* Grab/parse the modifier(s) */
static struct buf storemod;
do {
c = getword(imapd_in, &storemod);
ucase(storemod.s);
if (!strcmp(storemod.s, "UNCHANGEDSINCE")) {
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Missing required argument to %s %s\r\n",
tag, cmd, storemod.s);
eatline(imapd_in, c);
return;
}
c = getmodseq(imapd_in, &storeargs.unchangedsince);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Invalid argument to %s UNCHANGEDSINCE\r\n",
tag, cmd);
eatline(imapd_in, c);
return;
}
}
else {
prot_printf(imapd_out, "%s BAD Invalid %s modifier %s\r\n",
tag, cmd, storemod.s);
eatline(imapd_in, c);
return;
}
} while (c == ' ');
if (c != ')') {
prot_printf(imapd_out,
"%s BAD Missing close paren in store modifier entry \r\n",
tag);
eatline(imapd_in, c);
return;
}
c = prot_getc(imapd_in);
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Missing required argument to %s\r\n",
tag, cmd);
eatline(imapd_in, c);
return;
}
}
else
prot_ungetc(c, imapd_in);
c = getword(imapd_in, &operation);
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Missing required argument to %s\r\n", tag, cmd);
eatline(imapd_in, c);
return;
}
lcase(operation.s);
len = strlen(operation.s);
if (len > 7 && !strcmp(operation.s+len-7, ".silent")) {
storeargs.silent = 1;
operation.s[len-7] = '\0';
}
if (!strcmp(operation.s, "+flags")) {
storeargs.operation = STORE_ADD_FLAGS;
}
else if (!strcmp(operation.s, "-flags")) {
storeargs.operation = STORE_REMOVE_FLAGS;
}
else if (!strcmp(operation.s, "flags")) {
storeargs.operation = STORE_REPLACE_FLAGS;
}
else if (!strcmp(operation.s, "annotation")) {
storeargs.operation = STORE_ANNOTATION;
/* ANNOTATION has implicit .SILENT behaviour */
storeargs.silent = 1;
c = parse_annotate_store_data(tag, /*permessage_flag*/1,
&storeargs.entryatts);
if (c == EOF) {
eatline(imapd_in, c);
goto freeflags;
}
storeargs.namespace = &imapd_namespace;
storeargs.isadmin = imapd_userisadmin;
storeargs.userid = imapd_userid;
storeargs.authstate = imapd_authstate;
goto notflagsdammit;
}
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
strarray_append(&storeargs.flags, 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;
}
notflagsdammit:
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;
}
if ((storeargs.unchangedsince != ULONG_MAX) &&
!(imapd_client_capa & CAPA_CONDSTORE)) {
imapd_client_capa |= CAPA_CONDSTORE;
prot_printf(imapd_out, "* OK [HIGHESTMODSEQ " MODSEQ_FMT "] \r\n",
index_highestmodseq(imapd_index));
}
r = index_store(imapd_index, sequence, &storeargs);
/* format the MODIFIED response code */
if (storeargs.modified) {
char *seqstr = seqset_cstring(storeargs.modified);
assert(seqstr);
modified = strconcat("[MODIFIED ", seqstr, "] ", (char *)NULL);
free(seqstr);
}
else {
modified = xstrdup("");
}
if (r) {
prot_printf(imapd_out, "%s NO %s%s\r\n",
tag, modified, error_message(r));
}
else {
prot_printf(imapd_out, "%s OK %s%s\r\n",
tag, modified, error_message(IMAP_OK_COMPLETED));
}
freeflags:
strarray_fini(&storeargs.flags);
freeentryatts(storeargs.entryatts);
seqset_free(storeargs.modified);
free(modified);
}
static void cmd_search(char *tag, int usinguid)
{
int c;
struct searchargs *searchargs;
clock_t start = clock();
char mytime[100];
int n;
if (backend_current) {
/* remote mailbox */
const char *cmd = usinguid ? "UID Search" : "Search";
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
/* local mailbox */
searchargs = new_searchargs(tag, GETSEARCH_CHARSET_KEYWORD|GETSEARCH_RETURN,
&imapd_namespace, imapd_userid, imapd_authstate,
imapd_userisadmin || imapd_userisproxyadmin);
c = get_search_program(imapd_in, searchargs);
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 (searchargs->charset == -1) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(IMAP_UNRECOGNIZED_CHARSET));
}
else {
n = index_search(imapd_index, searchargs, usinguid);
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag,
error_message(IMAP_OK_COMPLETED), n, mytime);
}
freesearchargs(searchargs);
}
/*
* Perform a SORT/UID SORT command
*/
static void cmd_sort(char *tag, int usinguid)
{
int c;
struct sortcrit *sortcrit = NULL;
struct searchargs *searchargs = NULL;
clock_t start = clock();
char mytime[100];
int n;
if (backend_current) {
/* remote mailbox */
const char *cmd = usinguid ? "UID Sort" : "Sort";
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
/* local mailbox */
c = getsortcriteria(tag, &sortcrit);
if (c == EOF) goto error;
searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST,
&imapd_namespace, imapd_userid, imapd_authstate,
imapd_userisadmin || imapd_userisproxyadmin);
c = get_search_program(imapd_in, searchargs);
if (c == EOF) goto error;
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);
goto error;
}
n = index_sort(imapd_index, sortcrit, searchargs, usinguid);
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
if (CONFIG_TIMING_VERBOSE) {
char *s = sortcrit_as_string(sortcrit);
syslog(LOG_DEBUG, "SORT (%s) processing time: %d msg in %s sec",
s, n, mytime);
free(s);
}
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;
error:
eatline(imapd_in, (c == EOF ? ' ' : c));
freesortcrit(sortcrit);
freesearchargs(searchargs);
}
/*
* Perform a XCONVSORT or XCONVUPDATES command
*/
void cmd_xconvsort(char *tag, int updates)
{
int c;
struct sortcrit *sortcrit = NULL;
struct searchargs *searchargs = NULL;
struct windowargs *windowargs = NULL;
struct index_init init;
struct index_state *oldstate = NULL;
struct conversations_state *cstate = NULL;
clock_t start = clock();
char mytime[100];
int r;
if (backend_current) {
/* remote mailbox */
const char *cmd = "Xconvsort";
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
assert(imapd_index);
if (!config_getswitch(IMAPOPT_CONVERSATIONS)) {
prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag);
eatline(imapd_in, ' ');
return;
}
c = getsortcriteria(tag, &sortcrit);
if (c == EOF) goto error;
if (c != ' ') {
prot_printf(imapd_out, "%s BAD Missing window args in XConvSort\r\n",
tag);
goto error;
}
c = parse_windowargs(tag, &windowargs, updates);
if (c != ' ')
goto error;
/* open the conversations state first - we don't care if it fails,
* because that probably just means it's already open */
conversations_open_mbox(imapd_index->mailbox->name, &cstate);
if (updates) {
/* in XCONVUPDATES, need to force a re-read from scratch into
* a new index, because we ask for deleted messages */
oldstate = imapd_index;
imapd_index = NULL;
memset(&init, 0, sizeof(struct index_init));
init.userid = imapd_userid;
init.authstate = imapd_authstate;
init.out = imapd_out;
init.want_expunged = 1;
r = index_open(oldstate->mailbox->name, &init, &imapd_index);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(r));
goto error;
}
}
/* need index loaded to even parse searchargs! */
searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST,
&imapd_namespace, imapd_userid, imapd_authstate,
imapd_userisadmin || imapd_userisproxyadmin);
c = get_search_program(imapd_in, searchargs);
if (c == EOF) goto error;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to Xconvsort\r\n", tag);
goto error;
}
if (updates)
r = index_convupdates(imapd_index, sortcrit, searchargs, windowargs);
else
r = index_convsort(imapd_index, sortcrit, searchargs, windowargs);
if (oldstate) {
index_close(&imapd_index);
imapd_index = oldstate;
}
if (r < 0) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(r));
goto error;
}
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
if (CONFIG_TIMING_VERBOSE) {
char *s = sortcrit_as_string(sortcrit);
syslog(LOG_DEBUG, "XCONVSORT (%s) processing time %s sec",
s, mytime);
free(s);
}
prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag,
error_message(IMAP_OK_COMPLETED), mytime);
out:
if (cstate) conversations_commit(&cstate);
freesortcrit(sortcrit);
freesearchargs(searchargs);
free_windowargs(windowargs);
return;
error:
if (cstate) conversations_commit(&cstate);
if (oldstate) {
if (imapd_index) index_close(&imapd_index);
imapd_index = oldstate;
}
eatline(imapd_in, (c == EOF ? ' ' : c));
goto out;
}
/*
* Perform a XCONVMULTISORT command. This is like XCONVSORT but returns
* search results from multiple folders. It still requires a selected
* mailbox, for two reasons:
*
* a) it's a useful shorthand for choosing what the current
* conversations scope is, and
*
* b) the code to parse a search program currently relies on a selected
* mailbox.
*
* Unlike ESEARCH it doesn't take folder names for scope, instead the
* search scope is implicitly the current conversation scope. This is
* implemented more or less by accident because both the Sphinx index
* and the conversations database are hardcoded to be per-user.
*/
static void cmd_xconvmultisort(char *tag)
{
int c;
struct sortcrit *sortcrit = NULL;
struct searchargs *searchargs = NULL;
struct windowargs *windowargs = NULL;
struct conversations_state *cstate = NULL;
clock_t start = clock();
char mytime[100];
int r;
if (backend_current) {
/* remote mailbox */
const char *cmd = "Xconvmultisort";
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
assert(imapd_index);
if (!config_getswitch(IMAPOPT_CONVERSATIONS)) {
prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag);
eatline(imapd_in, ' ');
return;
}
c = getsortcriteria(tag, &sortcrit);
if (c == EOF) goto error;
if (c != ' ') {
prot_printf(imapd_out, "%s BAD Missing window args in XConvMultiSort\r\n",
tag);
goto error;
}
c = parse_windowargs(tag, &windowargs, /*updates*/0);
if (c != ' ')
goto error;
/* open the conversations state first - we don't care if it fails,
* because that probably just means it's already open */
conversations_open_mbox(imapd_index->mailbox->name, &cstate);
/* need index loaded to even parse searchargs! */
searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST,
&imapd_namespace, imapd_userid, imapd_authstate,
imapd_userisadmin || imapd_userisproxyadmin);
c = get_search_program(imapd_in, searchargs);
if (c == EOF) goto error;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to XconvMultiSort\r\n", tag);
goto error;
}
r = index_convmultisort(imapd_index, sortcrit, searchargs, windowargs);
if (r < 0) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(r));
goto error;
}
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
if (CONFIG_TIMING_VERBOSE) {
char *s = sortcrit_as_string(sortcrit);
syslog(LOG_DEBUG, "XCONVMULTISORT (%s) processing time %s sec",
s, mytime);
free(s);
}
prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag,
error_message(IMAP_OK_COMPLETED), mytime);
out:
if (cstate) conversations_commit(&cstate);
freesortcrit(sortcrit);
freesearchargs(searchargs);
free_windowargs(windowargs);
return;
error:
if (cstate) conversations_commit(&cstate);
eatline(imapd_in, (c == EOF ? ' ' : c));
goto out;
}
static void cmd_xsnippets(char *tag)
{
int c;
struct searchargs *searchargs = NULL;
struct snippetargs *snippetargs = NULL;
clock_t start = clock();
char mytime[100];
int r;
if (backend_current) {
/* remote mailbox */
const char *cmd = "Xsnippets";
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
assert(imapd_index);
c = get_snippetargs(&snippetargs);
if (c == EOF) {
prot_printf(imapd_out, "%s BAD Syntax error in snippet arguments\r\n", tag);
goto error;
}
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Unexpected arguments in Xsnippets\r\n", tag);
goto error;
}
/* need index loaded to even parse searchargs! */
searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST,
&imapd_namespace, imapd_userid, imapd_authstate,
imapd_userisadmin || imapd_userisproxyadmin);
c = get_search_program(imapd_in, searchargs);
if (c == EOF) goto error;
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to Xsnippets\r\n", tag);
goto error;
}
r = index_snippets(imapd_index, snippetargs, searchargs);
if (r < 0) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(r));
goto error;
}
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag,
error_message(IMAP_OK_COMPLETED), mytime);
out:
freesearchargs(searchargs);
free_snippetargs(&snippetargs);
return;
error:
eatline(imapd_in, (c == EOF ? ' ' : c));
goto out;
}
static void cmd_xstats(char *tag, int c)
{
int metric;
if (backend_current) {
/* remote mailbox */
const char *cmd = "Xstats";
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
if (c == EOF) {
prot_printf(imapd_out, "%s BAD Syntax error in Xstats arguments\r\n", tag);
goto error;
}
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to Xstats\r\n", tag);
goto error;
}
prot_printf(imapd_out, "* XSTATS");
for (metric = 0 ; metric < XSTATS_NUM_METRICS ; metric++)
prot_printf(imapd_out, " %s %u", xstats_names[metric], xstats[metric]);
prot_printf(imapd_out, "\r\n");
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
return;
error:
eatline(imapd_in, (c == EOF ? ' ' : c));
}
/*
* Perform a THREAD/UID THREAD command
*/
static void cmd_thread(char *tag, int usinguid)
{
static struct buf arg;
int c;
int alg;
struct searchargs *searchargs;
clock_t start = clock();
char mytime[100];
int n;
if (backend_current) {
/* remote mailbox */
const char *cmd = usinguid ? "UID Thread" : "Thread";
prot_printf(backend_current->out, "%s %s ", tag, cmd);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
/* local mailbox */
/* get algorithm */
c = getword(imapd_in, &arg);
if (c != ' ') {
prot_printf(imapd_out, "%s BAD Missing algorithm in Thread\r\n", tag);
eatline(imapd_in, c);
return;
}
if ((alg = find_thread_algorithm(arg.s)) == -1) {
prot_printf(imapd_out, "%s BAD Invalid Thread algorithm %s\r\n",
tag, arg.s);
eatline(imapd_in, c);
return;
}
searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST,
&imapd_namespace, imapd_userid, imapd_authstate,
imapd_userisadmin || imapd_userisproxyadmin);
c = get_search_program(imapd_in, searchargs);
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_index, alg, searchargs, usinguid);
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag,
error_message(IMAP_OK_COMPLETED), n, mytime);
freesearchargs(searchargs);
return;
}
/*
* Perform a COPY/UID COPY command
*/
static void cmd_copy(char *tag, char *sequence, char *name, int usinguid, int ismove)
{
int r, myrights;
char mailboxname[MAX_MAILBOX_BUFFER];
char *copyuid = NULL;
mbentry_t *mbentry = NULL;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(NULL, NULL, mailboxname, &mbentry);
}
if (!r) myrights = cyrus_acl_myrights(imapd_authstate, mbentry->acl);
if (!r && backend_current) {
/* remote mailbox -> local or remote mailbox */
/* xxx start of separate proxy-only code
(remove when we move to a unified environment) */
struct backend *s = NULL;
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
mboxlist_entry_free(&mbentry);
if (!s) {
r = IMAP_SERVER_UNAVAILABLE;
goto done;
}
if (s != backend_current) {
/* this is the hard case; we have to fetch the messages and append
them to the other mailbox */
proxy_copy(tag, sequence, name, myrights, usinguid, s);
goto cleanup;
}
/* xxx end of separate proxy-only code */
/* simply send the COPY to the backend */
prot_printf(
backend_current->out,
"%s %s %s {" SIZE_T_FMT "+}\r\n%s\r\n",
tag,
usinguid ? (ismove ? "UID Move" : "UID Copy") : (ismove ? "Move" : "Copy"),
sequence,
strlen(name),
name
);
pipe_including_tag(backend_current, tag, 0);
goto cleanup;
}
else if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* local mailbox -> remote mailbox
*
* fetch the messages and APPEND them to the backend
*
* xxx completely untested
*/
struct backend *s = NULL;
int res;
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
mboxlist_entry_free(&mbentry);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
else if (!CAPA(s, CAPA_MULTIAPPEND)) {
/* we need MULTIAPPEND for atomicity */
r = IMAP_REMOTE_NO_MULTIAPPEND;
}
if (r) goto done;
assert(!ismove); /* XXX - support proxying moves */
/* start the append */
prot_printf(s->out, "%s Append {" SIZE_T_FMT "+}\r\n%s",
tag, strlen(name), name);
/* append the messages */
r = index_copy_remote(imapd_index, sequence, usinguid, s->out);
if (!r) {
/* ok, finish the append; we need the UIDVALIDITY and UIDs
to return as part of our COPYUID response code */
char *appenduid, *b;
prot_printf(s->out, "\r\n");
res = pipe_until_tag(s, tag, 0);
if (res == PROXY_OK) {
if (myrights & ACL_READ) {
appenduid = strchr(s->last_result.s, '[');
/* skip over APPENDUID */
if (appenduid) {
appenduid += strlen("[appenduid ");
b = strchr(appenduid, ']');
if (b) *b = '\0';
prot_printf(imapd_out, "%s OK [COPYUID %s] %s\r\n", tag,
appenduid, error_message(IMAP_OK_COMPLETED));
} else
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
} else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
} else {
prot_printf(imapd_out, "%s %s", tag, s->last_result.s);
}
} else {
/* abort the append */
prot_printf(s->out, " {0}\r\n");
pipe_until_tag(s, tag, 0);
/* report failure */
prot_printf(imapd_out, "%s NO inter-server COPY failed\r\n", tag);
}
goto cleanup;
}
/* need permission to delete from source if it's a move */
if (ismove && !(imapd_index->myrights & ACL_EXPUNGE))
r = IMAP_PERMISSION_DENIED;
/* local mailbox -> local mailbox */
if (!r) {
r = index_copy(imapd_index, sequence, usinguid, mailboxname,
&copyuid, !config_getswitch(IMAPOPT_SINGLEINSTANCESTORE),
&imapd_namespace,
(imapd_userisadmin || imapd_userisproxyadmin), ismove,
ignorequota);
}
imapd_check(NULL, ismove || usinguid);
done:
if (r && !(usinguid && r == IMAP_NO_NOSUCHMSG)) {
prot_printf(imapd_out, "%s NO %s%s\r\n", tag,
(r == IMAP_MAILBOX_NONEXISTENT &&
mboxlist_createmailboxcheck(mailboxname, 0, 0,
imapd_userisadmin,
imapd_userid, imapd_authstate,
NULL, NULL, 0) == 0)
? "[TRYCREATE] " : "", error_message(r));
}
else if (copyuid) {
prot_printf(imapd_out, "%s OK [COPYUID %s] %s\r\n", tag,
copyuid, error_message(IMAP_OK_COMPLETED));
free(copyuid);
}
else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
cleanup:
mboxlist_entry_free(&mbentry);
}
/*
* Perform an EXPUNGE command
* sequence == NULL if this isn't a UID EXPUNGE
*/
static void cmd_expunge(char *tag, char *sequence)
{
modseq_t old;
modseq_t new;
int r = 0;
if (backend_current) {
/* remote mailbox */
if (sequence) {
prot_printf(backend_current->out, "%s UID Expunge %s\r\n", tag,
sequence);
} else {
prot_printf(backend_current->out, "%s Expunge\r\n", tag);
}
pipe_including_tag(backend_current, tag, 0);
return;
}
/* local mailbox */
if (!index_hasrights(imapd_index, ACL_EXPUNGE))
r = IMAP_PERMISSION_DENIED;
old = index_highestmodseq(imapd_index);
if (!r) r = index_expunge(imapd_index, sequence, 1);
/* tell expunges */
if (!r) index_tellchanges(imapd_index, 1, sequence ? 1 : 0, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
new = index_highestmodseq(imapd_index);
prot_printf(imapd_out, "%s OK ", tag);
if (new > old)
prot_printf(imapd_out, "[HIGHESTMODSEQ " MODSEQ_FMT "] ", new);
prot_printf(imapd_out, "%s\r\n", error_message(IMAP_OK_COMPLETED));
}
/*
* Perform a CREATE command
*/
static void cmd_create(char *tag, char *name, struct dlist *extargs, int localonly)
{
int r = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
int mbtype = 0;
const char *partition = NULL;
const char *server = NULL;
struct buf specialuse = BUF_INITIALIZER;
struct dlist *use;
dlist_getatom(extargs, "PARTITION", &partition);
dlist_getatom(extargs, "SERVER", &server);
const char *type = NULL;
dlist_getatom(extargs, "PARTITION", &partition);
dlist_getatom(extargs, "SERVER", &server);
if (dlist_getatom(extargs, "TYPE", &type)) {
if (!strcasecmp(type, "CALENDAR")) mbtype |= MBTYPE_CALENDAR;
else if (!strcasecmp(type, "ADDRESSBOOK")) mbtype |= MBTYPE_ADDRESSBOOK;
else {
r = IMAP_MAILBOX_BADTYPE;
goto err;
}
}
use = dlist_getchild(extargs, "USE");
if (use) {
struct dlist *item;
char *raw;
/* I would much prefer to create the specialuse annotation FIRST
* and do the sanity check on the values, so we can return the
* correct error. Sadly, that's a pain - so we compromise by
* "normalising" first */
strarray_t *su = strarray_new();
for (item = use->head; item; item = item->next) {
strarray_append(su, dlist_cstring(item));
}
raw = strarray_join(su, " ");
strarray_free(su);
r = specialuse_validate(raw, &specialuse);
free(raw);
if (r) {
prot_printf(imapd_out, "%s NO [USEATTR] %s\r\n", tag, error_message(r));
goto done;
}
}
// A non-admin is not allowed to specify the server nor partition on which
// to create the mailbox.
//
// However, this only applies to frontends. If we're a backend, a frontend will
// proxy the partition it wishes to create the mailbox on.
if ((server || partition) && !imapd_userisadmin) {
if (config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_STANDARD ||
config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_UNIFIED) {
if (!config_getstring(IMAPOPT_PROXYSERVERS)) {
r = IMAP_PERMISSION_DENIED;
goto err;
}
}
}
/* We don't care about trailing hierarchy delimiters. */
if (name[0] && name[strlen(name)-1] == imapd_namespace.hier_sep) {
name[strlen(name)-1] = '\0';
}
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (r) {
err:
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
goto done;
}
/* check for INBOX.INBOX creation by broken Apple clients */
char *copy = xstrdup(mailboxname);
lcase(copy);
if (strstr(copy, "inbox.inbox."))
r = IMAP_MAILBOX_BADNAME;
free(copy);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
goto done;
}
// If the create command does not mandate the mailbox must be created
// locally, let's go and find the most appropriate location.
if (!localonly) {
// If we're running in a Murder, things get more complicated.
if (config_mupdate_server) {
// Consider your actions on a per type of topology basis.
//
// First up: Standard / discrete murder topology, with dedicated
// imap frontends, or unified -- both allow the IMAP server to either
// need to proxy through, or create locally.
if (
config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_STANDARD ||
config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_UNIFIED
) {
// The way that we detect whether we're a frontend is by testing
// for the proxy servers setting ... :/
if (!config_getstring(IMAPOPT_PROXYSERVERS)) {
// Find the parent mailbox, if any.
mbentry_t *parent = NULL;
// mboxlist_findparent either supplies the parent
// or has a return code of IMAP_MAILBOX_NONEXISTENT.
r = mboxlist_findparent(mailboxname, &parent);
if (r) {
if (r != IMAP_MAILBOX_NONEXISTENT) {
prot_printf(imapd_out, "%s NO %s (%s:%d)\r\n", tag, error_message(r), __FILE__, __LINE__);
goto done;
}
}
if (!server && !partition) {
if (!parent) {
server = find_free_server();
if (!server) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_SERVER_UNAVAILABLE));
goto done;
}
} else {
server = parent->server;
partition = parent->partition;
}
}
struct backend *s_conn = NULL;
s_conn = proxy_findserver(
server,
&imap_protocol,
proxy_userid,
&backend_cached,
&backend_current,
&backend_inbox,
imapd_in
);
if (!s_conn) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_SERVER_UNAVAILABLE));
goto done;
}
// Huh?
if (imapd_userisadmin && supports_referrals) {
// "They are not an admin remotely, so let's refer them" --
// - Who is they?
// - How did imapd_userisadmin get set all of a sudden?
imapd_refer(tag, server, name);
referral_kick = 1;
return;
}
if (!CAPA(s_conn, CAPA_MUPDATE)) {
// Huh?
// "reserve mailbox on MUPDATE"
syslog(LOG_WARNING, "backend %s is not advertising any MUPDATE capability (%s:%d)", server, __FILE__, __LINE__);
}
// why not send a LOCALCREATE to the backend?
prot_printf(s_conn->out, "%s CREATE ", tag);
prot_printastring(s_conn->out, name);
// special use needs extended support, so pass through extargs
if (specialuse.len) {
prot_printf(s_conn->out, "(USE (%s)", buf_cstring(&specialuse));
if (partition) {
prot_printf(s_conn->out, " PARTITION ");
prot_printastring(s_conn->out, partition);
}
prot_putc(')', s_conn->out);
}
// Send partition as an atom, since its supported by older servers
else if (partition) {
prot_putc(' ', s_conn->out);
prot_printastring(s_conn->out, partition);
}
prot_printf(s_conn->out, "\r\n");
int res = pipe_until_tag(s_conn, tag, 0);
if (!CAPA(s_conn, CAPA_MUPDATE)) {
// Huh?
// "do MUPDATE create operations"
syslog(LOG_WARNING, "backend %s is not advertising any MUPDATE capability (%s:%d)", server, __FILE__, __LINE__);
}
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
imapd_check(s_conn, 0);
prot_printf(imapd_out, "%s %s", tag, s_conn->last_result.s);
goto done;
} else { // (!config_getstring(IMAPOPT_PROXYSERVERS))
// I have a standard murder config but also proxy servers configured; I'm a backend!
goto localcreate;
} // (!config_getstring(IMAPOPT_PROXYSERVERS))
} // (config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_STANDARD)
else if (config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_REPLICATED) {
// Everything is local
goto localcreate;
} // (config_mupdate_config == IMAP_ENUM_MUPDATE_CONFIG_REPLICATED)
else {
syslog(LOG_ERR, "murder configuration I cannot deal with");
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_SERVER_UNAVAILABLE));
goto done;
}
} else { // (config_mupdate_server)
// I'm no part of a Murder, *everything* is localcreate
goto localcreate;
} // (config_mupdate_server)
} else { // (!localonly)
goto localcreate;
}
localcreate:
r = mboxlist_createmailbox(
mailboxname, // const char name
mbtype, // int mbtype
partition, // const char partition
imapd_userisadmin || imapd_userisproxyadmin, // int isadmin
imapd_userid, // const char userid
imapd_authstate, // struct auth_state auth_state
localonly, // int localonly
localonly, // int forceuser
0, // int dbonly
1, // int notify
NULL // struct mailbox mailboxptr
);
#ifdef USE_AUTOCREATE
// Clausing autocreate for the INBOX
if (r == IMAP_PERMISSION_DENIED) {
if (strcasecmp(name, "INBOX")) {
if ((int autocreatequotastorage = config_getint(IMAPOPT_AUTOCREATE_QUOTA))) {
r = mboxlist_createmailbox(
mailboxname,
0,
partition,
1,
imapd_userid,
imapd_authstate,
0,
0,
0,
1,
NULL
);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
goto done;
}
int autocreatequotamessage = config_getint(IMAPOPT_AUTOCREATE_QUOTA_MESSAGES);
if ((autocreatequotastorage > 0) || (autocreatequotamessage > 0)) {
int newquotas[QUOTA_NUMRESOURCES];
int res;
for (res = 0; res < QUOTA_NUMRESOURCES; res++) {
newquotas[res] = QUOTA_UNLIMITED;
}
newquotas[QUOTA_STORAGE] = autocreatequotastorage;
newquotas[QUOTA_MESSAGE] = autocreatequotamessage;
(void) mboxlist_setquotas(mailboxname, newquotas, 0);
} // (autocreatequotastorage > 0) || (autocreatequotamessage > 0)
} else { // (autocreatequotastorage = config_getint(IMAPOPT_AUTOCREATEQUOTA))
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_PERMISSION_DENIED));
goto done;
} // (autocreatequotastorage = config_getint(IMAPOPT_AUTOCREATEQUOTA))
} else { // (!strcasecmp(name, "INBOX"))
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_PERMISSION_DENIED));
goto done;
} // (!strcasecmp(name, "INBOX"))
} else if (r) { // (r == IMAP_PERMISSION_DENIED)
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
goto done;
} else { // (r == IMAP_PERMISSION_DENIED)
prot_printf(imapd_out, "%s OK %s\r\n", tag, error_message(IMAP_OK_COMPLETED));
goto done;
} // (r == IMAP_PERMISSION_DENIED)
#else // USE_AUTOCREATE
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
goto done;
} // (r)
#endif // USE_AUTOCREATE
if (specialuse.len) {
const char *userid = mboxname_to_userid(mailboxname);
if (!userid) userid = imapd_userid;
r = annotatemore_write(mailboxname, "/specialuse", userid, &specialuse);
if (r) {
/* XXX - failure here SHOULD cause a cleanup of the created mailbox */
syslog(
LOG_ERR,
"IOERROR: failed to write specialuse for %s on %s (%s) (%s:%d)",
imapd_userid,
mailboxname,
buf_cstring(&specialuse),
__FILE__,
__LINE__
);
prot_printf(imapd_out, "%s NO %s (%s:%d)\r\n", tag, error_message(r), __FILE__, __LINE__);
goto done;
}
}
prot_printf(imapd_out, "%s OK Completed\r\n", tag);
imapd_check(NULL, 0);
done:
buf_free(&specialuse);
}
/* Callback for use by cmd_delete */
static int delmbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock __attribute__((unused)))
{
int r;
if (!mboxlist_delayed_delete_isenabled()) {
r = mboxlist_deletemailbox(name,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate, NULL,
0, 0, 0);
} else if ((imapd_userisadmin || imapd_userisproxyadmin) &&
mboxname_isdeletedmailbox(name, NULL)) {
r = mboxlist_deletemailbox(name,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate, NULL,
0, 0, 0);
} else {
r = mboxlist_delayed_deletemailbox(name,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate, NULL,
0, 0);
}
if(r) {
prot_printf(imapd_out, "* NO delete %s: %s\r\n",
name, error_message(r));
}
return 0;
}
/*
* Perform a DELETE command
*/
static void cmd_delete(char *tag, char *name, int localonly, int force)
{
int r;
char mailboxname[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
struct mboxevent *mboxevent = NULL;
char *p;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(NULL, NULL, mailboxname, &mbentry);
}
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
struct backend *s = NULL;
int res;
if (supports_referrals) {
imapd_refer(tag, mbentry->server, name);
referral_kick = 1;
mboxlist_entry_free(&mbentry);
return;
}
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
mboxlist_entry_free(&mbentry);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
if (!r) {
prot_printf(s->out, "%s DELETE {" SIZE_T_FMT "+}\r\n%s\r\n",
tag, strlen(name), name);
res = pipe_until_tag(s, tag, 0);
if (!CAPA(s, CAPA_MUPDATE) && res == PROXY_OK) {
/* do MUPDATE delete operations */
}
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
}
imapd_check(s, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
/* we're allowed to reference last_result since the noop, if
sent, went to a different server */
prot_printf(imapd_out, "%s %s", tag, s->last_result.s);
}
return;
}
mboxlist_entry_free(&mbentry);
mboxevent = mboxevent_new(EVENT_MAILBOX_DELETE);
/* local mailbox */
if (!r) {
if (localonly || !mboxlist_delayed_delete_isenabled()) {
r = mboxlist_deletemailbox(mailboxname,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate, mboxevent,
1-force, localonly, 0);
} else if ((imapd_userisadmin || imapd_userisproxyadmin) &&
mboxname_isdeletedmailbox(mailboxname, NULL)) {
r = mboxlist_deletemailbox(mailboxname,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate, mboxevent,
0 /* checkacl */, localonly, 0);
} else {
r = mboxlist_delayed_deletemailbox(mailboxname,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate, mboxevent,
1-force, 0);
}
}
/* send a MailboxDelete event notification */
if (!r)
mboxevent_notify(mboxevent);
mboxevent_free(&mboxevent);
/* was it a top-level user mailbox? */
/* localonly deletes are only per-mailbox */
if (!r && !localonly && mboxname_isusermailbox(mailboxname, 1)) {
size_t mailboxname_len = strlen(mailboxname);
const char *userid = mboxname_to_userid(mailboxname);
/* If we aren't too close to MAX_MAILBOX_BUFFER, append .* */
p = mailboxname + mailboxname_len; /* end of mailboxname */
if (mailboxname_len < sizeof(mailboxname) - 3) {
strcpy(p, ".*");
}
/* build a list of mailboxes - we're using internal names here */
mboxlist_findall(NULL, mailboxname,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid,
imapd_authstate, delmbox, NULL);
user_deletedata(userid, 1);
}
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
}
else {
if (config_mupdate_server)
kick_mupdate();
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
}
struct renrock
{
struct namespace *namespace;
int ol;
int nl;
int rename_user;
char *olduser, *newuser;
char *acl_olduser, *acl_newuser;
char *newmailboxname;
char *partition;
int found;
};
/* Callback for use by cmd_rename */
static int checkmboxname(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct renrock *text = (struct renrock *)rock;
int r;
text->found++;
if((text->nl + strlen(name + text->ol)) >= MAX_MAILBOX_BUFFER)
return IMAP_MAILBOX_BADNAME;
strcpy(text->newmailboxname + text->nl, name + text->ol);
/* force create, but don't ignore policy. This is a filthy hack that
will go away when we refactor this code */
r = mboxlist_createmailboxcheck(text->newmailboxname, 0, text->partition, 1,
imapd_userid, imapd_authstate, NULL, NULL, 2);
return r;
}
/* Callback for use by cmd_rename */
static int renmbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
char oldextname[MAX_MAILBOX_BUFFER];
char newextname[MAX_MAILBOX_BUFFER];
struct renrock *text = (struct renrock *)rock;
struct mboxlist_entry *mbentry = NULL;
int r = 0;
r = mboxlist_lookup(name, &mbentry, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT) {
/* skip these mailboxes */
r = 0;
goto done;
}
if (r) goto done;
if((text->nl + strlen(name + text->ol)) >= MAX_MAILBOX_BUFFER)
goto done;
strcpy(text->newmailboxname + text->nl, name + text->ol);
/* don't notify implied rename in mailbox hierarchy */
r = mboxlist_renamemailbox(name, text->newmailboxname,
text->partition, 0 /* uidvalidity */,
1, imapd_userid, imapd_authstate, NULL, 0,
text->rename_user);
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
name,
imapd_userid, oldextname);
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
text->newmailboxname,
imapd_userid, newextname);
if(r) {
prot_printf(imapd_out, "* NO rename %s %s: %s\r\n",
oldextname, newextname, error_message(r));
if (!RENAME_STOP_ON_ERROR) r = 0;
} else {
/* If we're renaming a user, change quotaroot and ACL */
if (text->rename_user) {
user_copyquotaroot(name, text->newmailboxname);
user_renameacl(text->namespace, text->newmailboxname,
text->acl_olduser, text->acl_newuser);
#ifdef WITH_DAV
if (mbentry->mbtype & (MBTYPE_CALENDAR|MBTYPE_ADDRESSBOOK)) {
struct mailbox *mailbox = NULL;
r = mailbox_open_irl(text->newmailboxname, &mailbox);
if (!r) r = mailbox_add_dav(mailbox);
mailbox_close(&mailbox);
}
#endif
}
prot_printf(imapd_out, "* OK rename %s %s\r\n",
oldextname, newextname);
sync_log_mailbox_double(name, text->newmailboxname);
}
done:
mboxlist_entry_free(&mbentry);
prot_flush(imapd_out);
return r;
}
/*
* Perform a RENAME command
*/
static void cmd_rename(char *tag, char *oldname, char *newname, char *location)
{
int r = 0;
char *c;
char oldmailboxname[MAX_MAILBOX_BUFFER];
char newmailboxname[MAX_MAILBOX_BUFFER];
char oldmailboxname2[MAX_MAILBOX_BUFFER];
char newmailboxname2[MAX_MAILBOX_BUFFER];
char oldextname[MAX_MAILBOX_BUFFER];
char newextname[MAX_MAILBOX_BUFFER];
int omlen, nmlen;
int subcount = 0; /* number of sub-folders found */
int recursive_rename = 1;
int rename_user = 0;
char olduser[128], newuser[128];
char acl_olduser[128], acl_newuser[128];
mbentry_t *mbentry = NULL;
if (location && !imapd_userisadmin) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_PERMISSION_DENIED));
return;
}
/* canonicalize names */
r = (*imapd_namespace.mboxname_tointernal)(
&imapd_namespace,
oldname,
imapd_userid,
oldmailboxname
);
// This really shouldn't happen, but here we go.
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
r = (*imapd_namespace.mboxname_tointernal)(
&imapd_namespace,
newname,
imapd_userid,
newmailboxname
);
// This really shouldn't happen, but here we go.
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
/* Keep temporary copy: master is trashed */
strcpy(oldmailboxname2, oldmailboxname);
strcpy(newmailboxname2, newmailboxname);
r = mlookup(NULL, NULL, oldmailboxname, &mbentry);
if (!r && mbentry->mbtype & MBTYPE_REMOTE) {
/* remote mailbox */
struct backend *s = NULL;
int res;
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_SERVER_UNAVAILABLE));
goto done;
}
// Server or partition is going to change
if (location) {
char *destserver = NULL;
char *destpart = NULL;
destserver = xstrdupnull(location);
c = strchr(location, '!');
if (c) {
*c++ = '\0';
destpart = xstrdupnull(c);
} else {
destpart = xstrdup(location);
free(destserver);
}
if (destserver) {
if (destpart && !strcmp(destserver,config_servername)) {
// XFER
prot_printf(s->out,
"%s XFER \"%s\" \"%s\" %s\r\n",
tag,
oldname,
newname,
location
);
} else {
// RENAME
prot_printf(s->out,
"%s RENAME \"%s\" \"%s\" %s\r\n",
tag,
oldname,
newname,
location
);
}
} // (destserver)
res = pipe_until_tag(s, tag, 0);
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
} else { // (location)
// a simple rename, old name and new name must not be the same
if (!strcmp(oldname, newname)) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_SERVER_UNAVAILABLE));
goto done;
}
prot_printf(s->out,
"%s RENAME \"%s\" \"%s\"\r\n",
tag,
oldname,
newname
);
res = pipe_until_tag(s, tag, 0);
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
}
imapd_check(s, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
/* we're allowed to reference last_result since the noop, if
sent, went to a different server */
prot_printf(imapd_out, "%s %s", tag, s->last_result.s);
}
goto done;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (location && !config_partitiondir(location)) {
/* invalid partition, assume its a server (remote destination) */
char *server;
if (strcmp(oldname, newname)) {
prot_printf(imapd_out,
"%s NO Cross-server or cross-partition move w/rename not supported\r\n",
tag);
goto done;
}
/* dest partition? */
server = location;
location = strchr(server, '!');
if (location) *location++ = '\0';
cmd_xfer(tag, oldname, server, location);
goto done;
}
/* local rename: it's OK if the mailbox doesn't exist, we'll check
* if sub mailboxes can be renamed */
if (r == IMAP_MAILBOX_NONEXISTENT)
r = 0;
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
goto done;
}
/* local destination */
/* if this is my inbox, don't do recursive renames */
if (!strcasecmp(oldname, "inbox")) {
recursive_rename = 0;
}
/* check if we're an admin renaming a user */
else if (config_getswitch(IMAPOPT_ALLOWUSERMOVES) &&
mboxname_isusermailbox(oldmailboxname, 1) &&
mboxname_isusermailbox(newmailboxname, 1) &&
strcmp(oldmailboxname, newmailboxname) && /* different user */
imapd_userisadmin) {
rename_user = 1;
}
/* if we're renaming something inside of something else,
don't recursively rename stuff */
omlen = strlen(oldmailboxname);
nmlen = strlen(newmailboxname);
if (omlen < nmlen) {
if (!strncmp(oldmailboxname, newmailboxname, omlen) &&
newmailboxname[omlen] == '.') {
recursive_rename = 0;
}
} else {
if (!strncmp(oldmailboxname, newmailboxname, nmlen) &&
oldmailboxname[nmlen] == '.') {
recursive_rename = 0;
}
}
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
oldmailboxname,
imapd_userid, oldextname);
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
newmailboxname,
imapd_userid, newextname);
/* rename all mailboxes matching this */
if (recursive_rename && strcmp(oldmailboxname, newmailboxname)) {
struct renrock rock;
int ol = omlen + 1;
int nl = nmlen + 1;
char ombn[MAX_MAILBOX_BUFFER];
char nmbn[MAX_MAILBOX_BUFFER];
strcpy(ombn, oldmailboxname);
strcpy(nmbn, newmailboxname);
strcat(ombn, ".*");
strcat(nmbn, ".");
/* setup the rock */
rock.namespace = &imapd_namespace;
rock.found = 0;
rock.newmailboxname = nmbn;
rock.ol = ol;
rock.nl = nl;
rock.olduser = olduser;
rock.newuser = newuser;
rock.acl_olduser = acl_olduser;
rock.acl_newuser = acl_newuser;
rock.partition = location;
rock.rename_user = rename_user;
/* Check mboxnames to ensure we can write them all BEFORE we start */
r = mboxlist_findall(NULL, ombn, 1, imapd_userid,
imapd_authstate, checkmboxname, &rock);
subcount = rock.found;
}
/* attempt to rename the base mailbox */
if (!r) {
struct mboxevent *mboxevent = NULL;
/* don't send rename notification if we only change the partition */
if (strcmp(oldmailboxname, newmailboxname))
mboxevent = mboxevent_new(EVENT_MAILBOX_RENAME);
r = mboxlist_renamemailbox(oldmailboxname, newmailboxname, location,
0 /* uidvalidity */, imapd_userisadmin,
imapd_userid, imapd_authstate, mboxevent,
0, rename_user);
/* it's OK to not exist if there are subfolders */
if (r == IMAP_MAILBOX_NONEXISTENT && subcount && !rename_user &&
mboxname_userownsmailbox(imapd_userid, oldmailboxname) &&
mboxname_userownsmailbox(imapd_userid, newmailboxname)) {
mboxevent_free(&mboxevent);
goto submboxes;
}
/* send a MailboxRename event notification if enabled */
if (!r)
mboxevent_notify(mboxevent);
mboxevent_free(&mboxevent);
}
/* If we're renaming a user, take care of changing quotaroot, ACL,
seen state, subscriptions and sieve scripts */
if (!r && rename_user) {
char *domain;
/* create canonified userids */
domain = strchr(oldmailboxname, '!');
strcpy(olduser, domain ? domain+6 : oldmailboxname+5);
if (domain)
sprintf(olduser+strlen(olduser), "@%.*s",
(int) (domain - oldmailboxname), oldmailboxname);
strcpy(acl_olduser, olduser);
/* Translate any separators in source old userid (for ACLs) */
mboxname_hiersep_toexternal(&imapd_namespace, acl_olduser,
config_virtdomains ?
strcspn(acl_olduser, "@") : 0);
domain = strchr(newmailboxname, '!');
strcpy(newuser, domain ? domain+6 : newmailboxname+5);
if (domain)
sprintf(newuser+strlen(newuser), "@%.*s",
(int) (domain - newmailboxname), newmailboxname);
strcpy(acl_newuser, newuser);
/* Translate any separators in destination new userid (for ACLs) */
mboxname_hiersep_toexternal(&imapd_namespace, acl_newuser,
config_virtdomains ?
strcspn(acl_newuser, "@") : 0);
user_copyquotaroot(oldmailboxname, newmailboxname);
user_renameacl(&imapd_namespace, newmailboxname, acl_olduser, acl_newuser);
user_renamedata(olduser, newuser, imapd_userid, imapd_authstate);
/* XXX report status/progress of meta-data */
}
/* rename all mailboxes matching this */
if (!r && recursive_rename) {
struct renrock rock;
prot_printf(imapd_out, "* OK rename %s %s\r\n",
oldextname, newextname);
prot_flush(imapd_out);
submboxes:
strcat(oldmailboxname, ".*");
strcat(newmailboxname, ".");
/* setup the rock */
rock.namespace = &imapd_namespace;
rock.newmailboxname = newmailboxname;
rock.ol = omlen + 1;
rock.nl = nmlen + 1;
rock.olduser = olduser;
rock.newuser = newuser;
rock.acl_olduser = acl_olduser;
rock.acl_newuser = acl_newuser;
rock.partition = location;
rock.rename_user = rename_user;
/* add submailboxes; we pretend we're an admin since we successfully
renamed the parent - we're using internal names here */
r = mboxlist_findall(NULL, oldmailboxname, 1, imapd_userid,
imapd_authstate, renmbox, &rock);
}
/* take care of deleting old ACLs, subscriptions, seen state and quotas */
if (!r && rename_user) {
user_deletedata(olduser, 1);
/* allow the replica to get the correct new quotaroot
* and acls copied across */
sync_log_user(newuser);
/* allow the replica to clean up the old meta files */
sync_log_unuser(olduser);
}
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
if (config_mupdate_server)
kick_mupdate();
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
sync_log_mailbox_double(oldmailboxname2, newmailboxname2);
if (rename_user) sync_log_user(newuser);
}
done:
mboxlist_entry_free(&mbentry);
}
/*
* Perform a RECONSTRUCT command
*/
static void cmd_reconstruct(const char *tag, const char *name, int recursive)
{
int r = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
char quotaroot[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
struct mailbox *mailbox = NULL;
/* 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 && !strcmpsafe(mailboxname, index_mboxname(imapd_index)))
r = IMAP_MAILBOX_LOCKED;
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
imapd_refer(tag, mbentry->server, name);
mboxlist_entry_free(&mbentry);
return;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (!r) {
int pid;
/* Reconstruct it */
pid = fork();
if (pid == -1) {
r = IMAP_SYS_ERROR;
} else if (pid == 0) {
char buf[4096];
int ret;
/* Child - exec reconstruct*/
syslog(LOG_NOTICE, "Reconstructing '%s' (%s) for user '%s'",
mailboxname, recursive ? "recursive" : "not recursive",
imapd_userid);
fclose(stdin);
fclose(stdout);
fclose(stderr);
ret = snprintf(buf, sizeof(buf), "%s/reconstruct", SERVICE_DIR);
if(ret < 0 || ret >= (int) sizeof(buf)) {
/* in child, so fatailing won't disconnect our user */
fatal("reconstruct buffer not sufficiently big", EC_CONFIG);
}
if(recursive) {
execl(buf, buf, "-C", config_filename, "-r", "-f",
mailboxname, NULL);
} else {
execl(buf, buf, "-C", config_filename, mailboxname, NULL);
}
/* if we are here, we have a problem */
exit(-1);
} else {
int status;
/* Parent, wait on child */
if(waitpid(pid, &status, 0) < 0) r = IMAP_SYS_ERROR;
/* Did we fail? */
if(WEXITSTATUS(status) != 0) r = IMAP_SYS_ERROR;
}
}
/* Still in parent, need to re-quota the mailbox*/
/* Find its quota root */
if (!r)
r = mailbox_open_irl(mailboxname, &mailbox);
if(!r) {
if(mailbox->quotaroot) {
strcpy(quotaroot, mailbox->quotaroot);
} else {
strcpy(quotaroot, mailboxname);
}
mailbox_close(&mailbox);
}
/* Run quota -f */
if (!r) {
int pid;
pid = fork();
if(pid == -1) {
r = IMAP_SYS_ERROR;
} else if(pid == 0) {
char buf[4096];
int ret;
/* Child - exec reconstruct*/
syslog(LOG_NOTICE,
"Regenerating quota roots starting with '%s' for user '%s'",
mailboxname, imapd_userid);
fclose(stdin);
fclose(stdout);
fclose(stderr);
ret = snprintf(buf, sizeof(buf), "%s/quota", SERVICE_DIR);
if(ret < 0 || ret >= (int) sizeof(buf)) {
/* in child, so fatailing won't disconnect our user */
fatal("quota buffer not sufficiently big", EC_CONFIG);
}
execl(buf, buf, "-C", config_filename, "-f", quotaroot, NULL);
/* if we are here, we have a problem */
exit(-1);
} else {
int status;
/* Parent, wait on child */
if(waitpid(pid, &status, 0) < 0) r = IMAP_SYS_ERROR;
/* Did we fail? */
if(WEXITSTATUS(status) != 0) r = IMAP_SYS_ERROR;
}
}
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
}
/* number of times the callbacks for findall/findsub have been called */
static int list_callback_calls;
/*
* Parse LIST command arguments.
*/
static void getlistargs(char *tag, struct listargs *listargs)
{
static struct buf reference, buf;
int c;
/* Check for and parse LIST-EXTENDED selection options */
c = prot_getc(imapd_in);
if (c == '(') {
listargs->cmd = LIST_CMD_EXTENDED;
listargs->ret = 0;
c = getlistselopts(tag, listargs);
if (c == EOF) {
eatline(imapd_in, c);
return;
}
}
else
prot_ungetc(c, imapd_in);
if (imapd_magicplus) listargs->sel |= LIST_SEL_SUBSCRIBED;
/* Read in reference name */
c = getastring(imapd_in, imapd_out, &reference);
if (c == EOF && !*reference.s) {
prot_printf(imapd_out,
"%s BAD Missing required argument to List: reference name\r\n",
tag);
eatline(imapd_in, c);
return;
}
listargs->ref = reference.s;
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Missing required argument to List: mailbox pattern\r\n", tag);
eatline(imapd_in, c);
return;
}
/* Read in mailbox pattern(s) */
c = prot_getc(imapd_in);
if (c == '(') {
listargs->cmd = LIST_CMD_EXTENDED;
listargs->ret = 0;
for (;;) {
c = getastring(imapd_in, imapd_out, &buf);
if (*buf.s)
strarray_append(&listargs->pat, buf.s);
if (c != ' ') break;
}
if (c != ')') {
prot_printf(imapd_out,
"%s BAD Invalid syntax in List command\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
c = prot_getc(imapd_in);
}
else {
prot_ungetc(c, imapd_in);
c = getastring(imapd_in, imapd_out, &buf);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing required argument to List: mailbox pattern\r\n",
tag);
eatline(imapd_in, c);
goto freeargs;
}
strarray_append(&listargs->pat, buf.s);
}
/* Check for and parse LIST-EXTENDED return options */
if (c == ' ') {
listargs->cmd = LIST_CMD_EXTENDED;
listargs->ret = 0;
c = getlistretopts(tag, listargs);
if (c == EOF) {
eatline(imapd_in, c);
goto freeargs;
}
}
/* check for CRLF */
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to List\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
#ifdef USE_AUTOCREATE
autocreate_inbox();
#endif // USE_AUTOCREATE
return;
freeargs:
strarray_fini(&listargs->pat);
return;
}
/*
* Perform a LIST, LSUB, RLIST or RLSUB command
*/
static void cmd_list(char *tag, struct listargs *listargs)
{
clock_t start = clock();
char mytime[100];
if (listargs->sel & LIST_SEL_REMOTE) {
if (!config_getswitch(IMAPOPT_PROXYD_DISABLE_MAILBOX_REFERRALS)) {
supports_referrals = !disable_referrals;
}
}
list_callback_calls = 0;
if (listargs->pat.count && !*(listargs->pat.data[0]) && !(listargs->cmd & LIST_CMD_LSUB)) {
/* special case: query top-level hierarchy separator */
prot_printf(imapd_out, "* LIST (\\Noselect) \"%c\" \"\"\r\n",
imapd_namespace.hier_sep);
} else if (((listargs->sel & LIST_SEL_SUBSCRIBED) ||
(listargs->ret & LIST_RET_SUBSCRIBED)) &&
(backend_inbox || (backend_inbox = proxy_findinboxserver(imapd_userid)))) {
/* remote inbox */
/* XXX If we are in a standard Murder, and are given
LIST () RETURN (SUBSCRIBED), we need to get the matching
mailboxes locally (frontend) and the subscriptions remotely
(INBOX backend). We can only pass the buck to the INBOX backend
if its running a unified config */
if (list_data_remote(tag, listargs))
return;
} else {
list_data(listargs);
}
strarray_fini(&listargs->pat);
imapd_check((listargs->sel & LIST_SEL_SUBSCRIBED) ? NULL : backend_inbox, 0);
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
prot_printf(imapd_out, "%s OK %s (%s secs", tag,
error_message(IMAP_OK_COMPLETED), mytime);
if (list_callback_calls)
prot_printf(imapd_out, " %u calls", list_callback_calls);
prot_printf(imapd_out, ")\r\n");
}
/*
* Perform a SUBSCRIBE (add is nonzero) or
* UNSUBSCRIBE (add is zero) command
*/
static void cmd_changesub(char *tag, char *namespace, char *name, int add)
{
const char *cmd = add ? "Subscribe" : "Unsubscribe";
int r = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
int force = config_getswitch(IMAPOPT_ALLOWALLSUBSCRIBE);
if (backend_inbox || (backend_inbox = proxy_findinboxserver(imapd_userid))) {
/* remote INBOX */
if (add) {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
name, imapd_userid,
mailboxname);
if (!r) r = mlookup(NULL, NULL, mailboxname, NULL);
/* Doesn't exist on murder */
}
imapd_check(backend_inbox, 0);
if (!r) {
if (namespace) {
prot_printf(backend_inbox->out,
"%s %s {" SIZE_T_FMT "+}\r\n%s"
" {" SIZE_T_FMT "+}\r\n%s\r\n",
tag, cmd,
strlen(namespace), namespace,
strlen(name), name);
} else {
prot_printf(backend_inbox->out, "%s %s {" SIZE_T_FMT "+}\r\n%s\r\n",
tag, cmd,
strlen(name), name);
}
pipe_including_tag(backend_inbox, tag, 0);
}
else {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
}
return;
}
/* local INBOX */
if (namespace) lcase(namespace);
if (!namespace || !strcmp(namespace, "mailbox")) {
size_t len = strlen(name);
if (force && imapd_namespace.isalt &&
(((len == strlen(imapd_namespace.prefix[NAMESPACE_USER]) - 1) &&
!strncmp(name, imapd_namespace.prefix[NAMESPACE_USER], len)) ||
((len == strlen(imapd_namespace.prefix[NAMESPACE_SHARED]) - 1) &&
!strncmp(name, imapd_namespace.prefix[NAMESPACE_SHARED], len)))) {
r = 0;
}
else {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mboxlist_changesub(mailboxname, imapd_userid,
imapd_authstate, add, force, 1);
}
}
}
else if (!strcmp(namespace, "bboard")) {
r = add ? IMAP_MAILBOX_NONEXISTENT : 0;
}
else {
prot_printf(imapd_out, "%s BAD Invalid %s subcommand\r\n", tag, cmd);
return;
}
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s: %s\r\n", tag, cmd, error_message(r));
}
else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
}
/*
* Perform a GETACL command
*/
static void cmd_getacl(const char *tag, const char *name)
{
char mailboxname[MAX_MAILBOX_BUFFER];
int r, access;
char *acl;
char *rights, *nextid;
char *freeme = NULL;
mbentry_t *mbentry = NULL;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
access = cyrus_acl_myrights(imapd_authstate, mbentry->acl);
if (!(access & ACL_ADMIN) &&
!imapd_userisadmin &&
!mboxname_userownsmailbox(imapd_userid, mailboxname)) {
r = (access&ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
mboxlist_entry_free(&mbentry);
return;
}
prot_printf(imapd_out, "* ACL ");
prot_printastring(imapd_out, name);
freeme = acl = xstrdupnull(mbentry->acl);
while (acl) {
rights = strchr(acl, '\t');
if (!rights) break;
*rights++ = '\0';
nextid = strchr(rights, '\t');
if (!nextid) break;
*nextid++ = '\0';
prot_printf(imapd_out, " ");
prot_printastring(imapd_out, acl);
prot_printf(imapd_out, " ");
prot_printastring(imapd_out, rights);
acl = nextid;
}
prot_printf(imapd_out, "\r\n");
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
free(freeme);
mboxlist_entry_free(&mbentry);
}
/*
* Perform a LISTRIGHTS command
*/
static void cmd_listrights(char *tag, char *name, char *identifier)
{
char mailboxname[MAX_MAILBOX_BUFFER];
int r, rights;
mbentry_t *mbentry = NULL;
struct auth_state *authstate;
const char *canon_identifier;
int implicit;
char rightsdesc[100], optional[33];
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
rights = cyrus_acl_myrights(imapd_authstate, mbentry->acl);
if (!rights && !imapd_userisadmin &&
!mboxname_userownsmailbox(imapd_userid, mailboxname)) {
r = IMAP_MAILBOX_NONEXISTENT;
}
}
mboxlist_entry_free(&mbentry);
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
authstate = auth_newstate(identifier);
if (global_authisa(authstate, IMAPOPT_ADMINS))
canon_identifier = identifier; /* don't canonify global admins */
else
canon_identifier = canonify_userid(identifier, imapd_userid, NULL);
auth_freestate(authstate);
if (!canon_identifier) {
implicit = 0;
}
else if (mboxname_userownsmailbox(canon_identifier, mailboxname)) {
/* identifier's personal mailbox */
implicit = config_implicitrights;
}
else if (mboxname_isusermailbox(mailboxname, 1)) {
/* anyone can post to an INBOX */
implicit = ACL_POST;
}
else {
implicit = 0;
}
/* calculate optional rights */
cyrus_acl_masktostr(implicit ^ (canon_identifier ? ACL_FULL : 0),
optional);
/* build the rights string */
if (implicit) {
cyrus_acl_masktostr(implicit, rightsdesc);
}
else {
strcpy(rightsdesc, "\"\"");
}
if (*optional) {
int i, n = strlen(optional);
char *p = rightsdesc + strlen(rightsdesc);
for (i = 0; i < n; i++) {
*p++ = ' ';
*p++ = optional[i];
}
*p = '\0';
}
prot_printf(imapd_out, "* LISTRIGHTS ");
prot_printastring(imapd_out, name);
(void)prot_putc(' ', imapd_out);
prot_printastring(imapd_out, identifier);
prot_printf(imapd_out, " %s", rightsdesc);
prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
static int printmyrights(const char *extname, mbentry_t *mbentry)
{
int rights = 0;
char str[ACL_MAXSTR];
rights = cyrus_acl_myrights(imapd_authstate, mbentry->acl);
/* Add in implicit rights */
if (imapd_userisadmin) {
rights |= ACL_LOOKUP|ACL_ADMIN;
}
else if (mboxname_userownsmailbox(imapd_userid, mbentry->name)) {
rights |= config_implicitrights;
}
if (!(rights & (ACL_LOOKUP|ACL_READ|ACL_INSERT|ACL_CREATE|ACL_DELETEMBOX|ACL_ADMIN))) {
return IMAP_MAILBOX_NONEXISTENT;
}
prot_printf(imapd_out, "* MYRIGHTS ");
prot_printastring(imapd_out, extname);
prot_printf(imapd_out, " ");
prot_printastring(imapd_out, cyrus_acl_masktostr(rights, str));
prot_printf(imapd_out, "\r\n");
return 0;
}
/*
* Perform a MYRIGHTS command
*/
static void cmd_myrights(const char *tag, const char *name)
{
char mailboxname[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
int r;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) r = mlookup(tag, name, mailboxname, &mbentry);
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) r = printmyrights(name, mbentry);
mboxlist_entry_free(&mbentry);
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
return;
}
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Perform a SETACL command
*/
static void cmd_setacl(char *tag, const char *name,
const char *identifier, const char *rights)
{
int r;
char mailboxname[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
/* is it remote? */
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
struct backend *s = NULL;
int res;
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
if (!r && imapd_userisadmin && supports_referrals) {
/* They aren't an admin remotely, so let's refer them */
imapd_refer(tag, mbentry->server, name);
referral_kick = 1;
mboxlist_entry_free(&mbentry);
return;
}
mboxlist_entry_free(&mbentry);
if (!r) {
if (rights) {
prot_printf(s->out,
"%s Setacl {" SIZE_T_FMT "+}\r\n%s"
" {" SIZE_T_FMT "+}\r\n%s {" SIZE_T_FMT "+}\r\n%s\r\n",
tag, strlen(name), name,
strlen(identifier), identifier,
strlen(rights), rights);
} else {
prot_printf(s->out,
"%s Deleteacl {" SIZE_T_FMT "+}\r\n%s"
" {" SIZE_T_FMT "+}\r\n%s\r\n",
tag, strlen(name), name,
strlen(identifier), identifier);
}
res = pipe_until_tag(s, tag, 0);
if (!CAPA(s, CAPA_MUPDATE) && res == PROXY_OK) {
/* setup new ACL in MUPDATE */
}
/* make sure we've seen the update */
if (ultraparanoid && res == PROXY_OK) kick_mupdate();
}
imapd_check(s, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
/* we're allowed to reference last_result since the noop, if
sent, went to a different server */
prot_printf(imapd_out, "%s %s", tag, s->last_result.s);
}
return;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (!r) {
r = mboxlist_setacl(&imapd_namespace, mailboxname, identifier, rights,
imapd_userisadmin || imapd_userisproxyadmin,
proxy_userid, imapd_authstate);
}
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
if (config_mupdate_server)
kick_mupdate();
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
}
static void print_quota_used(struct protstream *o, const struct quota *q)
{
int res;
const char *sep = "";
prot_putc('(', o);
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (q->limits[res] >= 0) {
prot_printf(o, "%s%s " QUOTA_T_FMT " " QUOTA_T_FMT,
sep, quota_names[res],
q->useds[res]/quota_units[res],
q->limits[res]);
sep = " ";
}
}
prot_putc(')', o);
}
static void print_quota_limits(struct protstream *o, const struct quota *q)
{
int res;
const char *sep = "";
prot_putc('(', o);
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
if (q->limits[res] >= 0) {
prot_printf(o, "%s%s " QUOTA_T_FMT,
sep, quota_names[res],
q->limits[res]);
sep = " ";
}
}
prot_putc(')', o);
}
/*
* Callback for (get|set)quota, to ensure that all of the
* submailboxes are on the same server.
*/
static int quota_cb(char *name, int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)), void *rock)
{
const char *servername = (const char *)rock;
mbentry_t *mbentry = NULL;
int r;
r = mlookup(NULL, NULL, name, &mbentry);
if (r) return r;
if (strcmp(servername, mbentry->server)) {
/* Not on same server as the root */
r = IMAP_NOT_SINGULAR_ROOT;
} else {
r = PROXY_OK;
}
mboxlist_entry_free(&mbentry);
return r;
}
/*
* Perform a GETQUOTA command
*/
static void cmd_getquota(const char *tag, const char *name)
{
int r;
char quotarootbuf[MAX_MAILBOX_BUFFER];
char internalname[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
struct quota q;
imapd_check(NULL, 0);
if (!imapd_userisadmin && !imapd_userisproxyadmin) {
r = IMAP_PERMISSION_DENIED;
} else {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, internalname);
}
if (!r) {
r = mlookup(NULL, NULL, internalname, &mbentry);
}
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", internalname);
r = mboxlist_findall(&imapd_namespace, quotarootbuf,
imapd_userisadmin, imapd_userid,
imapd_authstate, quota_cb, (void *)mbentry->server);
if (!r) {
struct backend *s;
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
imapd_check(s, 0);
if (!r) {
prot_printf(s->out, "%s Getquota {" SIZE_T_FMT "+}\r\n%s\r\n",
tag, strlen(name), name);
pipe_including_tag(s, tag, 0);
}
}
if (r) prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
mboxlist_entry_free(&mbentry);
return;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
quota_init(&q, internalname);
r = quota_read(&q, NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
goto done;
}
prot_printf(imapd_out, "* QUOTA ");
prot_printastring(imapd_out, name);
prot_printf(imapd_out, " ");
print_quota_used(imapd_out, &q);
prot_printf(imapd_out, "\r\n");
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
done:
quota_free(&q);
}
/*
* Perform a GETQUOTAROOT command
*/
static void cmd_getquotaroot(const char *tag, const char *name)
{
char mailboxname[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
struct mailbox *mailbox = NULL;
int myrights;
int r, doclose = 0;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
struct backend *s;
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
imapd_check(s, 0);
if (!r) {
prot_printf(s->out, "%s Getquotaroot {" SIZE_T_FMT "+}\r\n%s\r\n",
tag, strlen(name), name);
pipe_including_tag(s, tag, 0);
} else {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
}
mboxlist_entry_free(&mbentry);
return;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (!r) {
r = mailbox_open_irl(mailboxname, &mailbox);
if (!r) {
doclose = 1;
myrights = cyrus_acl_myrights(imapd_authstate, mailbox->acl);
}
}
if (!r) {
if (!imapd_userisadmin && !(myrights & ACL_READ)) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
if (!r) {
prot_printf(imapd_out, "* QUOTAROOT ");
prot_printastring(imapd_out, name);
if (mailbox->quotaroot) {
struct quota q;
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
mailbox->quotaroot,
imapd_userid, mailboxname);
prot_printf(imapd_out, " ");
prot_printastring(imapd_out, mailboxname);
quota_init(&q, mailbox->quotaroot);
r = quota_read(&q, NULL, 0);
if (!r) {
prot_printf(imapd_out, "\r\n* QUOTA ");
prot_printastring(imapd_out, mailboxname);
prot_putc(' ', imapd_out);
print_quota_used(imapd_out, &q);
}
quota_free(&q);
}
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;
}
imapd_check(NULL, 0);
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
/*
* Parse and perform a SETQUOTA command
* The command has been parsed up to the resource list
*/
void cmd_setquota(const char *tag, const char *quotaroot)
{
quota_t newquotas[QUOTA_NUMRESOURCES];
int res;
int c;
int force = 0;
static struct buf arg;
int r;
char mailboxname[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
if (!imapd_userisadmin && !imapd_userisproxyadmin) {
/* need to allow proxies so that mailbox moves can set initial quota
* roots */
r = IMAP_PERMISSION_DENIED;
goto out;
}
/* 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)
goto out;
r = mlookup(NULL, NULL, mailboxname, &mbentry);
if (r == IMAP_MAILBOX_NONEXISTENT)
r = 0; /* will create a quotaroot anyway */
if (r)
goto out;
if (mbentry && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
struct backend *s;
char quotarootbuf[MAX_MAILBOX_BUFFER];
snprintf(quotarootbuf, sizeof(quotarootbuf), "%s.*", mailboxname);
r = mboxlist_findall(&imapd_namespace, quotarootbuf,
imapd_userisadmin, imapd_userid,
imapd_authstate, quota_cb, (void *)mbentry->server);
if (r)
goto out;
imapd_check(NULL, 0);
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) {
r = IMAP_SERVER_UNAVAILABLE;
goto out;
}
imapd_check(s, 0);
prot_printf(s->out, "%s Setquota ", tag);
prot_printstring(s->out, quotaroot);
prot_putc(' ', s->out);
pipe_command(s, 0);
pipe_including_tag(s, tag, 0);
return;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
/* Now parse the arguments as a setquota_list */
c = prot_getc(imapd_in);
if (c != '(') goto badlist;
for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++)
newquotas[res] = QUOTA_UNLIMITED;
for (;;) {
/* XXX - limit is actually stored in an int value */
int32_t limit = 0;
c = getword(imapd_in, &arg);
if ((c == ')') && !arg.s[0]) break;
if (c != ' ') goto badlist;
res = quota_name_to_resource(arg.s);
if (res < 0) {
r = IMAP_UNSUPPORTED_QUOTA;
goto out;
}
c = getsint32(imapd_in, &limit);
/* note: we accept >= 0 according to rfc2087,
* and also -1 to fix Bug #3559 */
if (limit < -1) goto badlist;
newquotas[res] = limit;
if (c == ')') break;
else if (c != ' ') goto badlist;
}
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;
}
r = mboxlist_setquotas(mailboxname, newquotas, force);
imapd_check(NULL, 0);
out:
mboxlist_entry_free(&mbentry);
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 */
static void cmd_starttls(char *tag, int imaps)
{
int result;
int *layerp;
char *auth_id;
sasl_ssf_t ssf;
/* SASL and openssl have different ideas about whether ssf is signed */
layerp = (int *) &ssf;
if (imapd_starttls_done == 1)
{
prot_printf(imapd_out, "%s NO TLS already active\r\n", tag);
return;
}
result=tls_init_serverengine("imap",
5, /* depth to verify */
!imaps, /* can client auth? */
!imaps); /* TLS only? */
if (result == -1) {
syslog(LOG_ERR, "error initializing TLS");
if (imaps == 0) {
prot_printf(imapd_out, "%s NO Error initializing TLS\r\n", tag);
} else {
fatal("tls_init() failed", EC_CONFIG);
}
return;
}
if (imaps == 0)
{
prot_printf(imapd_out, "%s OK Begin TLS negotiation now\r\n", tag);
/* must flush our buffers before starting tls */
prot_flush(imapd_out);
}
result=tls_start_servertls(0, /* read */
1, /* write */
imaps ? 180 : imapd_timeout,
layerp,
&auth_id,
&tls_conn);
/* if error */
if (result==-1) {
if (imaps == 0) {
prot_printf(imapd_out, "%s NO Starttls negotiation failed\r\n",
tag);
syslog(LOG_NOTICE, "STARTTLS negotiation failed: %s",
imapd_clienthost);
return;
} else {
syslog(LOG_NOTICE, "imaps TLS negotiation failed: %s",
imapd_clienthost);
fatal("tls_start_servertls() failed", EC_TEMPFAIL);
return;
}
}
/* tell SASL about the negotiated layer */
result = sasl_setprop(imapd_saslconn, SASL_SSF_EXTERNAL, &ssf);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
saslprops.ssf = ssf;
result = sasl_setprop(imapd_saslconn, SASL_AUTH_EXTERNAL, auth_id);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
if(auth_id)
saslprops.authid = xstrdup(auth_id);
/* tell the prot layer about our new layers */
prot_settls(imapd_in, tls_conn);
prot_settls(imapd_out, tls_conn);
imapd_starttls_done = 1;
#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL)
imapd_tls_comp = (void *) SSL_get_current_compression(tls_conn);
#endif
}
#else
void cmd_starttls(char *tag __attribute__((unused)),
int imaps __attribute__((unused)))
{
fatal("cmd_starttls() executed, but starttls isn't implemented!",
EC_SOFTWARE);
}
#endif // (OPENSSL_VERSION_NUMBER >= 0x0090800fL)
static int parse_statusitems(unsigned *statusitemsp, const char **errstr)
{
static struct buf arg;
unsigned statusitems = 0;
int c;
int hasconv = config_getswitch(IMAPOPT_CONVERSATIONS);
c = prot_getc(imapd_in);
if (c != '(') return EOF;
c = getword(imapd_in, &arg);
if (arg.s[0] == '\0') return EOF;
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 if (!strcmp(arg.s, "highestmodseq")) {
statusitems |= STATUS_HIGHESTMODSEQ;
}
else if (hasconv && !strcmp(arg.s, "xconvexists")) {
statusitems |= STATUS_XCONVEXISTS;
}
else if (hasconv && !strcmp(arg.s, "xconvunseen")) {
statusitems |= STATUS_XCONVUNSEEN;
}
else if (hasconv && !strcmp(arg.s, "xconvmodseq")) {
statusitems |= STATUS_XCONVMODSEQ;
}
else {
static char buf[200];
snprintf(buf, 200, "Invalid Status attributes %s", arg.s);
*errstr = buf;
return EOF;
}
if (c == ' ') c = getword(imapd_in, &arg);
else break;
}
if (c != ')') {
*errstr = "Missing close parenthesis in Status";
return EOF;
}
c = prot_getc(imapd_in);
/* success */
*statusitemsp = statusitems;
return c;
}
static int print_statusline(const char *extname, unsigned statusitems,
struct statusdata *sd)
{
int sepchar;
prot_printf(imapd_out, "* STATUS ");
prot_printastring(imapd_out, extname);
prot_printf(imapd_out, " ");
sepchar = '(';
if (statusitems & STATUS_MESSAGES) {
prot_printf(imapd_out, "%cMESSAGES %u", sepchar, sd->messages);
sepchar = ' ';
}
if (statusitems & STATUS_RECENT) {
prot_printf(imapd_out, "%cRECENT %u", sepchar, sd->recent);
sepchar = ' ';
}
if (statusitems & STATUS_UIDNEXT) {
prot_printf(imapd_out, "%cUIDNEXT %u", sepchar, sd->uidnext);
sepchar = ' ';
}
if (statusitems & STATUS_UIDVALIDITY) {
prot_printf(imapd_out, "%cUIDVALIDITY %u", sepchar, sd->uidvalidity);
sepchar = ' ';
}
if (statusitems & STATUS_UNSEEN) {
prot_printf(imapd_out, "%cUNSEEN %u", sepchar, sd->unseen);
sepchar = ' ';
}
if (statusitems & STATUS_HIGHESTMODSEQ) {
prot_printf(imapd_out, "%cHIGHESTMODSEQ " MODSEQ_FMT,
sepchar, sd->highestmodseq);
sepchar = ' ';
}
if (statusitems & STATUS_XCONVEXISTS) {
prot_printf(imapd_out, "%cXCONVEXISTS %u", sepchar, sd->xconv.exists);
sepchar = ' ';
}
if (statusitems & STATUS_XCONVUNSEEN) {
prot_printf(imapd_out, "%cXCONVUNSEEN %u", sepchar, sd->xconv.unseen);
sepchar = ' ';
}
if (statusitems & STATUS_XCONVMODSEQ) {
prot_printf(imapd_out, "%cXCONVMODSEQ " MODSEQ_FMT, sepchar, sd->xconv.modseq);
sepchar = ' ';
}
prot_printf(imapd_out, ")\r\n");
return 0;
}
static int imapd_statusdata(const char *mailboxname, unsigned statusitems,
struct statusdata *sd)
{
int r;
/* use the index status if we can so we get the 'alive' Recent count */
if (!strcmpsafe(mailboxname, index_mboxname(imapd_index)))
r = index_status(imapd_index, sd);
/* fall back to generic lookup */
else
r = status_lookup(mailboxname, imapd_userid, statusitems, sd);
if (r) goto out;
if (statusitems & (STATUS_XCONVEXISTS | STATUS_XCONVUNSEEN
| STATUS_XCONVMODSEQ)) {
struct conversations_state *state = NULL;
/* use the existing state if possible */
state = conversations_get_mbox(mailboxname);
if (state) {
r = conversation_getstatus(state, mailboxname, &sd->xconv);
if (r) goto out;
}
/* otherwise fetch a new one and just use for this one thing! */
else {
r = conversations_open_mbox(mailboxname, &state);
if (r) goto out;
r = conversation_getstatus(state, mailboxname, &sd->xconv);
conversations_abort(&state);
if (r) goto out;
}
}
out:
return r;
}
/*
* Parse and perform a STATUS command
* The command has been parsed up to the attribute list
*/
static void cmd_status(char *tag, char *name)
{
int c;
unsigned statusitems = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
const char *errstr = "Bad status string";
mbentry_t *mbentry = NULL;
struct statusdata sdata;
int r = 0;
memset(&sdata, 0, sizeof(struct statusdata));
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(tag, name, mailboxname, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) {
/* Eat the argument */
eatline(imapd_in, prot_getc(imapd_in));
return;
}
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
if (supports_referrals
&& config_getswitch(IMAPOPT_PROXYD_ALLOW_STATUS_REFERRAL)) {
imapd_refer(tag, mbentry->server, name);
/* Eat the argument */
eatline(imapd_in, prot_getc(imapd_in));
}
else {
struct backend *s;
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
imapd_check(s, 0);
if (!r) {
prot_printf(s->out, "%s Status {" SIZE_T_FMT "+}\r\n%s ", tag,
strlen(name), name);
if (!pipe_command(s, 65536)) {
pipe_including_tag(s, tag, 0);
}
} else {
eatline(imapd_in, prot_getc(imapd_in));
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
}
}
goto done;
}
/* local mailbox */
imapd_check(NULL, 0);
c = parse_statusitems(&statusitems, &errstr);
if (c == EOF) {
prot_printf(imapd_out, "%s BAD %s\r\n", tag, errstr);
goto done;
}
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);
goto done;
}
/* check permissions */
if (!r) {
int myrights = cyrus_acl_myrights(imapd_authstate, mbentry->acl);
if (!(myrights & ACL_READ)) {
r = (imapd_userisadmin || (myrights & ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
if (!r) r = imapd_statusdata(mailboxname, statusitems, &sdata);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(r));
}
else {
print_statusline(name, statusitems, &sdata);
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
done:
mboxlist_entry_free(&mbentry);
return;
}
#ifdef ENABLE_X_NETSCAPE_HACK
/*
* Reply to Netscape's crock with a crock of my own
*/
void cmd_netscrape(char *tag)
{
const char *url;
url = config_getstring(IMAPOPT_NETSCAPEURL);
/* I only know of three things to reply with: */
prot_printf(imapd_out,
"* OK [NETSCAPE] Carnegie Mellon Cyrus IMAP\r\n"
"* VERSION %s\r\n",
cyrus_version());
if (url) prot_printf(imapd_out, "* ACCOUNT-URL %s\r\n", url);
prot_printf(imapd_out, "%s OK %s\r\n",
tag, error_message(IMAP_OK_COMPLETED));
}
#endif /* ENABLE_X_NETSCAPE_HACK */
/* Callback for cmd_namespace to be passed to mboxlist_findall.
* For each top-level mailbox found, print a bit of the response
* if it is a shared namespace. The rock is used as an integer in
* order to ensure the namespace response is correct on a server with
* no shared namespace.
*/
static int namespacedata(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
int* sawone = (int*) rock;
if (!name) {
return 0;
}
if ((!strncasecmp(name, "INBOX", 5) && (!name[5] || name[5] == '.'))) {
/* The user has a "personal" namespace. */
sawone[NAMESPACE_INBOX] = 1;
} else if (mboxname_isusermailbox(name, 0)) {
/* The user can see the "other users" namespace. */
sawone[NAMESPACE_USER] = 1;
} else {
/* The user can see the "shared" namespace. */
sawone[NAMESPACE_SHARED] = 1;
}
return 0;
}
/*
* Print out a response to the NAMESPACE command defined by
* RFC 2342.
*/
static void cmd_namespace(char* tag)
{
int sawone[3] = {0, 0, 0};
char* pattern;
if (SLEEZY_NAMESPACE) {
char inboxname[MAX_MAILBOX_BUFFER];
if (strlen(imapd_userid) + 5 >= MAX_MAILBOX_BUFFER)
sawone[NAMESPACE_INBOX] = 0;
else {
(*imapd_namespace.mboxname_tointernal)(&imapd_namespace, "INBOX",
imapd_userid, inboxname);
sawone[NAMESPACE_INBOX] =
!mboxlist_lookup(inboxname, NULL, NULL);
}
sawone[NAMESPACE_USER] = imapd_userisadmin ? 1 : imapd_namespace.accessible[NAMESPACE_USER];
sawone[NAMESPACE_SHARED] = imapd_userisadmin ? 1 : imapd_namespace.accessible[NAMESPACE_SHARED];
} else {
pattern = xstrdup("%");
/* now find all the exciting toplevel namespaces -
* we're using internal names here
*/
mboxlist_findall(NULL, pattern, imapd_userisadmin, imapd_userid,
imapd_authstate, namespacedata, (void*) sawone);
free(pattern);
}
prot_printf(imapd_out, "* NAMESPACE");
if (sawone[NAMESPACE_INBOX]) {
prot_printf(imapd_out, " ((\"%s\" \"%c\"))",
imapd_namespace.prefix[NAMESPACE_INBOX],
imapd_namespace.hier_sep);
} else {
prot_printf(imapd_out, " NIL");
}
if (sawone[NAMESPACE_USER]) {
prot_printf(imapd_out, " ((\"%s\" \"%c\"))",
imapd_namespace.prefix[NAMESPACE_USER],
imapd_namespace.hier_sep);
} else {
prot_printf(imapd_out, " NIL");
}
if (sawone[NAMESPACE_SHARED]) {
prot_printf(imapd_out, " ((\"%s\" \"%c\"))",
imapd_namespace.prefix[NAMESPACE_SHARED],
imapd_namespace.hier_sep);
} else {
prot_printf(imapd_out, " NIL");
}
prot_printf(imapd_out, "\r\n");
imapd_check(NULL, 0);
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
static int parsecreateargs(struct dlist **extargs)
{
int c;
static struct buf arg, val;
struct dlist *res;
struct dlist *sub;
char *p;
const char *name;
res = dlist_newkvlist(NULL, "CREATE");
c = prot_getc(imapd_in);
if (c == '(') {
/* new style RFC4466 arguments */
do {
c = getword(imapd_in, &arg);
name = ucase(arg.s);
if (c != ' ') goto fail;
c = prot_getc(imapd_in);
if (c == '(') {
/* fun - more lists! */
sub = dlist_newlist(res, name);
do {
c = getword(imapd_in, &val);
dlist_setatom(sub, name, val.s);
} while (c == ' ');
if (c != ')') goto fail;
c = prot_getc(imapd_in);
}
else {
prot_ungetc(c, imapd_in);
c = getword(imapd_in, &val);
dlist_setatom(res, name, val.s);
}
} while (c == ' ');
if (c != ')') goto fail;
c = prot_getc(imapd_in);
}
else {
prot_ungetc(c, imapd_in);
c = getword(imapd_in, &arg);
if (c == EOF) goto fail;
p = strchr(arg.s, '!');
if (p) {
/* with a server */
*p = '\0';
dlist_setatom(res, "SERVER", arg.s);
dlist_setatom(res, "PARTITION", p+1);
}
else {
dlist_setatom(res, "PARTITION", arg.s);
}
}
*extargs = res;
return c;
fail:
dlist_free(&res);
return EOF;
}
/*
* 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.
*/
static int parse_annotate_fetch_data(const char *tag,
int permessage_flag,
strarray_t *entries,
strarray_t *attribs)
{
int c;
static struct buf arg;
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 {
if (permessage_flag)
c = getastring(imapd_in, imapd_out, &arg);
else
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 */
strarray_append(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);
if (permessage_flag)
c = getastring(imapd_in, imapd_out, &arg);
else
c = getqstring(imapd_in, imapd_out, &arg);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing annotation entry\r\n", tag);
goto baddata;
}
strarray_append(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 {
if (permessage_flag)
c = getastring(imapd_in, imapd_out, &arg);
else
c = getqstring(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 */
strarray_append(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);
if (permessage_flag)
c = getastring(imapd_in, imapd_out, &arg);
else
c = getqstring(imapd_in, imapd_out, &arg);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing annotation attribute\r\n", tag);
goto baddata;
}
strarray_append(attribs, arg.s);
}
return c;
baddata:
if (c != EOF) prot_ungetc(c, imapd_in);
return EOF;
}
/*
* Parse either a single string or a (bracketed) list of strings.
* This is used up to three times in the GETMETADATA command.
*/
static int parse_metadata_string_or_list(const char *tag,
strarray_t *entries,
int *is_list)
{
int c;
static struct buf arg;
// Assume by default the arguments are a list of entries,
// until proven otherwise.
*is_list = 1;
c = prot_getc(imapd_in);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing metadata entry\r\n", tag);
goto baddata;
}
else if (c == '\r') {
return c;
}
else if (c == '(') {
/* entry list */
do {
c = getastring(imapd_in, imapd_out, &arg);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing metadata entry\r\n", tag);
goto baddata;
}
/* add the entry to the list */
strarray_append(entries, arg.s);
} while (c == ' ');
if (c != ')') {
prot_printf(imapd_out,
"%s BAD Missing close paren in metadata 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 = getastring(imapd_in, imapd_out, &arg);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing metadata entry\r\n", tag);
goto baddata;
}
strarray_append(entries, arg.s);
// It is only not a list if there are no wildcards
if (!strchr(arg.s, '*') && !strchr(arg.s, '%')) {
// Not a list
*is_list = 0;
}
}
if (c == ' ' || c == '\r') 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.
*
* Also parse RFC5257 per-message annotation store data, which
* is almost identical but differs in that entry names and attrib
* names are astrings rather than strings, and that the whole set
* of data *must* be enclosed in parentheses.
*/
static int parse_annotate_store_data(const char *tag,
int permessage_flag,
struct entryattlist **entryatts)
{
int c, islist = 0;
static struct buf entry, attrib, value;
struct attvaluelist *attvalues = NULL;
*entryatts = NULL;
c = prot_getc(imapd_in);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing annotation entry\r\n", tag);
goto baddata;
}
else if (c == '(') {
/* entry list */
islist = 1;
}
else if (permessage_flag) {
prot_printf(imapd_out,
"%s BAD Missing paren for annotation entry\r\n", tag);
goto baddata;
}
else {
/* single entry -- put the char back */
prot_ungetc(c, imapd_in);
}
do {
/* get entry */
if (permessage_flag)
c = getastring(imapd_in, imapd_out, &entry);
else
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 */
if (permessage_flag)
c = getastring(imapd_in, imapd_out, &attrib);
else
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 != ' ') {
prot_printf(imapd_out,
"%s BAD Missing annotation value\r\n", tag);
goto baddata;
}
c = getbnstring(imapd_in, imapd_out, &value);
if (c == 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);
} while (c == ' ');
if (c != ')') {
prot_printf(imapd_out,
"%s BAD Missing close paren in annotation "
"attribute-values list\r\n", tag);
goto baddata;
}
/* add the entry to the list */
appendentryatt(entryatts, entry.s, attvalues);
attvalues = NULL;
c = prot_getc(imapd_in);
} while (c == ' ');
if (islist) {
if (c != ')') {
prot_printf(imapd_out,
"%s BAD Missing close paren in annotation entry list \r\n",
tag);
goto baddata;
}
c = prot_getc(imapd_in);
}
return c;
baddata:
if (attvalues) freeattvalues(attvalues);
if (c != EOF) prot_ungetc(c, imapd_in);
return EOF;
}
/*
* Parse metadata 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.
*/
static int parse_metadata_store_data(const char *tag,
struct entryattlist **entryatts)
{
int c;
const char *name;
const char *att;
static struct buf entry, value;
struct attvaluelist *attvalues = NULL;
struct entryattlist *entryp;
int need_add;
*entryatts = NULL;
c = prot_getc(imapd_in);
if (c != '(') {
prot_printf(imapd_out,
"%s BAD Missing metadata entry list\r\n", tag);
goto baddata;
}
do {
/* get entry */
c = getastring(imapd_in, imapd_out, &entry);
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Missing metadata entry\r\n", tag);
goto baddata;
}
lcase(entry.s);
/* get value */
c = getbnstring(imapd_in, imapd_out, &value);
if (c == EOF) {
prot_printf(imapd_out,
"%s BAD Missing metadata value\r\n", tag);
goto baddata;
}
if (!strncmp(entry.s, "/private", 8) &&
(entry.s[8] == '\0' || entry.s[8] == '/')) {
att = "value.priv";
name = entry.s + 8;
}
else if (!strncmp(entry.s, "/shared", 7) &&
(entry.s[7] == '\0' || entry.s[7] == '/')) {
att = "value.shared";
name = entry.s + 7;
}
else {
prot_printf(imapd_out,
"%s BAD entry must begin with /shared or /private\r\n",
tag);
goto baddata;
}
need_add = 1;
for (entryp = *entryatts; entryp; entryp = entryp->next) {
if (strcmp(entryp->entry, name)) continue;
/* it's a match, have to append! */
appendattvalue(&entryp->attvalues, att, &value);
need_add = 0;
break;
}
if (need_add) {
appendattvalue(&attvalues, att, &value);
appendentryatt(entryatts, name, attvalues);
attvalues = NULL;
}
} 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);
return c;
baddata:
if (attvalues) freeattvalues(attvalues);
if (c != EOF) prot_ungetc(c, imapd_in);
return EOF;
}
static void getannotation_response(const char *mboxname,
uint32_t uid
__attribute__((unused)),
const char *entry,
struct attvaluelist *attvalues,
void *rock __attribute__((unused)))
{
int sep = '(';
struct attvaluelist *l;
char ext_mboxname[MAX_MAILBOX_BUFFER];
imapd_namespace.mboxname_toexternal(&imapd_namespace, mboxname,
imapd_userid, ext_mboxname);
prot_printf(imapd_out, "* ANNOTATION ");
prot_printastring(imapd_out, ext_mboxname);
prot_putc(' ', imapd_out);
prot_printstring(imapd_out, entry);
prot_putc(' ', imapd_out);
for (l = attvalues ; l ; l = l->next) {
prot_putc(sep, imapd_out);
sep = ' ';
prot_printstring(imapd_out, l->attrib);
prot_putc(' ', imapd_out);
prot_printmap(imapd_out, l->value.s, l->value.len);
}
prot_printf(imapd_out, ")\r\n");
}
struct annot_fetch_rock
{
strarray_t *entries;
strarray_t *attribs;
annotate_fetch_cb_t callback;
int *sizeptr;
};
static int annot_fetch_cb(annotate_state_t *astate, void *rock)
{
struct annot_fetch_rock *arock = rock;
return annotate_state_fetch(astate, arock->entries, arock->attribs,
arock->callback, NULL, arock->sizeptr);
}
struct annot_store_rock
{
struct entryattlist *entryatts;
};
static int annot_store_cb(annotate_state_t *astate, void *rock)
{
struct annot_store_rock *arock = rock;
return annotate_state_store(astate, arock->entryatts);
}
/*
* Common code used to apply a function to every mailbox which matches
* a mailbox pattern, with an annotate_state_t* set up to point to the
* mailbox.
*/
struct apply_rock {
annotate_state_t *state;
int (*proc)(annotate_state_t *, void *data);
void *data;
char lastname[MAX_MAILBOX_PATH+1];
int sawuser;
unsigned int nseen;
};
static int apply_cb(char *name, int matchlen,
int maycreate __attribute__((unused)), void* rock)
{
struct apply_rock *arock = (struct apply_rock *)rock;
annotate_state_t *state = arock->state;
mbentry_t *mbentry = NULL;
char int_mboxname[MAX_MAILBOX_BUFFER];
char ext_mboxname[MAX_MAILBOX_BUFFER];
int r;
/* Suppress any output of a partial match */
if (name[matchlen] && strncmp(arock->lastname, name, matchlen) == 0)
return 0;
/*
* 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 (arock->sawuser)
return 0;
arock->sawuser = 1;
}
strlcpy(arock->lastname, name, sizeof(arock->lastname));
arock->lastname[matchlen] = '\0';
if (!strncasecmp(arock->lastname, "INBOX", 5)) {
imapd_namespace.mboxname_tointernal(&imapd_namespace, "INBOX",
imapd_userid, int_mboxname);
strlcat(int_mboxname, arock->lastname+5, sizeof(int_mboxname));
}
else
strlcpy(int_mboxname, arock->lastname, sizeof(int_mboxname));
r = 0;
if (mboxlist_lookup(int_mboxname, &mbentry, NULL))
goto out;
r = annotate_state_set_mailbox_mbe(state, mbentry);
if (r)
goto out;
// Store the external name in the mbentry as ext_name, for later reference
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace, int_mboxname, imapd_userid, ext_mboxname);
mbentry->ext_name = xmalloc(sizeof(ext_mboxname)+1);
strlcpy(mbentry->ext_name, ext_mboxname, sizeof(ext_mboxname));
r = arock->proc(state, arock->data);
arock->nseen++;
out:
mboxlist_entry_free(&mbentry);
return r;
}
static int apply_mailbox_pattern(annotate_state_t *state,
const char *pattern,
int (*proc)(annotate_state_t *, void *),
void *data)
{
struct apply_rock arock;
char mboxpat[MAX_MAILBOX_BUFFER];
int r = 0;
memset(&arock, 0, sizeof(arock));
arock.state = state;
arock.proc = proc;
arock.data = data;
/* copy the pattern so we can change hiersep */
strlcpy(mboxpat, pattern, sizeof(mboxpat));
mboxname_hiersep_tointernal(&imapd_namespace, mboxpat,
config_virtdomains ?
strcspn(mboxpat, "@") : 0);
r = imapd_namespace.mboxlist_findall(&imapd_namespace,
mboxpat,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid,
imapd_authstate,
apply_cb, &arock);
if (!r && !arock.nseen)
r = IMAP_MAILBOX_NONEXISTENT;
return r;
}
static int apply_mailbox_array(annotate_state_t *state,
const strarray_t *mboxes,
int (*proc)(annotate_state_t *, void *),
void *rock)
{
int i;
mbentry_t *mbentry = NULL;
char int_mboxname[MAX_MAILBOX_BUFFER];
int r = 0;
for (i = 0 ; i < mboxes->count ; i++) {
imapd_namespace.mboxname_tointernal(&imapd_namespace,
mboxes->data[i],
imapd_userid,
int_mboxname);
r = mboxlist_lookup(int_mboxname, &mbentry, NULL);
if (r)
break;
r = annotate_state_set_mailbox_mbe(state, mbentry);
if (r)
break;
r = proc(state, rock);
if (r)
break;
mboxlist_entry_free(&mbentry);
}
mboxlist_entry_free(&mbentry);
return r;
}
/*
* Perform a GETANNOTATION command
*
* The command has been parsed up to the entries
*/
static void cmd_getannotation(const char *tag, char *mboxpat)
{
int c, r = 0;
strarray_t entries = STRARRAY_INITIALIZER;
strarray_t attribs = STRARRAY_INITIALIZER;
annotate_state_t *astate = NULL;
c = parse_annotate_fetch_data(tag, /*permessage_flag*/0, &entries, &attribs);
if (c == EOF) {
eatline(imapd_in, c);
goto freeargs;
}
/* 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;
}
astate = annotate_state_new();
annotate_state_set_auth(astate,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate);
if (!*mboxpat) {
r = annotate_state_set_server(astate);
if (!r)
r = annotate_state_fetch(astate, &entries, &attribs,
getannotation_response, NULL, 0);
}
else {
struct annot_fetch_rock arock;
arock.entries = &entries;
arock.attribs = &attribs;
arock.callback = getannotation_response;
arock.sizeptr = NULL;
r = apply_mailbox_pattern(astate, mboxpat, annot_fetch_cb, &arock);
}
/* we didn't write anything */
annotate_state_abort(&astate);
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
prot_printf(imapd_out, "%s OK %s\r\n",
tag, error_message(IMAP_OK_COMPLETED));
}
freeargs:
strarray_fini(&entries);
strarray_fini(&attribs);
}
static void getmetadata_response(const char *mboxname,
uint32_t uid
__attribute__((unused)),
const char *entry,
struct attvaluelist *attvalues,
void *rock __attribute__((unused)))
{
int sep = '(';
struct attvaluelist *l;
struct buf mentry = BUF_INITIALIZER;
char ext_mboxname[MAX_MAILBOX_BUFFER];
imapd_namespace.mboxname_toexternal(&imapd_namespace, mboxname,
imapd_userid, ext_mboxname);
prot_printf(imapd_out, "* METADATA ");
prot_printastring(imapd_out, ext_mboxname);
prot_putc(' ', imapd_out);
for (l = attvalues ; l ; l = l->next) {
/* check if it's a value we print... */
buf_reset(&mentry);
if (!strcmp(l->attrib, "value.shared"))
buf_appendcstr(&mentry, "/shared");
else if (!strcmp(l->attrib, "value.priv"))
buf_appendcstr(&mentry, "/private");
else
continue;
buf_appendcstr(&mentry, entry);
buf_cstring(&mentry);
prot_putc(sep, imapd_out);
sep = ' ';
prot_printastring(imapd_out, mentry.s);
prot_putc(' ', imapd_out);
prot_printmap(imapd_out, l->value.s, l->value.len);
}
prot_printf(imapd_out, ")\r\n");
buf_free(&mentry);
}
struct getmetadata_options
{
int maxsize;
int depth;
};
static int parse_getmetadata_options(const strarray_t *sa,
struct getmetadata_options *opts)
{
int i;
int n = 0;
struct getmetadata_options dummy;
if (!opts) opts = &dummy;
for (i = 0 ; i < sa->count ; i+=2) {
const char *option = sa->data[i];
const char *value = sa->data[i+1];
if (!value)
return -1;
if (!strcasecmp(option, "MAXSIZE")) {
char *end = NULL;
opts->maxsize = strtoul(value, &end, 10);
if (!end || *end || end == value)
return -1;
n++;
}
else if (!strcasecmp(option, "DEPTH")) {
if (!strcmp(value, "0"))
opts->depth = 0;
else if (!strcmp(value, "1"))
opts->depth = 1;
else if (!strcasecmp(value, "infinity"))
opts->depth = -1;
else
return -1;
n++;
}
else {
return 0;
}
}
return n;
}
/*
* Perform a GETMETADATA command
*
* The command has been parsed up to the mailbox
*/
static void cmd_getmetadata(const char *tag)
{
int c, r = 0;
strarray_t lists[3] = { STRARRAY_INITIALIZER,
STRARRAY_INITIALIZER,
STRARRAY_INITIALIZER };
int is_list[3] = { 1, 1, 1 };
int nlists = 0;
strarray_t *options = NULL;
strarray_t *mboxes = NULL;
strarray_t *entries = NULL;
strarray_t newe = STRARRAY_INITIALIZER;
strarray_t newa = STRARRAY_INITIALIZER;
struct buf arg1 = BUF_INITIALIZER;
int mbox_is_pattern = 0;
struct getmetadata_options opts;
int basesize = 0;
int *sizeptr = NULL;
int have_shared = 0;
int have_private = 0;
int i;
annotate_state_t *astate = NULL;
opts.maxsize = -1;
opts.depth = 0;
while (nlists < 3)
{
c = parse_metadata_string_or_list(tag, &lists[nlists], &is_list[nlists]);
nlists++;
if (c == '\r' || c == EOF)
break;
}
/* 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;
}
} else {
// Make sure this line is gone
eatline(imapd_in, c);
}
/*
* We have three strings or lists of strings. Now to figure out
* what's what. We have two complicating factors. First, due to
* a erratum in RFC5464 and our earlier misreading of the document,
* we historically supported specifying the options *after* the
* mailbox name. Second, we have for a few months now supported
* a non-standard extension where a list of mailbox names could
* be supplied instead of just a single one. So we have to apply
* some rules. We support the following syntaxes:
*
* --- no options
* mailbox entry
* mailbox (entries)
* (mailboxes) entry
* (mailboxes) (entries)
*
* --- options in the correct place (per the ABNF in RFC5464)
* (options) mailbox entry
* (options) mailbox (entries)
* (options) (mailboxes) entry
* (options) (mailboxes) (entries)
*
* --- options in the wrong place (per the examples in RFC5464)
* mailbox (options) entry
* mailbox (options) (entries)
* (mailboxes) (options) entry
* (mailboxes) (options) (entries)
*/
if (nlists < 2)
goto missingargs;
entries = &lists[nlists-1]; /* entries always last */
if (nlists == 2) {
/* no options */
mboxes = &lists[0];
mbox_is_pattern = !is_list[0];
}
if (nlists == 3) {
/* options, either before or after */
int r0 = (parse_getmetadata_options(&lists[0], NULL) > 0);
int r1 = (parse_getmetadata_options(&lists[1], NULL) > 0);
switch ((r1<<1)|r0) {
case 0:
/* neither are valid options */
goto missingargs;
case 1:
/* (options) (mailboxes) */
options = &lists[0];
mboxes = &lists[1];
mbox_is_pattern = is_list[1];
break;
case 2:
/* (mailboxes) (options) */
mboxes = &lists[0];
mbox_is_pattern = is_list[0];
options = &lists[1];
break;
case 3:
/* both appear like valid options */
prot_printf(imapd_out,
"%s BAD Too many option lists for Getmetadata\r\n",
tag);
eatline(imapd_in, c);
goto freeargs;
}
}
if (options) {
parse_getmetadata_options(options, &opts);
if (opts.maxsize >= 0)
sizeptr = &opts.maxsize;
}
/* we need to rewrite the entries and attribs to match the way that
* the old annotation system works. */
for (i = 0 ; i < entries->count ; i++) {
char *ent = entries->data[i];
char entry[MAX_MAILBOX_NAME];
lcase(ent);
/* there's no way to perfect this - unfortunately - the old style
* syntax doesn't support everything. XXX - will be nice to get
* rid of this... */
if (!strncmp(ent, "/private", 8) &&
(ent[8] == '\0' || ent[8] == '/')) {
xstrncpy(entry, ent + 8, MAX_MAILBOX_NAME);
have_private = 1;
}
else if (!strncmp(ent, "/shared", 7) &&
(ent[7] == '\0' || ent[7] == '/')) {
xstrncpy(entry, ent + 7, MAX_MAILBOX_NAME);
have_shared = 1;
}
else {
prot_printf(imapd_out,
"%s BAD entry must begin with /shared or /private\r\n",
tag);
goto freeargs;
}
strarray_append(&newe, entry);
if (opts.depth == 1) {
strncat(entry, "/%", MAX_MAILBOX_NAME);
strarray_append(&newe, entry);
}
else if (opts.depth == -1) {
strncat(entry, "/*", MAX_MAILBOX_NAME);
strarray_append(&newe, entry);
}
}
if (have_private) strarray_append(&newa, "value.priv");
if (have_shared) strarray_append(&newa, "value.shared");
astate = annotate_state_new();
annotate_state_set_auth(astate,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate);
basesize = opts.maxsize;
if (!mboxes->count || !strcmpsafe(mboxes->data[0], NULL)) {
r = annotate_state_set_server(astate);
if (!r)
r = annotate_state_fetch(astate, &newe, &newa,
getmetadata_response, NULL, sizeptr);
}
else {
struct annot_fetch_rock arock;
arock.entries = &newe;
arock.attribs = &newa;
arock.callback = getmetadata_response;
arock.sizeptr = sizeptr;
if (mbox_is_pattern)
r = apply_mailbox_pattern(astate, mboxes->data[0], annot_fetch_cb, &arock);
else
r = apply_mailbox_array(astate, mboxes, annot_fetch_cb, &arock);
}
/* we didn't write anything */
annotate_state_abort(&astate);
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else if (sizeptr && *sizeptr > basesize) {
prot_printf(imapd_out, "%s OK [METADATA LONGENTRIES %d] %s\r\n",
tag, *sizeptr, error_message(IMAP_OK_COMPLETED));
} else {
prot_printf(imapd_out, "%s OK %s\r\n",
tag, error_message(IMAP_OK_COMPLETED));
}
freeargs:
strarray_fini(&lists[0]);
strarray_fini(&lists[1]);
strarray_fini(&lists[2]);
strarray_fini(&newe);
strarray_fini(&newa);
buf_free(&arg1);
return;
missingargs:
prot_printf(imapd_out, "%s BAD Missing arguments to Getmetadata\r\n", tag);
eatline(imapd_in, c);
goto freeargs;
}
/*
* Perform a SETANNOTATION command
*
* The command has been parsed up to the entry-att list
*/
static void cmd_setannotation(const char *tag, char *mboxpat)
{
int c, r = 0;
struct entryattlist *entryatts = NULL;
annotate_state_t *astate = NULL;
c = parse_annotate_store_data(tag, 0, &entryatts);
if (c == EOF) {
eatline(imapd_in, c);
goto freeargs;
}
/* 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;
}
astate = annotate_state_new();
annotate_state_set_auth(astate, imapd_userisadmin,
imapd_userid, imapd_authstate);
if (!r) {
if (!*mboxpat) {
r = annotate_state_set_server(astate);
if (!r)
r = annotate_state_store(astate, entryatts);
}
else {
struct annot_store_rock arock;
arock.entryatts = entryatts;
r = apply_mailbox_pattern(astate, mboxpat, annot_store_cb, &arock);
}
}
if (!r)
annotate_state_commit(&astate);
else
annotate_state_abort(&astate);
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
freeargs:
if (entryatts) freeentryatts(entryatts);
}
/*
* Perform a SETMETADATA command
*
* The command has been parsed up to the entry-att list
*/
static void cmd_setmetadata(const char *tag, char *mboxpat)
{
int c, r = 0;
struct entryattlist *entryatts = NULL;
annotate_state_t *astate = NULL;
c = parse_metadata_store_data(tag, &entryatts);
if (c == EOF) {
eatline(imapd_in, c);
goto freeargs;
}
/* check for CRLF */
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to Setmetadata\r\n",
tag);
eatline(imapd_in, c);
goto freeargs;
}
astate = annotate_state_new();
annotate_state_set_auth(astate, imapd_userisadmin,
imapd_userid, imapd_authstate);
if (!r) {
if (!*mboxpat) {
r = annotate_state_set_server(astate);
if (!r)
r = annotate_state_store(astate, entryatts);
}
else {
struct annot_store_rock arock;
arock.entryatts = entryatts;
r = apply_mailbox_pattern(astate, mboxpat, annot_store_cb, &arock);
}
}
if (!r)
r = annotate_state_commit(&astate);
else
annotate_state_abort(&astate);
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
} else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
freeargs:
if (entryatts) freeentryatts(entryatts);
return;
}
static void cmd_xrunannotator(const char *tag, const char *sequence,
int usinguid)
{
const char *cmd = usinguid ? "UID Xrunannotator" : "Xrunannotator";
clock_t start = clock();
char mytime[100];
int c, r = 0;
if (backend_current) {
/* remote mailbox */
prot_printf(backend_current->out, "%s %s %s ", tag, cmd, sequence);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
return;
}
/* local mailbox */
/* we're expecting no more arguments */
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 %s\r\n", tag, cmd);
eatline(imapd_in, c);
return;
}
r = index_run_annotator(imapd_index, sequence, usinguid,
&imapd_namespace, imapd_userisadmin);
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
if (r)
prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag,
error_message(r), mytime);
else
prot_printf(imapd_out, "%s OK %s (%s sec)\r\n", tag,
error_message(IMAP_OK_COMPLETED), mytime);
}
static void cmd_xwarmup(const char *tag)
{
const char *cmd = "Xwarmup";
clock_t start = clock();
char mytime[100];
struct buf arg = BUF_INITIALIZER;
int warmup_flags = 0;
/* We deal with the mboxlist API instead of the index_state API or
* mailbox API to avoid the overhead of index_open(), which will
* block while reading all the cyrus.index...we want to be
* non-blocking */
struct mboxlist_entry *mbentry = NULL;
int myrights;
int c, r = 0;
char mboxname[MAX_MAILBOX_BUFFER];
/* parse arguments: expect <mboxname> '('<warmup-items>')' */
c = getastring(imapd_in, imapd_out, &arg);
if (c != ' ') {
syntax_error:
prot_printf(imapd_out, "%s BAD syntax error in %s\r\n", tag, cmd);
goto out_noprint;
}
r = imapd_namespace.mboxname_tointernal(&imapd_namespace, arg.s,
imapd_userid, mboxname);
if (r) {
prot_printf(imapd_out, "%s BAD Invalid mboxname in %s\r\n", tag, cmd);
goto out_noprint;
}
r = mboxlist_lookup(mboxname, &mbentry, NULL);
if (r) goto out;
/* Do a permissions check to avoid server DoS opportunity. But we
* only need read permission to warmup a mailbox. Also, be careful
* to avoid telling the client about the existance of mailboxes to
* which he doesn't have LOOKUP rights. */
r = IMAP_PERMISSION_DENIED;
myrights = (mbentry->acl ? cyrus_acl_myrights(imapd_authstate, mbentry->acl) : 0);
if (imapd_userisadmin)
r = 0;
else if (!(myrights & ACL_LOOKUP))
r = IMAP_MAILBOX_NONEXISTENT;
else if (myrights & ACL_READ)
r = 0;
if (r) goto out;
if (mbentry->mbtype & MBTYPE_REMOTE) {
/* remote mailbox */
struct backend *be;
be = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!be) {
r = IMAP_SERVER_UNAVAILABLE;
goto out;
}
prot_printf(be->out, "%s %s %s ", tag, cmd, arg.s);
if (!pipe_command(backend_current, 65536)) {
pipe_including_tag(backend_current, tag, 0);
}
goto out;
}
/* local mailbox */
/* parse the arguments after the mailbox */
c = prot_getc(imapd_in);
if (c != '(') goto syntax_error;
for (;;) {
c = getword(imapd_in, &arg);
if (arg.len) {
if (!strcasecmp(arg.s, "index"))
warmup_flags |= WARMUP_INDEX;
else if (!strcasecmp(arg.s, "conversations"))
warmup_flags |= WARMUP_CONVERSATIONS;
else if (!strcasecmp(arg.s, "annotations"))
warmup_flags |= WARMUP_ANNOTATIONS;
else if (!strcasecmp(arg.s, "folderstatus"))
warmup_flags |= WARMUP_FOLDERSTATUS;
else if (!strcasecmp(arg.s, "all"))
warmup_flags |= WARMUP_ALL;
else
goto syntax_error;
}
if (c == ')')
break;
if (c != ' ') goto syntax_error;
}
/* we're expecting no more arguments */
c = prot_getc(imapd_in);
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') goto syntax_error;
r = index_warmup(mbentry, warmup_flags);
out:
snprintf(mytime, sizeof(mytime), "%2.3f",
(clock() - start) / (double) CLOCKS_PER_SEC);
if (r)
prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag,
error_message(r), mytime);
else
prot_printf(imapd_out, "%s OK %s (%s sec)\r\n", tag,
error_message(IMAP_OK_COMPLETED), mytime);
out_noprint:
if (r)
eatline(imapd_in, c);
mboxlist_entry_free(&mbentry);
buf_free(&arg);
}
static void free_snippetargs(struct snippetargs **sap)
{
while (*sap) {
struct snippetargs *sa = *sap;
*sap = sa->next;
free(sa->mboxname);
free(sa->uids.data);
free(sa);
}
}
static int get_snippetargs(struct snippetargs **sap)
{
int r;
int c;
struct snippetargs **prevp = sap;
struct snippetargs *sa = NULL;
struct buf arg = BUF_INITIALIZER;
uint32_t uid;
char mboxname[MAX_MAILBOX_NAME];
c = prot_getc(imapd_in);
if (c != '(') goto syntax_error;
for (;;) {
c = prot_getc(imapd_in);
if (c == ')') break;
if (c != '(') goto syntax_error;
c = getastring(imapd_in, imapd_out, &arg);
if (c != ' ') goto syntax_error;
r = imapd_namespace.mboxname_tointernal(&imapd_namespace, buf_cstring(&arg),
imapd_userid, mboxname);
if (r) goto out;
/* allocate a new snippetargs */
sa = xzmalloc(sizeof(struct snippetargs));
sa->mboxname = xstrdup(mboxname);
/* append to the list */
*prevp = sa;
prevp = &sa->next;
c = getuint32(imapd_in, &sa->uidvalidity);
if (c != ' ') goto syntax_error;
c = prot_getc(imapd_in);
if (c != '(') break;
for (;;) {
c = getuint32(imapd_in, &uid);
if (c != ' ' && c != ')') goto syntax_error;
if (sa->uids.count + 1 > sa->uids.alloc) {
sa->uids.alloc += 64;
sa->uids.data = xrealloc(sa->uids.data,
sizeof(uint32_t) * sa->uids.alloc);
}
sa->uids.data[sa->uids.count++] = uid;
if (c == ')') break;
}
c = prot_getc(imapd_in);
if (c != ')') goto syntax_error;
}
c = prot_getc(imapd_in);
if (c != ' ') goto syntax_error;
out:
buf_free(&arg);
return c;
syntax_error:
free_snippetargs(sap);
c = EOF;
goto out;
}
static void cmd_dump(char *tag, char *name, int uid_start)
{
int r = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
struct mailbox *mailbox = NULL;
/* 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 = mailbox_open_irl(mailboxname, &mailbox);
if (!r) r = dump_mailbox(tag, mailbox, uid_start, MAILBOX_MINOR_VERSION,
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));
}
if (mailbox) mailbox_close(&mailbox);
}
static void cmd_undump(char *tag, char *name)
{
int r = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
/* 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);
}
if (r == IMAP_MAILBOX_MOVED) return;
if (!r) {
/* XXX - interface change to match dump? */
r = undump_mailbox(mailboxname, imapd_in, imapd_out, imapd_authstate);
}
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) == 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, const char *tag)
{
char buf[4096];
char *str = (char *) buf;
while(1) {
if (!prot_fgets(str, sizeof(buf), p)) {
return IMAP_SERVER_UNAVAILABLE;
}
if (!strncmp(str, tag, strlen(tag))) {
str += strlen(tag);
if(!*str) {
/* We got a tag, but no response */
return IMAP_SERVER_UNAVAILABLE;
}
str++;
if (!strncasecmp(str, "OK ", 3)) { return 0; }
if (!strncasecmp(str, "NO ", 3)) { return IMAP_REMOTE_DENIED; }
return IMAP_SERVER_UNAVAILABLE; /* huh? */
}
/* skip this line, we don't really care */
}
}
/* given 2 protstreams and a mailbox, gets the acl and then wipes it */
static int trashacl(struct protstream *pin, struct protstream *pout,
char *mailbox)
{
int i=0, j=0;
char tagbuf[128];
int c; /* getword() returns an int */
struct buf cmd, tmp, user;
int r = 0;
memset(&cmd, 0, sizeof(struct buf));
memset(&tmp, 0, sizeof(struct buf));
memset(&user, 0, sizeof(struct buf));
prot_printf(pout, "ACL0 GETACL {" SIZE_T_FMT "+}\r\n%s\r\n",
strlen(mailbox), mailbox);
while(1) {
c = prot_getc(pin);
if (c != '*') {
prot_ungetc(c, pin);
r = getresult(pin, "ACL0");
break;
}
c = prot_getc(pin); /* skip SP */
c = getword(pin, &cmd);
if (c == EOF) {
r = IMAP_SERVER_UNAVAILABLE;
break;
}
if (!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') break; /* end of * ACL */
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 {" SIZE_T_FMT "+}\r\n%s"
" {" SIZE_T_FMT "+}\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 */
}
}
else {
/* skip this line, we don't really care */
eatline(pin, c);
}
}
cleanup:
/* Now cleanup after all the DELETEACL commands */
if(!r) {
while(j < i) {
snprintf(tagbuf, sizeof(tagbuf), "ACL%d", ++j);
r = getresult(pin, tagbuf);
if (r) break;
}
}
if(r) eatline(pin, c);
buf_free(&user);
buf_free(&tmp);
buf_free(&cmd);
return r;
}
static int dumpacl(struct protstream *pin, struct protstream *pout,
const char *mboxname, const char *acl_in)
{
int r = 0;
char tag[128];
int tagnum = 1;
char *rights, *nextid;
char *acl_safe = acl_in ? xstrdup(acl_in) : NULL;
char *acl = acl_safe;
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 {" SIZE_T_FMT "+}\r\n%s"
" {" SIZE_T_FMT "+}\r\n%s {" SIZE_T_FMT "+}\r\n%s\r\n",
tag,
strlen(mboxname), mboxname,
strlen(acl), acl,
strlen(rights), rights);
r = getresult(pin, tag);
if (r) break;
acl = nextid;
}
if(acl_safe) free(acl_safe);
return r;
}
enum {
XFER_DEACTIVATED = 1,
XFER_REMOTE_CREATED,
XFER_LOCAL_MOVING,
XFER_UNDUMPED,
};
struct xfer_item {
mbentry_t *mbentry;
char extname[MAX_MAILBOX_NAME];
struct mailbox *mailbox;
int state;
struct xfer_item *next;
};
struct xfer_header {
mupdate_handle *mupdate_h;
struct backend *be;
int remoteversion;
char *toserver;
char *topart;
struct seen *seendb;
struct xfer_item *items;
};
static int xfer_mupdate(struct xfer_header *xfer, int isactivate,
const char *mboxname, const char *part,
const char *servername, const char *acl)
{
char buf[MAX_PARTITION_LEN+HOSTNAME_SIZE+2];
int retry = 0;
int r = 0;
/* no mupdate handle */
if (!xfer->mupdate_h)
return 0;
snprintf(buf, sizeof(buf), "%s!%s", servername, part);
retry:
/* make the change */
if (isactivate)
r = mupdate_activate(xfer->mupdate_h, mboxname, buf, acl);
else
r = mupdate_deactivate(xfer->mupdate_h, mboxname, buf);
if (r && !retry) {
syslog(LOG_INFO, "MUPDATE: lost connection, retrying");
mupdate_disconnect(&xfer->mupdate_h);
r = mupdate_connect(config_mupdate_server, NULL,
&xfer->mupdate_h, NULL);
retry = 1;
goto retry;
}
return r;
}
/* nothing you can do about failures, just try to clean up */
static void xfer_done(struct xfer_header **xferptr)
{
struct xfer_header *xfer = *xferptr;
struct xfer_item *item, *next;
/* remove items */
item = xfer->items;
while (item) {
next = item->next;
mboxlist_entry_free(&item->mbentry);
free(item);
item = next;
}
/* disconnect */
if (xfer->mupdate_h) mupdate_disconnect(&xfer->mupdate_h);
if (xfer->be) backend_disconnect(xfer->be);
free(xfer->toserver);
free(xfer->topart);
seen_close(&xfer->seendb);
free(xfer);
*xferptr = NULL;
}
static int backend_version(struct backend *be)
{
const char *minor;
/* master branch? */
if (strstr(be->banner, "git2.5")) {
return 13;
}
/* check for current version */
if (strstr(be->banner, "v2.4.") || strstr(be->banner, "git2.4.")) {
return 12;
}
minor = strstr(be->banner, "v2.3.");
if (!minor) return 6;
/* at least version 2.3.10 */
if (minor[1] != ' ') {
return 10;
}
/* single digit version, figure out which */
switch (minor[0]) {
case '0':
case '1':
case '2':
case '3':
return 7;
break;
case '4':
case '5':
case '6':
return 8;
break;
case '7':
case '8':
case '9':
return 9;
break;
}
/* fallthrough, shouldn't happen */
return 6;
}
static int xfer_init(const char *toserver, const char *topart,
struct xfer_header **xferptr)
{
struct xfer_header *xfer = xzmalloc(sizeof(struct xfer_header));
int r;
/* Get a connection to the remote backend */
xfer->be = backend_connect(NULL, toserver, &imap_protocol,
"", NULL, NULL, -1);
if (!xfer->be) {
r = IMAP_SERVER_UNAVAILABLE;
goto fail;
}
xfer->remoteversion = backend_version(xfer->be);
xfer->toserver = xstrdup(toserver);
xfer->topart = xstrdup(topart);
xfer->seendb = NULL;
/* connect to mupdate server if configured */
if (config_mupdate_server) {
r = mupdate_connect(config_mupdate_server, NULL,
&xfer->mupdate_h, NULL);
if (r) goto fail;
}
*xferptr = xfer;
return 0;
fail:
xfer_done(&xfer);
return r;
}
static void xfer_addmbox(struct xfer_header *xfer,
mbentry_t *mbentry)
{
struct xfer_item *item = xzmalloc(sizeof(struct xfer_item));
item->mbentry = mbentry;
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace, mbentry->name,
imapd_userid, item->extname);
item->mailbox = NULL;
item->state = 0;
/* and link on to the list (reverse order) */
item->next = xfer->items;
xfer->items = item;
}
static int xfer_localcreate(struct xfer_header *xfer)
{
struct xfer_item *item;
int r;
for (item = xfer->items; item; item = item->next) {
if (xfer->topart) {
/* need to send partition as an atom */
prot_printf(xfer->be->out, "LC1 LOCALCREATE {" SIZE_T_FMT "+}\r\n%s %s\r\n",
strlen(item->extname), item->extname, xfer->topart);
} else {
prot_printf(xfer->be->out, "LC1 LOCALCREATE {" SIZE_T_FMT "+}\r\n%s\r\n",
strlen(item->extname), item->extname);
}
r = getresult(xfer->be->in, "LC1");
if (r) {
syslog(LOG_ERR, "Could not move mailbox: %s, LOCALCREATE failed",
item->mbentry->name);
return r;
}
item->state = XFER_REMOTE_CREATED;
}
return 0;
}
static int xfer_backport_seen_item(struct xfer_item *item,
struct seen *seendb)
{
struct mailbox *mailbox = item->mailbox;
struct seqset *outlist = NULL;
struct index_record record;
struct seendata sd = SEENDATA_INITIALIZER;
unsigned recno;
int r;
outlist = seqset_init(mailbox->i.last_uid, SEQ_MERGE);
for (recno = 1; recno < mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue;
if (record.system_flags & FLAG_EXPUNGED)
continue;
if (record.system_flags & FLAG_SEEN)
seqset_add(outlist, record.uid, 1);
else
seqset_add(outlist, record.uid, 0);
}
sd.lastread = mailbox->i.recenttime;
sd.lastuid = mailbox->i.recentuid;
sd.lastchange = mailbox->i.last_appenddate;
sd.seenuids = seqset_cstring(outlist);
if (!sd.seenuids) sd.seenuids = xstrdup("");
r = seen_write(seendb, mailbox->uniqueid, &sd);
seen_freedata(&sd);
seqset_free(outlist);
return r;
}
static int xfer_deactivate(struct xfer_header *xfer)
{
struct xfer_item *item;
int r;
/* Step 3: mupdate.DEACTIVATE(mailbox, newserver) */
for (item = xfer->items; item; item = item->next) {
r = xfer_mupdate(xfer, 0, item->mbentry->name, item->mbentry->partition,
config_servername, item->mbentry->acl);
if (r) {
syslog(LOG_ERR,
"Could not move mailbox: %s, MUPDATE DEACTIVATE failed",
item->mbentry->name);
return r;
}
item->state = XFER_DEACTIVATED;
}
return 0;
}
static int xfer_undump(struct xfer_header *xfer)
{
struct xfer_item *item;
int r;
mbentry_t *newentry;
struct mailbox *mailbox = NULL;
for (item = xfer->items; item; item = item->next) {
r = mailbox_open_irl(item->mbentry->name, &mailbox);
if (r) {
syslog(LOG_ERR,
"Failed to open mailbox %s for dump_mailbox() %s",
item->mbentry->name, error_message(r));
return r;
}
/* Step 3.5: Set mailbox as MOVING on local server */
/* XXX - this code is awful... need a sane way to manage mbentries */
newentry = mboxlist_entry_create();
newentry->name = xstrdupnull(item->mbentry->name);
newentry->acl = xstrdupnull(item->mbentry->acl);
newentry->server = xstrdupnull(xfer->toserver);
newentry->partition = xstrdupnull(xfer->topart);
newentry->mbtype = item->mbentry->mbtype|MBTYPE_MOVING;
r = mboxlist_update(newentry, 1);
mboxlist_entry_free(&newentry);
if (r) {
syslog(LOG_ERR,
"Could not move mailbox: %s, mboxlist_update() failed %s",
item->mbentry->name, error_message(r));
}
else item->state = XFER_LOCAL_MOVING;
if (!r && xfer->seendb) {
/* Backport the user's seendb on-the-fly */
item->mailbox = mailbox;
r = xfer_backport_seen_item(item, xfer->seendb);
/* Need to close seendb before dumping Inbox (last item) */
if (!item->next) seen_close(&xfer->seendb);
}
/* Step 4: Dump local -> remote */
if (!r) {
prot_printf(xfer->be->out, "D01 UNDUMP {" SIZE_T_FMT "+}\r\n%s ",
strlen(item->extname), item->extname);
r = dump_mailbox(NULL, mailbox, 0, xfer->remoteversion,
xfer->be->in, xfer->be->out, imapd_authstate);
if (r) {
syslog(LOG_ERR,
"Could not move mailbox: %s, dump_mailbox() failed %s",
item->mbentry->name, error_message(r));
}
}
mailbox_close(&mailbox);
if (r) return r;
r = getresult(xfer->be->in, "D01");
if (r) {
syslog(LOG_ERR, "Could not move mailbox: %s, UNDUMP failed %s",
item->mbentry->name, error_message(r));
return r;
}
/* Step 5: Set ACL on remote */
r = trashacl(xfer->be->in, xfer->be->out,
item->extname);
if (r) {
syslog(LOG_ERR, "Could not clear remote acl on %s",
item->mbentry->name);
return r;
}
r = dumpacl(xfer->be->in, xfer->be->out,
item->extname, item->mbentry->acl);
if (r) {
syslog(LOG_ERR, "Could not set remote acl on %s",
item->mbentry->name);
return r;
}
item->state = XFER_UNDUMPED;
}
return 0;
}
static int xfer_reactivate(struct xfer_header *xfer)
{
struct xfer_item *item;
int r;
if (!xfer->mupdate_h) return 0;
/* 6.5) Kick remote server to correct mupdate entry */
for (item = xfer->items; item; item = item->next) {
prot_printf(xfer->be->out, "MP1 MUPDATEPUSH {" SIZE_T_FMT "+}\r\n%s\r\n",
strlen(item->extname), item->extname);
r = getresult(xfer->be->in, "MP1");
if (r) {
syslog(LOG_ERR, "MUPDATE: can't activate mailbox entry '%s'",
item->mbentry->name);
return r;
}
}
return 0;
}
static int xfer_delete(struct xfer_header *xfer)
{
mbentry_t *newentry = NULL;
struct xfer_item *item;
int r;
/* 7) local delete of mailbox
* & remove local "remote" mailboxlist entry */
for (item = xfer->items; item; item = item->next) {
/* Set mailbox as DELETED on local server
(need to also reset to local partition,
otherwise mailbox can not be opened for deletion) */
/* XXX - this code is awful... need a sane way to manage mbentries */
newentry = mboxlist_entry_create();
newentry->name = xstrdupnull(item->mbentry->name);
newentry->acl = xstrdupnull(item->mbentry->acl);
newentry->server = xstrdupnull(item->mbentry->server);
newentry->partition = xstrdupnull(item->mbentry->partition);
newentry->mbtype = item->mbentry->mbtype|MBTYPE_DELETED;
r = mboxlist_update(newentry, 1);
mboxlist_entry_free(&newentry);
if (r) {
syslog(LOG_ERR,
"Could not move mailbox: %s, mboxlist_update failed (%s)",
item->mbentry->name, error_message(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 */
/* On a unified system, the subsequent MUPDATE PUSH on the remote
should repopulate the local mboxlist entry */
r = mboxlist_deletemailbox(item->mbentry->name,
imapd_userisadmin || imapd_userisproxyadmin,
imapd_userid, imapd_authstate, NULL, 0, 1, 0);
if (r) {
syslog(LOG_ERR,
"Could not delete local mailbox during move of %s",
item->mbentry->name);
/* can't abort now! */
}
}
return 0;
}
static void xfer_recover(struct xfer_header *xfer)
{
mbentry_t *newentry = NULL;
struct xfer_item *item;
int r;
/* Backout any changes - we stop on first untouched mailbox */
for (item = xfer->items; item && item->state; item = item->next) {
switch (item->state) {
case XFER_UNDUMPED:
case XFER_LOCAL_MOVING:
/* Unset mailbox as MOVING on local server */
/* XXX - this code is awful... need a sane way to manage mbentries */
newentry = mboxlist_entry_create();
newentry->name = xstrdupnull(item->mbentry->name);
newentry->acl = xstrdupnull(item->mbentry->acl);
newentry->server = xstrdupnull(item->mbentry->server);
newentry->partition = xstrdupnull(item->mbentry->partition);
newentry->mbtype = item->mbentry->mbtype;
r = mboxlist_update(newentry, 1);
mboxlist_entry_free(&newentry);
if (r) {
syslog(LOG_ERR,
"Could not back out MOVING flag during move of %s (%s)",
item->mbentry->name, error_message(r));
}
case XFER_REMOTE_CREATED:
/* Delete remote mailbox */
prot_printf(xfer->be->out,
"LD1 LOCALDELETE {" SIZE_T_FMT "+}\r\n%s\r\n",
strlen(item->extname), item->extname);
r = getresult(xfer->be->in, "LD1");
if (r) {
syslog(LOG_ERR,
"Could not back out remote mailbox during move of %s (%s)",
item->mbentry->name, error_message(r));
}
case XFER_DEACTIVATED:
/* Tell murder it's back here and active */
r = xfer_mupdate(xfer, 1, item->mbentry->name, item->mbentry->partition,
config_servername, item->mbentry->acl);
if (r) {
syslog(LOG_ERR,
"Could not back out mupdate during move of %s (%s)",
item->mbentry->name, error_message(r));
}
}
}
}
static int xfer_user_cb(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct xfer_header *xfer = (struct xfer_header *)rock;
mbentry_t *mbentry = NULL;
int r;
/* NOTE: NOT mlookup() because we don't want to issue a referral */
r = mboxlist_lookup(name, &mbentry, NULL);
if (r) return r;
/* Skip remote mailbox */
if (mbentry->mbtype & MBTYPE_REMOTE)
mboxlist_entry_free(&mbentry);
else
xfer_addmbox(xfer, mbentry);
return 0;
}
static int do_xfer(struct xfer_header *xfer)
{
int r;
r = xfer_deactivate(xfer);
if (!r) r = xfer_localcreate(xfer);
if (!r) r = xfer_undump(xfer);
if (r) {
/* Something failed, revert back to local server */
xfer_recover(xfer);
return r;
}
/* Successful dump of all mailboxes to remote server.
* Remove them locally and activate them on remote.
* Note - we don't report errors if this fails! */
xfer_delete(xfer);
xfer_reactivate(xfer);
return 0;
}
static int xfer_setquotaroot(struct xfer_header *xfer, const char *mboxname)
{
struct quota q;
int r;
char extname[MAX_MAILBOX_NAME];
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace, mboxname,
imapd_userid, extname);
quota_init(&q, mboxname);
r = quota_read(&q, NULL, 0);
if (r == IMAP_QUOTAROOT_NONEXISTENT) return 0;
if (r) return r;
/* note use of + to force the setting of a nonexistant
* quotaroot */
prot_printf(xfer->be->out, "Q01 SETQUOTA {" SIZE_T_FMT "+}\r\n+%s ",
strlen(extname)+1, extname);
print_quota_limits(xfer->be->out, &q);
prot_printf(xfer->be->out, "\r\n");
quota_free(&q);
r = getresult(xfer->be->in, "Q01");
if (r) syslog(LOG_ERR,
"Could not move mailbox: %s, " \
"failed setting initial quota root\r\n",
mboxname);
return r;
}
static int xfer_addsubmailboxes(struct xfer_header *xfer, const char *mboxname)
{
char buf[MAX_MAILBOX_NAME];
int r;
snprintf(buf, sizeof(buf), "%s.*", mboxname);
r = mboxlist_findall(NULL, buf, 1, imapd_userid,
imapd_authstate, xfer_user_cb,
xfer);
if (r) return r;
/* also move DELETED maiboxes for this user */
if (mboxlist_delayed_delete_isenabled()) {
snprintf(buf, sizeof(buf), "%s.%s.*",
config_getstring(IMAPOPT_DELETEDPREFIX), mboxname);
r = mboxlist_findall(NULL, buf, 1, imapd_userid,
imapd_authstate, xfer_user_cb,
xfer);
}
return r;
}
static void cmd_xfer(const char *tag, const char *name,
const char *toserver, const char *topart)
{
int r = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
int moving_user = 0;
char *p, *mbox = mailboxname;
mbentry_t *mbentry = NULL;
struct xfer_header *xfer = NULL;
/* administrators only please */
/* however, proxys can do this, if their authzid is an admin */
if (!imapd_userisadmin && !imapd_userisproxyadmin) {
r = IMAP_PERMISSION_DENIED;
goto done;
}
if (!strcmp(toserver, config_servername)) {
r = IMAP_BAD_SERVER;
goto done;
}
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
name,
imapd_userid,
mailboxname);
if (r) goto done;
/* NOTE: Since XFER can only be used by an admin, and we always connect
* to the destination backend as an admin, we take advantage of the fact
* that admins *always* use a consistent mailbox naming scheme.
* So, 'name' should be used in any command we send to a backend, and
* 'mailboxname' is the internal name to be used for mupdate and findall.
*/
if (config_virtdomains && (p = strchr(mailboxname, '!'))) {
/* pointer to mailbox w/o domain prefix */
mbox = p + 1;
}
if (!strncmp(mbox, "user.", 5) && !strchr(mbox+5, '.')) {
if ((strlen(mbox+5) == (strlen(imapd_userid) - (mbox - mailboxname))) &&
!strncmp(mbox+5, imapd_userid, strlen(mbox+5))) {
/* don't move your own inbox, that could be troublesome */
r = IMAP_MAILBOX_NOTSUPPORTED;
} else if (!config_getswitch(IMAPOPT_ALLOWUSERMOVES)) {
/* not configured to allow user moves */
r = IMAP_MAILBOX_NOTSUPPORTED;
} else {
moving_user = 1;
}
}
if (r) goto done;
r = mboxlist_lookup(mailboxname, &mbentry, NULL);
if (r) goto done;
if (!topart) topart = mbentry->partition;
r = xfer_init(toserver, topart, &xfer);
if (r) goto done;
/* we're always moving this mailbox */
xfer_addmbox(xfer, mbentry);
mbentry = NULL;
/* if we are not moving a user, just move the one mailbox */
if (!moving_user) {
/* is the selected mailbox the one we're moving? */
if (!strcmpsafe(mailboxname, index_mboxname(imapd_index))) {
r = IMAP_MAILBOX_LOCKED;
goto done;
}
r = do_xfer(xfer);
} else {
const char *userid = mboxname_to_userid(mailboxname);
/* is the selected mailbox in the namespace we're moving? */
if (!strncmpsafe(mailboxname, index_mboxname(imapd_index),
strlen(mailboxname))) {
r = IMAP_MAILBOX_LOCKED;
goto done;
}
/* set the quotaroot if needed */
r = xfer_setquotaroot(xfer, mailboxname);
if (r) goto done;
/* add all submailboxes to the move list as well */
r = xfer_addsubmailboxes(xfer, mailboxname);
if (r) goto done;
/* backport the seen file if needed */
if (xfer->remoteversion < 12) {
r = seen_open(userid, SEEN_CREATE, &xfer->seendb);
if (r) goto done;
}
/* NOTE: mailboxes were added in reverse, so the inbox is
* done last */
r = do_xfer(xfer);
if (r) goto done;
/* this was a successful user delete, and we need to delete
certain user meta-data (but not seen state!) */
user_deletedata(userid, 0);
}
done:
if (xfer) xfer_done(&xfer);
imapd_check(NULL, 0);
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag,
error_message(r));
} else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
return;
}
#define SORTGROWSIZE 10
/*
* Parse sort criteria
*/
static int getsortcriteria(char *tag, struct sortcrit **sortcrit)
{
int c;
static struct buf criteria;
int nsort, n;
int hasconv = config_getswitch(IMAPOPT_CONVERSATIONS);
*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, "displayfrom"))
(*sortcrit)[n].key = SORT_DISPLAYFROM;
else if (!strcmp(criteria.s, "displayto"))
(*sortcrit)[n].key = SORT_DISPLAYTO;
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;
else if (!strcmp(criteria.s, "annotation")) {
const char *userid = NULL;
(*sortcrit)[n].key = SORT_ANNOTATION;
if (c != ' ') goto missingarg;
c = getastring(imapd_in, imapd_out, &criteria);
if (c != ' ') goto missingarg;
(*sortcrit)[n].args.annot.entry = xstrdup(criteria.s);
c = getastring(imapd_in, imapd_out, &criteria);
if (c == EOF) goto missingarg;
if (!strcmp(criteria.s, "value.shared"))
userid = "";
else if (!strcmp(criteria.s, "value.priv"))
userid = imapd_userid;
else
goto missingarg;
(*sortcrit)[n].args.annot.userid = xstrdup(userid);
}
else if (!strcmp(criteria.s, "modseq"))
(*sortcrit)[n].key = SORT_MODSEQ;
else if (!strcmp(criteria.s, "uid"))
(*sortcrit)[n].key = SORT_UID;
else if (!strcmp(criteria.s, "hasflag")) {
(*sortcrit)[n].key = SORT_HASFLAG;
if (c != ' ') goto missingarg;
c = getastring(imapd_in, imapd_out, &criteria);
if (c == EOF) goto missingarg;
(*sortcrit)[n].args.flag.name = xstrdup(criteria.s);
}
else if (hasconv && !strcmp(criteria.s, "convmodseq"))
(*sortcrit)[n].key = SORT_CONVMODSEQ;
else if (hasconv && !strcmp(criteria.s, "convexists"))
(*sortcrit)[n].key = SORT_CONVEXISTS;
else if (hasconv && !strcmp(criteria.s, "convsize"))
(*sortcrit)[n].key = SORT_CONVSIZE;
else if (hasconv && !strcmp(criteria.s, "hasconvflag")) {
(*sortcrit)[n].key = SORT_HASCONVFLAG;
if (c != ' ') goto missingarg;
c = getastring(imapd_in, imapd_out, &criteria);
if (c == EOF) goto missingarg;
(*sortcrit)[n].args.flag.name = xstrdup(criteria.s);
}
else if (!strcmp(criteria.s, "folder"))
(*sortcrit)[n].key = SORT_FOLDER;
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;
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;
}
-static char *sortcrit_as_string(const struct sortcrit *sortcrit)
-{
- struct buf b = BUF_INITIALIZER;
- static const char * const key_names[] = {
- "SEQUENCE", "ARRIVAL", "CC", "DATE",
- "DISPLAYFROM", "DISPLAYTO", "FROM",
- "SIZE", "SUBJECT", "TO", "ANNOTATION",
- "MODSEQ", "UID"
- };
-
- for ( ; sortcrit->key ; sortcrit++) {
- if (b.len)
- buf_putc(&b, ' ');
- if (sortcrit->flags & SORT_REVERSE)
- buf_appendcstr(&b, "REVERSE ");
-
- if (sortcrit->key < VECTOR_SIZE(key_names))
- buf_appendcstr(&b, key_names[sortcrit->key]);
- else
- buf_printf(&b, "UNKNOWN%u", sortcrit->key);
-
- switch (sortcrit->key) {
- case SORT_ANNOTATION:
- buf_printf(&b, " \"%s\" \"%s\"",
- sortcrit->args.annot.entry,
- *sortcrit->args.annot.userid ?
- "value.priv" : "value.shared");
- break;
- }
- }
- return buf_release(&b);
-}
-
static int parse_windowargs(const char *tag,
struct windowargs **wa,
int updates)
{
struct windowargs windowargs;
struct buf arg = BUF_INITIALIZER;
struct buf ext_folder = BUF_INITIALIZER;
int c;
memset(&windowargs, 0, sizeof(windowargs));
c = prot_getc(imapd_in);
if (c == EOF)
goto out;
if (c != '(') {
/* no window args at all */
prot_ungetc(c, imapd_in);
goto out;
}
for (;;)
{
c = prot_getc(imapd_in);
if (c == EOF)
goto out;
if (c == ')')
break; /* end of window args */
prot_ungetc(c, imapd_in);
c = getword(imapd_in, &arg);
if (!arg.len)
goto syntax_error;
if (!strcasecmp(arg.s, "CONVERSATIONS"))
windowargs.conversations = 1;
else if (!strcasecmp(arg.s, "POSITION")) {
if (updates)
goto syntax_error;
if (c != ' ')
goto syntax_error;
c = prot_getc(imapd_in);
if (c != '(')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.position);
if (c != ' ')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.limit);
if (c != ')')
goto syntax_error;
c = prot_getc(imapd_in);
if (windowargs.position == 0)
goto syntax_error;
}
else if (!strcasecmp(arg.s, "ANCHOR")) {
if (updates)
goto syntax_error;
if (c != ' ')
goto syntax_error;
c = prot_getc(imapd_in);
if (c != '(')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.anchor);
if (c != ' ')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.offset);
if (c != ' ')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.limit);
if (c != ')')
goto syntax_error;
c = prot_getc(imapd_in);
if (windowargs.anchor == 0)
goto syntax_error;
}
else if (!strcasecmp(arg.s, "MULTIANCHOR")) {
if (updates)
goto syntax_error;
if (c != ' ')
goto syntax_error;
c = prot_getc(imapd_in);
if (c != '(')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.anchor);
if (c != ' ')
goto syntax_error;
c = getastring(imapd_in, imapd_out, &ext_folder);
if (c != ' ')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.offset);
if (c != ' ')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.limit);
if (c != ')')
goto syntax_error;
c = prot_getc(imapd_in);
if (windowargs.anchor == 0)
goto syntax_error;
}
else if (!strcasecmp(arg.s, "CHANGEDSINCE")) {
if (!updates)
goto syntax_error;
windowargs.changedsince = 1;
if (c != ' ')
goto syntax_error;
c = prot_getc(imapd_in);
if (c != '(')
goto syntax_error;
c = getmodseq(imapd_in, &windowargs.modseq);
if (c != ' ')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.uidnext);
if (c != ')')
goto syntax_error;
c = prot_getc(imapd_in);
} else if (!strcasecmp(arg.s, "UPTO")) {
if (!updates)
goto syntax_error;
if (c != ' ')
goto syntax_error;
c = prot_getc(imapd_in);
if (c != '(')
goto syntax_error;
c = getuint32(imapd_in, &windowargs.upto);
if (c != ')')
goto syntax_error;
c = prot_getc(imapd_in);
if (windowargs.upto == 0)
goto syntax_error;
}
else
goto syntax_error;
if (c == ')')
break;
if (c != ' ')
goto syntax_error;
}
c = prot_getc(imapd_in);
if (c != ' ')
goto syntax_error;
out:
/* these two are mutually exclusive */
if (windowargs.anchor && windowargs.position)
goto syntax_error;
/* changedsince is mandatory for XCONVUPDATES
* and illegal for XCONVSORT */
if (!!updates != windowargs.changedsince)
goto syntax_error;
if (ext_folder.len) {
int r;
char int_mboxname[MAX_MAILBOX_PATH+1];
r = imapd_namespace.mboxname_tointernal(&imapd_namespace,
buf_cstring(&ext_folder),
imapd_userid, int_mboxname);
if (!r)
windowargs.anchorfolder = xstrdup(int_mboxname);
}
*wa = xmemdup(&windowargs, sizeof(windowargs));
buf_free(&ext_folder);
buf_free(&arg);
return c;
syntax_error:
free(windowargs.anchorfolder);
buf_free(&ext_folder);
prot_printf(imapd_out, "%s BAD Syntax error in window arguments\r\n", tag);
return EOF;
}
static void free_windowargs(struct windowargs *wa)
{
if (!wa)
return;
free(wa->anchorfolder);
free(wa);
}
/*
* Parse LIST selection options.
* The command has been parsed up to and including the opening '('.
*/
static int getlistselopts(char *tag, struct listargs *args)
{
int c;
static struct buf buf;
if ( (c = prot_getc(imapd_in)) == ')')
return prot_getc(imapd_in);
else
prot_ungetc(c, imapd_in);
for (;;) {
c = getword(imapd_in, &buf);
if (!*buf.s) {
prot_printf(imapd_out,
"%s BAD Invalid syntax in List command\r\n",
tag);
return EOF;
}
lcase(buf.s);
if (!strcmp(buf.s, "subscribed")) {
args->sel |= LIST_SEL_SUBSCRIBED | LIST_RET_SUBSCRIBED;
} else if (!strcmp(buf.s, "remote")) {
args->sel |= LIST_SEL_REMOTE;
} else if (!strcmp(buf.s, "recursivematch")) {
args->sel |= LIST_SEL_RECURSIVEMATCH;
} else if (!strcmp(buf.s, "special-use")) {
args->sel |= LIST_SEL_SPECIALUSE;
} else {
prot_printf(imapd_out,
"%s BAD Invalid List selection option \"%s\"\r\n",
tag, buf.s);
return EOF;
}
if (c != ' ') break;
}
if (c != ')') {
prot_printf(imapd_out,
"%s BAD Missing close parenthesis for List selection options\r\n", tag);
return EOF;
}
if (args->sel & list_select_mod_opts
&& ! (args->sel & list_select_base_opts)) {
prot_printf(imapd_out,
"%s BAD Invalid combination of selection options\r\n",
tag);
return EOF;
}
return prot_getc(imapd_in);
}
/*
* Parse LIST return options.
* The command has been parsed up to and including the ' ' before RETURN.
*/
static int getlistretopts(char *tag, struct listargs *args)
{
static struct buf buf;
int c;
c = getword(imapd_in, &buf);
if (!*buf.s) {
prot_printf(imapd_out,
"%s BAD Invalid syntax in List command\r\n", tag);
return EOF;
}
lcase(buf.s);
if (strcasecmp(buf.s, "return")) {
prot_printf(imapd_out,
"%s BAD Unexpected extra argument to List: \"%s\"\r\n",
tag, buf.s);
return EOF;
}
if (c != ' ' || (c = prot_getc(imapd_in)) != '(') {
prot_printf(imapd_out,
"%s BAD Missing return argument list\r\n", tag);
return EOF;
}
if ( (c = prot_getc(imapd_in)) == ')')
return prot_getc(imapd_in);
else
prot_ungetc(c, imapd_in);
for (;;) {
c = getword(imapd_in, &buf);
if (!*buf.s) {
prot_printf(imapd_out,
"%s BAD Invalid syntax in List command\r\n", tag);
return EOF;
}
lcase(buf.s);
if (!strcmp(buf.s, "subscribed"))
args->ret |= LIST_RET_SUBSCRIBED;
else if (!strcmp(buf.s, "children"))
args->ret |= LIST_RET_CHILDREN;
else if (!strcmp(buf.s, "myrights"))
args->ret |= LIST_RET_MYRIGHTS;
else if (!strcmp(buf.s, "special-use"))
args->ret |= LIST_RET_SPECIALUSE;
else if (!strcmp(buf.s, "status")) {
const char *errstr = "Bad status string";
args->ret |= LIST_RET_STATUS;
c = parse_statusitems(&args->statusitems, &errstr);
if (c == EOF) {
prot_printf(imapd_out, "%s BAD %s", tag, errstr);
return EOF;
}
}
else {
prot_printf(imapd_out,
"%s BAD Invalid List return option \"%s\"\r\n",
tag, buf.s);
return EOF;
}
if (c != ' ') break;
}
if (c != ')') {
prot_printf(imapd_out,
"%s BAD Missing close parenthesis for List return options\r\n", tag);
return EOF;
}
return prot_getc(imapd_in);
}
/*
* Parse a string in IMAP date-time format (and some more
* obscure legacy formats too) to a time_t. Parses both
* date and time parts. See cyrus_parsetime() for formats.
*
* Returns: the next character read from imapd_in, or
* or EOF on error.
*/
static int getdatetime(time_t *date)
{
int c;
int r;
int i = 0;
char buf[RFC3501_DATETIME_MAX+1];
c = prot_getc(imapd_in);
if (c != '\"')
goto baddate;
while ((c = prot_getc(imapd_in)) != '\"') {
if (i >= RFC3501_DATETIME_MAX)
goto baddate;
buf[i++] = c;
}
buf[i] = '\0';
r = time_from_rfc3501(buf, date);
if (r < 0)
goto baddate;
c = prot_getc(imapd_in);
return c;
baddate:
prot_ungetc(c, imapd_in);
return EOF;
}
/*
* Append 'section', 'fields', 'trail' to the fieldlist 'l'.
*/
static void appendfieldlist(struct fieldlist **l, char *section,
strarray_t *fields, char *trail,
void *d, size_t size)
{
struct fieldlist **tail = l;
while (*tail) tail = &(*tail)->next;
*tail = (struct fieldlist *)xmalloc(sizeof(struct fieldlist));
(*tail)->section = xstrdup(section);
(*tail)->fields = fields;
(*tail)->trail = xstrdup(trail);
if(d && size) {
(*tail)->rock = xmalloc(size);
memcpy((*tail)->rock, d, size);
} else {
(*tail)->rock = NULL;
}
(*tail)->next = 0;
}
/*
* Free the fieldlist 'l'
*/
static void freefieldlist(struct fieldlist *l)
{
struct fieldlist *n;
while (l) {
n = l->next;
free(l->section);
strarray_free(l->fields);
free(l->trail);
if (l->rock) free(l->rock);
free((char *)l);
l = n;
}
}
static int set_haschildren(char *name, int matchlen,
int maycreate __attribute__((unused)),
int *attributes)
{
list_callback_calls++;
if (name[matchlen]) {
*attributes |= MBOX_ATTRIBUTE_HASCHILDREN;
return CYRUSDB_DONE;
}
return 0;
}
static void specialuse_flags(mbentry_t *mbentry, const char *sep,
int isxlist)
{
char inboxname[MAX_MAILBOX_PATH+1];
int inboxlen;
if (!mbentry) return;
(*imapd_namespace.mboxname_tointernal)(&imapd_namespace, "INBOX",
imapd_userid, inboxname);
inboxlen = strlen(inboxname);
/* doesn't match inbox, not xlistable */
if (strncmp(mbentry->name, inboxname, inboxlen))
return;
/* inbox - only print if command is XLIST */
if (mbentry->name[inboxlen] == '\0') {
if (isxlist) prot_printf(imapd_out, "%s\\Inbox", sep);
}
/* subdir */
else if (mbentry->name[inboxlen] == '.') {
struct buf attrib = BUF_INITIALIZER;
/* check if there's a special use flag set */
if (!annotatemore_lookup(mbentry->name, "/specialuse", imapd_userid, &attrib)) {
if (attrib.len)
prot_printf(imapd_out, "%s%s", sep, buf_cstring(&attrib));
}
buf_free(&attrib);
}
/* otherwise it's actually another user who matches for
* the substr. Ok to just print nothing */
}
/* Print LIST or LSUB untagged response */
static void list_response(const char *name, int attributes,
struct listargs *listargs)
{
const struct mbox_name_attribute *attr;
char internal_name[MAX_MAILBOX_PATH+1];
int r;
char mboxname[MAX_MAILBOX_PATH+1];
const char *sep;
const char *cmd;
mbentry_t *mbentry = NULL;
struct statusdata sdata;
if (!name) return;
memset(&sdata, 0, sizeof(struct statusdata));
/* first convert "INBOX" to "user.<userid>" */
if (!strncasecmp(name, "inbox", 5)
&& (!name[5] || name[5] == '.') ) {
(*imapd_namespace.mboxname_tointernal)(&imapd_namespace, "INBOX",
imapd_userid, internal_name);
strlcat(internal_name, name+5, sizeof(internal_name));
}
else
strlcpy(internal_name, name, sizeof(internal_name));
/* get info and set flags */
r = mboxlist_lookup(internal_name, &mbentry, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT) {
attributes |= (listargs->cmd & LIST_CMD_EXTENDED) ?
MBOX_ATTRIBUTE_NONEXISTENT : MBOX_ATTRIBUTE_NOSELECT;
}
else if (r) return;
else if (listargs->scan) {
/* SCAN mailbox for content */
if ((mbentry->mbtype & MBTYPE_REMOTE) &&
!hash_lookup(mbentry->partition, &listargs->server_table)) {
/* remote mailbox that we haven't proxied to yet */
struct backend *s;
hash_insert(mbentry->server, (void *)0xDEADBEEF, &listargs->server_table);
s = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!s) r = IMAP_SERVER_UNAVAILABLE;
if (!r) {
char mytag[128];
proxy_gentag(mytag, sizeof(mytag));
prot_printf(s->out,
"%s Scan {%tu+}\r\n%s {%tu+}\r\n%s {%tu+}\r\n%s\r\n",
mytag,
strlen(listargs->ref), listargs->ref,
strlen(listargs->pat.data[0]), listargs->pat.data[0],
strlen(listargs->scan), listargs->scan);
r = pipe_until_tag(s, mytag, 0);
}
goto done;
}
else if (!strcmpsafe(internal_name, index_mboxname(imapd_index))) {
/* currently selected mailbox */
if (!index_scan(imapd_index, listargs->scan))
goto done; /* no matching messages */
}
else {
/* other local mailbox */
struct index_state *state;
struct index_init init;
int doclose = 0;
memset(&init, 0, sizeof(struct index_init));
init.userid = imapd_userid;
init.authstate = imapd_authstate;
init.out = imapd_out;
r = index_open(internal_name, &init, &state);
if (!r)
doclose = 1;
if (!r && index_hasrights(state, ACL_READ)) {
r = (imapd_userisadmin || index_hasrights(state, ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
if (!r) {
if (!index_scan(state, listargs->scan)) {
r = -1; /* no matching messages */
}
}
if (doclose) index_close(&state);
if (r) goto done;
}
}
/* figure out \Has(No)Children if necessary
This is mainly used for LIST (SUBSCRIBED) RETURN (CHILDREN)
*/
if (listargs->ret & LIST_RET_CHILDREN
&& ! (attributes & MBOX_ATTRIBUTE_HASCHILDREN)
&& ! (attributes & MBOX_ATTRIBUTE_HASNOCHILDREN) ) {
mboxlist_findall(&imapd_namespace, name,
imapd_userisadmin, imapd_userid, imapd_authstate,
set_haschildren, &attributes);
if ( ! (attributes & MBOX_ATTRIBUTE_HASCHILDREN) )
attributes |= MBOX_ATTRIBUTE_HASNOCHILDREN;
}
if (attributes & (MBOX_ATTRIBUTE_NONEXISTENT | MBOX_ATTRIBUTE_NOSELECT)) {
int keep = 0;
/* extended get told everything */
if (listargs->cmd & LIST_CMD_EXTENDED) {
keep = 1;
}
/* we have to mention this, it has children */
if (listargs->cmd & LIST_CMD_LSUB) {
/* subscribed children need a mention */
if (attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED)
keep = 1;
/* if mupdate is configured we can't drop out, we might
* be a backend and need to report folders that don't
* exist on this backend - this is awful and complex
* and brittle and should be changed */
if (config_mupdate_server)
keep = 1;
}
else if (attributes & MBOX_ATTRIBUTE_HASCHILDREN)
keep = 1;
if (!keep) goto done;
}
if (listargs->cmd & LIST_CMD_LSUB) {
/* \Noselect has a special second meaning with (R)LSUB */
if ( !(attributes & MBOX_ATTRIBUTE_SUBSCRIBED)
&& attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED)
attributes |= MBOX_ATTRIBUTE_NOSELECT | MBOX_ATTRIBUTE_HASCHILDREN;
attributes &= ~MBOX_ATTRIBUTE_SUBSCRIBED;
}
/* no inferiors means no children (this basically means the INBOX
* in alt namespace mode */
if (attributes & MBOX_ATTRIBUTE_NOINFERIORS)
attributes &= ~MBOX_ATTRIBUTE_HASCHILDREN;
/* remove redundant flags */
if (listargs->cmd & LIST_CMD_EXTENDED) {
/* \NoInferiors implies \HasNoChildren */
if (attributes & MBOX_ATTRIBUTE_NOINFERIORS)
attributes &= ~MBOX_ATTRIBUTE_HASNOCHILDREN;
/* \NonExistent implies \Noselect */
if (attributes & MBOX_ATTRIBUTE_NONEXISTENT)
attributes &= ~MBOX_ATTRIBUTE_NOSELECT;
}
if (listargs->sel & LIST_SEL_SPECIALUSE) {
struct buf attrib = BUF_INITIALIZER;
if (!mbentry) goto done;
/* check that this IS a specialuse folder */
if (annotatemore_lookup(mbentry->name, "/specialuse", imapd_userid, &attrib))
goto done;
if (!attrib.len) {
buf_free(&attrib);
goto done;
}
buf_free(&attrib);
}
/* can we read the status data ? */
if ((listargs->ret & LIST_RET_STATUS) &&
!(attributes & MBOX_ATTRIBUTE_NOSELECT)) {
r = imapd_statusdata(internal_name, listargs->statusitems, &sdata);
if (r) {
/* RFC 5819: the STATUS response MUST NOT be returned and the
* LIST response MUST include the \NoSelect attribute. */
attributes |= MBOX_ATTRIBUTE_NOSELECT;
}
}
switch (listargs->cmd) {
case LIST_CMD_LSUB:
cmd = "LSUB";
break;
case LIST_CMD_XLIST:
cmd = "XLIST";
break;
default:
cmd = "LIST";
break;
}
prot_printf(imapd_out, "* %s (", cmd);
for (sep = "", attr = mbox_name_attributes; attr->id; attr++) {
if (attributes & attr->flag) {
prot_printf(imapd_out, "%s%s", sep, attr->id);
sep = " ";
}
}
(*imapd_namespace.mboxname_toexternal)(&imapd_namespace, name,
imapd_userid, mboxname);
if (config_getswitch(IMAPOPT_SPECIALUSEALWAYS) ||
listargs->cmd == LIST_CMD_XLIST ||
listargs->ret & LIST_RET_SPECIALUSE ||
listargs->sel & LIST_SEL_SPECIALUSE) {
specialuse_flags(mbentry, sep, listargs->cmd == LIST_CMD_XLIST);
}
prot_printf(imapd_out, ") ");
prot_printf(imapd_out, "\"%c\" ", imapd_namespace.hier_sep);
prot_printastring(imapd_out, mboxname);
if (listargs->cmd & LIST_CMD_EXTENDED &&
attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED) {
prot_printf(imapd_out, " (CHILDINFO (");
/* RFC 5258:
* ; Note 2: The selection options are always returned
* ; quoted, unlike their specification in
* ; the extended LIST command.
*/
if (attributes & MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED)
prot_printf(imapd_out, "\"SUBSCRIBED\"");
prot_printf(imapd_out, "))");
}
prot_printf(imapd_out, "\r\n");
if ((listargs->ret & LIST_RET_STATUS) &&
!(attributes & MBOX_ATTRIBUTE_NOSELECT)) {
/* output the status line now, per rfc 5819 */
print_statusline(mboxname, listargs->statusitems, &sdata);
}
if ((listargs->ret & LIST_RET_MYRIGHTS) &&
!(attributes & MBOX_ATTRIBUTE_NOSELECT)) {
/*ignore result*/printmyrights(mboxname, mbentry);
}
done:
mboxlist_entry_free(&mbentry);
}
static int set_subscribed(char *name, int matchlen,
int maycreate __attribute__((unused)),
void *rock)
{
int *attributes = (int *)rock;
list_callback_calls++;
if (!name[matchlen])
*attributes |= MBOX_ATTRIBUTE_SUBSCRIBED;
else
*attributes |= MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED;
return 0;
}
static void perform_output(const char *name, size_t matchlen,
struct list_rock *rock)
{
if (rock->last_name) {
if (strlen(rock->last_name) == matchlen && name &&
!strncmp(rock->last_name, name, matchlen))
return; /* skip duplicate calls */
list_response(rock->last_name, rock->last_attributes, rock->listargs);
free(rock->last_name);
rock->last_name = NULL;
}
if (name) {
rock->last_name = xstrndup(name, matchlen);
rock->last_attributes = 0;
}
}
/* callback for mboxlist_findall
* used when the SUBSCRIBED selection option is NOT given */
static int list_cb(char *name, int matchlen, int maycreate,
struct list_rock *rock)
{
int last_len;
int last_name_is_ancestor =
rock->last_name
&& matchlen >= (last_len = strlen(rock->last_name))
&& (name[last_len] == '.' || name[last_len] == imapd_namespace.hier_sep)
&& !(rock->last_attributes & MBOX_ATTRIBUTE_NOINFERIORS)
&& !memcmp(rock->last_name, name, last_len);
list_callback_calls++;
if (last_name_is_ancestor)
rock->last_attributes |= MBOX_ATTRIBUTE_HASCHILDREN;
/* tidy up flags */
if (!(rock->last_attributes & MBOX_ATTRIBUTE_HASCHILDREN))
rock->last_attributes |= MBOX_ATTRIBUTE_HASNOCHILDREN;
perform_output(name, matchlen, rock);
if (!maycreate)
rock->last_attributes |= MBOX_ATTRIBUTE_NOINFERIORS;
else if (name[matchlen] == '.')
rock->last_attributes |= MBOX_ATTRIBUTE_HASCHILDREN;
/* XXX: is there a cheaper way to figure out \Subscribed? */
if (rock->listargs->ret & LIST_RET_SUBSCRIBED)
mboxlist_findsub(&imapd_namespace, name, imapd_userisadmin,
imapd_userid, imapd_authstate, set_subscribed,
&rock->last_attributes, 0);
return 0;
}
/* callback for mboxlist_findsub
* used when SUBSCRIBED but not RECURSIVEMATCH is given */
static int subscribed_cb(const char *name, int matchlen, int maycreate,
struct list_rock *rock)
{
int last_len;
int last_name_is_ancestor =
rock->last_name
&& matchlen >= (last_len = strlen(rock->last_name))
&& name[last_len] == '.'
&& !(rock->last_attributes & MBOX_ATTRIBUTE_NOINFERIORS)
&& !memcmp(rock->last_name, name, last_len);
list_callback_calls++;
if (last_name_is_ancestor)
rock->last_attributes |= MBOX_ATTRIBUTE_HASCHILDREN;
if (!name[matchlen]) {
perform_output(name, matchlen, rock);
rock->last_attributes |= MBOX_ATTRIBUTE_SUBSCRIBED;
if (!maycreate)
rock->last_attributes |= MBOX_ATTRIBUTE_NOINFERIORS;
}
else if (name[matchlen] == '.' &&
rock->listargs->cmd & LIST_CMD_LSUB) {
/* special case: for LSUB,
* mailbox names that match the pattern but aren't subscribed
* must also be returned if they have a child mailbox that is
* subscribed */
perform_output(name, matchlen, rock);
rock->last_attributes |= MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED;
}
return 0;
}
/*
* Takes the "reference name" and "mailbox name" arguments of the LIST command
* and returns a "canonical LIST pattern". The caller is responsible for
* free()ing the returned string.
*/
static char *canonical_list_pattern(const char *reference, const char *pattern)
{
int patlen = strlen(pattern);
int reflen = strlen(reference);
char *buf = xmalloc(patlen + reflen + 1);
buf[0] = '\0';
if (*reference) {
if (reference[reflen-1] == imapd_namespace.hier_sep &&
pattern[0] == imapd_namespace.hier_sep)
--reflen;
memcpy(buf, reference, reflen);
buf[reflen] = '\0';
}
strcat(buf, pattern);
return buf;
}
/*
* Turns the strings in patterns into "canonical LIST pattern"s. Also
* translates any hierarchy separators.
*/
static void canonical_list_patterns(const char *reference,
strarray_t *patterns)
{
static int ignorereference = 0;
int i;
/* Ignore the reference argument?
(the behavior in 1.5.10 & older) */
if (ignorereference == 0)
ignorereference = config_getswitch(IMAPOPT_IGNOREREFERENCE);
for (i = 0 ; i < patterns->count ; i++) {
char *p = patterns->data[i];
if (!ignorereference || p[0] == imapd_namespace.hier_sep) {
strarray_setm(patterns, i,
canonical_list_pattern(reference, p));
p = patterns->data[i];
}
/* Translate any separators in pattern */
mboxname_hiersep_tointernal(&imapd_namespace, p,
config_virtdomains ?
strcspn(p, "@") : 0);
}
}
/* callback for mboxlist_findsub
* used by list_data_recursivematch */
static int recursivematch_cb(char *name, int matchlen, int maycreate,
struct list_rock_recursivematch *rock) {
list_callback_calls++;
if (name[matchlen]) {
char c = name[matchlen];
if (c == '.' || c == imapd_namespace.hier_sep) {
int *parent_info;
name[matchlen] = '\0';
parent_info = hash_lookup(name, &rock->table);
if (!parent_info) {
parent_info = xzmalloc(sizeof(int));
if (!maycreate) *parent_info |= MBOX_ATTRIBUTE_NOINFERIORS;
hash_insert(name, parent_info, &rock->table);
rock->count++;
}
*parent_info |= MBOX_ATTRIBUTE_CHILDINFO_SUBSCRIBED;
name[matchlen] = c;
}
} else {
int *list_info = hash_lookup(name, &rock->table);
if (!list_info) {
list_info = xzmalloc(sizeof(int));
*list_info |= MBOX_ATTRIBUTE_SUBSCRIBED;
if (!maycreate) *list_info |= MBOX_ATTRIBUTE_NOINFERIORS;
hash_insert(name, list_info, &rock->table);
rock->count++;
}
}
return 0;
}
/* callback for hash_enumerate */
static void copy_to_array(const char *key, void *data, void *void_rock)
{
int *attributes = (int *)data;
struct list_rock_recursivematch *rock =
(struct list_rock_recursivematch *)void_rock;
assert(rock->count > 0);
rock->array[--rock->count].name = key;
rock->array[rock->count].attributes = *attributes;
}
/* Comparator for reverse-sorting an array of struct list_entry by mboxname. */
static int list_entry_comparator(const void *p1, const void *p2) {
const struct list_entry *e1 = (struct list_entry *)p1;
const struct list_entry *e2 = (struct list_entry *)p2;
return bsearch_compare_mbox(e2->name, e1->name);
}
static void list_data_recursivematch(struct listargs *listargs,
int (*findsub)(struct namespace *,
const char *, int, const char *,
struct auth_state *, int (*)(),
void *, int)) {
char **pattern;
struct list_rock_recursivematch rock;
rock.count = 0;
rock.listargs = listargs;
construct_hash_table(&rock.table, 100, 1);
/* find */
for (pattern = listargs->pat.data ; *pattern ; pattern++) {
findsub(&imapd_namespace, *pattern, imapd_userisadmin, imapd_userid,
imapd_authstate, recursivematch_cb, &rock, 1);
}
if (rock.count) {
/* sort */
int entries = rock.count;
rock.array = xmalloc(entries * (sizeof(struct list_entry)));
hash_enumerate(&rock.table, copy_to_array, &rock);
qsort(rock.array, entries, sizeof(struct list_entry),
list_entry_comparator);
assert(rock.count == 0);
/* print */
for (entries--; entries >= 0; entries--)
list_response(rock.array[entries].name,
rock.array[entries].attributes,
rock.listargs);
free(rock.array);
}
free_hash_table(&rock.table, free);
}
/* Retrieves the data and prints the untagged responses for a LIST command. */
static void list_data(struct listargs *listargs)
{
int (*findall)(struct namespace *namespace,
const char *pattern, int isadmin, const char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock);
int (*findsub)(struct namespace *namespace,
const char *pattern, int isadmin, const char *userid,
struct auth_state *auth_state, int (*proc)(),
void *rock, int force);
canonical_list_patterns(listargs->ref, &listargs->pat);
/* Check to see if we should only list the personal namespace */
if (!(listargs->cmd & LIST_CMD_EXTENDED)
&& !strcmp(listargs->pat.data[0], "*")
&& config_getswitch(IMAPOPT_FOOLSTUPIDCLIENTS)) {
strarray_set(&listargs->pat, 0, "INBOX*");
findsub = mboxlist_findsub;
findall = mboxlist_findall;
} else {
findsub = imapd_namespace.mboxlist_findsub;
findall = imapd_namespace.mboxlist_findall;
}
if (listargs->sel & LIST_SEL_RECURSIVEMATCH) {
list_data_recursivematch(listargs, findsub);
} else {
char **pattern;
struct list_rock rock;
memset(&rock, 0, sizeof(struct list_rock));
rock.listargs = listargs;
if (listargs->sel & LIST_SEL_SUBSCRIBED) {
for (pattern = listargs->pat.data ; pattern && *pattern ; pattern++) {
findsub(&imapd_namespace, *pattern, imapd_userisadmin,
imapd_userid, imapd_authstate, subscribed_cb, &rock, 1);
perform_output(NULL, 0, &rock);
}
} else {
if (listargs->scan) {
construct_hash_table(&listargs->server_table, 10, 1);
}
for (pattern = listargs->pat.data ; pattern && *pattern ; pattern++) {
findall(&imapd_namespace, *pattern, imapd_userisadmin,
imapd_userid, imapd_authstate, list_cb, &rock);
perform_output(NULL, 0, &rock);
}
if (listargs->scan)
free_hash_table(&listargs->server_table, NULL);
}
}
}
/*
* Retrieves the data and prints the untagged responses for a LIST command in
* the case of a remote inbox.
*/
static int list_data_remote(char *tag, struct listargs *listargs)
{
if ((listargs->cmd & LIST_CMD_EXTENDED) &&
!CAPA(backend_inbox, CAPA_LISTEXTENDED)) {
/* client wants to use extended list command but backend doesn't
* support it */
prot_printf(imapd_out,
"%s NO Backend server does not support LIST-EXTENDED\r\n",
tag);
return IMAP_MAILBOX_NOTSUPPORTED;
}
/* print tag, command and list selection options */
if (listargs->cmd & LIST_CMD_LSUB) {
prot_printf(backend_inbox->out, "%s Lsub ", tag);
} else {
prot_printf(backend_inbox->out, "%s List (subscribed", tag);
if (listargs->sel & LIST_SEL_REMOTE) {
prot_printf(backend_inbox->out, " remote");
}
if (listargs->sel & LIST_SEL_RECURSIVEMATCH) {
prot_printf(backend_inbox->out, " recursivematch");
}
prot_printf(backend_inbox->out, ") ");
}
/* print reference argument */
prot_printf(backend_inbox->out,
"{%tu+}\r\n%s ", strlen(listargs->ref), listargs->ref);
/* print mailbox pattern(s) */
if (listargs->pat.count > 1) {
char **p;
char c = '(';
for (p = listargs->pat.data ; *p ; p++) {
prot_printf(backend_inbox->out,
"%c{%tu+}\r\n%s", c, strlen(*p), *p);
c = ' ';
}
(void)prot_putc(')', backend_inbox->out);
} else {
prot_printf(backend_inbox->out,
"{%tu+}\r\n%s", strlen(listargs->pat.data[0]), listargs->pat.data[0]);
}
/* print list return options */
if (listargs->ret) {
char c = '(';
prot_printf(backend_inbox->out, " return ");
if (listargs->ret & LIST_RET_SUBSCRIBED) {
prot_printf(backend_inbox->out, "%csubscribed", c);
c = ' ';
}
if (listargs->ret & LIST_RET_CHILDREN) {
prot_printf(backend_inbox->out, "%cchildren", c);
c = ' ';
}
(void)prot_putc(')', backend_inbox->out);
}
prot_printf(backend_inbox->out, "\r\n");
pipe_lsub(backend_inbox, imapd_userid, tag, 0,
(listargs->cmd & LIST_CMD_LSUB) ? "LSUB" : "LIST");
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(0);
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;
}
static void cmd_mupdatepush(char *tag, char *name)
{
int r = 0;
char mailboxname[MAX_MAILBOX_BUFFER];
mbentry_t *mbentry = NULL;
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, &mbentry);
}
if (r == IMAP_MAILBOX_MOVED) return;
/* Push mailbox to mupdate server */
if (!r) {
r = mupdate_connect(config_mupdate_server, NULL, &mupdate_h, NULL);
}
if (!r) {
snprintf(buf, sizeof(buf), "%s!%s",
config_servername, mbentry->partition);
r = mupdate_activate(mupdate_h, mailboxname, buf, mbentry->acl);
}
mboxlist_entry_free(&mbentry);
if (mupdate_h) {
mupdate_disconnect(&mupdate_h);
}
if (r) {
prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r));
}
else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
}
#ifdef HAVE_SSL
enum {
URLAUTH_ALG_HMAC_SHA1 = 0 /* HMAC-SHA1 */
};
static void cmd_urlfetch(char *tag)
{
struct mboxkey *mboxkey_db;
int c, r, doclose;
static struct buf arg, param;
struct imapurl url;
char mailboxname[MAX_MAILBOX_BUFFER];
struct index_state *state;
uint32_t msgno;
mbentry_t *mbentry = NULL;
time_t now = time(NULL);
unsigned extended, params;
prot_printf(imapd_out, "* URLFETCH");
do {
extended = params = 0;
/* See if its an extended URLFETCH */
c = prot_getc(imapd_in);
if (c == '(') extended = 1;
else prot_ungetc(c, imapd_in);
c = getastring(imapd_in, imapd_out, &arg);
(void)prot_putc(' ', imapd_out);
prot_printstring(imapd_out, arg.s);
if (extended) {
while (c == ' ') {
c = getword(imapd_in, &param);
ucase(param.s);
if (!strcmp(param.s, "BODY")) {
if (params & (URLFETCH_BODY | URLFETCH_BINARY)) goto badext;
params |= URLFETCH_BODY;
} else if (!strcmp(param.s, "BINARY")) {
if (params & (URLFETCH_BODY | URLFETCH_BINARY)) goto badext;
params |= URLFETCH_BINARY;
} else if (!strcmp(param.s, "BODYPARTSTRUCTURE")) {
if (params & URLFETCH_BODYPARTSTRUCTURE) goto badext;
params |= URLFETCH_BODYPARTSTRUCTURE;
} else {
goto badext;
}
}
if (c != ')') goto badext;
c = prot_getc(imapd_in);
}
doclose = 0;
r = imapurl_fromURL(&url, arg.s);
/* validate the URL */
if (r || !url.user || !url.server || !url.mailbox || !url.uid ||
(url.section && !*url.section) ||
(url.urlauth.access && !(url.urlauth.mech && url.urlauth.token))) {
/* missing info */
r = IMAP_BADURL;
} else if (strcmp(url.server, config_servername)) {
/* wrong server */
r = IMAP_BADURL;
} else if (url.urlauth.expire &&
url.urlauth.expire < mktime(gmtime(&now))) {
/* expired */
r = IMAP_BADURL;
} else if (url.urlauth.access) {
/* check mechanism & authorization */
int authorized = 0;
if (!strcasecmp(url.urlauth.mech, "INTERNAL")) {
if (!strncasecmp(url.urlauth.access, "submit+", 7) &&
global_authisa(imapd_authstate, IMAPOPT_SUBMITSERVERS)) {
/* authorized submit server */
authorized = 1;
} else if (!strncasecmp(url.urlauth.access, "user+", 5) &&
!strcmp(url.urlauth.access+5, imapd_userid)) {
/* currently authorized user */
authorized = 1;
} else if (!strcasecmp(url.urlauth.access, "authuser") &&
strcmp(imapd_userid, "anonymous")) {
/* any non-anonymous authorized user */
authorized = 1;
} else if (!strcasecmp(url.urlauth.access, "anonymous")) {
/* anyone */
authorized = 1;
}
}
if (!authorized) r = IMAP_BADURL;
}
if (!r) {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
url.mailbox,
url.user, mailboxname);
}
if (!r) {
r = mlookup(NULL, NULL, mailboxname, &mbentry);
}
if (!r && (mbentry->mbtype & MBTYPE_REMOTE)) {
/* remote mailbox */
struct backend *be;
be = proxy_findserver(mbentry->server, &imap_protocol,
proxy_userid, &backend_cached,
&backend_current, &backend_inbox, imapd_in);
if (!be) {
r = IMAP_SERVER_UNAVAILABLE;
} else {
/* XXX proxy command to backend */
}
free(url.freeme);
mboxlist_entry_free(&mbentry);
continue;
}
mboxlist_entry_free(&mbentry);
/* local mailbox */
if (!r) {
if (url.urlauth.token) {
/* validate the URLAUTH token */
/* yes, this is evil, in-place conversion from hex
* to binary */
if (hex_to_bin(url.urlauth.token, 0,
(unsigned char *) url.urlauth.token) < 1) {
r = IMAP_BADURL;
break;
}
/* first byte is the algorithm used to create token */
switch (url.urlauth.token[0]) {
case URLAUTH_ALG_HMAC_SHA1: {
const char *key;
size_t keylen;
unsigned char vtoken[EVP_MAX_MD_SIZE];
unsigned int vtoken_len;
r = mboxkey_open(url.user, 0, &mboxkey_db);
if (r) break;
r = mboxkey_read(mboxkey_db, mailboxname, &key, &keylen);
if (r) break;
HMAC(EVP_sha1(), key, keylen, (unsigned char *) arg.s,
url.urlauth.rump_len, vtoken, &vtoken_len);
mboxkey_close(mboxkey_db);
if (memcmp(vtoken, url.urlauth.token+1, vtoken_len)) {
r = IMAP_BADURL;
}
break;
}
default:
r = IMAP_BADURL;
break;
}
}
if (!r) {
if (!strcmp(index_mboxname(imapd_index), mailboxname)) {
state = imapd_index;
}
else {
/* not the currently selected mailbox, so try to open it */
r = index_open(mailboxname, NULL, &state);
if (!r)
doclose = 1;
if (!r && !url.urlauth.access &&
!(state->myrights & ACL_READ)) {
r = (imapd_userisadmin ||
(state->myrights & ACL_LOOKUP)) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
}
if (r) {
/* nothing to do, handled up top */
} else if (url.uidvalidity &&
(state->mailbox->i.uidvalidity != url.uidvalidity)) {
r = IMAP_BADURL;
} else if (!url.uid || !(msgno = index_finduid(state, url.uid)) ||
(index_getuid(state, msgno) != url.uid)) {
r = IMAP_BADURL;
} else {
r = index_urlfetch(state, msgno, params, url.section,
url.start_octet, url.octet_count,
imapd_out, NULL);
}
free(url.freeme);
if (doclose)
index_close(&state);
}
if (r) prot_printf(imapd_out, " NIL");
} while (c == ' ');
prot_printf(imapd_out, "\r\n");
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to URLFETCH\r\n", tag);
eatline(imapd_in, c);
}
else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
return;
badext:
prot_printf(imapd_out, " NIL\r\n");
prot_printf(imapd_out,
"%s BAD Invalid extended URLFETCH parameters\r\n", tag);
eatline(imapd_in, c);
}
#define MBOX_KEY_LEN 16 /* 128 bits */
static void cmd_genurlauth(char *tag)
{
struct mboxkey *mboxkey_db;
int first = 1;
int c, r;
static struct buf arg1, arg2;
struct imapurl url;
char mailboxname[MAX_MAILBOX_BUFFER];
char newkey[MBOX_KEY_LEN];
char *urlauth = NULL;
const char *key;
size_t keylen;
unsigned char token[EVP_MAX_MD_SIZE+1]; /* +1 for algorithm */
unsigned int token_len;
mbentry_t *mbentry = NULL;
time_t now = time(NULL);
r = mboxkey_open(imapd_userid, MBOXKEY_CREATE, &mboxkey_db);
if (r) {
prot_printf(imapd_out,
"%s NO Cannot open mailbox key db for %s: %s\r\n",
tag, imapd_userid, error_message(r));
return;
}
do {
c = getastring(imapd_in, imapd_out, &arg1);
if (c != ' ') {
prot_printf(imapd_out,
"%s BAD Missing required argument to Genurlauth\r\n",
tag);
eatline(imapd_in, c);
return;
}
c = getword(imapd_in, &arg2);
if (strcasecmp(arg2.s, "INTERNAL")) {
prot_printf(imapd_out,
"%s BAD Unknown auth mechanism to Genurlauth %s\r\n",
tag, arg2.s);
eatline(imapd_in, c);
return;
}
r = imapurl_fromURL(&url, arg1.s);
/* validate the URL */
if (r || !url.user || !url.server || !url.mailbox || !url.uid ||
(url.section && !*url.section) || !url.urlauth.access) {
r = IMAP_BADURL;
} else if (strcmp(url.user, imapd_userid)) {
/* not using currently authorized user's namespace */
r = IMAP_BADURL;
} else if (strcmp(url.server, config_servername)) {
/* wrong server */
r = IMAP_BADURL;
} else if (url.urlauth.expire &&
url.urlauth.expire < mktime(gmtime(&now))) {
/* already expired */
r = IMAP_BADURL;
}
if (!r) {
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
url.mailbox,
imapd_userid, mailboxname);
}
if (!r) {
r = mlookup(NULL, NULL, mailboxname, &mbentry);
}
if (r) {
prot_printf(imapd_out,
"%s BAD Poorly specified URL to Genurlauth %s\r\n",
tag, arg1.s);
eatline(imapd_in, c);
return;
}
if (mbentry->mbtype & MBTYPE_REMOTE) {
/* XXX proxy to backend */
mboxlist_entry_free(&mbentry);
continue;
}
mboxlist_entry_free(&mbentry);
/* lookup key */
r = mboxkey_read(mboxkey_db, mailboxname, &key, &keylen);
if (r) {
syslog(LOG_ERR, "DBERROR: error fetching mboxkey: %s",
cyrusdb_strerror(r));
}
else if (!key) {
/* create a new key */
RAND_bytes((unsigned char *) newkey, MBOX_KEY_LEN);
key = newkey;
keylen = MBOX_KEY_LEN;
r = mboxkey_write(mboxkey_db, mailboxname, key, keylen);
if (r) {
syslog(LOG_ERR, "DBERROR: error writing new mboxkey: %s",
cyrusdb_strerror(r));
}
}
if (r) {
eatline(imapd_in, c);
prot_printf(imapd_out,
"%s NO Error authorizing %s: %s\r\n",
tag, arg1.s, cyrusdb_strerror(r));
return;
}
/* first byte is the algorithm used to create token */
token[0] = URLAUTH_ALG_HMAC_SHA1;
HMAC(EVP_sha1(), key, keylen, (unsigned char *) arg1.s, strlen(arg1.s),
token+1, &token_len);
token_len++;
urlauth = xrealloc(urlauth, strlen(arg1.s) + 10 +
2 * (EVP_MAX_MD_SIZE+1) + 1);
strcpy(urlauth, arg1.s);
strcat(urlauth, ":internal:");
bin_to_hex(token, token_len, urlauth+strlen(urlauth), BH_LOWER);
if (first) {
prot_printf(imapd_out, "* GENURLAUTH");
first = 0;
}
(void)prot_putc(' ', imapd_out);
prot_printstring(imapd_out, urlauth);
} while (c == ' ');
if (!first) prot_printf(imapd_out, "\r\n");
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to GENURLAUTH\r\n", tag);
eatline(imapd_in, c);
}
else {
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
free(urlauth);
mboxkey_close(mboxkey_db);
}
static void cmd_resetkey(char *tag, char *mailbox,
char *mechanism __attribute__((unused)))
/* XXX we don't support any external mechanisms, so we ignore it */
{
int r;
if (mailbox) {
/* delete key for specified mailbox */
char mailboxname[MAX_MAILBOX_BUFFER];
struct mboxkey *mboxkey_db;
mbentry_t *mbentry = NULL;
r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace,
mailbox,
imapd_userid, mailboxname);
if (!r) {
r = mlookup(NULL, NULL, mailboxname, &mbentry);
}
if (r) {
prot_printf(imapd_out, "%s NO Error removing key: %s\r\n",
tag, error_message(r));
return;
}
if (mbentry->mbtype & MBTYPE_REMOTE) {
/* XXX proxy to backend */
mboxlist_entry_free(&mbentry);
return;
}
mboxlist_entry_free(&mbentry);
r = mboxkey_open(imapd_userid, MBOXKEY_CREATE, &mboxkey_db);
if (!r) {
r = mboxkey_write(mboxkey_db, mailboxname, NULL, 0);
mboxkey_close(mboxkey_db);
}
if (r) {
prot_printf(imapd_out, "%s NO Error removing key: %s\r\n",
tag, cyrusdb_strerror(r));
} else {
prot_printf(imapd_out,
"%s OK [URLMECH INTERNAL] key removed\r\n", tag);
}
}
else {
/* delete ALL keys */
/* XXX what do we do about multiple backends? */
r = mboxkey_delete_user(imapd_userid);
if (r) {
prot_printf(imapd_out, "%s NO Error removing keys: %s\r\n",
tag, cyrusdb_strerror(r));
} else {
prot_printf(imapd_out, "%s OK All keys removed\r\n", tag);
}
}
}
#endif /* HAVE_SSL */
#ifdef HAVE_ZLIB
static void cmd_compress(char *tag, char *alg)
{
if (imapd_compress_done) {
prot_printf(imapd_out,
"%s BAD [COMPRESSIONACTIVE] DEFLATE active via COMPRESS\r\n",
tag);
}
#if defined(HAVE_SSL) && (OPENSSL_VERSION_NUMBER >= 0x0090800fL)
else if (imapd_tls_comp) {
prot_printf(imapd_out,
"%s NO [COMPRESSIONACTIVE] %s active via TLS\r\n",
tag, SSL_COMP_get_name(imapd_tls_comp));
}
#endif // defined(HAVE_SSL) && (OPENSSL_VERSION_NUMBER >= 0x0090800fL)
else if (strcasecmp(alg, "DEFLATE")) {
prot_printf(imapd_out,
"%s NO Unknown COMPRESS algorithm: %s\r\n", tag, alg);
}
else if (ZLIB_VERSION[0] != zlibVersion()[0]) {
prot_printf(imapd_out,
"%s NO Error initializing %s (incompatible zlib version)\r\n",
tag, alg);
}
else {
prot_printf(imapd_out,
"%s OK %s active\r\n", tag, alg);
/* enable (de)compression for the prot layer */
prot_setcompress(imapd_in);
prot_setcompress(imapd_out);
imapd_compress_done = 1;
}
}
#endif /* HAVE_ZLIB */
static void cmd_enable(char *tag)
{
static struct buf arg;
int c;
unsigned new_capa = imapd_client_capa;
do {
c = getword(imapd_in, &arg);
if (!arg.s[0]) {
prot_printf(imapd_out,
"\r\n%s BAD Missing required argument to Enable\r\n",
tag);
eatline(imapd_in, c);
return;
}
if (!strcasecmp(arg.s, "condstore"))
new_capa |= CAPA_CONDSTORE;
else if (!strcasecmp(arg.s, "qresync"))
new_capa |= CAPA_QRESYNC | CAPA_CONDSTORE;
} while (c == ' ');
/* check for CRLF */
if (c == '\r') c = prot_getc(imapd_in);
if (c != '\n') {
prot_printf(imapd_out,
"%s BAD Unexpected extra arguments to Enable\r\n", tag);
eatline(imapd_in, c);
return;
}
prot_printf(imapd_out, "* ENABLED");
if (!(imapd_client_capa & CAPA_CONDSTORE) &&
(new_capa & CAPA_CONDSTORE)) {
prot_printf(imapd_out, " CONDSTORE");
}
if (!(imapd_client_capa & CAPA_QRESYNC) &&
(new_capa & CAPA_QRESYNC)) {
prot_printf(imapd_out, " QRESYNC");
/* RFC5161 says that enable while selected is actually bogus,
* but it's no skin off our nose to support it */
if (imapd_index) imapd_index->qresync = 1;
}
prot_printf(imapd_out, "\r\n");
/* track the new capabilities */
imapd_client_capa = new_capa;
prot_printf(imapd_out, "%s OK %s\r\n", tag,
error_message(IMAP_OK_COMPLETED));
}
diff --git a/imap/index.c b/imap/index.c
index 1ec3a9341..367e142f9 100644
--- a/imap/index.c
+++ b/imap/index.c
@@ -1,7440 +1,7475 @@
/* index.c -- Routines for dealing with the index file in the imapd
*
* Copyright (c) 1994-2008 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 legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@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 <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <syslog.h>
#include <ctype.h>
#include <stdlib.h>
#include "acl.h"
#include "annotate.h"
#include "append.h"
#include "assert.h"
#include "charset.h"
#include "conversations.h"
#include "dlist.h"
#include "exitcodes.h"
#include "hash.h"
#include "hashu64.h"
#include "imap_err.h"
#include "global.h"
#include "times.h"
#include "imapd.h"
#include "lsort.h"
#include "mailbox.h"
#include "map.h"
#include "message.h"
#include "parseaddr.h"
#include "search_engines.h"
#include "seen.h"
#include "statuscache.h"
#include "strhash.h"
#include "user.h"
#include "util.h"
#include "xstats.h"
#include "ptrarray.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "index.h"
#include "sync_log.h"
/* Forward declarations */
static void index_refresh(struct index_state *state);
static void index_tellexists(struct index_state *state);
static int index_lock(struct index_state *state);
static void index_unlock(struct index_state *state);
// extern struct namespace imapd_namespace;
struct index_modified_flags {
int added_flags;
bit32 added_system_flags;
bit32 added_user_flags[MAX_USER_FLAGS/32];
int removed_flags;
bit32 removed_system_flags;
bit32 removed_user_flags[MAX_USER_FLAGS/32];
};
static int index_writeseen(struct index_state *state);
static void index_fetchmsg(struct index_state *state,
const struct buf *msg,
unsigned offset, unsigned size,
unsigned start_octet, unsigned octet_count);
static int index_fetchsection(struct index_state *state, const char *resp,
const struct buf *msg,
char *section,
const char *cachestr, unsigned size,
unsigned start_octet, unsigned octet_count);
static void index_fetchfsection(struct index_state *state,
const char *msg_base, unsigned long msg_size,
struct fieldlist *fsection,
const char *cachestr,
unsigned start_octet, unsigned octet_count);
static char *index_readheader(const char *msg_base, unsigned long msg_size,
unsigned offset, unsigned size);
static void index_fetchheader(struct index_state *state,
const char *msg_base, unsigned long msg_size,
unsigned size,
const strarray_t *headers,
const strarray_t *headers_not);
static void index_fetchcacheheader(struct index_state *state, struct index_record *record,
const strarray_t *headers, unsigned start_octet,
unsigned octet_count);
static void index_listflags(struct index_state *state);
static void index_fetchflags(struct index_state *state, uint32_t msgno);
static int index_search_evaluate(struct index_state *state,
const struct searchargs *searchargs,
uint32_t msgno);
static int _index_search(unsigned **msgno_list, struct index_state *state,
struct searchargs *searchargs,
modseq_t *highestmodseq);
static int index_copysetup(struct index_state *state, uint32_t msgno,
struct copyargs *copyargs, int is_same_user);
static int index_storeflag(struct index_state *state,
struct index_modified_flags *modified_flags,
uint32_t msgno, struct index_record *record,
struct storeargs *storeargs);
static int index_store_annotation(struct index_state *state, uint32_t msgno,
struct storeargs *storeargs);
static int index_fetchreply(struct index_state *state, uint32_t msgno,
const struct fetchargs *fetchargs);
static void index_printflags(struct index_state *state, uint32_t msgno,
int usinguid, int printmodseq);
static char *get_localpart_addr(const char *header);
static char *get_displayname(const char *header);
static char *index_extract_subject(const char *subj, size_t len, int *is_refwd);
static char *_index_extract_subject(char *s, int *is_refwd);
static void index_get_ids(MsgData *msgdata,
char *envtokens[], const char *headers, unsigned size);
static MsgData **index_msgdata_load(struct index_state *state, unsigned *msgno_list, int n,
const struct sortcrit *sortcrit,
unsigned int anchor, int *found_anchor);
static void index_msgdata_free(MsgData **, unsigned int);
static int index_sort_compare(MsgData *md1, MsgData *md2,
const struct sortcrit *call_data);
static void index_msgdata_free(MsgData *md);
static int index_sort_compare_qsort(const void *v1, const void *v2);
static void *index_thread_getnext(Thread *thread);
static void index_thread_setnext(Thread *thread, Thread *next);
static int index_thread_compare(Thread *t1, Thread *t2,
const struct sortcrit *call_data);
static void index_thread_orderedsubj(struct index_state *state,
unsigned *msgno_list, unsigned int nmsg,
int usinguid);
static void index_thread_sort(Thread *root, const struct sortcrit *sortcrit);
static void index_thread_print(struct index_state *state,
Thread *threads, int usinguid);
static void index_thread_ref(struct index_state *state,
unsigned *msgno_list, unsigned int nmsg,
int usinguid);
static struct seqset *_parse_sequence(struct index_state *state,
const char *sequence, int usinguid);
static void appendsequencelist(struct index_state *state, struct seqset **l,
char *sequence, int usinguid);
static void massage_header(char *hdr);
/* NOTE: Make sure these are listed in CAPABILITY_STRING */
static const struct thread_algorithm thread_algs[] = {
{ "ORDEREDSUBJECT", index_thread_orderedsubj },
{ "REFERENCES", index_thread_ref },
{ NULL, NULL }
};
static int index_reload_record(struct index_state *state,
uint32_t msgno,
struct index_record *recordp)
{
struct index_map *im = &state->map[msgno-1];
int r = 0;
int i;
if (!im->recno) {
/* doh, gotta just fill in what we know */
memset(recordp, 0, sizeof(struct index_record));
recordp->uid = im->uid;
}
else {
r = mailbox_read_index_record(state->mailbox, im->recno, recordp);
}
/* NOTE: we have released the cyrus.index lock at this point, but are
* still holding the mailbox name relock. This means nobody can rewrite
* the file under us - so the offsets are still guaranteed to be correct,
* and all the immutable fields are unchanged. That said, we can get a
* read of a partially updated record which contains an invalid checksum
* due to incomplete concurrent changes to mutable fields.
*
* That's OK in just this case, because we're about to overwrite all the
* parsed mutable fields with the clean values we cached back when we had
* a cyrus.index lock and got a complete read. */
if (r == IMAP_MAILBOX_CHECKSUM) r = 0;
/* but other errors are still bad */
if (r) return r;
/* better be! */
assert(recordp->uid == im->uid);
/* restore mutable fields */
recordp->modseq = im->modseq;
recordp->system_flags = im->system_flags;
for (i = 0; i < MAX_USER_FLAGS/32; i++)
recordp->user_flags[i] = im->user_flags[i];
return 0;
}
static int index_rewrite_record(struct index_state *state,
uint32_t msgno,
struct index_record *recordp)
{
struct index_map *im = &state->map[msgno-1];
int i;
int r;
assert(recordp->uid == im->uid);
r = mailbox_rewrite_index_record(state->mailbox, recordp);
if (r) return r;
/* update tracking of mutable fields */
im->modseq = recordp->modseq;
im->system_flags = recordp->system_flags;
for (i = 0; i < MAX_USER_FLAGS/32; i++)
im->user_flags[i] = recordp->user_flags[i];
return 0;
}
EXPORTED void index_release(struct index_state *state)
{
if (!state) return;
if (state->mailbox) {
mailbox_close(&state->mailbox);
state->mailbox = NULL; /* should be done by close anyway */
}
}
static struct sortcrit *the_sortcrit;
/*
* A mailbox is about to be closed.
*/
EXPORTED void index_close(struct index_state **stateptr)
{
unsigned i;
struct index_state *state = *stateptr;
if (!state) return;
index_release(state);
free(state->map);
free(state->mboxname);
free(state->userid);
for (i = 0; i < MAX_USER_FLAGS; i++)
free(state->flagname[i]);
free(state);
*stateptr = NULL;
}
/*
* A new mailbox has been selected, map it into memory and do the
* initial CHECK.
*/
EXPORTED int index_open(const char *name, struct index_init *init,
struct index_state **stateptr)
{
int r;
struct index_state *state = xzmalloc(sizeof(struct index_state));
if (init) {
state->authstate = init->authstate;
state->examining = init->examine_mode;
state->mboxname = xstrdup(name);
state->out = init->out;
state->qresync = init->qresync;
state->userid = xstrdupnull(init->userid);
state->want_expunged = init->want_expunged;
if (state->examining) {
r = mailbox_open_irl(state->mboxname, &state->mailbox);
if (r) goto fail;
}
else {
r = mailbox_open_iwl(state->mboxname, &state->mailbox);
if (r) goto fail;
}
state->myrights = cyrus_acl_myrights(init->authstate,
state->mailbox->acl);
if (state->examining)
state->myrights &= ~ACL_READ_WRITE;
state->internalseen = mailbox_internal_seen(state->mailbox,
state->userid);
}
else {
r = mailbox_open_iwl(name, &state->mailbox);
if (r) goto fail;
}
if (state->mailbox->mbtype & MBTYPES_NONIMAP) {
r = IMAP_MAILBOX_BADTYPE;
goto fail;
}
/* initialise the index_state */
index_refresh(state);
/* have to get the vanished list while we're still locked */
if (init)
init->vanishedlist = index_vanished(state, &init->vanished);
index_unlock(state);
*stateptr = state;
return 0;
fail:
mailbox_close(&state->mailbox);
free(state->mboxname);
free(state->userid);
free(state);
return r;
}
EXPORTED int index_expunge(struct index_state *state, char *sequence,
int need_deleted)
{
int r;
uint32_t msgno;
struct index_map *im;
struct seqset *seq = NULL;
struct index_record record;
int numexpunged = 0;
struct mboxevent *mboxevent = NULL;
modseq_t oldmodseq;
r = index_lock(state);
if (r) return r;
/* XXX - earlier list if the sequence names UIDs that don't exist? */
seq = _parse_sequence(state, sequence, 1);
/* don't notify for messages that don't need \Deleted flag because
* a notification should be already send (eg. MessageMove) */
if (need_deleted)
mboxevent = mboxevent_new(EVENT_MESSAGE_EXPUNGE);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
if (im->system_flags & FLAG_EXPUNGED)
continue; /* already expunged */
if (need_deleted && !(im->system_flags & FLAG_DELETED))
continue; /* no \Deleted flag */
/* if there is a sequence list, check it */
if (sequence && !seqset_ismember(seq, im->uid))
continue; /* not in the list */
/* load first once we know we have to process this one */
if (index_reload_record(state, msgno, &record))
continue;
oldmodseq = im->modseq;
if (!im->isseen) {
state->numunseen--;
im->isseen = 1;
}
if (im->isrecent) {
state->numrecent--;
im->isrecent = 0;
}
if (state->want_expunged)
state->num_expunged++;
/* set the flags */
record.system_flags |= FLAG_DELETED | FLAG_EXPUNGED;
numexpunged++;
r = index_rewrite_record(state, msgno, &record);
if (r) break;
/* avoid telling again (equivalent to STORE FLAGS.SILENT) */
if (im->told_modseq == oldmodseq)
im->told_modseq = im->modseq;
mboxevent_extract_record(mboxevent, state->mailbox, &record);
}
seqset_free(seq);
mboxevent_extract_mailbox(mboxevent, state->mailbox);
mboxevent_set_access(mboxevent, NULL, NULL, state->userid, state->mailbox->name, 1);
mboxevent_set_numunseen(mboxevent, state->mailbox, state->numunseen);
/* unlock before responding */
index_unlock(state);
if (!r && (numexpunged > 0)) {
syslog(LOG_NOTICE, "Expunged %d messages from %s",
numexpunged, state->mboxname);
/* send the MessageExpunge event notification for "immediate", "default"
* and "delayed" expunge */
mboxevent_notify(mboxevent);
}
mboxevent_free(&mboxevent);
return r;
}
static char *index_buildseen(struct index_state *state, const char *oldseenuids)
{
struct seqset *outlist;
uint32_t msgno;
unsigned oldmax;
struct index_map *im;
char *out;
outlist = seqset_init(0, SEQ_MERGE);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
seqset_add(outlist, im->uid, im->isseen);
}
/* there may be future already seen UIDs that this process isn't
* allowed to know about, but we can't blat them either! This is
* a massive pain... */
oldmax = seq_lastnum(oldseenuids, NULL);
if (oldmax > state->last_uid) {
struct seqset *seq = seqset_parse(oldseenuids, NULL, oldmax);
uint32_t uid;
/* for each future UID, copy the state in the old seenuids */
for (uid = state->last_uid + 1; uid <= oldmax; uid++)
seqset_add(outlist, uid, seqset_ismember(seq, uid));
seqset_free(seq);
}
out = seqset_cstring(outlist);
seqset_free(outlist);
return out;
}
static int index_writeseen(struct index_state *state)
{
int r;
struct seen *seendb = NULL;
struct seendata oldsd = SEENDATA_INITIALIZER;
struct seendata sd = SEENDATA_INITIALIZER;
struct mailbox *mailbox = state->mailbox;
const char *userid = (mailbox->i.options & OPT_IMAP_SHAREDSEEN) ? "anyone" : state->userid;
if (!state->seen_dirty)
return 0;
state->seen_dirty = 0;
/* only examining, can't write any changes */
if (state->examining)
return 0;
/* already handled! Just update the header fields */
if (state->internalseen) {
mailbox_index_dirty(mailbox);
mailbox->i.recenttime = time(0);
if (mailbox->i.recentuid < state->last_uid)
mailbox->i.recentuid = state->last_uid;
return 0;
}
r = seen_open(userid, SEEN_CREATE, &seendb);
if (r) return r;
r = seen_lockread(seendb, mailbox->uniqueid, &oldsd);
if (r) {
oldsd.lastread = 0;
oldsd.lastuid = 0;
oldsd.lastchange = 0;
oldsd.seenuids = xstrdup("");
}
/* fields of interest... */
sd.lastuid = oldsd.lastuid;
sd.seenuids = index_buildseen(state, oldsd.seenuids);
if (!sd.seenuids) sd.seenuids = xstrdup("");
/* make comparison only catch some changes */
sd.lastread = oldsd.lastread;
sd.lastchange = oldsd.lastchange;
/* update \Recent lowmark */
if (sd.lastuid < state->last_uid)
sd.lastuid = state->last_uid;
/* only commit if interesting fields have changed */
if (!seen_compare(&sd, &oldsd)) {
sd.lastread = time(NULL);
sd.lastchange = mailbox->i.last_appenddate;
r = seen_write(seendb, mailbox->uniqueid, &sd);
}
seen_close(&seendb);
seen_freedata(&oldsd);
seen_freedata(&sd);
return r;
}
/* caller must free the list with seqset_free() when done */
static struct seqset *_readseen(struct index_state *state, unsigned *recentuid)
{
struct mailbox *mailbox = state->mailbox;
struct seqset *seenlist = NULL;
/* Obtain seen information */
if (state->internalseen) {
*recentuid = mailbox->i.recentuid;
}
else if (state->userid) {
struct seen *seendb = NULL;
struct seendata sd = SEENDATA_INITIALIZER;
const char *userid = (mailbox->i.options & OPT_IMAP_SHAREDSEEN) ? "anyone" : state->userid;
int r;
r = seen_open(userid, SEEN_CREATE, &seendb);
if (!r) r = seen_read(seendb, mailbox->uniqueid, &sd);
seen_close(&seendb);
/* handle no seen DB gracefully */
if (r) {
*recentuid = mailbox->i.last_uid;
prot_printf(state->out, "* OK (seen state failure) %s: %s\r\n",
error_message(IMAP_NO_CHECKPRESERVE), error_message(r));
syslog(LOG_ERR, "Could not open seen state for %s (%s)",
userid, error_message(r));
}
else {
*recentuid = sd.lastuid;
seenlist = seqset_parse(sd.seenuids, NULL, *recentuid);
seen_freedata(&sd);
}
}
else {
*recentuid = mailbox->i.last_uid; /* nothing is recent! */
}
return seenlist;
}
void index_refresh(struct index_state *state)
{
struct mailbox *mailbox = state->mailbox;
struct index_record record;
uint32_t recno;
uint32_t msgno = 1;
uint32_t firstnotseen = 0;
uint32_t numrecent = 0;
uint32_t numunseen = 0;
uint32_t recentuid;
struct index_map *im;
modseq_t delayed_modseq = 0;
uint32_t need_records;
struct seqset *seenlist;
int i;
/* need to start by having enough space for the entire index state
* before telling of any expunges (which happens after this refresh
* if the command allows it). In the update case, where there's
* already a map, we have to theoretically fit the number that existed
* last time plus however many new records might be unEXPUNGEd on the
* end */
if (state->last_uid) {
need_records = state->exists + (mailbox->i.last_uid - state->last_uid);
}
else if (state->want_expunged) {
/* could need the lot! */
need_records = mailbox->i.num_records;
}
else {
/* init case */
need_records = mailbox->i.exists;
}
/* make sure we have space */
if (need_records >= state->mapsize) {
state->mapsize = (need_records | 0xff) + 1; /* round up 1-256 */
state->map = xrealloc(state->map,
state->mapsize * sizeof(struct index_map));
}
seenlist = _readseen(state, &recentuid);
/* walk through all records */
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue; /* bogus read... should probably be fatal */
/* skip over map records where the mailbox doesn't have any
* data at all for the record any more (this can only happen
* after a repack), otherwise there will still be a readable
* record, which is handled below */
im = &state->map[msgno-1];
while (msgno <= state->exists && im->uid < record.uid) {
/* NOTE: this same logic is repeated below for messages
* past the end of recno (repack removing the trailing
* records). Make sure to keep them in sync */
if (!(im->system_flags & FLAG_EXPUNGED)) {
/* we don't even know the modseq of when it was wiped,
* but we can be sure it's since the last given highestmodseq,
* so simulate the lowest possible value. This is fine for
* our told_modseq logic, and doesn't have to be exact because
* QRESYNC/CONDSTORE clients will see deletedmodseq and fall
* back to the inefficient codepath anyway */
im->modseq = state->highestmodseq + 1;
}
if (!delayed_modseq || im->modseq < delayed_modseq)
delayed_modseq = im->modseq - 1;
im->recno = 0;
/* simulate expunged flag so we get an EXPUNGE response and
* tell about unlinked so we don't get IO errors trying to
* find the file */
im->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
im = &state->map[msgno++];
}
/* expunged record not in map, can skip immediately. It's
* never been told to this connection, so it doesn't need to
* get its own msgno */
if (!state->want_expunged
&& (msgno > state->exists || record.uid < im->uid)
&& (record.system_flags & FLAG_EXPUNGED))
continue;
/* make sure our UID map is consistent */
if (msgno <= state->exists) {
assert(im->uid == record.uid);
}
else {
im->uid = record.uid;
}
/* copy all mutable fields */
im->recno = recno;
im->modseq = record.modseq;
im->system_flags = record.system_flags;
for (i = 0; i < MAX_USER_FLAGS/32; i++)
im->user_flags[i] = record.user_flags[i];
/* for expunged records, just track the modseq */
if (!state->want_expunged && (im->system_flags & FLAG_EXPUNGED)) {
/* http://www.rfc-editor.org/errata_search.php?rfc=5162
* Errata ID: 1809 - if there are expunged records we
* aren't telling about, need to make the highestmodseq
* be one lower so the client can safely resync */
if (!delayed_modseq || im->modseq < delayed_modseq)
delayed_modseq = im->modseq - 1;
}
else {
/* re-calculate seen flags */
if (state->internalseen)
im->isseen = (im->system_flags & FLAG_SEEN) ? 1 : 0;
else
im->isseen = seqset_ismember(seenlist, im->uid) ? 1 : 0;
if (msgno > state->exists) {
/* don't auto-tell new records */
im->told_modseq = im->modseq;
if (im->uid > recentuid) {
/* mark recent if it's newly being added to the index and also
* greater than the recentuid - ensures only one session gets
* the \Recent flag for any one message */
im->isrecent = 1;
state->seen_dirty = 1;
}
else
im->isrecent = 0;
}
/* track select values */
if (!im->isseen) {
numunseen++;
if (!firstnotseen)
firstnotseen = msgno;
}
if (im->isrecent) {
numrecent++;
}
}
msgno++;
/* make sure we don't overflow the memory we mapped */
if (msgno > state->mapsize) {
char buf[2048];
sprintf(buf, "Exists wrong %u %u %u %u", msgno,
state->mapsize, mailbox->i.exists, mailbox->i.num_records);
fatal(buf, EC_IOERR);
}
}
/* may be trailing records which need to be considered for
* delayed_modseq purposes, and to get the count right for
* later expunge processing */
im = &state->map[msgno-1];
while (msgno <= state->exists) {
/* this is the same logic as the block above in the main loop,
* see comments up there, and make sure the blocks are kept
* in sync! */
if (!(im->system_flags & FLAG_EXPUNGED))
im->modseq = state->highestmodseq + 1;
if (!delayed_modseq || im->modseq < delayed_modseq)
delayed_modseq = im->modseq - 1;
im->recno = 0;
im->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
im = &state->map[msgno++];
}
seqset_free(seenlist);
/* update the header tracking data */
state->oldexists = state->exists; /* we last knew about this many */
state->exists = msgno - 1; /* we actually got this many */
state->delayed_modseq = delayed_modseq;
state->highestmodseq = mailbox->i.highestmodseq;
state->generation = mailbox->i.generation_no;
state->uidvalidity = mailbox->i.uidvalidity;
state->last_uid = mailbox->i.last_uid;
state->num_records = mailbox->i.num_records;
state->firstnotseen = firstnotseen;
state->numunseen = numunseen;
state->numrecent = numrecent;
}
EXPORTED modseq_t index_highestmodseq(struct index_state *state)
{
if (state->delayed_modseq)
return state->delayed_modseq;
return state->highestmodseq;
}
EXPORTED void index_select(struct index_state *state, struct index_init *init)
{
index_tellexists(state);
/* always print flags */
index_checkflags(state, 1, 1);
if (state->firstnotseen)
prot_printf(state->out, "* OK [UNSEEN %u] Ok\r\n",
state->firstnotseen);
prot_printf(state->out, "* OK [UIDVALIDITY %u] Ok\r\n",
state->mailbox->i.uidvalidity);
prot_printf(state->out, "* OK [UIDNEXT %lu] Ok\r\n",
state->last_uid + 1);
prot_printf(state->out, "* OK [HIGHESTMODSEQ " MODSEQ_FMT "] Ok\r\n",
state->highestmodseq);
prot_printf(state->out, "* OK [URLMECH INTERNAL] Ok\r\n");
/*
* RFC5257. Note that we must report a maximum size for annotations
* but we don't enforce any such limit, so pick a "large" number.
*/
prot_printf(state->out, "* OK [ANNOTATIONS %u] Ok\r\n", 64*1024);
if (init->vanishedlist) {
char *vanished;
const char *sequence = NULL;
struct seqset *seq = NULL;
struct index_map *im;
uint32_t msgno;
/* QRESYNC response:
* UID FETCH seq FLAGS (CHANGEDSINCE modseq VANISHED)
*/
vanished = seqset_cstring(init->vanishedlist);
if (vanished) {
prot_printf(state->out, "* VANISHED (EARLIER) %s\r\n", vanished);
free(vanished);
}
sequence = init->vanished.sequence;
if (sequence) seq = _parse_sequence(state, sequence, 1);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
if (sequence && !seqset_ismember(seq, im->uid))
continue;
if (im->modseq <= init->vanished.modseq)
continue;
index_printflags(state, msgno, 1, 0);
}
seqset_free(seq);
}
}
/*
* Check for and report updates
*/
EXPORTED int index_check(struct index_state *state, int usinguid, int printuid)
{
int r;
if (!state) return 0;
r = index_lock(state);
/* Check for deleted mailbox */
if (r == IMAP_MAILBOX_NONEXISTENT) {
/* Mailbox has been (re)moved */
if (config_getswitch(IMAPOPT_DISCONNECT_ON_VANISHED_MAILBOX)) {
syslog(LOG_WARNING,
"Mailbox %s has been (re)moved out from under client",
state->mboxname);
mailbox_close(&state->mailbox);
fatal("Mailbox has been (re)moved", EC_IOERR);
}
if (state->exists && state->qresync) {
/* XXX - is it OK to just expand to entire possible range? */
prot_printf(state->out, "* VANISHED 1:%lu\r\n", state->last_uid);
}
else {
int exists;
for (exists = state->exists; exists > 0; exists--) {
prot_printf(state->out, "* 1 EXPUNGE\r\n");
}
}
state->exists = 0;
return IMAP_MAILBOX_NONEXISTENT;
}
if (r) return r;
index_tellchanges(state, usinguid, printuid, 0);
#if TOIMSP
if (state->firstnotseen) {
toimsp(state->mboxname, state->mailbox->i.uidvalidity, "SEENsnn", state->userid,
0, state->mailbox->i.recenttime, 0);
}
else {
toimsp(state->mboxname, state->mailbox->i.uidvalidity, "SEENsnn", state->userid,
state->mailbox->last_uid, state->mailbox->i.recenttime, 0);
}
#endif
index_unlock(state);
return r;
}
/*
* Perform UID FETCH (VANISHED) on a sequence.
*/
struct seqset *index_vanished(struct index_state *state,
struct vanished_params *params)
{
struct mailbox *mailbox = state->mailbox;
struct index_record record;
struct seqset *outlist;
struct seqset *seq;
uint32_t recno;
/* check uidvalidity match */
if (params->uidvalidity_is_max) {
if (params->uidvalidity < mailbox->i.uidvalidity) return NULL;
}
else {
if (params->uidvalidity != mailbox->i.uidvalidity) return NULL;
}
/* No recently expunged messages */
if (params->modseq >= state->highestmodseq) return NULL;
outlist = seqset_init(0, SEQ_SPARSE);
seq = _parse_sequence(state, params->sequence, 1);
/* XXX - use match_seq and match_uid */
if (params->modseq >= mailbox->i.deletedmodseq) {
/* all records are significant */
/* List only expunged UIDs with MODSEQ > requested */
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue;
if (!(record.system_flags & FLAG_EXPUNGED))
continue;
if (record.modseq <= params->modseq)
continue;
if (!params->sequence || seqset_ismember(seq, record.uid))
seqset_add(outlist, record.uid, 1);
}
}
else {
unsigned prevuid = 0;
struct seqset *msgnolist;
struct seqset *uidlist;
uint32_t msgno;
unsigned uid;
syslog(LOG_NOTICE, "inefficient qresync ("
MODSEQ_FMT " > " MODSEQ_FMT ") %s",
mailbox->i.deletedmodseq, params->modseq,
mailbox->name);
recno = 1;
/* use the sequence to uid mapping provided by the client to
* skip over any initial matches - see RFC 5162 section 3.1 */
if (params->match_seq && params->match_uid) {
msgnolist = _parse_sequence(state, params->match_seq, 0);
uidlist = _parse_sequence(state, params->match_uid, 1);
while ((msgno = seqset_getnext(msgnolist)) != 0) {
uid = seqset_getnext(uidlist);
/* first non-match, we'll start here */
if (state->map[msgno-1].uid != uid)
break;
/* ok, they matched - so we can start at the recno and UID
* first past the match */
prevuid = uid;
recno = state->map[msgno-1].recno + 1;
}
seqset_free(msgnolist);
seqset_free(uidlist);
}
/* possible efficiency improvement - use "seq_getnext" on seq
* to avoid incrementing through every single number for prevuid.
* Only really an issue if there's a giant block of thousands of
* expunged messages. Only likely to be seen in the wild if
* last_uid winds up being bumped up a few million by a bug... */
/* for the rest of the mailbox, we're just going to have to assume
* every record in the requested range which DOESN'T exist has been
* expunged, so build a complete sequence */
for (; recno <= mailbox->i.num_records; recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue;
if (record.system_flags & FLAG_EXPUNGED)
continue;
while (++prevuid < record.uid) {
if (!params->sequence || seqset_ismember(seq, prevuid))
seqset_add(outlist, prevuid, 1);
}
prevuid = record.uid;
}
/* include the space past the final record up to last_uid as well */
while (++prevuid <= mailbox->i.last_uid) {
if (!params->sequence || seqset_ismember(seq, prevuid))
seqset_add(outlist, prevuid, 1);
}
}
seqset_free(seq);
return outlist;
}
static int _fetch_setseen(struct index_state *state,
struct mboxevent *mboxevent,
uint32_t msgno)
{
struct index_map *im = &state->map[msgno-1];
struct index_record record;
int r;
/* already seen */
if (im->isseen)
return 0;
/* no rights to change it */
if (!(state->myrights & ACL_SETSEEN))
return 0;
r = index_reload_record(state, msgno, &record);
if (r) return r;
/* track changes internally */
state->numunseen--;
state->seen_dirty = 1;
im->isseen = 1;
/* also store in the record if it's internal seen */
if (state->internalseen)
record.system_flags |= FLAG_SEEN;
/* need to bump modseq anyway, so always rewrite it */
r = index_rewrite_record(state, msgno, &record);
if (r) return r;
mboxevent_extract_record(mboxevent, state->mailbox, &record);
/* RFC2060 says:
* The \Seen flag is implicitly set; if this causes
* the flags to change they SHOULD be included as part
* of the FETCH responses. This is handled later by
* always including flags if the modseq has changed.
*/
return 0;
}
/* seq can be NULL - means "ALL" */
EXPORTED void index_fetchresponses(struct index_state *state,
struct seqset *seq,
int usinguid,
const struct fetchargs *fetchargs,
int *fetchedsomething)
{
uint32_t msgno, start, end;
struct index_map *im;
int fetched = 0;
annotate_db_t *annot_db = NULL;
/* Keep an open reference on the per-mailbox db to avoid
* doing too many slow database opens during the fetch */
if ((fetchargs->fetchitems & FETCH_ANNOTATION))
annotate_getdb(state->mboxname, &annot_db);
start = 1;
end = state->exists;
/* compress the search range down if a sequence was given */
if (seq) {
unsigned first = seqset_first(seq);
unsigned last = seqset_last(seq);
if (usinguid) {
if (first > 1)
start = index_finduid(state, first);
if (first == last)
end = start;
else if (last < state->last_uid)
end = index_finduid(state, last);
}
else {
start = first;
end = last;
}
}
/* make sure we didn't go outside the range! */
if (start < 1) start = 1;
if (end > state->exists) end = state->exists;
for (msgno = start; msgno <= end; msgno++) {
im = &state->map[msgno-1];
if (seq && !seqset_ismember(seq, usinguid ? im->uid : msgno))
continue;
if (index_fetchreply(state, msgno, fetchargs))
break;
fetched = 1;
}
if (fetchedsomething) *fetchedsomething = fetched;
annotate_putdb(&annot_db);
}
/*
* Perform a FETCH-related command on a sequence.
* Fetchedsomething argument is 0 if nothing was fetched, 1 if something was
* fetched. (A fetch command that fetches nothing is not a valid fetch
* command.)
*/
EXPORTED int index_fetch(struct index_state *state,
const char *sequence,
int usinguid,
const struct fetchargs *fetchargs,
int *fetchedsomething)
{
struct seqset *seq;
struct seqset *vanishedlist = NULL;
struct index_map *im;
uint32_t msgno;
int r;
struct mboxevent *mboxevent = NULL;
r = index_lock(state);
if (r) return r;
seq = _parse_sequence(state, sequence, usinguid);
/* set the \Seen flag if necessary - while we still have the lock */
if (fetchargs->fetchitems & FETCH_SETSEEN && !state->examining && state->myrights & ACL_SETSEEN) {
mboxevent = mboxevent_new(EVENT_MESSAGE_READ);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
if (!seqset_ismember(seq, usinguid ? im->uid : msgno))
continue;
r = _fetch_setseen(state, mboxevent, msgno);
if (r) break;
}
mboxevent_extract_mailbox(mboxevent, state->mailbox);
mboxevent_set_access(mboxevent, NULL, NULL, state->userid, state->mailbox->name, 1);
mboxevent_set_numunseen(mboxevent, state->mailbox,
state->numunseen);
}
if (fetchargs->vanished) {
struct vanished_params v;
v.sequence = sequence;;
v.uidvalidity = state->mailbox->i.uidvalidity;
v.modseq = fetchargs->changedsince;
v.match_seq = fetchargs->match_seq;
v.match_uid = fetchargs->match_uid;
/* XXX - return error unless usinguid? */
vanishedlist = index_vanished(state, &v);
}
index_unlock(state);
/* send MessageRead event notification for successfully rewritten records */
mboxevent_notify(mboxevent);
mboxevent_free(&mboxevent);
index_checkflags(state, 1, 0);
if (vanishedlist && vanishedlist->len) {
char *vanished = seqset_cstring(vanishedlist);
prot_printf(state->out, "* VANISHED (EARLIER) %s\r\n", vanished);
free(vanished);
}
seqset_free(vanishedlist);
index_fetchresponses(state, seq, usinguid, fetchargs, fetchedsomething);
seqset_free(seq);
index_tellchanges(state, usinguid, usinguid, 0);
return r;
}
/*
* Perform a STORE command on a sequence
*/
EXPORTED int index_store(struct index_state *state, char *sequence,
struct storeargs *storeargs)
{
struct mailbox *mailbox;
int i, r = 0;
uint32_t msgno;
int userflag;
struct seqset *seq;
struct index_map *im;
const strarray_t *flags = &storeargs->flags;
struct mboxevent *mboxevents = NULL;
struct mboxevent *flagsset = NULL, *flagsclear = NULL;
struct index_modified_flags modified_flags;
struct index_record record;
/* First pass at checking permission */
if ((storeargs->seen && !(state->myrights & ACL_SETSEEN)) ||
((storeargs->system_flags & FLAG_DELETED) &&
!(state->myrights & ACL_DELETEMSG)) ||
(((storeargs->system_flags & ~FLAG_DELETED) || flags->count) &&
!(state->myrights & ACL_WRITE))) {
return IMAP_PERMISSION_DENIED;
}
r = index_lock(state);
if (r) return r;
mailbox = state->mailbox;
seq = _parse_sequence(state, sequence, storeargs->usinguid);
for (i = 0; i < flags->count ; i++) {
r = mailbox_user_flag(mailbox, flags->data[i], &userflag, 1);
if (r) goto out;
storeargs->user_flags[userflag/32] |= 1<<(userflag&31);
}
storeargs->update_time = time((time_t *)0);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
if (!seqset_ismember(seq, storeargs->usinguid ? im->uid : msgno))
continue;
/* if it's expunged already, skip it now */
if ((im->system_flags & FLAG_EXPUNGED))
continue;
/* if it's changed already, skip it now */
if (im->modseq > storeargs->unchangedsince) {
if (!storeargs->modified) {
uint32_t maxval = (storeargs->usinguid ?
state->last_uid : state->exists);
storeargs->modified = seqset_init(maxval, SEQ_SPARSE);
}
seqset_add(storeargs->modified,
(storeargs->usinguid ? im->uid : msgno),
/*ismember*/1);
continue;
}
r = index_reload_record(state, msgno, &record);
if (r) goto out;
switch (storeargs->operation) {
case STORE_ADD_FLAGS:
case STORE_REMOVE_FLAGS:
case STORE_REPLACE_FLAGS:
r = index_storeflag(state, &modified_flags, msgno, &record, storeargs);
if (r)
break;
if (modified_flags.added_flags) {
if (flagsset == NULL)
flagsset = mboxevent_enqueue(EVENT_FLAGS_SET, &mboxevents);
mboxevent_add_flags(flagsset, mailbox->flagname,
modified_flags.added_system_flags,
modified_flags.added_user_flags);
mboxevent_extract_record(flagsset, mailbox, &record);
}
if (modified_flags.removed_flags) {
if (flagsclear == NULL)
flagsclear = mboxevent_enqueue(EVENT_FLAGS_CLEAR, &mboxevents);
mboxevent_add_flags(flagsclear, mailbox->flagname,
modified_flags.removed_system_flags,
modified_flags.removed_user_flags);
mboxevent_extract_record(flagsclear, mailbox, &record);
}
break;
case STORE_ANNOTATION:
r = index_store_annotation(state, msgno, storeargs);
break;
default:
r = IMAP_INTERNAL;
break;
}
if (r) goto out;
}
/* let mboxevent_notify split FlagsSet into MessageRead, MessageTrash
* and FlagsSet events */
mboxevent_extract_mailbox(flagsset, mailbox);
mboxevent_set_numunseen(flagsset, mailbox, state->numunseen);
mboxevent_set_access(flagsset, NULL, NULL, state->userid, state->mailbox->name, 1);
mboxevent_extract_mailbox(flagsclear, mailbox);
mboxevent_set_access(flagsclear, NULL, NULL, state->userid, state->mailbox->name, 1);
mboxevent_set_numunseen(flagsclear, mailbox, state->numunseen);
mboxevent_notify(mboxevents);
mboxevent_freequeue(&mboxevents);
out:
if (storeargs->operation == STORE_ANNOTATION && r)
annotate_state_abort(&mailbox->annot_state);
seqset_free(seq);
index_unlock(state);
index_tellchanges(state, storeargs->usinguid, storeargs->usinguid,
(storeargs->unchangedsince != ~0ULL));
return r;
}
static void prefetch_messages(struct index_state *state,
struct seqset *seq,
int usinguid)
{
struct mailbox *mailbox = state->mailbox;
struct index_map *im;
uint32_t msgno;
const char *fname;
struct index_record record;
syslog(LOG_ERR, "Prefetching initial parts of messages\n");
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
if (!seqset_ismember(seq, usinguid ? im->uid : msgno))
continue;
if (index_reload_record(state, msgno, &record))
continue;
fname = mailbox_record_fname(mailbox, &record);
if (!fname)
continue;
warmup_file(fname, 0, 16384);
}
}
/*
* Perform the XRUNANNOTATOR command which runs the
* annotator callout for each message in the given sequence.
*/
EXPORTED int index_run_annotator(struct index_state *state,
const char *sequence, int usinguid,
struct namespace *namespace, int isadmin)
{
struct index_record record;
struct seqset *seq = NULL;
struct index_map *im;
uint32_t msgno;
struct appendstate as;
int r = 0;
/* We do the acl check here rather than in append_setup_mbox()
* to account for the EXAMINE command where state->myrights has
* fewer rights than the ACL actually grants */
if (!(state->myrights & (ACL_WRITE|ACL_ANNOTATEMSG)))
return IMAP_PERMISSION_DENIED;
if (!config_getstring(IMAPOPT_ANNOTATION_CALLOUT))
return 0;
r = index_lock(state);
if (r) return r;
r = append_setup_mbox(&as, state->mailbox,
state->userid, state->authstate,
0, NULL, namespace, isadmin, 0);
if (r) goto out;
seq = _parse_sequence(state, sequence, usinguid);
if (!seq) goto out;
prefetch_messages(state, seq, usinguid);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
if (!seqset_ismember(seq, usinguid ? im->uid : msgno))
continue;
/* if it's expunged already, skip it now */
if ((im->system_flags & FLAG_EXPUNGED))
continue;
r = index_reload_record(state, msgno, &record);
if (r) goto out;
r = append_run_annotator(&as, &record);
if (r) goto out;
r = index_rewrite_record(state, msgno, &record);
if (r) goto out;
}
out:
seqset_free(seq);
if (!r) {
r = append_commit(&as);
}
else {
append_abort(&as);
}
index_unlock(state);
index_tellchanges(state, usinguid, usinguid, 1);
return r;
}
EXPORTED int index_warmup(struct mboxlist_entry *mbentry, unsigned int warmup_flags)
{
const char *fname = NULL;
char *tofree1 = NULL;
char *tofree2 = NULL;
int r = 0;
if (warmup_flags & WARMUP_INDEX) {
fname = mboxname_metapath(mbentry->partition, mbentry->name, META_INDEX, 0);
r = warmup_file(fname, 0, 0);
if (r) goto out;
}
if (warmup_flags & WARMUP_CONVERSATIONS) {
if (config_getswitch(IMAPOPT_CONVERSATIONS)) {
fname = tofree1 = conversations_getmboxpath(mbentry->name);
r = warmup_file(fname, 0, 0);
if (r) goto out;
}
}
if (warmup_flags & WARMUP_ANNOTATIONS) {
fname = mboxname_metapath(mbentry->partition, mbentry->name, META_ANNOTATIONS, 0);
r = warmup_file(fname, 0, 0);
if (r) goto out;
}
if (warmup_flags & WARMUP_FOLDERSTATUS) {
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
fname = tofree2 = statuscache_filename();
r = warmup_file(fname, 0, 0);
if (r) goto out;
}
}
out:
if (r == ENOENT || r == ENOSYS)
r = 0;
if (r)
syslog(LOG_ERR, "IOERROR: unable to warmup file %s: %s",
fname, error_message(r));
free(tofree1);
free(tofree2);
return r;
}
static void build_query_part(search_builder_t *bx,
struct strlist **slp,
int part, int remove,
int *nmatchesp)
{
struct strlist *s;
for (s = (*slp) ; s ; s = s->next) {
bx->match(bx, part, s->s);
(*nmatchesp)++;
}
if (remove) {
freestrlist(*slp);
*slp = NULL;
}
}
static void build_query(search_builder_t *bx,
struct searchargs *searchargs,
int remove,
int *nmatchesp)
{
struct searchsub* sub;
bx->begin_boolean(bx, SEARCH_OP_AND);
build_query_part(bx, &searchargs->from, SEARCH_PART_FROM, remove, nmatchesp);
build_query_part(bx, &searchargs->to, SEARCH_PART_TO, remove, nmatchesp);
build_query_part(bx, &searchargs->cc, SEARCH_PART_CC, remove, nmatchesp);
build_query_part(bx, &searchargs->bcc, SEARCH_PART_BCC, remove, nmatchesp);
build_query_part(bx, &searchargs->subject, SEARCH_PART_SUBJECT, remove, nmatchesp);
/* Note - we cannot remove when searching headers, as we need
* to match both the header field name and value, which always
* requires a post filter pass */
build_query_part(bx, &searchargs->header_name, SEARCH_PART_HEADERS, 0, nmatchesp);
build_query_part(bx, &searchargs->header, SEARCH_PART_HEADERS, 0, nmatchesp);
build_query_part(bx, &searchargs->body, SEARCH_PART_BODY, remove, nmatchesp);
build_query_part(bx, &searchargs->listid, SEARCH_PART_LISTID, remove, nmatchesp);
build_query_part(bx, &searchargs->contenttype, SEARCH_PART_TYPE, remove, nmatchesp);
build_query_part(bx, &searchargs->text, SEARCH_PART_ANY, remove, nmatchesp);
for (sub = searchargs->sublist ; sub ; sub = sub->next) {
assert(!remove);
if (sub->sub2 == NULL) {
/* do nothing; because our search is conservative (may include false
positives) we can't compute the NOT (since the result might include
false negatives, which we do not allow) */
/* Note that it's OK to do nothing. We'll just be returning more
false positives. */
} else {
bx->begin_boolean(bx, SEARCH_OP_OR);
build_query(bx, sub->sub1, remove, nmatchesp);
build_query(bx, sub->sub2, remove, nmatchesp);
bx->end_boolean(bx, SEARCH_OP_OR);
}
}
bx->end_boolean(bx, SEARCH_OP_AND);
}
static int index_prefilter_messages(unsigned* msg_list,
struct index_state *state,
struct searchargs *searchargs __attribute((unused)))
{
unsigned int msgno;
xstats_inc(SEARCH_TRIVIAL);
/* Just put in all possible messages. This falls back to Cyrus' default
* search. */
for (msgno = 1; msgno <= state->exists; msgno++)
msg_list[msgno-1] = msgno;
return state->exists;
}
static int index_scan_work(const char *s, unsigned long len,
const char *match, unsigned long min)
{
while (len > min) {
if (!strncasecmp(s, match, min)) return(1);
s++;
len--;
}
return(0);
}
/*
* Guts of the SCAN command, lifted from _index_search()
*
* Returns 1 if we get a hit, otherwise returns 0.
*/
EXPORTED int index_scan(struct index_state *state, const char *contents)
{
unsigned *msgno_list;
uint32_t msgno;
int n = 0;
int listindex;
int listcount;
struct searchargs searchargs;
unsigned long length;
struct mailbox *mailbox = state->mailbox;
if (!(contents && contents[0])) return(0);
if (index_check(state, 0, 0))
return 0;
if (state->exists <= 0) return 0;
length = strlen(contents);
memset(&searchargs, 0, sizeof(struct searchargs));
searchargs.root = search_expr_new(NULL, SEOP_MATCH);
searchargs.root->attr = search_attr_find("text");
/* Use US-ASCII to emulate fgrep */
searchargs.root->value.s = charset_convert(contents, charset_lookupname("US-ASCII"),
charset_flags);
search_expr_internalise(mailbox, searchargs.root);
msgno_list = (unsigned *) xmalloc(state->exists * sizeof(unsigned));
listcount = index_prefilter_messages(msgno_list, state, &searchargs);
for (listindex = 0; !n && listindex < listcount; listindex++) {
struct buf buf = BUF_INITIALIZER;
struct index_record record;
msgno = msgno_list[listindex];
if (index_reload_record(state, msgno, &record))
continue;
if (mailbox_map_record(mailbox, &record, &buf))
continue;
n += index_scan_work(buf.s, buf.len, contents, length);
buf_free(&buf);
}
search_expr_free(searchargs.root);
free(msgno_list);
return n;
}
EXPORTED message_t *index_get_message(struct index_state *state, uint32_t msgno)
{
struct index_map *im = &state->map[msgno-1];
uint32_t indexflags = 0;
if (im->isseen) indexflags |= MESSAGE_SEEN;
if (im->isrecent) indexflags |= MESSAGE_RECENT;
return message_new_from_index(state->mailbox, &record,
msgno, indexflags);
}
/*
* Guts of the SEARCH command.
*
* Returns message numbers in an array. This function is used by
* SEARCH, SORT and THREAD.
*/
static int _index_search(unsigned **msgno_list, struct index_state *state,
struct searchargs *searchargs,
modseq_t *highestmodseq)
{
uint32_t msgno;
int n = 0;
int listindex, min;
int listcount;
struct index_map *im;
if (state->exists <= 0) return 0;
*msgno_list = (unsigned *) xmalloc(state->exists * sizeof(unsigned));
/* OK, so I'm being a bit clever here. We fill the msgno list with
a list of message IDs returned by the search engine. Then we
scan through the list and store matching message IDs back into the
list. This is OK because we only overwrite message IDs that we've
already looked at. */
listcount = index_prefilter_messages(*msgno_list, state, searchargs);
if (searchargs->returnopts == SEARCH_RETURN_MAX) {
/* If we only want MAX, then skip forward search,
and do complete reverse search */
listindex = listcount;
min = 0;
} else {
/* Otherwise use forward search, potentially skipping reverse search */
listindex = 0;
min = listcount;
}
/* Forward search. Used for everything other than MAX-only */
for (; listindex < listcount; listindex++) {
msgno = (*msgno_list)[listindex];
im = &state->map[msgno-1];
/* expunged messages hardly ever match */
if (!state->want_expunged && (im->system_flags & FLAG_EXPUNGED))
continue;
if (index_search_evaluate(state, searchargs, msgno)) {
(*msgno_list)[n++] = msgno;
if (highestmodseq && im->modseq > *highestmodseq) {
*highestmodseq = im->modseq;
}
/* See if we should short-circuit
(we want MIN, but NOT COUNT or ALL) */
if ((searchargs->returnopts & SEARCH_RETURN_MIN) &&
!(searchargs->returnopts & SEARCH_RETURN_COUNT) &&
!(searchargs->returnopts & SEARCH_RETURN_ALL)) {
if (searchargs->returnopts & SEARCH_RETURN_MAX) {
/* If we want MAX, setup for reverse search */
min = listindex;
}
/* We're done */
listindex = listcount;
if (highestmodseq)
*highestmodseq = im->modseq;
}
}
}
/* Reverse search. Stops at previously found MIN (if any) */
for (listindex = listcount; listindex > min; listindex--) {
msgno = (*msgno_list)[listindex-1];
im = &state->map[msgno-1];
/* expunged messages hardly ever match */
if (!state->want_expunged && (im->system_flags & FLAG_EXPUNGED))
continue;
if (index_search_evaluate(state, searchargs, msgno)) {
(*msgno_list)[n++] = msgno;
if (highestmodseq && im->modseq > *highestmodseq) {
*highestmodseq = im->modseq;
}
/* We only care about MAX, so we're done on first match */
listindex = 0;
}
}
/* if we didn't find any matches, free msgno_list */
if (!n && *msgno_list) {
free(*msgno_list);
*msgno_list = NULL;
}
return n;
}
EXPORTED uint32_t index_getuid(struct index_state *state, uint32_t msgno)
{
assert(msgno <= state->exists);
return state->map[msgno-1].uid;
}
/* 'uid_list' is malloc'd string representing the hits from searchargs;
returns number of hits */
EXPORTED int index_getuidsequence(struct index_state *state,
struct searchargs *searchargs,
unsigned **uid_list)
{
unsigned *msgno_list;
int i, n;
n = _index_search(&msgno_list, state, searchargs, NULL);
if (n == 0) {
*uid_list = NULL;
return 0;
}
*uid_list = msgno_list;
/* filthy in-place replacement */
for (i = 0; i < n; i++)
(*uid_list)[i] = index_getuid(state, msgno_list[i]);
return n;
}
static int index_lock(struct index_state *state)
{
int r;
if (state->mailbox) {
if (state->examining) {
r = mailbox_lock_index(state->mailbox, LOCK_SHARED);
if (r) return r;
}
else {
r = mailbox_lock_index(state->mailbox, LOCK_EXCLUSIVE);
if (r) return r;
}
}
else {
if (state->examining) {
r = mailbox_open_irl(state->mboxname, &state->mailbox);
if (r) return r;
}
else {
r = mailbox_open_iwl(state->mboxname, &state->mailbox);
if (r) return r;
}
}
/* if the UIDVALIDITY has changed, treat as a delete */
if (state->mailbox->i.uidvalidity != state->uidvalidity) {
mailbox_close(&state->mailbox);
return IMAP_MAILBOX_NONEXISTENT;
}
/* if highestmodseq has changed or file is repacked, read updates */
if (state->highestmodseq != state->mailbox->i.highestmodseq
|| state->generation != state->mailbox->i.generation_no)
index_refresh(state);
return 0;
}
EXPORTED int index_status(struct index_state *state, struct statusdata *sdata)
{
int items = STATUS_MESSAGES | STATUS_UIDNEXT | STATUS_UIDVALIDITY |
STATUS_HIGHESTMODSEQ | STATUS_RECENT | STATUS_UNSEEN;
index_refresh(state);
statuscache_fill(sdata, state->userid, state->mailbox, items,
state->numrecent, state->numunseen);
return 0;
}
static void index_unlock(struct index_state *state)
{
/* XXX - errors */
index_writeseen(state);
/* grab the latest modseq */
state->highestmodseq = state->mailbox->i.highestmodseq;
mailbox_unlock_index(state->mailbox, NULL);
}
/*
* RFC 4551 says:
* If client specifies a MODSEQ criterion in a SEARCH command
* and the server returns a non-empty SEARCH result, the server
* MUST also append (to the end of the untagged SEARCH response)
* the highest mod-sequence for all messages being returned.
*/
static int needs_modseq(const struct searchargs *searchargs,
const struct sortcrit *sortcrit)
{
int i;
if (search_expr_uses_attr(searchargs->root, "modseq"))
return 1;
if (sortcrit) {
for (i = 0 ; sortcrit[i].key != SORT_SEQUENCE ; i++)
if (sortcrit[i].key == SORT_MODSEQ)
return 1;
}
return 0;
}
/*
* Performs a SEARCH command.
* This is a wrapper around _index_search() which simply prints the results.
*/
EXPORTED int index_search(struct index_state *state, struct searchargs *searchargs,
int usinguid)
{
unsigned *list = NULL;
int i, n;
modseq_t highestmodseq = 0;
/* update the index */
if (index_check(state, 0, 0))
return 0;
search_expr_internalise(state->mailbox, searchargs->root);
/* now do the search */
n = _index_search(&list, state, searchargs,
needs_modseq(searchargs, NULL)
? &highestmodseq : NULL);
/* replace the values now */
if (usinguid)
for (i = 0; i < n; i++)
list[i] = state->map[list[i]-1].uid;
if (searchargs->returnopts) {
prot_printf(state->out, "* ESEARCH");
if (searchargs->tag) {
prot_printf(state->out, " (TAG \"%s\")", searchargs->tag);
}
if (n) {
if (usinguid) prot_printf(state->out, " UID");
if (searchargs->returnopts & SEARCH_RETURN_MIN)
prot_printf(state->out, " MIN %u", list[0]);
if (searchargs->returnopts & SEARCH_RETURN_MAX)
prot_printf(state->out, " MAX %u", list[n-1]);
if (highestmodseq)
prot_printf(state->out, " MODSEQ " MODSEQ_FMT, highestmodseq);
if (searchargs->returnopts & SEARCH_RETURN_ALL) {
struct seqset *seq;
char *str;
/* Create a sequence-set */
seq = seqset_init(0, SEQ_SPARSE);
for (i = 0; i < n; i++)
seqset_add(seq, list[i], 1);
if (seq->len) {
str = seqset_cstring(seq);
prot_printf(state->out, " ALL %s", str);
free(str);
}
seqset_free(seq);
}
}
if (searchargs->returnopts & SEARCH_RETURN_COUNT) {
prot_printf(state->out, " COUNT %u", n);
}
}
else {
prot_printf(state->out, "* SEARCH");
for (i = 0; i < n; i++)
prot_printf(state->out, " %u", list[i]);
if (highestmodseq)
prot_printf(state->out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq);
}
if (n) free(list);
prot_printf(state->out, "\r\n");
return n;
}
/*
* Performs a SORT command
*/
EXPORTED int index_sort(struct index_state *state,
const struct sortcrit *sortcrit,
struct searchargs *searchargs, int usinguid)
{
unsigned *msgno_list = NULL;
MsgData **msgdata = NULL;
int mi;
int nmsg = 0;
modseq_t highestmodseq = 0;
/* update the index */
if (index_check(state, 0, 0))
return 0;
search_expr_internalise(state->mailbox, searchargs->root);
/* Search for messages based on the given criteria */
nmsg = _index_search(&msgno_list, state, searchargs,
needs_modseq(searchargs, sortcrit) ?
&highestmodseq : NULL);
prot_printf(state->out, "* SORT");
if (nmsg) {
/* Create/load the msgdata array */
msgdata = index_msgdata_load(state, msgno_list, nmsg, sortcrit, 0, NULL);
free(msgno_list);
/* Sort the messages based on the given criteria */
the_sortcrit = sortcrit;
qsort(msgdata, nmsg, sizeof(MsgData *), index_sort_compare_qsort);
/* Output the sorted messages */
for (mi = 0 ; mi < nmsg ; mi++) {
MsgData *msg = msgdata[mi];
unsigned no = usinguid ? state->map[msg->msgno-1].uid
: msg->msgno;
prot_printf(state->out, " %u", no);
}
/* free the msgdata array */
index_msgdata_free(msgdata, nmsg);
}
if (highestmodseq)
prot_printf(state->out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq);
prot_printf(state->out, "\r\n");
return nmsg;
}
static int is_mutable_sort(struct sortcrit *sortcrit)
{
int i;
if (!sortcrit) return 0;
for (i = 0; sortcrit[i].key; i++) {
switch (sortcrit[i].key) {
/* these are the mutable fields */
case SORT_ANNOTATION:
case SORT_MODSEQ:
case SORT_HASFLAG:
case SORT_CONVMODSEQ:
case SORT_CONVEXISTS:
case SORT_CONVSIZE:
case SORT_HASCONVFLAG:
return 1;
default:
break;
}
}
return 0;
}
/* This function will return a TRUE value if anything in the
* sort or search criteria returns a MUTABLE ordering, i.e.
* the user can take actions which will change the order in
* which the results are returned. For example, the base
* case of UID sort and all messages is NOT mutable */
static int is_mutable_ordering(struct sortcrit *sortcrit,
struct searchargs *searchargs)
{
if (is_mutable_sort(sortcrit))
return 1;
if (search_expr_is_mutable(searchargs->root))
return 1;
return 0;
}
#define UNPREDICTABLE (-1)
static int search_predict_total(struct index_state *state,
struct conversations_state *cstate,
const struct searchargs *searchargs,
int conversations,
modseq_t *xconvmodseqp)
{
conv_status_t convstatus = CONV_STATUS_INIT;
uint32_t exists;
if (conversations) {
conversation_getstatus(cstate, state->mailbox->name, &convstatus);
/* always grab xconvmodseq, so we report a growing
* highestmodseq to all callers */
if (xconvmodseqp) *xconvmodseqp = convstatus.modseq;
exists = convstatus.exists;
}
else {
if (xconvmodseqp) *xconvmodseqp = state->highestmodseq;
/* we may be in xconvupdates, where expunged are present */
exists = state->exists - state->num_expunged;
}
switch (search_expr_get_countability(searchargs->root)) {
case SEC_EXISTS:
return exists;
case SEC_EXISTS|SEC_NOT:
return 0;
/* we don't try to optimise searches on \Recent */
case SEC_SEEN:
assert(state->exists >= state->numunseen);
return state->exists - state->numunseen;
case SEC_SEEN|SEC_NOT:
return state->numunseen;
case SEC_CONVSEEN:
assert(conversations);
assert(convstatus.exists >= convstatus.unseen);
return convstatus.exists - convstatus.unseen;
case SEC_CONVSEEN|SEC_NOT:
assert(conversations);
return convstatus.unseen;
default:
return UNPREDICTABLE;
}
}
/*
* Performs a XCONVSORT command
*/
EXPORTED int index_convsort(struct index_state *state,
struct sortcrit *sortcrit,
struct searchargs *searchargs,
const struct windowargs *windowargs)
{
MsgData **msgdata = NULL;
unsigned int mi;
modseq_t xconvmodseq = 0;
int i;
hashu64_table seen_cids = HASHU64_TABLE_INITIALIZER;
uint32_t pos = 0;
int found_anchor = 0;
uint32_t anchor_pos = 0;
uint32_t first_pos = 0;
unsigned int ninwindow = 0;
ptrarray_t results = PTRARRAY_INITIALIZER;
int total = 0;
int r = 0;
struct conversations_state *cstate = NULL;
assert(windowargs);
assert(!windowargs->changedsince);
assert(!windowargs->upto);
/* Check the client didn't specify MULTIANCHOR. */
if (windowargs->anchor && windowargs->anchorfolder)
return IMAP_PROTOCOL_BAD_PARAMETERS;
/* make sure \Deleted messages are expunged. Will also lock the
* mailbox state and read any new information */
r = index_expunge(state, NULL, 1);
if (r) return r;
if (windowargs->conversations) {
cstate = conversations_get_mbox(state->mailbox->name);
if (!cstate)
return IMAP_INTERNAL;
}
search_expr_internalise(state->mailbox, searchargs->root);
/* this works both with and without conversations */
total = search_predict_total(state, cstate, searchargs,
windowargs->conversations,
&xconvmodseq);
/* not going to match anything? bonus */
if (!total)
goto out;
construct_hashu64_table(&seen_cids, state->exists/4+4, 0);
/* Create/load the msgdata array.
* load data for ALL messages always. We sort before searching so
* we can take advantage of the window arguments to stop searching
* early */
msgdata = index_msgdata_load(state, NULL, state->exists, sortcrit,
windowargs->anchor, &found_anchor);
if (windowargs->anchor && !found_anchor) {
r = IMAP_ANCHOR_NOT_FOUND;
goto out;
}
/* Sort the messages based on the given criteria */
the_sortcrit = sortcrit;
qsort(msgdata, state->exists, sizeof(MsgData *), index_sort_compare_qsort);
/* One pass through the message list */
for (mi = 0 ; mi < state->exists ; mi++) {
MsgData *msg = msgdata[mi];
struct index_record *record = &state->map[msg->msgno-1].record;
/* can happen if we didn't "tellchanges" yet */
if (record->system_flags & FLAG_EXPUNGED)
continue;
/* run the search program against all messages */
if (!index_search_evaluate(state, searchargs, msg->msgno))
continue;
/* figure out whether this message is an exemplar */
if (windowargs->conversations) {
/* in conversations mode => only the first message seen
* with each unique CID is an exemplar */
if (hashu64_lookup(record->cid, &seen_cids))
continue;
hashu64_insert(record->cid, (void *)1, &seen_cids);
}
/* else not in conversations mode => all messages are exemplars */
pos++;
if (!anchor_pos &&
windowargs->anchor == record->uid) {
/* we've found the anchor's position, rejoice! */
anchor_pos = pos;
}
if (windowargs->anchor) {
if (!anchor_pos)
continue;
if (pos < anchor_pos + windowargs->offset)
continue;
}
else if (windowargs->position) {
if (pos < windowargs->position)
continue;
}
if (windowargs->limit &&
++ninwindow > windowargs->limit) {
if (total == UNPREDICTABLE) {
/* the total was not predictable, so we need to keep
* going over the whole list to count it */
continue;
}
break;
}
if (!first_pos)
first_pos = pos;
ptrarray_push(&results, record);
}
if (total == UNPREDICTABLE) {
/* the total was not predictable prima facie */
total = pos;
}
if (windowargs->anchor && !anchor_pos) {
/* the anchor was present but not an exemplar */
assert(results.count == 0);
r = IMAP_ANCHOR_NOT_FOUND;
goto out;
}
/* Print the resulting list */
/* Yes, we could use a seqset here, but apparently the most common
* sort order seen in the field is reverse date, which is basically
* the worst case for seqset. So we don't bother */
if (results.count) {
prot_printf(state->out, "* SORT"); /* uids */
for (i = 0 ; i < results.count ; i++) {
struct index_record *record = results.data[i];
prot_printf(state->out, " %u", record->uid);
}
prot_printf(state->out, "\r\n");
}
out:
if (!r) {
if (first_pos)
prot_printf(state->out, "* OK [POSITION %u]\r\n", first_pos);
prot_printf(state->out, "* OK [HIGHESTMODSEQ " MODSEQ_FMT "]\r\n",
MAX(xconvmodseq, state->mailbox->i.highestmodseq));
prot_printf(state->out, "* OK [UIDVALIDITY %u]\r\n",
state->mailbox->i.uidvalidity);
prot_printf(state->out, "* OK [UIDNEXT %u]\r\n",
state->mailbox->i.last_uid + 1);
prot_printf(state->out, "* OK [TOTAL %u]\r\n",
total);
}
/* free all our temporary data */
index_msgdata_free(msgdata, state->exists);
ptrarray_fini(&results);
free_hashu64_table(&seen_cids, NULL);
return r;
}
static int add_search_folder(void *rock,
const char *key,
size_t keylen,
const char *val __attribute((unused)),
size_t vallen __attribute((unused)))
{
ptrarray_t *folders = (ptrarray_t *)rock;
SearchFolder *sf = xzmalloc(sizeof(SearchFolder));
sf->mboxname = xstrndup(key, keylen);
sf->id = -1; /* unassigned */
ptrarray_append(folders, sf);
return 0;
}
struct multisort_item {
modseq_t cid;
int32_t folderid; /* can be -1 */
uint32_t uid;
};
struct multisort_folder {
char *name;
uint32_t uidvalidity;
};
struct multisort_result {
struct multisort_folder *folders;
struct multisort_item *msgs;
uint32_t nfolders;
uint32_t nmsgs;
};
static struct db *sortcache_db(struct index_state *state)
{
const char *dbtype = config_getstring(IMAPOPT_SORTCACHE_DB);
const char *userid = mboxname_to_userid(state->mailbox->name);
char *fname = NULL;
struct db *db = NULL;
int r;
/* we just don't cache if there's no userid. Alternative would
* be to write some global file */
if (!userid)
return NULL;
fname = user_hash_meta(userid, "sortcache");
r = cyrusdb_open(dbtype, fname, CYRUSDB_CREATE, &db);
free(fname);
return r ? NULL : db;
}
struct sortcache_cleanup_rock {
struct db *db;
const char *prefix;
size_t prefixlen;
};
static int sortcache_cleanup_cb(void *rock,
const char *key,
size_t keylen,
const char *val __attribute__((unused)),
size_t vallen __attribute__((unused)))
{
struct sortcache_cleanup_rock *scr = (struct sortcache_cleanup_rock *)rock;
if (keylen < scr->prefixlen || memcmp(key, scr->prefix, scr->prefixlen)) {
/* doesn't match the prefix, remove it */
cyrusdb_delete(scr->db, key, keylen, NULL, /*force*/1);
}
return 0;
}
static int folder_may_be_in_search(const char *mboxname,
struct searchargs *searchargs)
{
struct searchsub *s;
struct strlist *l;
for (l = searchargs->folder; l; l = l->next) {
if (strcmpsafe(l->s, mboxname)) return 0;
}
for (s = searchargs->sublist; s; s = s->next) {
if (folder_may_be_in_search(mboxname, s->sub1)) {
if (!s->sub2) return 0;
}
else {
if (s->sub2 && !folder_may_be_in_search(mboxname, s->sub1))
return 0;
}
}
return 1;
}
/* NOTE: tostate MAY be the same as state - we still copy with magic
* folder processing logic */
static struct searchargs *dupsearchargs(struct index_state *state,
const struct searchargs *searchargs,
struct index_state *tostate)
{
struct searchargs *out = xzmalloc(sizeof(struct searchargs));
struct strlist *l;
struct searchannot *sa;
struct searchsub *s;
struct seqset *seq;
out->flags = searchargs->flags;
out->smaller = searchargs->smaller;
out->larger = searchargs->larger;
out->before = searchargs->before;
out->after = searchargs->after;
out->sentbefore = searchargs->sentbefore;
out->sentafter = searchargs->sentafter;
out->system_flags_set = searchargs->system_flags_set;
out->system_flags_unset = searchargs->system_flags_unset;
/* for user flags, they will need to be converted to the bitmap
* again after calling this function */
for (l = searchargs->keywords; l; l = l->next)
appendstrlist(&out->keywords, l->s);
for (l = searchargs->unkeywords; l; l = l->next)
appendstrlist(&out->unkeywords, l->s);
for (seq = searchargs->sequence; seq; seq = seq->nextseq) {
char *str = seqset_cstring(seq);
appendsequencelist(tostate, &out->sequence, str, 0);
free(str);
}
for (seq = searchargs->uidsequence; seq; seq = seq->nextseq) {
char *str = seqset_cstring(seq);
appendsequencelist(tostate, &out->uidsequence, str, 1);
free(str);
}
/* strlistpat for most fields */
for (l = searchargs->from; l; l = l->next)
appendstrlistpat(&out->from, xstrdup(l->s));
for (l = searchargs->to; l; l = l->next)
appendstrlistpat(&out->to, xstrdup(l->s));
for (l = searchargs->cc; l; l = l->next)
appendstrlistpat(&out->cc, xstrdup(l->s));
for (l = searchargs->bcc; l; l = l->next)
appendstrlistpat(&out->bcc, xstrdup(l->s));
for (l = searchargs->subject; l; l = l->next)
appendstrlistpat(&out->subject, xstrdup(l->s));
for (l = searchargs->messageid; l; l = l->next)
appendstrlistpat(&out->messageid, xstrdup(l->s));
for (l = searchargs->header; l; l = l->next)
appendstrlistpat(&out->header, xstrdup(l->s));
for (l = searchargs->body; l; l = l->next)
appendstrlistpat(&out->body, xstrdup(l->s));
for (l = searchargs->text; l; l = l->next)
appendstrlistpat(&out->text, xstrdup(l->s));
/* only strlist, not strlistpat for header NAMES */
for (l = searchargs->header_name; l; l = l->next)
appendstrlist(&out->header_name, l->s);
/* special folder logic */
for (l = searchargs->folder; l; l = l->next) {
/* not a match */
if (strcmpsafe(tostate->mailbox->name, l->s))
out->flags |= (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET);
/* otherwise will always match - no rule needed */
}
for (s = searchargs->sublist; s; s = s->next) {
struct searchsub **tail = &out->sublist;
while (*tail) tail = &(*tail)->next;
*tail = xzmalloc(sizeof(struct searchsub));
(*tail)->sub1 = dupsearchargs(state, s->sub1, tostate);
if (s->sub2)
(*tail)->sub2 = dupsearchargs(state, s->sub2, tostate);
}
out->modseq = searchargs->modseq;
for (sa = searchargs->annotations ; sa ; sa = sa->next) {
struct searchannot **tail = &out->annotations;
while (*tail) tail = &(*tail)->next;
*tail = xzmalloc(sizeof(struct searchannot));
(*tail)->entry = xstrdupnull(sa->entry);
(*tail)->attrib = xstrdupnull(sa->attrib);
(*tail)->namespace = sa->namespace;
(*tail)->isadmin = sa->isadmin;
(*tail)->userid = sa->userid; /* reference to external, not cleaned up */
(*tail)->auth_state = sa->auth_state;
buf_copy(&(*tail)->value, &sa->value);
}
for (l = searchargs->convflags; l; l = l->next)
appendstrlist(&out->convflags, l->s);
out->convmodseq = searchargs->convmodseq;
out->cache_atleast = searchargs->cache_atleast;
out->tag = searchargs->tag;
out->returnopts = searchargs->returnopts;
out->namespace = searchargs->namespace;
out->userid = searchargs->userid;
out->authstate = searchargs->authstate;
return out;
}
static struct multisort_result *multisort_run(struct index_state *state,
struct sortcrit *sortcrit,
struct searchargs *searchargs)
{
int fi;
int mi;
int nfolders = 0;
ptrarray_t folders = PTRARRAY_INITIALIZER;
ptrarray_t merged_msgdata = PTRARRAY_INITIALIZER;
int r = 0;
struct index_state *state2 = NULL;
unsigned msgno;
struct multisort_result *result = NULL;
struct searchargs *searchargs2 = NULL;
/* in the case where the search can only match a single folder
* at the top level, we can optimise. Otherwise we do a listing
* to find potentially matching folders */
if (searchargs->folder && !searchargs->folder->next) {
const char *s = searchargs->folder->s;
add_search_folder(&folders, s, strlen(s), NULL, 0);
}
else {
r = mboxlist_allusermbox(mboxname_to_userid(state->mailbox->name),
add_search_folder, &folders, /*+deleted*/0);
if (r) return NULL;
}
for (fi = 0; fi < folders.count; fi++) {
SearchFolder *sf = ptrarray_nth(&folders, fi);
unsigned int *msgs;
int count = 0;
if (!folder_may_be_in_search(sf->mboxname, searchargs))
continue;
if (state2 && state2 != state)
index_close(&state2);
if (searchargs2) {
freesearchargs(searchargs2);
searchargs2 = NULL;
}
/* open an index_state */
if (!strcmp(state->mailbox->name, sf->mboxname)) {
state2 = state;
}
else {
struct index_init init;
memset(&init, 0, sizeof(struct index_init));
init.userid = searchargs->userid;
init.authstate = searchargs->authstate;
init.out = state->out;
r = index_open(sf->mboxname, &init, &state2);
if (r) continue;
index_checkflags(state2, 0, 0);
}
/* make sure \Deleted messages are expunged. Will also lock the
* mailbox state and read any new information */
r = index_expunge(state2, NULL, 1);
if (r) continue;
if (!state2->exists) continue;
msgs = xmalloc(state2->exists * sizeof(uint32_t));
/* we need to copy the searchargs to:
* a) change user flag numbers to match up
* b) make the "folder" match efficient
*/
searchargs2 = dupsearchargs(state, searchargs, state2);
search_expr_internalise(state2->mailbox, searchargs2->root);
/* One pass through the folder's message list */
for (msgno = 1 ; msgno <= state2->exists ; msgno++) {
struct index_record *record = &state2->map[msgno-1].record;
/* can happen if we didn't "tellchanges" yet */
if (record->system_flags & FLAG_EXPUNGED)
continue;
/* run the search program */
if (!index_search_evaluate(state2, searchargs2, msgno))
continue;
msgs[count++] = msgno;
}
/* Delay assigning ids to folders until we can be
* certain that any results will be reported for
* the folder */
if (count) {
sf->id = nfolders++;
sf->uidvalidity = state2->mailbox->i.uidvalidity;
/* Create/load the msgdata array. */
sf->msgdata = index_msgdata_load(state2, msgs, count,
sortcrit, 0, 0);
for (mi = 0; mi < count; mi++) {
sf->msgdata[mi]->folder = sf;
/* merged_msgdata is now "owner" of the pointer */
ptrarray_append(&merged_msgdata, sf->msgdata[mi]);
}
}
free(msgs);
}
if (state2 && state2 != state)
index_close(&state2);
if (searchargs2) {
freesearchargs(searchargs2);
searchargs2 = NULL;
}
/* Sort the merged messages based on the given criteria */
the_sortcrit = sortcrit;
qsort(merged_msgdata.data, merged_msgdata.count,
sizeof(MsgData *), index_sort_compare_qsort);
/* convert the result for caching */
result = xzmalloc(sizeof(struct multisort_result));
result->nmsgs = merged_msgdata.count;
result->msgs = xmalloc(result->nmsgs * sizeof(struct multisort_item));
for (mi = 0; mi < merged_msgdata.count; mi++) {
MsgData *msg = ptrarray_nth(&merged_msgdata, mi);
result->msgs[mi].folderid = msg->folder->id;
result->msgs[mi].uid = msg->uid;
result->msgs[mi].cid = msg->cid;
}
result->nfolders = nfolders;
result->folders = xmalloc(result->nmsgs * sizeof(struct multisort_folder));
for (fi = 0; fi < folders.count; fi++) {
SearchFolder *sf = ptrarray_nth(&folders, fi);
if (sf->id >= 0) {
result->folders[sf->id].name = xstrdup(sf->mboxname);
result->folders[sf->id].uidvalidity = sf->uidvalidity;
}
free(sf->mboxname);
free(sf->msgdata);
free(sf);
}
/* free all our temporary data */
ptrarray_fini(&folders);
ptrarray_fini(&merged_msgdata);
return result;
}
static int index_format_search(struct dlist *parent,
struct index_state *state,
const struct searchargs *searchargs)
{
struct strlist *l, *h;
struct searchannot *sa;
struct searchsub *s;
struct seqset *seq;
int count = 0;
if (searchargs->flags & SEARCH_RECENT_SET) {
dlist_setatom(parent, NULL, "RECENT");
count++;
}
if (searchargs->flags & SEARCH_RECENT_UNSET) {
dlist_setatom(parent, NULL, "UNRECENT");
count++;
}
if (searchargs->flags & SEARCH_SEEN_SET) {
dlist_setatom(parent, NULL, "SEEN");
count++;
}
if (searchargs->flags & SEARCH_SEEN_UNSET) {
dlist_setatom(parent, NULL, "UNSEEN");
count++;
}
if (searchargs->flags & SEARCH_CONVSEEN_SET) {
dlist_setatom(parent, NULL, "CONVSEEN");
count++;
}
if (searchargs->flags & SEARCH_CONVSEEN_UNSET) {
dlist_setatom(parent, NULL, "UNCONVSEEN");
count++;
}
if (searchargs->smaller) {
dlist_setatom(parent, NULL, "SMALLER");
dlist_setnum64(parent, NULL, searchargs->smaller);
count++;
}
if (searchargs->larger) {
dlist_setatom(parent, NULL, "LARGER");
dlist_setnum64(parent, NULL, searchargs->larger);
count++;
}
if (searchargs->before) {
dlist_setatom(parent, NULL, "BEFORE");
dlist_setdate(parent, NULL, searchargs->before);
count++;
}
if (searchargs->after) {
dlist_setatom(parent, NULL, "AFTER");
dlist_setdate(parent, NULL, searchargs->after);
count++;
}
if (searchargs->sentbefore) {
dlist_setatom(parent, NULL, "SENTBEFORE");
dlist_setdate(parent, NULL, searchargs->sentbefore);
count++;
}
if (searchargs->sentafter) {
dlist_setatom(parent, NULL, "SENTAFTER");
dlist_setdate(parent, NULL, searchargs->sentafter);
count++;
}
if (searchargs->system_flags_set & FLAG_ANSWERED) {
dlist_setatom(parent, NULL, "ANSWERED");
count++;
}
if (searchargs->system_flags_set & FLAG_DELETED) {
dlist_setatom(parent, NULL, "DELETED");
count++;
}
if (searchargs->system_flags_set & FLAG_DRAFT) {
dlist_setatom(parent, NULL, "DRAFT");
count++;
}
if (searchargs->system_flags_set & FLAG_FLAGGED) {
dlist_setatom(parent, NULL, "FLAGGED");
count++;
}
if (searchargs->system_flags_unset & FLAG_ANSWERED) {
dlist_setatom(parent, NULL, "UNANSWERED");
count++;
}
if (searchargs->system_flags_unset & FLAG_DELETED) {
dlist_setatom(parent, NULL, "UNDELETED");
count++;
}
if (searchargs->system_flags_unset & FLAG_DRAFT) {
dlist_setatom(parent, NULL, "UNDRAFT");
count++;
}
if (searchargs->system_flags_unset & FLAG_FLAGGED) {
dlist_setatom(parent, NULL, "UNFLAGGED");
count++;
}
for (l = searchargs->keywords; l; l = l->next) {
dlist_setatom(parent, NULL, "KEYWORD");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->unkeywords; l; l = l->next) {
dlist_setatom(parent, NULL, "UNKEYWORD");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (seq = searchargs->sequence; seq; seq = seq->nextseq) {
char *str = seqset_cstring(seq);
dlist_setatom(parent, NULL, str);
free(str);
count++;
}
for (seq = searchargs->uidsequence; seq; seq = seq->nextseq) {
char *str = seqset_cstring(seq);
dlist_setatom(parent, NULL, "UID");
dlist_setatom(parent, NULL, str);
free(str);
count++;
}
for (l = searchargs->from; l; l = l->next) {
dlist_setatom(parent, NULL, "FROM");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->to; l; l = l->next) {
dlist_setatom(parent, NULL, "TO");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->cc; l; l = l->next) {
dlist_setatom(parent, NULL, "CC");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->bcc; l; l = l->next) {
dlist_setatom(parent, NULL, "BCC");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->subject; l; l = l->next) {
dlist_setatom(parent, NULL, "SUBJECT");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->messageid; l; l = l->next) {
dlist_setatom(parent, NULL, "MESSAGEID");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->body; l; l = l->next) {
dlist_setatom(parent, NULL, "BODY");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->text; l; l = l->next) {
dlist_setatom(parent, NULL, "TEXT");
dlist_setatom(parent, NULL, l->s);
count++;
}
h = searchargs->header_name;
for (l = searchargs->header; l; (l = l->next), (h = h->next)) {
dlist_setatom(parent, NULL, "HEADER");
dlist_setatom(parent, NULL, h->s);
dlist_setatom(parent, NULL, l->s);
count++;
}
for (l = searchargs->folder; l; l = l->next) {
dlist_setatom(parent, NULL, "FOLDER");
dlist_setatom(parent, NULL, l->s);
count++;
}
for (s = searchargs->sublist; s; s = s->next) {
struct dlist *child;
int nchildren;
if (s->sub2)
dlist_setatom(parent, NULL, "OR");
else
dlist_setatom(parent, NULL, "NOT");
child = dlist_newlist(parent, NULL);
nchildren = index_format_search(child, state, s->sub1);
if (nchildren == 1)
dlist_splat(parent, child);
if (s->sub2) {
child = dlist_newlist(parent, NULL);
nchildren = index_format_search(child, state, s->sub2);
if (nchildren == 1)
dlist_splat(parent, child);
}
count++;
}
if (searchargs->modseq) {
dlist_setatom(parent, NULL, "MODSEQ");
dlist_setnum64(parent, NULL, searchargs->modseq);
count++;
}
for (sa = searchargs->annotations ; sa ; sa = sa->next) {
dlist_setatom(parent, NULL, "ANNOTATION");
dlist_setatom(parent, NULL, sa->entry);
dlist_setatom(parent, NULL, sa->attrib);
dlist_setmap(parent, NULL, sa->value.s, sa->value.len);
count++;
}
for (l = searchargs->convflags; l; l = l->next) {
dlist_setatom(parent, NULL, "CONVFLAG");
dlist_setatom(parent, NULL, l->s);
count++;
}
if (searchargs->convmodseq) {
dlist_setatom(parent, NULL, "CONVMODSEQ");
dlist_setnum64(parent, NULL, searchargs->convmodseq);
count++;
}
return count;
}
static void index_format_sort(struct dlist *parent,
struct sortcrit *sortcrit)
{
int i = 0;
for (i = 0; sortcrit[i].key != SORT_SEQUENCE ; i++) {
/* determine sort order from reverse flag bit */
if (sortcrit[i].flags & SORT_REVERSE)
dlist_setatom(parent, NULL, "REVERSE");
switch (sortcrit[i].key) {
case SORT_ARRIVAL:
dlist_setatom(parent, NULL, "ARRIVAL");
break;
case SORT_CC:
dlist_setatom(parent, NULL, "CC");
break;
case SORT_DATE:
dlist_setatom(parent, NULL, "DATE");
break;
case SORT_FROM:
dlist_setatom(parent, NULL, "FROM");
break;
case SORT_SIZE:
dlist_setatom(parent, NULL, "SIZE");
break;
case SORT_SUBJECT:
dlist_setatom(parent, NULL, "SUBJECT");
break;
case SORT_TO:
dlist_setatom(parent, NULL, "TO");
break;
case SORT_ANNOTATION:
dlist_setatom(parent, NULL, "ANNOTATION");
dlist_setatom(parent, NULL, sortcrit[i].args.annot.entry);
dlist_setatom(parent, NULL, sortcrit[i].args.annot.userid);
break;
case SORT_MODSEQ:
dlist_setatom(parent, NULL, "MODSEQ");
break;
case SORT_DISPLAYFROM:
dlist_setatom(parent, NULL, "DISPLAYFROM");
break;
case SORT_DISPLAYTO:
dlist_setatom(parent, NULL, "DISPLAYTO");
break;
case SORT_UID:
dlist_setatom(parent, NULL, "UID");
break;
case SORT_CONVMODSEQ:
dlist_setatom(parent, NULL, "CONVMODSEQ");
break;
case SORT_CONVEXISTS:
dlist_setatom(parent, NULL, "CONVEXISTS");
break;
case SORT_CONVSIZE:
dlist_setatom(parent, NULL, "CONVSIZE");
break;
case SORT_HASFLAG:
dlist_setatom(parent, NULL, "FLAG");
dlist_setatom(parent, NULL, sortcrit[i].args.flag.name);
break;
case SORT_HASCONVFLAG:
dlist_setatom(parent, NULL, "CONVFLAG");
dlist_setatom(parent, NULL, sortcrit[i].args.flag.name);
break;
case SORT_FOLDER:
dlist_setatom(parent, NULL, "FOLDER");
break;
}
}
}
static char *multisort_cachekey(struct sortcrit *sortcrit,
struct index_state *state,
struct searchargs *searchargs)
{
struct buf b = BUF_INITIALIZER;
struct dlist *dl = dlist_newlist(NULL, NULL);
struct dlist *sc = dlist_newlist(dl, NULL);
struct dlist *sa = dlist_newlist(dl, NULL);
index_format_sort(sc, sortcrit);
index_format_search(sa, state, searchargs);
dlist_printbuf(dl, 0, &b);
dlist_free(&dl);
return buf_release(&b);
}
#define SORTCACHE_VERSION 0
static struct multisort_result *multisort_cache_load(struct db *db,
modseq_t hms,
const char *cachekey)
{
struct sortcache_cleanup_rock rock;
struct multisort_result *sortres = NULL;
struct buf prefix = BUF_INITIALIZER;
const char *val = NULL;
size_t vallen = 0;
struct dlist *dl = NULL;
struct dlist *dc;
struct dlist *di;
unsigned i;
if (!db) goto done;
buf_printf(&prefix, MODSEQ_FMT " %d ", hms, SORTCACHE_VERSION);
memset(&rock, 0, sizeof(struct sortcache_cleanup_rock));
rock.db = db;
rock.prefix = prefix.s;
rock.prefixlen = prefix.len;
if (cyrusdb_foreach(rock.db, "", 0, NULL,
sortcache_cleanup_cb, &rock, NULL))
goto done;
buf_appendcstr(&prefix, cachekey);
if (cyrusdb_fetch(rock.db, prefix.s, prefix.len, &val, &vallen, NULL))
goto done;
/* OK, we have found value! */
if (dlist_parsemap(&dl, 0, val, vallen))
goto done;
sortres = xzmalloc(sizeof(struct multisort_result));
dlist_getnum32(dl, "NFOLDERS", &sortres->nfolders);
dlist_getnum32(dl, "NMSGS", &sortres->nmsgs);
sortres->folders = xzmalloc(sortres->nfolders * sizeof(struct multisort_folder));
sortres->msgs = xzmalloc(sortres->nmsgs * sizeof(struct multisort_item));
dc = dlist_getchild(dl, "FOLDERS");
i = 0;
for (di = dc->head; di; di = di->next) {
struct dlist *item = di->head;
if (i >= sortres->nfolders) goto err;
sortres->folders[i].name = xstrdup(dlist_cstring(item));
item = item->next;
sortres->folders[i].uidvalidity = dlist_num(item);
i++;
}
if (i != sortres->nfolders) goto err;
dc = dlist_getchild(dl, "MSGS");
i = 0;
for (di = dc->head; di; di = di->next) {
struct dlist *item = di->head;
if (i >= sortres->nmsgs) goto err;
sortres->msgs[i].folderid = dlist_num(item);
item = item->next;
sortres->msgs[i].uid = dlist_num(item);
item = item->next;
sortres->msgs[i].cid = dlist_num(item);
i++;
}
if (i != sortres->nmsgs) goto err;
done:
dlist_free(&dl);
buf_free(&prefix);
return sortres;
err:
dlist_free(&dl);
buf_free(&prefix);
syslog(LOG_ERR, "invalid search cache record %s %.*s",
cachekey, (int)vallen, val);
/* clean up memory */
for (i = 0; i < sortres->nfolders; i++) {
free(sortres->folders[i].name);
}
free(sortres->folders);
free(sortres->msgs);
free(sortres);
return NULL;
}
static void multisort_cache_save(struct db *db,
modseq_t hms,
const char *cachekey,
struct multisort_result *sortres)
{
struct buf prefix = BUF_INITIALIZER;
struct buf result = BUF_INITIALIZER;
struct dlist *dl = NULL;
struct dlist *dc;
struct dlist *di;
int i;
if (!db) goto done;
buf_printf(&prefix, MODSEQ_FMT " %d %s", hms, SORTCACHE_VERSION, cachekey);
dl = dlist_newkvlist(NULL, NULL);
dlist_setnum32(dl, "NFOLDERS", sortres->nfolders);
dlist_setnum32(dl, "NMSGS", sortres->nmsgs);
dc = dlist_newlist(dl, "FOLDERS");
for (i = 0; i < (int)sortres->nfolders; i++) {
di = dlist_newlist(dc, NULL);
dlist_setatom(di, NULL, sortres->folders[i].name);
dlist_setnum32(di, NULL, sortres->folders[i].uidvalidity);
}
dc = dlist_newlist(dl, "MSGS");
for (i = 0; i < (int)sortres->nmsgs; i++) {
di = dlist_newlist(dc, NULL);
dlist_setnum32(di, NULL, sortres->msgs[i].folderid);
dlist_setnum32(di, NULL, sortres->msgs[i].uid);
dlist_setnum64(di, NULL, sortres->msgs[i].cid);
}
dlist_printbuf(dl, 0, &result);
if (cyrusdb_store(db, prefix.s, prefix.len, result.s, result.len, NULL))
goto done;
done:
dlist_free(&dl);
buf_free(&prefix);
buf_free(&result);
}
struct multisort_response {
struct multisort_item *item;
ptrarray_t cidother;
};
/*
* Performs a XCONVMULTISORT command
*/
EXPORTED int index_convmultisort(struct index_state *state,
struct sortcrit *sortcrit,
struct searchargs *searchargs,
const struct windowargs *windowargs)
{
unsigned int mi;
unsigned int fi;
int i;
hashu64_table seen_cids = HASHU64_TABLE_INITIALIZER;
uint32_t pos = 0;
uint32_t anchor_pos = 0;
uint32_t first_pos = 0;
unsigned int ninwindow = 0;
ptrarray_t results = PTRARRAY_INITIALIZER;
struct multisort_response dummy_response;
int total = UNPREDICTABLE;
int r = 0;
int32_t anchor_folderid = -1;
char extname[MAX_MAILBOX_BUFFER];
modseq_t hms;
struct multisort_result *sortres = NULL;
char *cachekey = NULL;
struct db *db = NULL;
assert(windowargs);
assert(!windowargs->changedsince);
assert(!windowargs->upto);
/* Client needs to have specified MULTIANCHOR which includes
* the folder name instead of just ANCHOR. Check that here
* 'cos it's easier than doing so during parsing */
if (windowargs->anchor && !windowargs->anchorfolder)
return IMAP_PROTOCOL_BAD_PARAMETERS;
hms = mboxname_readmodseq(state->mailbox->name);
cachekey = multisort_cachekey(sortcrit, state, searchargs);
db = sortcache_db(state);
sortres = multisort_cache_load(db, hms, cachekey);
if (!sortres) {
sortres = multisort_run(state, sortcrit, searchargs);
/* OK if it fails */
multisort_cache_save(db, hms, cachekey, sortres);
}
if (windowargs->anchorfolder) {
for (fi = 0; fi < sortres->nfolders; fi++) {
if (strcmpsafe(windowargs->anchorfolder, sortres->folders[fi].name))
continue;
anchor_folderid = fi;
break;
}
if (anchor_folderid < 0) {
r = IMAP_ANCHOR_NOT_FOUND;
goto out;
}
}
/* going to need to do conversation-level breakdown */
if (windowargs->conversations)
construct_hashu64_table(&seen_cids, sortres->nmsgs/4+4, 0);
/* no need */
else
total = sortres->nmsgs;
/* Another pass through the merged message list */
for (mi = 0; mi < sortres->nmsgs; mi++) {
struct multisort_item *item = &sortres->msgs[mi];
struct multisort_response *response = NULL;
/* figure out whether this message is an exemplar */
if (windowargs->conversations) {
response = hashu64_lookup(item->cid, &seen_cids);
/* in conversations mode => only the first message seen
* with each unique CID is an exemplar */
if (response) {
if (response != &dummy_response)
ptrarray_append(&response->cidother, item);
continue;
}
hashu64_insert(item->cid, &dummy_response, &seen_cids);
}
/* else not in conversations mode => all messages are exemplars */
pos++;
if (!anchor_pos &&
windowargs->anchor == item->uid &&
anchor_folderid == item->folderid) {
/* we've found the anchor's position, rejoice! */
anchor_pos = pos;
}
if (windowargs->anchor) {
if (!anchor_pos)
continue;
if (pos < anchor_pos + windowargs->offset)
continue;
}
else if (windowargs->position) {
if (pos < windowargs->position)
continue;
}
if (windowargs->limit &&
++ninwindow > windowargs->limit) {
if (total == UNPREDICTABLE) {
/* the total was not predictable, so we need to keep
* going over the whole list to count it */
continue;
}
break;
}
if (!first_pos)
first_pos = pos;
response = xzmalloc(sizeof(struct multisort_response));
response->item = item;
ptrarray_push(&results, response);
if (windowargs->conversations) {
hashu64_insert(item->cid, response, &seen_cids);
}
}
if (total == UNPREDICTABLE) {
/* the total was not predictable prima facie */
total = pos;
}
if (windowargs->anchor && !anchor_pos) {
/* the anchor was not found */
assert(results.count == 0);
r = IMAP_ANCHOR_NOT_FOUND;
goto out;
}
/* Print the resulting list */
xstats_add(SEARCH_RESULT, results.count);
if (results.count) {
/* The untagged reponse would be XCONVMULTISORT but
* Mail::IMAPTalk has an undocumented hack whereby any untagged
* response matching /sort/i is assumed to be a sequence of
* numeric uids. Meh. */
prot_printf(state->out, "* XCONVMULTI (");
for (fi = 0 ; fi < sortres->nfolders ; fi++) {
struct multisort_folder *mf = &sortres->folders[fi];
searchargs->namespace->mboxname_toexternal(searchargs->namespace,
mf->name,
searchargs->userid,
extname);
if (fi)
prot_printf(state->out, " ");
prot_printf(state->out, "(");
prot_printstring(state->out, extname);
prot_printf(state->out, " %u)", mf->uidvalidity);
}
prot_printf(state->out, ") (");
for (i = 0 ; i < results.count ; i++) {
struct multisort_response *response = results.data[i];
struct multisort_item *item = response->item;
int j;
if (i)
prot_printf(state->out, " ");
prot_printf(state->out, "(%s" , conversation_id_encode(item->cid));
/* exemplar item */
prot_printf(state->out, " (%u %u)", item->folderid, item->uid);
/* rest of the items too */
for (j = 0; j < response->cidother.count; j++) {
item = response->cidother.data[j];
prot_printf(state->out, " (%u %u)", item->folderid, item->uid);
}
prot_printf(state->out, ")");
}
prot_printf(state->out, ")\r\n");
}
out:
if (!r) {
if (first_pos)
prot_printf(state->out, "* OK [POSITION %u]\r\n", first_pos);
prot_printf(state->out, "* OK [HIGHESTMODSEQ " MODSEQ_FMT "]\r\n",
hms);
#if 0
prot_printf(state->out, "* OK [UIDNEXT %u]\r\n",
state->mailbox->i.last_uid + 1);
#endif
prot_printf(state->out, "* OK [TOTAL %u]\r\n",
total);
}
if (db)
cyrusdb_close(db);
/* free all our temporary data */
free_hashu64_table(&seen_cids, NULL);
for (i = 0 ; i < results.count ; i++) {
struct multisort_response *response = results.data[i];
ptrarray_fini(&response->cidother);
free(response);
}
ptrarray_fini(&results);
for (fi = 0 ; fi < sortres->nfolders ; fi++) {
free(sortres->folders[fi].name);
}
free(sortres->folders);
free(sortres->msgs);
free(sortres);
free(cachekey);
return r;
}
struct snippet_rock {
struct protstream *out;
struct namespace *namespace;
const char *userid;
};
static int emit_snippet(struct mailbox *mailbox, uint32_t uid,
int part, const char *snippet, void *rock)
{
struct snippet_rock *sr = (struct snippet_rock *)rock;
const char *partname = search_part_as_string(part);
int r;
char extname[MAX_MAILBOX_BUFFER];
if (!partname) return 0;
r = sr->namespace->mboxname_toexternal(sr->namespace, mailbox->name,
sr->userid, extname);
if (r) return r;
prot_printf(sr->out, "* SNIPPET ");
prot_printstring(sr->out, extname);
prot_printf(sr->out, " %u %u %s ", mailbox->i.uidvalidity, uid, partname);
prot_printstring(sr->out, snippet);
prot_printf(sr->out, "\r\n");
return 0;
}
EXPORTED int index_snippets(struct index_state *state,
const struct snippetargs *snippetargs,
struct searchargs *searchargs)
{
void *intquery = NULL;
search_builder_t *bx = NULL;
search_text_receiver_t *rx = NULL;
struct mailbox *mailbox = NULL;
int i;
int r = 0;
int nmatches = 0;
struct snippet_rock srock;
bx = search_begin_search(state->mailbox, SEARCH_MULTIPLE);
if (!bx) {
r = IMAP_INTERNAL;
goto out;
}
build_query(bx, searchargs, 0, &nmatches);
if (!bx->get_internalised) goto out;
intquery = bx->get_internalised(bx);
search_end_search(bx);
if (!intquery) goto out;
srock.out = state->out;
srock.namespace = searchargs->namespace;
srock.userid = searchargs->userid;
rx = search_begin_snippets(intquery, 0/*verbose*/,
emit_snippet, &srock);
if (!rx) goto out;
for ( ; snippetargs ; snippetargs = snippetargs->next) {
mailbox = NULL;
if (!strcmp(snippetargs->mboxname, state->mailbox->name)) {
mailbox = state->mailbox;
}
else {
r = mailbox_open_iwl(snippetargs->mboxname, &mailbox);
if (r) goto out;
}
if (snippetargs->uidvalidity &&
snippetargs->uidvalidity != mailbox->i.uidvalidity) {
r = IMAP_NOTFOUND;
goto out;
}
r = rx->begin_mailbox(rx, mailbox, /*incremental*/0);
for (i = 0 ; i < snippetargs->uids.count ; i++) {
uint32_t uid = snippetargs->uids.data[i];
struct index_record record;
message_t *msg;
/* This UID didn't appear in the old index file */
r = mailbox_find_index_record(mailbox, uid, &record, NULL);
if (r) goto out;
msg = message_new_from_record(mailbox, &record);
index_getsearchtext(msg, rx, /*snippet*/1);
message_unref(&msg);
}
r = rx->end_mailbox(rx, mailbox);
if (r) goto out;
if (mailbox != state->mailbox)
mailbox_close(&mailbox);
}
out:
if (rx) search_end_snippets(rx);
if (intquery) search_free_internalised(intquery);
if (mailbox != state->mailbox)
mailbox_close(&mailbox);
return r;
}
static modseq_t get_modseq_of(struct index_record *record,
struct conversations_state *cstate)
{
modseq_t modseq = 0;
if (cstate) {
conversation_get_modseq(cstate, record->cid, &modseq);
/* TODO: error handling dammit */
} else {
modseq = record->modseq;
}
return modseq;
}
/*
* Performs a XCONVUPDATES command
*/
EXPORTED int index_convupdates(struct index_state *state,
struct sortcrit *sortcrit,
struct searchargs *searchargs,
const struct windowargs *windowargs)
{
MsgData **msgdata = NULL;
modseq_t xconvmodseq = 0;
unsigned int mi;
int i;
hashu64_table seen_cids = HASHU64_TABLE_INITIALIZER;
hashu64_table old_seen_cids = HASHU64_TABLE_INITIALIZER;
int32_t pos = 0;
uint32_t upto_pos = 0;
ptrarray_t added = PTRARRAY_INITIALIZER;
ptrarray_t removed = PTRARRAY_INITIALIZER;
ptrarray_t changed = PTRARRAY_INITIALIZER;
int total = 0;
struct conversations_state *cstate = NULL;
int search_is_mutable = is_mutable_ordering(sortcrit, searchargs);
int r = 0;
assert(windowargs);
assert(windowargs->changedsince);
assert(windowargs->offset == 0);
assert(!windowargs->position);
/* make sure \Deleted messages are expunged. Will also lock the
* mailbox state and read any new information */
r = index_expunge(state, NULL, 1);
if (r) return r;
cstate = conversations_get_mbox(state->mailbox->name);
if (!cstate)
return IMAP_INTERNAL;
search_expr_internalise(state->mailbox, searchargs->root);
total = search_predict_total(state, cstate, searchargs,
windowargs->conversations,
&xconvmodseq);
/* If there are no current and no expunged messages, we won't
* have any results at all and can short circuit the main loop;
* note that is a righter criterion than for XCONVSORT. */
if (!total && !state->exists)
goto out;
construct_hashu64_table(&seen_cids, state->exists/4+4, 0);
construct_hashu64_table(&old_seen_cids, state->exists/4+4, 0);
/* Create/load the msgdata array
* initial list - load data for ALL messages always */
msgdata = index_msgdata_load(state, NULL, state->exists, sortcrit, 0, NULL);
/* Sort the messages based on the given criteria */
the_sortcrit = sortcrit;
qsort(msgdata, state->exists, sizeof(MsgData *), index_sort_compare_qsort);
/* Discover exemplars */
for (mi = 0 ; mi < state->exists ; mi++) {
MsgData *msg = msgdata[mi];
struct index_record *record = &state->map[msg->msgno-1].record;
int was_old_exemplar = 0;
int is_new_exemplar = 0;
int is_deleted = 0;
int is_new = 0;
int was_deleted = 0;
int is_changed = 0;
int in_search = 0;
in_search = index_search_evaluate(state, searchargs, msg->msgno);
is_deleted = !!(record->system_flags & FLAG_EXPUNGED);
is_new = (record->uid >= windowargs->uidnext);
is_changed = (record->modseq > windowargs->modseq);
was_deleted = is_deleted && !is_changed;
/* is this message a current exemplar? */
if (!is_deleted &&
in_search &&
(!windowargs->conversations || !hashu64_lookup(record->cid, &seen_cids))) {
is_new_exemplar = 1;
pos++;
if (windowargs->conversations)
hashu64_insert(record->cid, (void *)1, &seen_cids);
}
/* optimisation for when the total is
* not known but we've hit 'upto' */
if (upto_pos)
continue;
/* was this message an old exemplar, or in the case of mutable
* searches, possible an old exemplar? */
if (!is_new &&
!was_deleted &&
(in_search || search_is_mutable) &&
(!windowargs->conversations || !hashu64_lookup(record->cid, &old_seen_cids))) {
was_old_exemplar = 1;
if (windowargs->conversations)
hashu64_insert(record->cid, (void *)1, &old_seen_cids);
}
if (was_old_exemplar && !is_new_exemplar) {
ptrarray_push(&removed, record);
} else if (!was_old_exemplar && is_new_exemplar) {
msg->msgno = pos; /* hacky: reuse ->msgno for pos */
ptrarray_push(&added, msg);
} else if (was_old_exemplar && is_new_exemplar) {
modseq_t modseq = get_modseq_of(record,
windowargs->conversations ? cstate : NULL);
if (modseq > windowargs->modseq) {
ptrarray_push(&changed, record);
if (search_is_mutable) {
/* is the search is mutable, we're in a whole world of
* uncertainty about the client's state, so we just
* report the exemplar in all three lists and let the
* client sort it out. */
ptrarray_push(&removed, record);
msg->msgno = pos; /* hacky: reuse ->msgno for pos */
ptrarray_push(&added, msg);
}
}
}
/* if this is the last message the client cares about ('upto')
* then we can break early...unless its a mutable search or
* we need to keep going to calculate an accurate total */
if (!search_is_mutable &&
!upto_pos &&
msg->uid == windowargs->upto) {
if (total != UNPREDICTABLE)
break;
upto_pos = pos;
}
}
/* unlike 'anchor', the case of not finding 'upto' is not an error */
if (total == UNPREDICTABLE) {
/* the total was not predictable prima facie */
total = pos;
}
/* Print the resulting lists */
if (added.count) {
prot_printf(state->out, "* ADDED"); /* (uid pos) tuples */
for (i = 0 ; i < added.count ; i++) {
MsgData *msg = added.data[i];
prot_printf(state->out, " (%u %u)",
msg->uid, msg->msgno);
}
prot_printf(state->out, "\r\n");
}
if (removed.count) {
prot_printf(state->out, "* REMOVED"); /* uids */
for (i = 0 ; i < removed.count ; i++) {
struct index_record *record = removed.data[i];
prot_printf(state->out, " %u", record->uid);
}
prot_printf(state->out, "\r\n");
}
if (changed.count) {
prot_printf(state->out, "* CHANGED"); /* cids or uids */
for (i = 0 ; i < changed.count ; i++) {
struct index_record *record = changed.data[i];
if (windowargs->conversations)
prot_printf(state->out, " %s",
conversation_id_encode(record->cid));
else
prot_printf(state->out, " %u", record->uid);
}
prot_printf(state->out, "\r\n");
}
out:
if (!r) {
prot_printf(state->out, "* OK [HIGHESTMODSEQ " MODSEQ_FMT "]\r\n",
MAX(xconvmodseq, state->mailbox->i.highestmodseq));
prot_printf(state->out, "* OK [UIDVALIDITY %u]\r\n",
state->mailbox->i.uidvalidity);
prot_printf(state->out, "* OK [UIDNEXT %u]\r\n",
state->mailbox->i.last_uid + 1);
prot_printf(state->out, "* OK [TOTAL %u]\r\n",
total);
}
/* free all our temporary data */
index_msgdata_free(msgdata, state->exists);
ptrarray_fini(&added);
ptrarray_fini(&removed);
ptrarray_fini(&changed);
free_hashu64_table(&seen_cids, NULL);
free_hashu64_table(&old_seen_cids, NULL);
return r;
}
/*
* Performs a THREAD command
*/
EXPORTED int index_thread(struct index_state *state, int algorithm,
struct searchargs *searchargs, int usinguid)
{
unsigned *msgno_list;
int nmsg;
clock_t start;
modseq_t highestmodseq = 0;
/* update the index */
if (index_check(state, 0, 0))
return 0;
search_expr_internalise(state->mailbox, searchargs->root);
if(CONFIG_TIMING_VERBOSE)
start = clock();
/* Search for messages based on the given criteria */
nmsg = _index_search(&msgno_list, state, searchargs,
needs_modseq(searchargs, NULL) ?
&highestmodseq : NULL);
if (nmsg) {
/* Thread messages using given algorithm */
(*thread_algs[algorithm].threader)(state, msgno_list, nmsg, usinguid);
free(msgno_list);
if (highestmodseq)
prot_printf(state->out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq);
}
/* print an empty untagged response */
else
index_thread_print(state, NULL, usinguid);
prot_printf(state->out, "\r\n");
if (CONFIG_TIMING_VERBOSE) {
/* debug */
syslog(LOG_DEBUG, "THREAD %s processing time: %d msg in %f sec",
thread_algs[algorithm].alg_name, nmsg,
(clock() - start) / (double) CLOCKS_PER_SEC);
}
return nmsg;
}
/*
* Performs a COPY command
*/
EXPORTED int
index_copy(struct index_state *state,
char *sequence,
int usinguid,
char *name,
char **copyuidp,
int nolink,
struct namespace *namespace,
int isadmin,
int ismove,
int ignorequota)
{
static struct copyargs copyargs;
int i;
quota_t qdiffs[QUOTA_NUMRESOURCES] = QUOTA_DIFFS_INITIALIZER;
quota_t *qptr = NULL;
int r;
struct appendstate appendstate;
uint32_t msgno, checkval;
long docopyuid;
struct seqset *seq;
struct mailbox *mailbox;
struct mailbox *destmailbox = NULL;
struct index_map *im;
int is_same_user;
*copyuidp = NULL;
copyargs.nummsg = 0;
is_same_user = mboxname_same_userid(mailbox->name, name);
if (is_same_user < 0)
return is_same_user;
r = index_check(state, usinguid, usinguid);
if (r) return r;
mailbox = state->mailbox;
seq = _parse_sequence(state, sequence, usinguid);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
checkval = usinguid ? im->uid : msgno;
if (!seqset_ismember(seq, checkval))
continue;
index_copysetup(state, msgno, &copyargs, is_same_user);
}
seqset_free(seq);
if (copyargs.nummsg == 0) return IMAP_NO_NOSUCHMSG;
r = mailbox_open_iwl(name, &destmailbox);
if (r) return r;
/* not moving or different quota root - need to check quota */
if (!ismove || strcmpsafe(mailbox->quotaroot, destmailbox->quotaroot)) {
for (i = 0; i < copyargs.nummsg; i++)
qdiffs[QUOTA_STORAGE] += copyargs.copymsg[i].size;
qdiffs[QUOTA_MESSAGE] = copyargs.nummsg;
qptr = qdiffs;
}
r = append_setup_mbox(&appendstate, destmailbox, state->userid,
state->authstate, ACL_INSERT,
ignorequota ? NULL : qptr, namespace, isadmin,
ismove ? EVENT_MESSAGE_MOVE : EVENT_MESSAGE_COPY);
if (r) goto done;
docopyuid = (appendstate.myrights & ACL_READ);
r = append_copy(mailbox, &appendstate, copyargs.nummsg,
copyargs.copymsg, nolink);
if (r) {
append_abort(&appendstate);
goto done;
}
r = append_commit(&appendstate);
if (r) goto done;
/* unlock first so we don't hold the lock while expunging
* the source */
mailbox_unlock_index(destmailbox, NULL);
if (docopyuid || ismove) {
char *source;
struct seqset *seq;
unsigned uidvalidity = destmailbox->i.uidvalidity;
seq = seqset_init(0, SEQ_SPARSE);
for (i = 0; i < copyargs.nummsg; i++)
seqset_add(seq, copyargs.copymsg[i].uid, 1);
source = seqset_cstring(seq);
/* remove the source messages */
if (ismove)
r = index_expunge(state, source, 0);
if (docopyuid) {
*copyuidp = xmalloc(strlen(source) + 50);
if (appendstate.nummsg == 1)
sprintf(*copyuidp, "%u %s %u", uidvalidity, source,
appendstate.baseuid);
else
sprintf(*copyuidp, "%u %s %u:%u", uidvalidity, source,
appendstate.baseuid,
appendstate.baseuid + appendstate.nummsg - 1);
}
free(source);
seqset_free(seq);
}
/* we log the first name to get GUID-copy magic */
if (!r)
sync_log_mailbox_double(mailbox->name, name);
done:
mailbox_close(&destmailbox);
return r;
}
/*
* Helper function to multiappend a message to remote mailbox
*/
static int index_appendremote(struct index_state *state, uint32_t msgno,
struct protstream *pout)
{
struct mailbox *mailbox = state->mailbox;
struct buf buf = BUF_INITIALIZER;
unsigned flag, flagmask = 0;
char datebuf[RFC3501_DATETIME_MAX+1];
char sepchar = '(';
struct index_record record;
int r;
r = index_reload_record(state, msgno, &record);
if (r) return r;
/* Open the message file */
if (mailbox_map_record(mailbox, &record, &buf))
return IMAP_NO_MSGGONE;
/* start the individual append */
prot_printf(pout, " ");
/* add system flags */
if (record.system_flags & FLAG_ANSWERED) {
prot_printf(pout, "%c\\Answered", sepchar);
sepchar = ' ';
}
if (record.system_flags & FLAG_FLAGGED) {
prot_printf(pout, "%c\\Flagged", sepchar);
sepchar = ' ';
}
if (record.system_flags & FLAG_DRAFT) {
prot_printf(pout, "%c\\Draft", sepchar);
sepchar = ' ';
}
if (record.system_flags & FLAG_DELETED) {
prot_printf(pout, "%c\\Deleted", sepchar);
sepchar = ' ';
}
if (record.system_flags & FLAG_SEEN) {
prot_printf(pout, "%c\\Seen", sepchar);
sepchar = ' ';
}
/* add user flags */
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if ((flag & 31) == 0) {
flagmask = record.user_flags[flag/32];
}
if (state->flagname[flag] && (flagmask & (1<<(flag & 31)))) {
prot_printf(pout, "%c%s", sepchar, state->flagname[flag]);
sepchar = ' ';
}
}
/* add internal date */
time_to_rfc3501(record.internaldate, datebuf, sizeof(datebuf));
prot_printf(pout, ") \"%s\" ", datebuf);
/* message literal */
index_fetchmsg(state, &buf, 0, record.size, 0, 0);
/* close the message file */
buf_free(&buf);
return 0;
}
/*
* Performs a COPY command from a local mailbox to a remote mailbox
*/
EXPORTED int index_copy_remote(struct index_state *state, char *sequence,
int usinguid, struct protstream *pout)
{
uint32_t msgno;
struct seqset *seq;
struct index_map *im;
int r;
r = index_check(state, usinguid, usinguid);
if (r) return r;
seq = _parse_sequence(state, sequence, usinguid);
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
if (!seqset_ismember(seq, usinguid ? im->uid : msgno))
continue;
index_appendremote(state, msgno, pout);
}
seqset_free(seq);
return 0;
}
/*
* Returns the msgno of the message with UID 'uid'.
* If no message with UID 'uid', returns the message with
* the higest UID not greater than 'uid'.
*/
EXPORTED uint32_t index_finduid(struct index_state *state, uint32_t uid)
{
unsigned low = 1;
unsigned high = state->exists;
unsigned mid;
unsigned miduid;
while (low <= high) {
mid = (high - low)/2 + low;
miduid = index_getuid(state, mid);
if (miduid == uid)
return mid;
else if (miduid > uid)
high = mid - 1;
else
low = mid + 1;
}
return high;
}
/* Helper function to determine domain of data */
enum {
DOMAIN_7BIT = 0,
DOMAIN_8BIT,
DOMAIN_BINARY
};
static int data_domain(const char *p, size_t n)
{
while (n--) {
if (!*p) return DOMAIN_BINARY;
if (*p & 0x80) return DOMAIN_8BIT;
p++;
}
return DOMAIN_7BIT;
}
/*
* Helper function to fetch data from a message file. Writes a
* quoted-string or literal containing data from 'msg_base', which is
* of size 'msg_size', starting at 'offset' and containing 'size'
* octets. If 'octet_count' is nonzero, the data is
* further constrained by 'start_octet' and 'octet_count' as per the
* IMAP command PARTIAL.
*/
void index_fetchmsg(struct index_state *state, const struct buf *msg,
unsigned offset,
unsigned size, /* this is the correct size for a news message after
having LF translated to CRLF */
unsigned start_octet, unsigned octet_count)
{
unsigned n, domain;
/* If no data, output NIL */
if (!msg || !msg->s) {
prot_printf(state->out, "NIL");
return;
}
/* partial fetch: adjust 'size' */
if (octet_count) {
if (size <= start_octet) {
size = 0;
}
else {
size -= start_octet;
}
if (size > octet_count) size = octet_count;
}
/* If zero-length data, output empty quoted string */
if (size == 0) {
prot_printf(state->out, "\"\"");
return;
}
/* Seek over PARTIAL constraint */
offset += start_octet;
n = size;
if (offset + size > msg->len) {
if (msg->len > offset) {
n = msg->len - offset;
}
else {
prot_printf(state->out, "\"\"");
return;
}
}
/* Get domain of the data */
domain = data_domain(msg->s + offset, n);
if (domain == DOMAIN_BINARY) {
/* Write size of literal8 */
prot_printf(state->out, "~{%u}\r\n", size);
} else {
/* Write size of literal */
prot_printf(state->out, "{%u}\r\n", size);
}
/* Non-text literal -- tell the protstream about it */
if (domain != DOMAIN_7BIT) prot_data_boundary(state->out);
prot_write(state->out, msg->s + offset, n);
while (n++ < size) {
/* File too short, resynch client.
*
* This can only happen if the reported size of the part
* is incorrect and would push us past EOF.
*/
(void)prot_putc(' ', state->out);
}
/* End of non-text literal -- tell the protstream about it */
if (domain != DOMAIN_7BIT) prot_data_boundary(state->out);
}
/*
* Helper function to fetch a body section
*/
static int index_fetchsection(struct index_state *state, const char *resp,
const struct buf *inmsg,
char *section, const char *cachestr, unsigned size,
unsigned start_octet, unsigned octet_count)
{
const char *p;
int32_t skip = 0;
int fetchmime = 0;
unsigned offset = 0;
char *decbuf = NULL;
struct buf msg = BUF_INITIALIZER;
buf_init_ro(&msg, inmsg->s, inmsg->len);
p = section;
/* Special-case BODY[] */
if (*p == ']') {
if (strstr(resp, "BINARY.SIZE")) {
prot_printf(state->out, "%s%u", resp, size);
} else {
prot_printf(state->out, "%s", resp);
index_fetchmsg(state, &msg, 0, size,
start_octet, octet_count);
}
return 0;
}
while (*p != ']' && *p != 'M') {
int num_parts = CACHE_ITEM_BIT32(cachestr);
int r;
/* Generate the actual part number */
r = parseint32(p, &p, &skip);
if (*p == '.') p++;
/* Handle .0, .HEADER, and .TEXT */
if (r || skip == 0) {
skip = 0;
/* We don't have any digits, so its a string */
switch (*p) {
case 'H':
p += 6;
fetchmime++; /* .HEADER maps internally to .0.MIME */
break;
case 'T':
p += 4;
break; /* .TEXT maps internally to .0 */
default:
fetchmime++; /* .0 maps internally to .0.MIME */
break;
}
}
/* section number too large */
if (skip >= num_parts) goto badpart;
if (*p != ']' && *p != 'M') {
/* We are NOT at the end of a part specification, so there's
* a subpart being requested. Find the subpart in the tree. */
/* Skip the headers for this part, along with the number of
* sub parts */
cachestr += num_parts * 5 * 4 + CACHE_ITEM_SIZE_SKIP;
/* Skip to the correct part */
while (--skip) {
if (CACHE_ITEM_BIT32(cachestr) > 0) {
/* Skip each part at this level */
skip += CACHE_ITEM_BIT32(cachestr)-1;
cachestr += CACHE_ITEM_BIT32(cachestr) * 5 * 4;
}
cachestr += CACHE_ITEM_SIZE_SKIP;
}
}
}
if (*p == 'M') fetchmime++;
cachestr += skip * 5 * 4 + CACHE_ITEM_SIZE_SKIP + (fetchmime ? 0 : 2 * 4);
if (CACHE_ITEM_BIT32(cachestr + CACHE_ITEM_SIZE_SKIP) == (bit32) -1)
goto badpart;
offset = CACHE_ITEM_BIT32(cachestr);
size = CACHE_ITEM_BIT32(cachestr + CACHE_ITEM_SIZE_SKIP);
if (msg.s && (p = strstr(resp, "BINARY"))) {
/* BINARY or BINARY.SIZE */
int encoding = CACHE_ITEM_BIT32(cachestr + 2 * 4) & 0xff;
size_t newsize;
/* check that the offset isn't corrupt */
if (offset + size > msg_size) {
syslog(LOG_ERR, "invalid part offset in %s", state_mboxname(state));
return IMAP_IOERROR;
}
msg.s = (char *)charset_decode_mimebody(msg.s + offset, size, encoding,
&decbuf, &newsize);
if (!msg.s) {
/* failed to decode */
if (decbuf) free(decbuf);
return IMAP_NO_UNKNOWN_CTE;
}
else if (p[6] == '.') {
/* BINARY.SIZE */
prot_printf(state->out, "%s%zd", resp, newsize);
if (decbuf) free(decbuf);
return 0;
}
else {
/* BINARY */
offset = 0;
size = newsize;
msg.len = newsize;
}
}
/* Output body part */
prot_printf(state->out, "%s", resp);
index_fetchmsg(state, &msg, offset, size,
start_octet, octet_count);
if (decbuf) free(decbuf);
return 0;
badpart:
if (strstr(resp, "BINARY.SIZE"))
prot_printf(state->out, "%s0", resp);
else
prot_printf(state->out, "%sNIL", resp);
return 0;
}
/*
* Helper function to fetch a HEADER.FIELDS[.NOT] body section
*/
static void index_fetchfsection(struct index_state *state,
const char *msg_base,
unsigned long msg_size,
struct fieldlist *fsection,
const char *cachestr,
unsigned start_octet, unsigned octet_count)
{
const char *p;
int32_t skip = 0;
int fields_not = 0;
unsigned crlf_start = 0;
unsigned crlf_size = 2;
char *buf;
unsigned size;
int r;
/* If no data, output null quoted string */
if (!msg_base) {
prot_printf(state->out, "\"\"");
return;
}
p = fsection->section;
while (*p != 'H') {
int num_parts = CACHE_ITEM_BIT32(cachestr);
r = parseint32(p, &p, &skip);
if (*p == '.') p++;
/* section number too large */
if (r || skip == 0 || skip >= num_parts) goto badpart;
cachestr += num_parts * 5 * 4 + CACHE_ITEM_SIZE_SKIP;
while (--skip) {
if (CACHE_ITEM_BIT32(cachestr) > 0) {
skip += CACHE_ITEM_BIT32(cachestr)-1;
cachestr += CACHE_ITEM_BIT32(cachestr) * 5 * 4;
}
cachestr += CACHE_ITEM_SIZE_SKIP;
}
}
/* leaf object */
if (0 == CACHE_ITEM_BIT32(cachestr)) goto badpart;
cachestr += 4;
if (CACHE_ITEM_BIT32(cachestr+CACHE_ITEM_SIZE_SKIP) == (bit32) -1)
goto badpart;
if (p[13]) fields_not++; /* Check for "." after "HEADER.FIELDS" */
buf = index_readheader(msg_base, msg_size,
CACHE_ITEM_BIT32(cachestr),
CACHE_ITEM_BIT32(cachestr+CACHE_ITEM_SIZE_SKIP));
if (fields_not) {
message_pruneheader(buf, 0, fsection->fields);
}
else {
message_pruneheader(buf, fsection->fields, 0);
}
size = strlen(buf);
/* partial fetch: adjust 'size' */
if (octet_count) {
if (size <= start_octet) {
crlf_start = start_octet - size;
size = 0;
start_octet = 0;
if (crlf_size <= crlf_start) {
crlf_size = 0;
}
else {
crlf_size -= crlf_start;
}
}
else {
size -= start_octet;
}
if (size > octet_count) {
size = octet_count;
crlf_size = 0;
}
else if (size + crlf_size > octet_count) {
crlf_size = octet_count - size;
}
}
/* If no data, output null quoted string */
if (size + crlf_size == 0) {
prot_printf(state->out, "\"\"");
return;
}
/* Write literal */
prot_printf(state->out, "{%u}\r\n", size + crlf_size);
prot_write(state->out, buf + start_octet, size);
prot_write(state->out, "\r\n" + crlf_start, crlf_size);
return;
badpart:
prot_printf(state->out, "NIL");
}
/*
* Helper function to read a header section into a static buffer
*/
static char *index_readheader(const char *msg_base, unsigned long msg_size,
unsigned offset, unsigned size)
{
static struct buf buf = BUF_INITIALIZER;
if (offset + size > msg_size) {
/* Message file is too short, truncate request */
if (offset < msg_size) {
size = msg_size - offset;
}
else {
size = 0;
}
}
buf_reset(&buf);
buf_appendmap(&buf, msg_base+offset, size);
return (char *)buf_cstring(&buf);
}
/*
* Prune the header section in buf to include only those headers
* listed in headers or (if headers_not is non-empty) those headers
* not in headers_not.
*/
static void index_pruneheader(char *buf, const strarray_t *headers,
const strarray_t *headers_not)
{
char *p, *colon, *nextheader;
int goodheader;
char *endlastgood = buf;
char **l;
int count = 0;
int maxlines = config_getint(IMAPOPT_MAXHEADERLINES);
p = buf;
while (*p && *p != '\r') {
colon = strchr(p, ':');
/*
* If there is no colon in remaining buffer,
* there is no valid header, leave loop
*/
if (!colon) break;
if (colon && headers_not && headers_not->count) {
goodheader = 1;
for (l = headers_not->data ; *l ; l++) {
if ((size_t) (colon - p) == strlen(*l) &&
!strncasecmp(p, *l, colon - p)) {
goodheader = 0;
break;
}
}
} else {
goodheader = 0;
}
if (colon && headers && headers->count) {
for (l = headers->data ; *l ; l++) {
if ((size_t) (colon - p) == strlen(*l) &&
!strncasecmp(p, *l, colon - p)) {
goodheader = 1;
break;
}
}
}
nextheader = p;
do {
nextheader = strchr(nextheader, '\n');
if (nextheader) nextheader++;
else nextheader = p + strlen(p);
} while (*nextheader == ' ' || *nextheader == '\t');
if (goodheader) {
if (endlastgood != p) {
/* memmove and not strcpy since this is all within a
* single buffer */
memmove(endlastgood, p, strlen(p) + 1);
nextheader -= p - endlastgood;
}
endlastgood = nextheader;
}
p = nextheader;
/* stop giant headers causing massive loops */
if (maxlines) {
count++;
if (count > maxlines) break;
}
}
*endlastgood = '\0';
}
/*
* Handle a FETCH RFC822.HEADER.LINES or RFC822.HEADER.LINES.NOT
* that can't use the cacheheaders in cyrus.cache
*/
static void index_fetchheader(struct index_state *state,
const char *msg_base,
unsigned long msg_size,
unsigned size,
const strarray_t *headers,
const strarray_t *headers_not)
{
char *buf;
/* If no data, output null quoted string */
if (!msg_base) {
prot_printf(state->out, "\"\"");
return;
}
buf = index_readheader(msg_base, msg_size, 0, size);
message_pruneheader(buf, headers, headers_not);
size = strlen(buf);
prot_printf(state->out, "{%u}\r\n%s\r\n", size+2, buf);
}
/*
* Handle a FETCH RFC822.HEADER.LINES that can use the
* cacheheaders in cyrus.cache
*/
static void
index_fetchcacheheader(struct index_state *state, struct index_record *record,
const strarray_t *headers, unsigned start_octet,
unsigned octet_count)
{
static struct buf buf = BUF_INITIALIZER;
unsigned size;
unsigned crlf_start = 0;
unsigned crlf_size = 2;
struct mailbox *mailbox = state->mailbox;
if (mailbox_cacherecord(mailbox, record)) {
/* bogus cache record */
prot_printf(state->out, "\"\"");
return;
}
buf_setmap(&buf, cacheitem_base(record, CACHE_HEADERS),
cacheitem_size(record, CACHE_HEADERS));
buf_cstring(&buf);
message_pruneheader(buf.s, headers, 0);
size = strlen(buf.s); /* not buf.len, it has been pruned */
/* partial fetch: adjust 'size' */
if (octet_count) {
if (size <= start_octet) {
crlf_start = start_octet - size;
size = 0;
start_octet = 0;
if (crlf_size <= crlf_start) {
crlf_size = 0;
}
else {
crlf_size -= crlf_start;
}
}
else {
size -= start_octet;
}
if (size > octet_count) {
size = octet_count;
crlf_size = 0;
}
else if (size + crlf_size > octet_count) {
crlf_size = octet_count - size;
}
}
if (size + crlf_size == 0) {
prot_printf(state->out, "\"\"");
}
else {
prot_printf(state->out, "{%u}\r\n", size + crlf_size);
prot_write(state->out, buf.s + start_octet, size);
prot_write(state->out, "\r\n" + crlf_start, crlf_size);
}
}
/*
* Send a * FLAGS response.
*/
static void index_listflags(struct index_state *state)
{
unsigned i;
int cancreate = 0;
char sepchar = '(';
prot_printf(state->out, "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen");
for (i = 0; i < MAX_USER_FLAGS; i++) {
if (state->flagname[i]) {
prot_printf(state->out, " %s", state->flagname[i]);
}
else cancreate++;
}
prot_printf(state->out, ")\r\n* OK [PERMANENTFLAGS ");
if (!state->examining) {
if (state->myrights & ACL_WRITE) {
prot_printf(state->out, "%c\\Answered \\Flagged \\Draft", sepchar);
sepchar = ' ';
}
if (state->myrights & ACL_DELETEMSG) {
prot_printf(state->out, "%c\\Deleted", sepchar);
sepchar = ' ';
}
if (state->myrights & ACL_SETSEEN) {
prot_printf(state->out, "%c\\Seen", sepchar);
sepchar = ' ';
}
if (state->myrights & ACL_WRITE) {
for (i = 0; i < MAX_USER_FLAGS; i++) {
if (state->flagname[i]) {
prot_printf(state->out, " %s", state->flagname[i]);
}
}
if (cancreate) {
prot_printf(state->out, " \\*");
}
}
}
if (sepchar == '(') prot_printf(state->out, "(");
prot_printf(state->out, ")] Ok\r\n");
}
EXPORTED void index_checkflags(struct index_state *state, int print, int dirty)
{
struct mailbox *mailbox = state->mailbox;
unsigned i;
for (i = 0; i < MAX_USER_FLAGS; i++) {
/* both empty */
if (!mailbox->flagname[i] && !state->flagname[i])
continue;
/* both same */
if (mailbox->flagname[i] && state->flagname[i] &&
!strcmp(mailbox->flagname[i], state->flagname[i]))
continue;
/* ok, got something to change! */
if (state->flagname[i])
free(state->flagname[i]);
if (mailbox->flagname[i])
state->flagname[i] = xstrdup(mailbox->flagname[i]);
else
state->flagname[i] = NULL;
dirty = 1;
}
if (dirty && print)
index_listflags(state);
}
static void index_tellexpunge(struct index_state *state)
{
unsigned oldmsgno;
uint32_t msgno = 1;
struct seqset *vanishedlist;
struct index_map *im;
unsigned exists = state->exists;
vanishedlist = seqset_init(0, SEQ_SPARSE);
for (oldmsgno = 1; oldmsgno <= exists; oldmsgno++) {
im = &state->map[oldmsgno-1];
/* inform about expunges */
if (im->system_flags & FLAG_EXPUNGED) {
state->exists--;
/* they never knew about this one, skip */
if (msgno > state->oldexists)
continue;
state->oldexists--;
if (state->qresync)
seqset_add(vanishedlist, im->uid, 1);
else
prot_printf(state->out, "* %u EXPUNGE\r\n", msgno);
continue;
}
/* copy back if necessary (after first expunge) */
if (msgno < oldmsgno)
state->map[msgno-1] = *im;
msgno++;
}
/* report all vanished if we're doing it this way */
if (vanishedlist->len) {
char *vanished = seqset_cstring(vanishedlist);
prot_printf(state->out, "* VANISHED %s\r\n", vanished);
free(vanished);
}
seqset_free(vanishedlist);
/* highestmodseq can now come forward to real-time */
state->highestmodseq = state->mailbox->i.highestmodseq;
}
static void index_tellexists(struct index_state *state)
{
prot_printf(state->out, "* %u EXISTS\r\n", state->exists);
prot_printf(state->out, "* %u RECENT\r\n", state->numrecent);
state->oldexists = state->exists;
}
EXPORTED void index_tellchanges(struct index_state *state, int canexpunge,
int printuid, int printmodseq)
{
uint32_t msgno;
struct index_map *im;
if (canexpunge) index_tellexpunge(state);
if (state->oldexists != state->exists) index_tellexists(state);
index_checkflags(state, 1, 0);
/* print any changed message flags */
for (msgno = 1; msgno <= state->exists; msgno++) {
im = &state->map[msgno-1];
/* report if it's changed since last told */
if (im->modseq > im->told_modseq)
index_printflags(state, msgno, printuid, printmodseq);
}
}
struct fetch_annotation_rock {
struct protstream *pout;
const char *sep;
};
static void fetch_annotation_response(const char *mboxname
__attribute__((unused)),
uint32_t uid
__attribute__((unused)),
const char *entry,
struct attvaluelist *attvalues,
void *rock)
{
char sep2 = '(';
struct attvaluelist *l;
struct fetch_annotation_rock *frock = rock;
prot_printf(frock->pout, "%s", frock->sep);
prot_printastring(frock->pout, entry);
prot_putc(' ', frock->pout);
for (l = attvalues ; l ; l = l->next) {
prot_putc(sep2, frock->pout);
sep2 = ' ';
prot_printastring(frock->pout, l->attrib);
prot_putc(' ', frock->pout);
prot_printmap(frock->pout, l->value.s, l->value.len);
}
prot_putc(')', frock->pout);
frock->sep = " ";
}
/*
* Helper function to send FETCH data for the ANNOTATION
* fetch item.
*/
static int index_fetchannotations(struct index_state *state,
uint32_t msgno,
const struct fetchargs *fetchargs)
{
annotate_state_t *astate = NULL;
struct fetch_annotation_rock rock;
int r = 0;
r = mailbox_get_annotate_state(state->mailbox,
state->map[msgno-1].uid,
&astate);
if (r) return r;
annotate_state_set_auth(astate, fetchargs->isadmin,
fetchargs->userid, fetchargs->authstate);
memset(&rock, 0, sizeof(rock));
rock.pout = state->out;
rock.sep = "";
r = annotate_state_fetch(astate,
&fetchargs->entries, &fetchargs->attribs,
fetch_annotation_response, &rock,
0);
return r;
}
/*
* Helper function to send * FETCH (FLAGS data.
* Does not send the terminating close paren or CRLF.
* Also sends preceeding * FLAGS if necessary.
*/
static void index_fetchflags(struct index_state *state,
uint32_t msgno)
{
int sepchar = '(';
unsigned flag;
bit32 flagmask = 0;
struct index_map *im = &state->map[msgno-1];
prot_printf(state->out, "* %u FETCH (FLAGS ", msgno);
if (im->isrecent) {
prot_printf(state->out, "%c\\Recent", sepchar);
sepchar = ' ';
}
if (im->system_flags & FLAG_ANSWERED) {
prot_printf(state->out, "%c\\Answered", sepchar);
sepchar = ' ';
}
if (im->system_flags & FLAG_FLAGGED) {
prot_printf(state->out, "%c\\Flagged", sepchar);
sepchar = ' ';
}
if (im->system_flags & FLAG_DRAFT) {
prot_printf(state->out, "%c\\Draft", sepchar);
sepchar = ' ';
}
if (im->system_flags & FLAG_DELETED) {
prot_printf(state->out, "%c\\Deleted", sepchar);
sepchar = ' ';
}
if (im->isseen) {
prot_printf(state->out, "%c\\Seen", sepchar);
sepchar = ' ';
}
for (flag = 0; flag < VECTOR_SIZE(state->flagname); flag++) {
if ((flag & 31) == 0) {
flagmask = im->user_flags[flag/32];
}
if (state->flagname[flag] && (flagmask & (1<<(flag & 31)))) {
prot_printf(state->out, "%c%s", sepchar, state->flagname[flag]);
sepchar = ' ';
}
}
if (sepchar == '(') (void)prot_putc('(', state->out);
(void)prot_putc(')', state->out);
im->told_modseq = im->modseq;
}
static void index_printflags(struct index_state *state,
uint32_t msgno, int usinguid,
int printmodseq)
{
struct index_map *im = &state->map[msgno-1];
index_fetchflags(state, msgno);
/* http://www.rfc-editor.org/errata_search.php?rfc=5162
* Errata ID: 1807 - MUST send UID and MODSEQ to all
* untagged FETCH unsolicited responses */
if (usinguid || state->qresync)
prot_printf(state->out, " UID %u", im->uid);
if (printmodseq || state->qresync)
prot_printf(state->out, " MODSEQ (" MODSEQ_FMT ")", im->modseq);
prot_printf(state->out, ")\r\n");
}
/*
* Helper function to send requested * FETCH data for a message
*/
static int index_fetchreply(struct index_state *state, uint32_t msgno,
const struct fetchargs *fetchargs)
{
struct mailbox *mailbox = state->mailbox;
int fetchitems = fetchargs->fetchitems;
struct buf buf = BUF_INITIALIZER;
struct octetinfo *oi = NULL;
int sepchar = '(';
int started = 0;
struct section *section;
struct fieldlist *fsection;
char respbuf[100];
int r = 0;
struct index_map *im = &state->map[msgno-1];
struct index_record record;
/* Check the modseq against changedsince */
if (fetchargs->changedsince && im->modseq <= fetchargs->changedsince)
return 0;
/* skip missing records entirely */
if (!im->recno)
return 0;
r = index_reload_record(state, msgno, &record);
if (r) {
prot_printf(state->out, "* OK ");
prot_printf(state->out, error_message(IMAP_NO_MSGGONE), msgno);
prot_printf(state->out, "\r\n");
return 0;
}
/* Check against the CID list filter */
if (fetchargs->cidhash) {
const char *key = conversation_id_encode(record.cid);
if (!hash_lookup(key, fetchargs->cidhash))
return 0;
}
/* Open the message file if we're going to need it */
if ((fetchitems & (FETCH_HEADER|FETCH_TEXT|FETCH_SHA1|FETCH_RFC822)) ||
fetchargs->cache_atleast > record.cache_version ||
fetchargs->binsections || fetchargs->sizesections ||
fetchargs->bodysections) {
if (mailbox_map_record(mailbox, &record, &buf)) {
prot_printf(state->out, "* OK ");
prot_printf(state->out, error_message(IMAP_NO_MSGGONE), msgno);
prot_printf(state->out, "\r\n");
return 0;
}
}
/* display flags if asked _OR_ if they've changed */
if (fetchitems & FETCH_FLAGS || im->told_modseq < record.modseq) {
index_fetchflags(state, msgno);
sepchar = ' ';
}
else if ((fetchitems & ~FETCH_SETSEEN) || fetchargs->fsections ||
fetchargs->headers.count || fetchargs->headers_not.count) {
/* these fetch items will always succeed, so start the response */
prot_printf(state->out, "* %u FETCH ", msgno);
started = 1;
}
if (fetchitems & FETCH_UID) {
prot_printf(state->out, "%cUID %u", sepchar, record.uid);
sepchar = ' ';
}
if (fetchitems & FETCH_GUID) {
prot_printf(state->out, "%cDIGEST.SHA1 %s", sepchar,
message_guid_encode(&record.guid));
sepchar = ' ';
}
if (fetchitems & FETCH_INTERNALDATE) {
time_t msgdate = record.internaldate;
char datebuf[RFC3501_DATETIME_MAX+1];
time_to_rfc3501(msgdate, datebuf, sizeof(datebuf));
prot_printf(state->out, "%cINTERNALDATE \"%s\"",
sepchar, datebuf);
sepchar = ' ';
}
if (fetchitems & FETCH_MODSEQ) {
prot_printf(state->out, "%cMODSEQ (" MODSEQ_FMT ")",
sepchar, record.modseq);
sepchar = ' ';
}
if (fetchitems & FETCH_SIZE) {
prot_printf(state->out, "%cRFC822.SIZE %u",
sepchar, record.size);
sepchar = ' ';
}
if ((fetchitems & FETCH_ANNOTATION)) {
prot_printf(state->out, "%cANNOTATION (", sepchar);
r = index_fetchannotations(state, msgno, fetchargs);
r = 0;
prot_printf(state->out, ")");
sepchar = ' ';
}
if (fetchitems & FETCH_FILESIZE) {
if (!msg_base) {
char *fname = mailbox_message_fname(mailbox, im->uid);
struct stat sbuf;
/* Find the size of the message file */
if (stat(fname, &sbuf) == -1)
syslog(LOG_ERR, "IOERROR: stat on %s: %m", fname);
else
msg_size = sbuf.st_size;
}
prot_printf(state->out, "%cRFC822.FILESIZE %lu", sepchar,
(long unsigned)msg_size);
sepchar = ' ';
}
if (fetchitems & FETCH_SHA1) {
struct message_guid tmpguid;
message_guid_generate(&tmpguid, msg_base, msg_size);
prot_printf(state->out, "%cRFC822.SHA1 %s", sepchar, message_guid_encode(&tmpguid));
sepchar = ' ';
}
if ((fetchitems & FETCH_CID) &&
config_getswitch(IMAPOPT_CONVERSATIONS)) {
struct buf buf = BUF_INITIALIZER;
if (!record.cid)
buf_appendcstr(&buf, "NIL");
else
buf_printf(&buf, CONV_FMT, record.cid);
prot_printf(state->out, "%cCID %s", sepchar, buf_cstring(&buf));
buf_free(&buf);
sepchar = ' ';
}
if ((fetchitems & FETCH_FOLDER)) {
struct namespace *ns = fetchargs->namespace;
char mboxname[MAX_MAILBOX_PATH+1];
r = ns->mboxname_toexternal(ns, state->mailbox->name,
fetchargs->userid, mboxname);
if (!r) {
prot_printf(state->out, "%cFOLDER ", sepchar);
prot_printastring(state->out, mboxname);
sepchar = ' ';
}
r = 0;
}
if ((fetchitems & FETCH_UIDVALIDITY)) {
prot_printf(state->out, "%cUIDVALIDITY %u", sepchar,
state->mailbox->i.uidvalidity);
sepchar = ' ';
}
if (fetchitems & FETCH_ENVELOPE) {
if (!mailbox_cacherecord(mailbox, &record)) {
prot_printf(state->out, "%cENVELOPE ", sepchar);
sepchar = ' ';
prot_putbuf(state->out, cacheitem_buf(&record, CACHE_ENVELOPE));
}
}
if (fetchitems & FETCH_BODYSTRUCTURE) {
if (!mailbox_cacherecord(mailbox, &record)) {
prot_printf(state->out, "%cBODYSTRUCTURE ", sepchar);
sepchar = ' ';
prot_putbuf(state->out, cacheitem_buf(&record, CACHE_BODYSTRUCTURE));
}
}
if (fetchitems & FETCH_BODY) {
if (!mailbox_cacherecord(mailbox, &record)) {
prot_printf(state->out, "%cBODY ", sepchar);
sepchar = ' ';
prot_putbuf(state->out, cacheitem_buf(&record, CACHE_BODY));
}
}
if (fetchitems & FETCH_HEADER) {
prot_printf(state->out, "%cRFC822.HEADER ", sepchar);
sepchar = ' ';
index_fetchmsg(state, &buf, 0,
record.header_size,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : 0,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : 0);
}
else if (fetchargs->headers.count || fetchargs->headers_not.count) {
prot_printf(state->out, "%cRFC822.HEADER ", sepchar);
sepchar = ' ';
if (fetchargs->cache_atleast > record.cache_version) {
index_fetchheader(state, buf.s, buf.len,
record.header_size,
&fetchargs->headers, &fetchargs->headers_not);
} else {
index_fetchcacheheader(state, &record, &fetchargs->headers, 0, 0);
}
}
if (fetchitems & FETCH_TEXT) {
prot_printf(state->out, "%cRFC822.TEXT ", sepchar);
sepchar = ' ';
index_fetchmsg(state, &buf,
record.header_size, record.size - record.header_size,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : 0,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : 0);
}
if (fetchitems & FETCH_RFC822) {
prot_printf(state->out, "%cRFC822 ", sepchar);
sepchar = ' ';
index_fetchmsg(state, &buf, 0, record.size,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : 0,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : 0);
}
for (fsection = fetchargs->fsections; fsection; fsection = fsection->next) {
int i;
prot_printf(state->out, "%cBODY[%s ", sepchar, fsection->section);
sepchar = '(';
for (i = 0 ; i < fsection->fields->count ; i++) {
(void)prot_putc(sepchar, state->out);
sepchar = ' ';
prot_printastring(state->out, fsection->fields->data[i]);
}
(void)prot_putc(')', state->out);
sepchar = ' ';
oi = (struct octetinfo *)fsection->rock;
prot_printf(state->out, "%s ", fsection->trail);
if (fetchargs->cache_atleast > record.cache_version) {
if (!mailbox_cacherecord(mailbox, &record))
index_fetchfsection(state, buf.s, buf.len,
fsection,
cacheitem_base(&record, CACHE_SECTION),
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
else
prot_printf(state->out, "NIL");
}
else {
index_fetchcacheheader(state, &record, fsection->fields,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
}
}
for (section = fetchargs->bodysections; section; section = section->next) {
respbuf[0] = 0;
if (sepchar == '(' && !started) {
/* we haven't output a fetch item yet, so start the response */
snprintf(respbuf, sizeof(respbuf), "* %u FETCH ", msgno);
}
snprintf(respbuf+strlen(respbuf), sizeof(respbuf)-strlen(respbuf),
"%cBODY[%s ", sepchar, section->name);
oi = &section->octetinfo;
if (!mailbox_cacherecord(mailbox, &record)) {
r = index_fetchsection(state, respbuf, &buf,
section->name, cacheitem_base(&record, CACHE_SECTION),
record.size,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
if (!r) sepchar = ' ';
}
}
for (section = fetchargs->binsections; section; section = section->next) {
respbuf[0] = 0;
if (sepchar == '(' && !started) {
/* we haven't output a fetch item yet, so start the response */
snprintf(respbuf, sizeof(respbuf), "* %u FETCH ", msgno);
}
snprintf(respbuf+strlen(respbuf), sizeof(respbuf)-strlen(respbuf),
"%cBINARY[%s ", sepchar, section->name);
if (!mailbox_cacherecord(mailbox, &record)) {
oi = &section->octetinfo;
r = index_fetchsection(state, respbuf, &buf,
section->name, cacheitem_base(&record, CACHE_SECTION),
record.size,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
if (!r) sepchar = ' ';
}
}
for (section = fetchargs->sizesections; section; section = section->next) {
respbuf[0] = 0;
if (sepchar == '(' && !started) {
/* we haven't output a fetch item yet, so start the response */
snprintf(respbuf, sizeof(respbuf), "* %u FETCH ", msgno);
}
snprintf(respbuf+strlen(respbuf), sizeof(respbuf)-strlen(respbuf),
"%cBINARY.SIZE[%s ", sepchar, section->name);
if (!mailbox_cacherecord(mailbox, &record)) {
r = index_fetchsection(state, respbuf, &buf,
section->name, cacheitem_base(&record, CACHE_SECTION),
record.size,
fetchargs->start_octet, fetchargs->octet_count);
if (!r) sepchar = ' ';
}
}
if (sepchar != '(') {
/* finsh the response if we have one */
prot_printf(state->out, ")\r\n");
}
buf_free(&buf);
return r;
}
/*
* Fetch the text data associated with an IMAP URL.
*
* If outsize is NULL, the data will be output as a literal (URLFETCH),
* otherwise just the data will be output (CATENATE), and its size returned
* in *outsize.
*
* This is an amalgamation of index_fetchreply(), index_fetchsection()
* and index_fetchmsg().
*/
EXPORTED int index_urlfetch(struct index_state *state, uint32_t msgno,
unsigned params, const char *section,
unsigned long start_octet, unsigned long octet_count,
struct protstream *pout, unsigned long *outsize)
{
/* dumbass eM_Client sends this:
* A4 APPEND "INBOX.Junk Mail" () "14-Jul-2013 17:01:02 +0000"
* CATENATE (URL "/INBOX/;uid=83118/;section=TEXT.MIME"
* URL "/INBOX/;uid=83118/;section=TEXT")
*
* genius. I can sort of see how TEXT.MIME kinda == "HEADER",
* so there we go */
static char text_mime[] = "HEADER";
struct buf buf = BUF_INITIALIZER;
const char *cacheitem;
int fetchmime = 0, domain = DOMAIN_7BIT;
const char *data;
size_t size;
int32_t skip = 0;
int n, r = 0;
char *decbuf = NULL;
struct mailbox *mailbox = state->mailbox;
struct index_record record;
if (!strcasecmp(section, "TEXT.MIME"))
section = text_mime;
if (outsize) *outsize = 0;
r = index_reload_record(state, msgno, &record);
if (r) return r;
r = mailbox_cacherecord(mailbox, &record);
if (r) return r;
/* Open the message file */
if (mailbox_map_record(mailbox, &record, &buf))
return IMAP_NO_MSGGONE;
data = buf.s;
size = buf.len;
cacheitem = cacheitem_base(&record, CACHE_SECTION);
/* Special-case BODY[] */
if (!section || !*section) {
/* whole message, no further parsing */
}
else {
const char *p = ucase((char *) section);
while (*p && *p != 'M') {
int num_parts = CACHE_ITEM_BIT32(cacheitem);
/* Generate the actual part number */
r = parseint32(p, &p, &skip);
if (*p == '.') p++;
/* Handle .0, .HEADER, and .TEXT */
if (r || skip == 0) {
skip = 0;
/* We don't have any digits, so its a string */
switch (*p) {
case 'H':
p += 6;
fetchmime++; /* .HEADER maps internally to .0.MIME */
break;
case 'T':
p += 4;
break; /* .TEXT maps internally to .0 */
default:
fetchmime++; /* .0 maps internally to .0.MIME */
break;
}
}
/* section number too large */
if (skip >= num_parts) {
r = IMAP_BADURL;
goto done;
}
if (*p && *p != 'M') {
/* We are NOT at the end of a part specification, so there's
* a subpart being requested. Find the subpart in the tree. */
/* Skip the headers for this part, along with the number of
* sub parts */
cacheitem += num_parts * 5 * 4 + CACHE_ITEM_SIZE_SKIP;
/* Skip to the correct part */
while (--skip) {
if (CACHE_ITEM_BIT32(cacheitem) > 0) {
/* Skip each part at this level */
skip += CACHE_ITEM_BIT32(cacheitem)-1;
cacheitem += CACHE_ITEM_BIT32(cacheitem) * 5 * 4;
}
cacheitem += CACHE_ITEM_SIZE_SKIP;
}
}
}
if (*p == 'M') fetchmime++;
cacheitem += skip * 5 * 4 + CACHE_ITEM_SIZE_SKIP +
(fetchmime ? 0 : 2 * 4);
if (CACHE_ITEM_BIT32(cacheitem + CACHE_ITEM_SIZE_SKIP) == (bit32) -1) {
r = IMAP_BADURL;
goto done;
}
data += CACHE_ITEM_BIT32(cacheitem);
size = CACHE_ITEM_BIT32(cacheitem + CACHE_ITEM_SIZE_SKIP);
}
/* Handle extended URLFETCH parameters */
if (params & URLFETCH_BODYPARTSTRUCTURE) {
prot_printf(pout, " (BODYPARTSTRUCTURE");
/* XXX Calculate body part structure */
prot_printf(pout, " NIL");
prot_printf(pout, ")");
}
if (params & URLFETCH_BODY) {
prot_printf(pout, " (BODY");
}
else if (params & URLFETCH_BINARY) {
int encoding = CACHE_ITEM_BIT32(cacheitem + 2 * 4) & 0xff;
prot_printf(pout, " (BINARY");
data = charset_decode_mimebody(data, size, encoding,
&decbuf, &size);
if (!data) {
/* failed to decode */
prot_printf(pout, " NIL)");
r = 0;
goto done;
}
}
/* Handle PARTIAL request */
n = octet_count ? octet_count : size;
/* Sanity check the requested size */
if (start_octet + n > size) n = size - start_octet;
if (outsize) {
/* Return size (CATENATE) */
*outsize = n;
} else {
domain = data_domain(data + start_octet, n);
if (domain == DOMAIN_BINARY) {
/* Write size of literal8 */
prot_printf(pout, " ~{%u}\r\n", n);
} else {
/* Write size of literal */
prot_printf(pout, " {%u}\r\n", n);
}
}
/* Non-text literal -- tell the protstream about it */
if (domain != DOMAIN_7BIT) prot_data_boundary(pout);
prot_write(pout, data + start_octet, n);
/* End of non-text literal -- tell the protstream about it */
if (domain != DOMAIN_7BIT) prot_data_boundary(pout);
/* Complete extended URLFETCH response */
if (params & (URLFETCH_BODY | URLFETCH_BINARY)) prot_printf(pout, ")");
r = 0;
done:
/* Close the message file */
buf_free(&buf);
if (decbuf) free(decbuf);
return r;
}
/*
* Helper function to perform a STORE command for flags.
*/
static int index_storeflag(struct index_state *state,
struct index_modified_flags *modified_flags,
uint32_t msgno, struct index_record *record,
struct storeargs *storeargs)
{
uint32_t old, new, keep;
unsigned i;
int dirty = 0;
modseq_t oldmodseq;
struct index_map *im = &state->map[msgno-1];
int r;
memset(modified_flags, 0, sizeof(struct index_modified_flags));
oldmodseq = im->modseq;
/* Change \Seen flag. This gets done on the index first and will only be
copied into the record later if internalseen is set */
if (state->myrights & ACL_SETSEEN) {
old = im->isseen ? 1 : 0;
new = old;
if (storeargs->operation == STORE_REPLACE_FLAGS)
new = storeargs->seen ? 1 : 0;
else if (storeargs->seen)
new = (storeargs->operation == STORE_ADD_FLAGS) ? 1 : 0;
if (new != old) {
state->numunseen += (old - new);
im->isseen = new;
state->seen_dirty = 1;
dirty++;
}
}
keep = record->system_flags & FLAGS_INTERNAL;
old = record->system_flags & FLAGS_SYSTEM;
new = storeargs->system_flags & FLAGS_SYSTEM;
/* all other updates happen directly to the record */
if (storeargs->operation == STORE_REPLACE_FLAGS) {
if (!(state->myrights & ACL_WRITE)) {
/* ACL_DELETE handled in index_store() */
if ((old & FLAG_DELETED) != (new & FLAG_DELETED)) {
dirty++;
record->system_flags = (old & ~FLAG_DELETED) | (new & FLAG_DELETED);
}
}
else {
if (!(state->myrights & ACL_DELETEMSG)) {
if ((old & ~FLAG_DELETED) != (new & ~FLAG_DELETED)) {
dirty++;
record->system_flags = (old & FLAG_DELETED) | (new & ~FLAG_DELETED);
}
}
else {
if (old != new) {
dirty++;
record->system_flags = new;
}
}
for (i = 0; i < (MAX_USER_FLAGS/32); i++) {
if (record->user_flags[i] != storeargs->user_flags[i]) {
uint32_t changed;
dirty++;
changed = ~record->user_flags[i] & storeargs->user_flags[i];
if (changed) {
modified_flags->added_user_flags[i] = changed;
modified_flags->added_flags++;
}
changed = record->user_flags[i] & ~storeargs->user_flags[i];
if (changed) {
modified_flags->removed_user_flags[i] = changed;
modified_flags->removed_flags++;
}
record->user_flags[i] = storeargs->user_flags[i];
}
}
}
}
else if (storeargs->operation == STORE_ADD_FLAGS) {
uint32_t added;
if (~old & new) {
dirty++;
record->system_flags = old | new;
}
for (i = 0; i < (MAX_USER_FLAGS/32); i++) {
added = ~record->user_flags[i] & storeargs->user_flags[i];
if (added) {
dirty++;
record->user_flags[i] |= storeargs->user_flags[i];
modified_flags->added_user_flags[i] = added;
modified_flags->added_flags++;
}
}
}
else { /* STORE_REMOVE_FLAGS */
uint32_t removed;
if (old & new) {
dirty++;
record->system_flags &= ~storeargs->system_flags;
}
for (i = 0; i < (MAX_USER_FLAGS/32); i++) {
removed = record->user_flags[i] & storeargs->user_flags[i];
if (removed) {
dirty++;
record->user_flags[i] &= ~storeargs->user_flags[i];
modified_flags->removed_user_flags[i] = removed;
modified_flags->removed_flags++;
}
}
}
/* rfc4551:
* 3.8. Additional Quality-of-Implementation Issues
*
* Server implementations should follow the following rule, which
* applies to any successfully completed STORE/UID STORE (with and
* without UNCHANGEDSINCE modifier), as well as to a FETCH command that
* implicitly sets \Seen flag:
*
* Adding the flag when it is already present or removing when it is
* not present SHOULD NOT change the mod-sequence.
*
* This will prevent spurious client synchronization requests.
*/
if (!dirty) return 0;
if (state->internalseen) {
/* copy the seen flag from the index */
if (im->isseen)
record->system_flags |= FLAG_SEEN;
else
record->system_flags &= ~FLAG_SEEN;
}
/* add back the internal tracking flags */
record->system_flags |= keep;
modified_flags->added_system_flags = ~old & record->system_flags & FLAGS_SYSTEM;
if (modified_flags->added_system_flags)
modified_flags->added_flags++;
modified_flags->removed_system_flags = old & ~record->system_flags & FLAGS_SYSTEM;
if (modified_flags->removed_system_flags)
modified_flags->removed_flags++;
r = index_rewrite_record(state, msgno, record);
if (r) return r;
/* if it's silent and unchanged, update the seen value, but
* not if qresync is enabled - RFC 4551 says that the MODSEQ
* must always been told, and we prefer just to tell flags
* as well in this case, it's simpler and not much more
* bandwidth */
if (!state->qresync && storeargs->silent && im->told_modseq == oldmodseq)
im->told_modseq = im->modseq;
return 0;
}
/*
* Helper function to perform a STORE command for annotations
*/
static int index_store_annotation(struct index_state *state,
uint32_t msgno,
struct storeargs *storeargs)
{
modseq_t oldmodseq;
struct index_record record;
annotate_state_t *astate = NULL;
struct index_map *im = &state->map[msgno-1];
int r;
r = index_reload_record(state, msgno, &record);
if (r) goto out;
oldmodseq = record.modseq;
r = mailbox_get_annotate_state(state->mailbox, record.uid, &astate);
if (r) goto out;
annotate_state_set_auth(astate, storeargs->isadmin,
storeargs->userid, storeargs->authstate);
r = annotate_state_store(astate, storeargs->entryatts);
if (r) goto out;
/* It would be nice if the annotate layer told us whether it
* actually made a change to the database, but it doesn't, so
* we have to assume the message is dirty */
r = index_rewrite_record(state, msgno, &record);
if (r) goto out;
/* if it's silent and unchanged, update the seen value */
if (!state->qresync && storeargs->silent && im->told_modseq == oldmodseq)
im->told_modseq = im->modseq;
out:
return r;
}
/*
* Evaluate a searchargs structure on a msgno
*/
static int index_search_evaluate(struct index_state *state,
const struct searchargs *searchargs,
uint32_t msgno)
{
struct index_map *im = &state->map[msgno-1];
int r;
message_t *m;
struct index_record record;
r = index_reload_record(state, msgno, &record);
if (r) return r;
xstats_inc(SEARCH_EVALUATE);
m = message_new_from_index(state->mailbox, &record, msgno,
(im->isrecent ? MESSAGE_RECENT : 0) |
(im->isseen ? MESSAGE_SEEN : 0));
r = search_expr_evaluate(m, searchargs->root);
message_unref(&m);
return r;
}
struct getsearchtext_rock
{
search_text_receiver_t *receiver;
int partcount;
int charset_flags;
};
static void stuff_part(search_text_receiver_t *receiver,
int part, const struct buf *buf)
{
if (part == SEARCH_PART_HEADERS &&
!config_getswitch(IMAPOPT_SEARCH_INDEX_HEADERS))
return;
receiver->begin_part(receiver, part);
receiver->append_text(receiver, buf);
receiver->end_part(receiver, part);
}
static void extract_cb(const struct buf *text, void *rock)
{
struct getsearchtext_rock *str = (struct getsearchtext_rock *)rock;
str->receiver->append_text(str->receiver, text);
}
static int getsearchtext_cb(int partno, int charset, int encoding,
const char *subtype, struct buf *data,
void *rock)
{
struct getsearchtext_rock *str = (struct getsearchtext_rock *)rock;
char *q;
struct buf text = BUF_INITIALIZER;
if (!partno) {
/* header-like */
q = charset_decode_mimeheader(buf_cstring(data), str->charset_flags);
buf_init_ro_cstr(&text, q);
if (++str->partcount == 1) {
stuff_part(str->receiver, SEARCH_PART_HEADERS, &text);
str->receiver->begin_part(str->receiver, SEARCH_PART_BODY);
} else {
str->receiver->append_text(str->receiver, &text);
}
free(q);
buf_free(&text);
}
else {
/* body-like */
charset_extract(extract_cb, str, data, charset, encoding, subtype,
str->charset_flags);
}
return 0;
}
static void append_alnum(struct buf *buf, const char *ss)
{
const unsigned char *s = (const unsigned char *)ss;
for ( ; *s ; ++s) {
if (Uisalnum(*s))
buf_putc(buf, *s);
}
}
EXPORTED int index_getsearchtext(message_t *msg,
search_text_receiver_t *receiver,
int snippet)
{
struct getsearchtext_rock str;
struct buf buf = BUF_INITIALIZER;
uint32_t uid = 0;
int format = MESSAGE_SEARCH;
strarray_t types = STRARRAY_INITIALIZER;
int i;
int r;
message_get_uid(msg, &uid);
receiver->begin_message(receiver, uid);
str.receiver = receiver;
str.partcount = 0;
str.charset_flags = charset_flags;
if (snippet) {
str.charset_flags |= CHARSET_SNIPPET;
format = MESSAGE_SNIPPET;
}
message_foreach_text_section(msg, getsearchtext_cb, &str);
receiver->end_part(receiver, SEARCH_PART_BODY);
if (!message_get_field(msg, "From", format, &buf))
stuff_part(receiver, SEARCH_PART_FROM, &buf);
if (!message_get_field(msg, "To", format, &buf))
stuff_part(receiver, SEARCH_PART_TO, &buf);
if (!message_get_field(msg, "Cc", format, &buf))
stuff_part(receiver, SEARCH_PART_CC, &buf);
if (!message_get_field(msg, "Bcc", format, &buf))
stuff_part(receiver, SEARCH_PART_BCC, &buf);
if (!message_get_field(msg, "Subject", format, &buf))
stuff_part(receiver, SEARCH_PART_SUBJECT, &buf);
if (!message_get_field(msg, "List-Id", format, &buf))
stuff_part(receiver, SEARCH_PART_LISTID, &buf);
if (!message_get_field(msg, "Mailing-List", format, &buf))
stuff_part(receiver, SEARCH_PART_LISTID, &buf);
if (!message_get_leaf_types(msg, &types) && types.count) {
/* We add three search terms: the type, subtype, and a combined
* type+subtype string. We carefully control punctuation to
* ensure that each word in indexed as a single term. For
* example if the original message has "application/x-pdf" then
* we index "APPLICATION" "XPDF" "APPLICATION_XPDF". */
receiver->begin_part(receiver, SEARCH_PART_TYPE);
for (i = 0 ; i < types.count ; i+= 2) {
buf_reset(&buf);
if (i) buf_putc(&buf, ' ');
/* type */
append_alnum(&buf, types.data[i]);
buf_putc(&buf, ' ');
/* subtype */
append_alnum(&buf, types.data[i+1]);
buf_putc(&buf, ' ');
/* combined type_subtype */
append_alnum(&buf, types.data[i]);
buf_putc(&buf, '_');
append_alnum(&buf, types.data[i+1]);
receiver->append_text(receiver, &buf);
}
receiver->end_part(receiver, SEARCH_PART_TYPE);
}
r = receiver->end_message(receiver);
buf_free(&buf);
strarray_fini(&types);
return r;
}
/*
* Helper function to set up arguments to append_copy()
*/
#define COPYARGSGROW 30
static int index_copysetup(struct index_state *state, uint32_t msgno,
struct copyargs *copyargs, int is_same_user)
{
int flag = 0;
int userflag;
bit32 flagmask = 0;
int r;
struct mailbox *mailbox = state->mailbox;
struct index_map *im = &state->map[msgno-1];
struct index_record record;
r = index_reload_record(state, msgno, &record);
if (r) return 0;
r = mailbox_cacherecord(mailbox, &record);
if (r) return r;
if (copyargs->nummsg == copyargs->msgalloc) {
copyargs->msgalloc += COPYARGSGROW;
copyargs->copymsg = (struct copymsg *)
xrealloc((char *)copyargs->copymsg,
copyargs->msgalloc * sizeof(struct copymsg));
}
copyargs->copymsg[copyargs->nummsg].uid = record.uid;
copyargs->copymsg[copyargs->nummsg].internaldate = record.internaldate;
copyargs->copymsg[copyargs->nummsg].sentdate = record.sentdate;
copyargs->copymsg[copyargs->nummsg].gmtime = record.gmtime;
copyargs->copymsg[copyargs->nummsg].size = record.size;
copyargs->copymsg[copyargs->nummsg].header_size = record.header_size;
copyargs->copymsg[copyargs->nummsg].content_lines = record.content_lines;
copyargs->copymsg[copyargs->nummsg].cache_version = record.cache_version;
copyargs->copymsg[copyargs->nummsg].cache_crc = record.cache_crc;
copyargs->copymsg[copyargs->nummsg].crec = record.crec;
message_guid_copy(&copyargs->copymsg[copyargs->nummsg].guid,
&record.guid);
copyargs->copymsg[copyargs->nummsg].system_flags = record.system_flags;
for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) {
if ((userflag & 31) == 0) {
flagmask = record.user_flags[userflag/32];
}
if (mailbox->flagname[userflag] && (flagmask & (1<<(userflag&31)))) {
copyargs->copymsg[copyargs->nummsg].flag[flag++] =
mailbox->flagname[userflag];
}
}
copyargs->copymsg[copyargs->nummsg].flag[flag] = 0;
/* grab seen from our state - it's different for different users */
copyargs->copymsg[copyargs->nummsg].seen = im->isseen;
/* CIDs are per-user, so we can reuse the cid if we're copying
* between mailboxes owned by the same user. Otherwise we need
* to zap the cid and let append_copy() recalculate it. */
copyargs->copymsg[copyargs->nummsg].cid =
(is_same_user ? record.cid : NULLCONVERSATION);
copyargs->nummsg++;
return 0;
}
/*
* Creates a list, and optionally also an array of pointers to, of msgdata.
*
* We fill these structs with the processed info that will be needed
* by the specified sort criteria.
*/
static MsgData **index_msgdata_load(struct index_state *state,
unsigned *msgno_list, int n,
const struct sortcrit *sortcrit,
unsigned int anchor, int *found_anchor)
{
MsgData **ptrs, *md, *cur;
int i, j;
char *tmpenv;
char *envtokens[NUMENVTOKENS];
int did_cache, did_env, did_conv;
int label;
struct mailbox *mailbox = state->mailbox;
struct index_record record;
struct index_map *im;
struct conversations_state *cstate = NULL;
conversation_t *conv = NULL;
if (!n) return NULL;
/* create an array of MsgData */
ptrs = (MsgData **) xzmalloc(n * sizeof(MsgData *) + n * sizeof(MsgData));
md = (MsgData *)(ptrs + n);
xstats_add(MSGDATA_LOAD, n);
if (found_anchor)
*found_anchor = 0;
for (i = 0 ; i < n ; i++) {
cur = &md[i];
ptrs[i] = cur;
/* set msgno */
cur->msgno = (msgno_list ? msgno_list[i] : (unsigned)(i+1));
if (index_reload_record(state, cur->msgno, &record))
continue;
/* useful for convupdates */
cur->modseq = record.modseq;
im = &state->map[cur->msgno-1];
cur->uid = record.uid;
cur->cid = record.cid;
if (found_anchor && record.uid == anchor)
*found_anchor = 1;
did_cache = did_env = did_conv = 0;
tmpenv = NULL;
conv = NULL; /* XXX: use a hash to avoid re-reading? */
for (j = 0; sortcrit[j].key; j++) {
label = sortcrit[j].key;
if ((label == SORT_CC || label == SORT_DATE ||
label == SORT_FROM || label == SORT_SUBJECT ||
label == SORT_TO || label == LOAD_IDS ||
label == SORT_DISPLAYFROM || label == SORT_DISPLAYTO) &&
!did_cache) {
/* fetch cached info */
if (mailbox_cacherecord(mailbox, &record))
continue; /* can't do this with a broken cache */
did_cache++;
}
if ((label == LOAD_IDS) && !did_env) {
/* no point if we don't have enough data */
if (cacheitem_size(&record, CACHE_ENVELOPE) <= 2)
continue;
/* make a working copy of envelope -- strip outer ()'s */
/* +1 -> skip the leading paren */
/* -2 -> don't include the size of the outer parens */
tmpenv = xstrndup(cacheitem_base(&record, CACHE_ENVELOPE) + 1,
cacheitem_size(&record, CACHE_ENVELOPE) - 2);
/* parse envelope into tokens */
parse_cached_envelope(tmpenv, envtokens,
VECTOR_SIZE(envtokens));
did_env++;
}
if ((label == SORT_HASCONVFLAG || label == SORT_CONVMODSEQ ||
label == SORT_CONVEXISTS || label == SORT_CONVSIZE) && !did_conv) {
if (!cstate) cstate = conversations_get_mbox(state->mailbox->name);
assert(cstate);
if (conversation_load(cstate, record.cid, &conv))
continue;
if (!conv) conv = conversation_new(cstate);
did_conv++;
}
switch (label) {
case SORT_CC:
cur->cc = get_localpart_addr(cacheitem_base(&record, CACHE_CC));
break;
case SORT_DATE:
cur->sentdate = record.gmtime;
/* fall through */
case SORT_ARRIVAL:
cur->internaldate = record.internaldate;
break;
case SORT_FROM:
cur->from = get_localpart_addr(cacheitem_base(&record, CACHE_FROM));
break;
case SORT_MODSEQ:
/* already copied above */
break;
case SORT_SIZE:
cur->size = record.size;
break;
case SORT_SUBJECT:
cur->xsubj = index_extract_subject(cacheitem_base(&record, CACHE_SUBJECT),
cacheitem_size(&record, CACHE_SUBJECT),
&cur->is_refwd);
cur->xsubj_hash = strhash(cur->xsubj);
break;
case SORT_TO:
cur->to = get_localpart_addr(cacheitem_base(&record, CACHE_TO));
break;
case SORT_ANNOTATION: {
struct buf value = BUF_INITIALIZER;
annotatemore_msg_lookup(state->mboxname,
record.uid,
sortcrit[j].args.annot.entry,
sortcrit[j].args.annot.userid,
&value);
/* buf_release() never returns NULL, so if the lookup
* fails for any reason we just get an empty string here */
strarray_appendm(&cur->annot, buf_release(&value));
break;
}
case LOAD_IDS:
index_get_ids(cur, envtokens, cacheitem_base(&record, CACHE_HEADERS),
cacheitem_size(&record, CACHE_HEADERS));
break;
case SORT_DISPLAYFROM:
cur->displayfrom = get_displayname(
cacheitem_base(&record, CACHE_FROM));
break;
case SORT_DISPLAYTO:
cur->displayto = get_displayname(
cacheitem_base(&record, CACHE_TO));
break;
case SORT_HASFLAG: {
const char *name = sortcrit[j].args.flag.name;
if (mailbox_record_hasflag(mailbox, &record, name))
cur->hasflag |= (1<<j);
break;
}
case SORT_HASCONVFLAG: {
const char *name = sortcrit[j].args.flag.name;
int idx = strarray_find_case(cstate->counted_flags, name, 0);
/* flag exists in the conversation at all */
if (idx >= 0 && conv->counts[idx] > 0 && j < 31)
cur->hasconvflag |= (1<<j);
break;
}
case SORT_CONVEXISTS:
cur->convexists = conv->exists;
break;
case SORT_CONVSIZE:
cur->convsize = conv->size;
break;
case SORT_CONVMODSEQ:
cur->convmodseq = conv->modseq;
break;
}
}
free(tmpenv);
conversation_free(conv);
}
return ptrs;
}
static char *get_localpart_addr(const char *header)
{
struct address *addr = NULL;
char *ret = NULL;
parseaddr_list(header, &addr);
if (!addr) return NULL;
if (addr->mailbox)
ret = xstrdup(addr->mailbox);
parseaddr_free(addr);
return ret;
}
/*
* Get the 'display-name' of an address from a header
*/
static char *get_displayname(const char *header)
{
struct address *addr = NULL;
char *ret = NULL;
char *p;
parseaddr_list(header, &addr);
if (!addr) return NULL;
if (addr->name && addr->name[0]) {
/* pure RFC5255 compatible "searchform" conversion */
ret = charset_utf8_to_searchform(addr->name, /*flags*/0);
}
else if (addr->domain && addr->mailbox) {
ret = strconcat(addr->mailbox, "@", addr->domain, (char *)NULL);
/* gotta uppercase mailbox/domain */
for (p = ret; *p; p++)
*p = toupper(*p);
}
else if (addr->mailbox) {
ret = xstrdup(addr->mailbox);
/* gotta uppercase mailbox/domain */
for (p = ret; *p; p++)
*p = toupper(*p);
}
parseaddr_free(addr);
return ret;
}
/*
* Extract base subject from subject header
*
* This is a wrapper around _index_extract_subject() which preps the
* subj NSTRING and checks for Netscape "[Fwd: ]".
*/
static char *index_extract_subject(const char *subj, size_t len, int *is_refwd)
{
char *rawbuf, *buf, *s, *base;
/* parse the subj NSTRING and make a working copy */
if (!strcmp(subj, "NIL")) { /* NIL? */
return xstrdup(""); /* yes, return empty */
} else if (*subj == '"') { /* quoted? */
rawbuf = xstrndup(subj + 1, len - 2); /* yes, strip quotes */
} else {
s = strchr(subj, '}') + 3; /* literal, skip { }\r\n */
rawbuf = xstrndup(s, len - (s - subj));
}
buf = charset_parse_mimeheader(rawbuf);
free(rawbuf);
for (s = buf;;) {
base = _index_extract_subject(s, is_refwd);
/* If we have a Netscape "[Fwd: ...]", extract the contents */
if (!strncasecmp(base, "[fwd:", 5) &&
base[strlen(base) - 1] == ']') {
/* inc refwd counter */
*is_refwd += 1;
/* trim "]" */
base[strlen(base) - 1] = '\0';
/* trim "[fwd:" */
s = base + 5;
}
else /* otherwise, we're done */
break;
}
base = xstrdup(base);
free(buf);
for (s = base; *s; s++) {
*s = toupper(*s);
}
return base;
}
/*
* Guts of subject extraction.
*
* Takes a subject string and returns a pointer to the base.
*/
static char *_index_extract_subject(char *s, int *is_refwd)
{
char *base, *x;
/* trim trailer
*
* start at the end of the string and work towards the front,
* resetting the end of the string as we go.
*/
for (x = s + strlen(s) - 1; x >= s;) {
if (Uisspace(*x)) { /* whitespace? */
*x = '\0'; /* yes, trim it */
x--; /* skip past it */
}
else if (x - s >= 4 &&
!strncasecmp(x-4, "(fwd)", 5)) { /* "(fwd)"? */
*(x-4) = '\0'; /* yes, trim it */
x -= 5; /* skip past it */
*is_refwd += 1; /* inc refwd counter */
}
else
break; /* we're done */
}
/* trim leader
*
* start at the head of the string and work towards the end,
* skipping over stuff we don't care about.
*/
for (base = s; base;) {
if (Uisspace(*base)) base++; /* whitespace? */
/* possible refwd */
else if ((!strncasecmp(base, "re", 2) && /* "re"? */
(x = base + 2)) || /* yes, skip past it */
(!strncasecmp(base, "fwd", 3) && /* "fwd"? */
(x = base + 3)) || /* yes, skip past it */
(!strncasecmp(base, "fw", 2) && /* "fw"? */
(x = base + 2))) { /* yes, skip past it */
int count = 0; /* init counter */
while (Uisspace(*x)) x++; /* skip whitespace */
if (*x == '[') { /* start of blob? */
for (x++; x;) { /* yes, get count */
if (!*x) { /* end of subj, quit */
x = NULL;
break;
}
else if (*x == ']') { /* end of blob, done */
break;
/* if we have a digit, and we're still
counting, keep building the count */
} else if (cyrus_isdigit((int) *x) && count != -1) {
count = count * 10 + *x - '0';
if (count < 0) { /* overflow */
count = -1; /* abort counting */
}
} else { /* no digit, */
count = -1; /* abort counting */
}
x++;
}
if (x) /* end of blob? */
x++; /* yes, skip past it */
else
break; /* no, we're done */
}
while (Uisspace(*x)) x++; /* skip whitespace */
if (*x == ':') { /* ending colon? */
base = x + 1; /* yes, skip past it */
*is_refwd += (count > 0 ? count : 1); /* inc refwd counter
by count or 1 */
}
else
break; /* no, we're done */
}
#if 0 /* do nested blobs - wait for decision on this */
else if (*base == '[') { /* start of blob? */
int count = 1; /* yes, */
x = base + 1; /* find end of blob */
while (count) { /* find matching ']' */
if (!*x) { /* end of subj, quit */
x = NULL;
break;
}
else if (*x == '[') /* new open */
count++; /* inc counter */
else if (*x == ']') /* close */
count--; /* dec counter */
x++;
}
if (!x) /* blob didn't close */
break; /* so quit */
else if (*x) /* end of subj? */
base = x; /* no, skip blob */
#else
else if (*base == '[' && /* start of blob? */
(x = strpbrk(base+1, "[]")) && /* yes, end of blob */
*x == ']') { /* (w/o nesting)? */
if (*(x+1)) /* yes, end of subj? */
base = x + 1; /* no, skip blob */
#endif
else
break; /* yes, return blob */
}
else
break; /* we're done */
}
return base;
}
/* Get message-id, and references/in-reply-to */
void index_get_ids(MsgData *msgdata, char *envtokens[], const char *headers,
unsigned size)
{
static struct buf buf;
strarray_t refhdr = STRARRAY_INITIALIZER;
char *refstr, *ref, *in_reply_to;
buf_reset(&buf);
/* get msgid */
msgdata->msgid = find_msgid(envtokens[ENV_MSGID], NULL);
/* if we don't have one, create one */
if (!msgdata->msgid) {
buf_printf(&buf, "<Empty-ID: %u>", msgdata->msgno);
msgdata->msgid = xstrdup(buf.s);
buf_reset(&buf);
}
/* Copy headers to the buffer */
buf_appendmap(&buf, headers, size);
buf_cstring(&buf);
/* grab the References header */
strarray_append(&refhdr, "references");
message_pruneheader(buf.s, &refhdr, 0);
strarray_fini(&refhdr);
if (buf.s) {
/* allocate some space for refs */
/* find references */
refstr = buf.s;
massage_header(refstr);
while ((ref = find_msgid(refstr, &refstr)) != NULL)
strarray_appendm(&msgdata->ref, ref);
}
/* if we have no references, try in-reply-to */
if (!msgdata->ref.count) {
/* get in-reply-to id */
in_reply_to = find_msgid(envtokens[ENV_INREPLYTO], NULL);
/* if we have an in-reply-to id, make it the ref */
if (in_reply_to)
strarray_appendm(&msgdata->ref, in_reply_to);
}
}
/*
* Function for comparing two integers.
*/
static int numcmp(modseq_t n1, modseq_t n2)
{
return ((n1 < n2) ? -1 : (n1 > n2) ? 1 : 0);
}
/*
* Comparison function for sorting message lists.
*/
static int index_sort_compare(MsgData *md1, MsgData *md2,
const struct sortcrit *sortcrit)
{
int reverse, ret = 0, i = 0, ann = 0;
do {
/* determine sort order from reverse flag bit */
reverse = sortcrit[i].flags & SORT_REVERSE;
switch (sortcrit[i].key) {
case SORT_SEQUENCE:
ret = numcmp(md1->msgno, md2->msgno);
break;
case SORT_ARRIVAL:
ret = numcmp(md1->internaldate, md2->internaldate);
break;
case SORT_CC:
ret = strcmpsafe(md1->cc, md2->cc);
break;
case SORT_DATE: {
time_t d1 = md1->sentdate ? md1->sentdate : md1->internaldate;
time_t d2 = md2->sentdate ? md2->sentdate : md2->internaldate;
ret = numcmp(d1, d2);
break;
}
case SORT_FROM:
ret = strcmpsafe(md1->from, md2->from);
break;
case SORT_SIZE:
ret = numcmp(md1->size, md2->size);
break;
case SORT_SUBJECT:
ret = strcmpsafe(md1->xsubj, md2->xsubj);
break;
case SORT_TO:
ret = strcmpsafe(md1->to, md2->to);
break;
case SORT_ANNOTATION:
ret = strcmpsafe(md1->annot.data[ann], md2->annot.data[ann]);
ann++;
break;
case SORT_MODSEQ:
ret = numcmp(md1->modseq, md2->modseq);
break;
case SORT_DISPLAYFROM:
ret = strcmpsafe(md1->displayfrom, md2->displayfrom);
break;
case SORT_DISPLAYTO:
ret = strcmpsafe(md1->displayto, md2->displayto);
break;
case SORT_UID:
ret = numcmp(md1->uid, md2->uid);
break;
case SORT_CONVMODSEQ:
ret = numcmp(md1->convmodseq, md2->convmodseq);
break;
case SORT_CONVEXISTS:
ret = numcmp(md1->convexists, md2->convexists);
break;
case SORT_CONVSIZE:
ret = numcmp(md1->convsize, md2->convsize);
break;
case SORT_HASFLAG:
if (i < 31)
ret = numcmp(md1->hasflag & (1<<i),
md2->hasflag & (1<<i));
break;
case SORT_HASCONVFLAG:
if (i < 31)
ret = numcmp(md1->hasconvflag & (1<<i),
md2->hasconvflag & (1<<i));
break;
case SORT_FOLDER:
if (md1->folder && md2->folder)
ret = strcmpsafe(md1->folder->mboxname, md2->folder->mboxname);
}
} while (!ret && sortcrit[i++].key != SORT_SEQUENCE);
return (reverse ? -ret : ret);
}
static int index_sort_compare_qsort(const void *v1, const void *v2)
{
MsgData *md1 = *(MsgData **)v1;
MsgData *md2 = *(MsgData **)v2;
return index_sort_compare(md1, md2, the_sortcrit);
}
/*
* Free an array of MsgData* as built by index_msgdata_load()
*/
static void index_msgdata_free(MsgData **msgdata, unsigned int n)
{
unsigned int i;
if (!msgdata)
return;
for (i = 0 ; i < n ; i++) {
MsgData *md = msgdata[i];
free(md->cc);
free(md->from);
free(md->to);
free(md->displayfrom);
free(md->displayto);
free(md->xsubj);
free(md->msgid);
free(md->listid);
free(md->contenttype);
strarray_fini(&md->ref);
strarray_fini(&md->annot);
}
free(msgdata);
}
/*
* Getnext function for sorting thread lists.
*/
static void *index_thread_getnext(Thread *thread)
{
return thread->next;
}
/*
* Setnext function for sorting thread lists.
*/
static void index_thread_setnext(Thread *thread, Thread *next)
{
thread->next = next;
}
/*
* Comparison function for sorting threads.
*/
static int index_thread_compare(Thread *t1, Thread *t2,
const struct sortcrit *call_data)
{
MsgData *md1, *md2;
/* if the container is empty, use the first child's container */
md1 = t1->msgdata ? t1->msgdata : t1->child->msgdata;
md2 = t2->msgdata ? t2->msgdata : t2->child->msgdata;
return index_sort_compare(md1, md2, call_data);
}
/*
* Sort a list of threads.
*/
static void index_thread_sort(Thread *root,
const struct sortcrit *sortcrit)
{
Thread *child;
/* sort the grandchildren */
child = root->child;
while (child) {
/* if the child has children, sort them */
if (child->child)
index_thread_sort(child, sortcrit);
child = child->next;
}
/* sort the children */
root->child = lsort(root->child,
(void * (*)(void*)) index_thread_getnext,
(void (*)(void*,void*)) index_thread_setnext,
(int (*)(void*,void*,void*)) index_thread_compare,
(void *)sortcrit);
}
/*
* Thread a list of messages using the ORDEREDSUBJECT algorithm.
*/
static void index_thread_orderedsubj(struct index_state *state,
unsigned *msgno_list, unsigned int nmsg,
int usinguid)
{
MsgData **msgdata;
unsigned int mi;
static const struct sortcrit sortcrit[] =
{{ SORT_SUBJECT, 0, {{NULL, NULL}} },
{ SORT_DATE, 0, {{NULL, NULL}} },
{ SORT_SEQUENCE, 0, {{NULL, NULL}} }};
unsigned psubj_hash = 0;
char *psubj;
Thread *head, *newnode, *cur, *parent, *last;
/* Create/load the msgdata array */
msgdata = index_msgdata_load(state, msgno_list, nmsg, sortcrit, 0, NULL);
/* Sort messages by subject and date */
the_sortcrit = sortcrit;
qsort(msgdata, nmsg, sizeof(MsgData *), index_sort_compare_qsort);
/* create an array of Thread to use as nodes of thread tree
*
* we will be building threads under a dummy head,
* so we need (nmsg + 1) nodes
*/
head = (Thread *) xzmalloc((nmsg + 1) * sizeof(Thread));
newnode = head + 1; /* set next newnode to the second
one in the array (skip the head) */
parent = head; /* parent is the head node */
psubj = NULL; /* no previous subject */
cur = NULL; /* no current thread */
last = NULL; /* no last child */
for (mi = 0 ; mi < nmsg ; mi++) {
MsgData *msg = msgdata[mi];
newnode->msgdata = msg;
/* if no previous subj, or
current subj = prev subj (subjs have same hash, and
the strings are equal), then add message to current thread */
if (!psubj ||
(msg->xsubj_hash == psubj_hash &&
!strcmp(msg->xsubj, psubj))) {
/* if no children, create first child */
if (!parent->child) {
last = parent->child = newnode;
if (!cur) /* first thread */
parent = cur = parent->child;
}
/* otherwise, add to siblings */
else {
last->next = newnode;
last = last->next;
}
}
/* otherwise, create a new thread */
else {
cur->next = newnode; /* create and start a new thread */
parent = cur = cur->next; /* now work with the new thread */
}
psubj_hash = msg->xsubj_hash;
psubj = msg->xsubj;
newnode++;
}
/* Sort threads by date */
index_thread_sort(head, sortcrit+1);
/* Output the threaded messages */
index_thread_print(state, head, usinguid);
/* free the thread array */
free(head);
/* free the msgdata array */
index_msgdata_free(msgdata, nmsg);
}
/*
* Guts of thread printing. Recurses over children when necessary.
*
* Frees contents of msgdata as a side effect.
*/
static void _index_thread_print(struct index_state *state,
Thread *thread, int usinguid)
{
Thread *child;
/* for each thread... */
while (thread) {
/* start the thread */
prot_printf(state->out, "(");
/* if we have a message, print its identifier
* (do nothing for empty containers)
*/
if (thread->msgdata) {
prot_printf(state->out, "%u",
usinguid ? thread->msgdata->uid :
thread->msgdata->msgno);
/* if we have a child, print the parent-child separator */
if (thread->child) prot_printf(state->out, " ");
}
/* for each child, grandchild, etc... */
child = thread->child;
while (child) {
/* if the child has siblings, print new branch and break */
if (child->next) {
_index_thread_print(state, child, usinguid);
break;
}
/* otherwise print the only child */
else {
prot_printf(state->out, "%u",
usinguid ? child->msgdata->uid :
child->msgdata->msgno);
/* if we have a child, print the parent-child separator */
if (child->child) prot_printf(state->out, " ");
child = child->child;
}
}
/* end the thread */
prot_printf(state->out, ")");
thread = thread->next;
}
}
/*
* Print a list of threads.
*
* This is a wrapper around _index_thread_print() which simply prints the
* start and end of the untagged thread response.
*/
static void index_thread_print(struct index_state *state,
Thread *thread, int usinguid)
{
prot_printf(state->out, "* THREAD");
if (thread) {
prot_printf(state->out, " ");
_index_thread_print(state, thread->child, usinguid);
}
}
/*
* Find threading algorithm for given arg.
* Returns index into thread_algs[], or -1 if not found.
*/
EXPORTED int find_thread_algorithm(char *arg)
{
int alg;
ucase(arg);
for (alg = 0; thread_algs[alg].alg_name; alg++) {
if (!strcmp(arg, thread_algs[alg].alg_name))
return alg;
}
return -1;
}
/*
* The following code is an interpretation of JWZ's description
* and pseudo-code in http://www.jwz.org/doc/threading.html.
*
* It has been modified to match the THREAD=REFERENCES algorithm.
*/
/*
* Determines if child is a descendent of parent.
*
* Returns 1 if yes, 0 otherwise.
*/
static int thread_is_descendent(Thread *parent, Thread *child)
{
Thread *kid;
/* self */
if (parent == child)
return 1;
/* search each child's decendents */
for (kid = parent->child; kid; kid = kid->next) {
if (thread_is_descendent(kid, child))
return 1;
}
return 0;
}
/*
* Links child into parent's children.
*/
static void thread_adopt_child(Thread *parent, Thread *child)
{
child->parent = parent;
child->next = parent->child;
parent->child = child;
}
/*
* Unlinks child from it's parent's children.
*/
static void thread_orphan_child(Thread *child)
{
Thread *prev, *cur;
/* sanity check -- make sure child is actually a child of parent */
for (prev = NULL, cur = child->parent->child;
cur != child && cur != NULL; prev = cur, cur = cur->next);
if (!cur) {
/* uh oh! couldn't find the child in it's parent's children
* we should probably return NO to thread command
*/
return;
}
/* unlink child */
if (!prev) /* first child */
child->parent->child = child->next;
else
prev->next = child->next;
child->parent = child->next = NULL;
}
/*
* Link messages together using message-id and references.
*/
static void ref_link_messages(MsgData **msgdata, unsigned int nmsg,
Thread **newnode, struct hash_table *id_table)
{
Thread *cur, *parent, *ref;
unsigned int mi;
int dup_count = 0;
char buf[100];
int i;
/* for each message... */
for (mi = 0 ; mi < nmsg ; mi++) {
MsgData *msg = msgdata[mi];
/* fill the containers with msgdata
*
* if we already have a container, use it
*/
if ((cur = (Thread *) hash_lookup(msg->msgid, id_table))) {
/* If this container is not empty, then we have a duplicate
* Message-ID. Make this one unique so that we don't stomp
* on the old one.
*/
if (cur->msgdata) {
snprintf(buf, sizeof(buf), "-dup%d", dup_count++);
msg->msgid =
(char *) xrealloc(msg->msgid,
strlen(msg->msgid) + strlen(buf) + 1);
strcat(msg->msgid, buf);
/* clear cur so that we create a new container */
cur = NULL;
}
else
cur->msgdata = msg;
}
/* otherwise, make and index a new container */
if (!cur) {
cur = *newnode;
cur->msgdata = msg;
hash_insert(msg->msgid, cur, id_table);
(*newnode)++;
}
/* Step 1.A */
for (i = 0, parent = NULL; i < msg->ref.count; i++) {
/* if we don't already have a container for the reference,
* make and index a new (empty) container
*/
if (!(ref = (Thread *) hash_lookup(msg->ref.data[i], id_table))) {
ref = *newnode;
hash_insert(msg->ref.data[i], ref, id_table);
(*newnode)++;
}
/* link the references together as parent-child iff:
* - we won't change existing links, AND
* - we won't create a loop
*/
if (!ref->parent &&
parent && !thread_is_descendent(ref, parent)) {
thread_adopt_child(parent, ref);
}
parent = ref;
}
/* Step 1.B
*
* if we have a parent already, it is probably bogus (the result
* of a truncated references field), so unlink from it because
* we now have the actual parent
*/
if (cur->parent) thread_orphan_child(cur);
/* make the last reference the parent of our message iff:
* - we won't create a loop
*/
if (parent && !thread_is_descendent(cur, parent))
thread_adopt_child(parent, cur);
}
}
/*
* Gather orphan messages under the root node.
*/
static void ref_gather_orphans(const char *key __attribute__((unused)),
void *data, void *rock)
{
Thread *node = (Thread *)data;
struct rootset *rootset = (struct rootset *)rock;
/* we only care about nodes without parents */
if (!node->parent) {
if (node->next) {
/* uh oh! a node without a parent should not have a sibling
* we should probably return NO to thread command
*/
return;
}
/* add this node to root's children */
node->next = rootset->root->child;
rootset->root->child = node;
rootset->nroot++;
}
}
/*
* Prune tree of empty containers.
*/
static void ref_prune_tree(Thread *parent)
{
Thread *cur, *prev, *next, *child;
for (prev = NULL, cur = parent->child, next = cur->next;
cur;
prev = cur, cur = next, next = (cur ? cur->next : NULL)) {
retry:
/* if we have an empty container with no children, delete it */
if (!cur->msgdata && !cur->child) {
if (!prev) /* first child */
parent->child = cur->next;
else
prev->next = cur->next;
/* we just removed cur from our list,
* so we need to keep the same prev for the next pass
*/
cur = prev;
}
/* if we have an empty container with children, AND
* we're not at the root OR we only have one child,
* then remove the container but promote its children to this level
* (splice them into the current child list)
*/
else if (!cur->msgdata && cur->child &&
(cur->parent || !cur->child->next)) {
/* move cur's children into cur's place (start the splice) */
if (!prev) /* first child */
parent->child = cur->child;
else
prev->next = cur->child;
/* make cur's parent the new parent of cur's children
* (they're moving in with grandma!)
*/
child = cur->child;
do {
child->parent = cur->parent;
} while (child->next && (child = child->next));
/* make the cur's last child point to cur's next sibling
* (finish the splice)
*/
child->next = cur->next;
/* we just replaced cur with it's children
* so make it's first child the next node to process
*/
next = cur->child;
/* make cur childless and siblingless */
cur->child = cur->next = NULL;
/* we just removed cur from our list,
* so we need to keep the same prev for the next pass
*/
cur = prev;
}
/* if we have a message with children, prune it's children */
else if (cur->child) {
ref_prune_tree(cur);
if (!cur->msgdata && !cur->child) {
/* Did we end up with a completely empty node here?
* Go back and prune it too. See Bug 3784. */
goto retry;
}
}
}
}
/*
* Sort the messages in the root set by date.
*/
static void ref_sort_root(Thread *root)
{
Thread *cur;
static const struct sortcrit sortcrit[] =
{{ SORT_DATE, 0, {{NULL, NULL}} },
{ SORT_SEQUENCE, 0, {{NULL, NULL}} }};
cur = root->child;
while (cur) {
/* if the message is a dummy, sort its children */
if (!cur->msgdata) {
cur->child = lsort(cur->child,
(void * (*)(void*)) index_thread_getnext,
(void (*)(void*,void*)) index_thread_setnext,
(int (*)(void*,void*,void*)) index_thread_compare,
(void *)sortcrit);
}
cur = cur->next;
}
/* sort the root set */
root->child = lsort(root->child,
(void * (*)(void*)) index_thread_getnext,
(void (*)(void*,void*)) index_thread_setnext,
(int (*)(void*,void*,void*)) index_thread_compare,
(void *)sortcrit);
}
/*
* Group threads with same subject.
*/
static void ref_group_subjects(Thread *root, unsigned nroot, Thread **newnode)
{
Thread *cur, *old, *prev, *next, *child;
struct hash_table subj_table;
char *subj;
/* Step 5.A: create a subj_table with one bucket for every possible
* subject in the root set
*/
construct_hash_table(&subj_table, nroot, 1);
/* Step 5.B: populate the table with a container for each subject
* at the root
*/
for (cur = root->child; cur; cur = cur->next) {
/* Step 5.B.i: find subject of the thread
*
* if the container is not empty, use it's subject
*/
if (cur->msgdata)
subj = cur->msgdata->xsubj;
/* otherwise, use the subject of it's first child */
else
subj = cur->child->msgdata->xsubj;
/* Step 5.B.ii: if subject is empty, skip it */
if (!strlen(subj)) continue;
/* Step 5.B.iii: lookup this subject in the table */
old = (Thread *) hash_lookup(subj, &subj_table);
/* Step 5.B.iv: insert the current container into the table iff:
* - this subject is not in the table, OR
* - this container is empty AND the one in the table is not
* (the empty one is more interesting as a root), OR
* - the container in the table is a re/fwd AND this one is not
* (the non-re/fwd is the more interesting of the two)
*/
if (!old ||
(!cur->msgdata && old->msgdata) ||
(old->msgdata && old->msgdata->is_refwd &&
cur->msgdata && !cur->msgdata->is_refwd)) {
hash_insert(subj, cur, &subj_table);
}
}
/* 5.C - group containers with the same subject together */
for (prev = NULL, cur = root->child, next = cur->next;
cur;
prev = cur, cur = next, next = (next ? next->next : NULL)) {
/* Step 5.C.i: find subject of the thread
*
* if container is not empty, use it's subject
*/
if (cur->msgdata)
subj = cur->msgdata->xsubj;
/* otherwise, use the subject of it's first child */
else
subj = cur->child->msgdata->xsubj;
/* Step 5.C.ii: if subject is empty, skip it */
if (!strlen(subj)) continue;
/* Step 5.C.iii: lookup this subject in the table */
old = (Thread *) hash_lookup(subj, &subj_table);
/* Step 5.C.iv: if we found ourselves, skip it */
if (!old || old == cur) continue;
/* ok, we already have a container which contains our current subject,
* so pull this container out of the root set, because we are going to
* merge this node with another one
*/
if (!prev) /* we're at the root */
root->child = cur->next;
else
prev->next = cur->next;
cur->next = NULL;
/* if both containers are dummies, append cur's children to old's */
if (!old->msgdata && !cur->msgdata) {
/* find old's last child */
for (child = old->child; child->next; child = child->next);
/* append cur's children to old's children list */
child->next = cur->child;
/* make old the parent of cur's children */
for (child = cur->child; child; child = child->next)
child->parent = old;
/* make cur childless */
cur->child = NULL;
}
/* if:
* - old container is empty, OR
* - the current message is a re/fwd AND the old one is not,
* make the current container a child of the old one
*
* Note: we don't have to worry about the reverse cases
* because step 5.B guarantees that they won't happen
*/
else if (!old->msgdata ||
(cur->msgdata && cur->msgdata->is_refwd &&
!old->msgdata->is_refwd)) {
thread_adopt_child(old, cur);
}
/* if both messages are re/fwds OR neither are re/fwds,
* then make them both children of a new dummy container
* (we don't want to assume any parent-child relationship between them)
*
* perhaps we can create a parent-child relationship
* between re/fwds by counting the number of re/fwds
*
* Note: we need the hash table to still point to old,
* so we must make old the dummy and make the contents of the
* new container a copy of old's original contents
*/
else {
Thread *new = (*newnode)++;
/* make new a copy of old (except parent and next) */
new->msgdata = old->msgdata;
new->child = old->child;
new->next = NULL;
/* make new the parent of it's newly adopted children */
for (child = new->child; child; child = child->next)
child->parent = new;
/* make old the parent of cur and new */
cur->parent = old;
new->parent = old;
/* empty old and make it have two children (cur and new) */
old->msgdata = NULL;
old->child = cur;
cur->next = new;
}
/* we just removed cur from our list,
* so we need to keep the same prev for the next pass
*/
cur = prev;
}
free_hash_table(&subj_table, NULL);
}
/*
* Guts of thread searching. Recurses over children when necessary.
*/
static int _index_thread_search(struct index_state *state,
Thread *thread, int (*searchproc) (MsgData *))
{
Thread *child;
/* test the head node */
if (thread->msgdata && searchproc(thread->msgdata)) return 1;
/* test the children recursively */
child = thread->child;
while (child) {
if (_index_thread_search(state, child, searchproc)) return 1;
child = child->next;
}
/* if we get here, we struck out */
return 0;
}
/*
* Search a thread to see if it contains a message which matches searchproc().
*
* This is a wrapper around _index_thread_search() which iterates through
* each thread and removes any which fail the searchproc().
*/
static void index_thread_search(struct index_state *state,
Thread *root, int (*searchproc) (MsgData *))
{
Thread *cur, *prev, *next;
for (prev = NULL, cur = root->child, next = cur->next;
cur;
prev = cur, cur= next, next = (cur ? cur->next : NULL)) {
if (!_index_thread_search(state, cur, searchproc)) {
/* unlink the thread from the list */
if (!prev) /* first thread */
root->child = cur->next;
else
prev->next = cur->next;
/* we just removed cur from our list,
* so we need to keep the same prev for the next pass
*/
cur = prev;
}
}
}
/*
* Guts of the REFERENCES algorithms. Behavior is tweaked with loadcrit[],
* searchproc() and sortcrit[].
*/
static void _index_thread_ref(struct index_state *state, unsigned *msgno_list,
unsigned int nmsg,
const struct sortcrit loadcrit[],
int (*searchproc) (MsgData *),
const struct sortcrit sortcrit[], int usinguid)
{
MsgData **msgdata;
unsigned int mi;
int tref, nnode;
Thread *newnode;
struct hash_table id_table;
struct rootset rootset;
/* Create/load the msgdata array */
msgdata = index_msgdata_load(state, msgno_list, nmsg, loadcrit, 0, NULL);
/* calculate the sum of the number of references for all messages */
for (mi = 0, tref = 0 ; mi < nmsg ; mi++)
tref += msgdata[mi]->ref.count;
/* create an array of Thread to use as nodes of thread tree (including
* empty containers)
*
* - We will be building threads under a dummy root, so we need at least
* (nmsg + 1) nodes.
* - We also will need containers for references to non-existent messages.
* To make sure we have enough, we will take the worst case and
* use the sum of the number of references for all messages.
* - Finally, we will need containers to group threads with the same
* subject together. To make sure we have enough, we will take the
* worst case which will be half of the number of messages.
*
* This is overkill, but it is the only way to make sure we have enough
* ahead of time. If we tried to use xrealloc(), the array might be moved,
* and our parent/child/next pointers will no longer be correct
* (been there, done that).
*/
nnode = (int) (1.5 * nmsg + 1 + tref);
rootset.root = (Thread *) xmalloc(nnode * sizeof(Thread));
memset(rootset.root, 0, nnode * sizeof(Thread));
newnode = rootset.root + 1; /* set next newnode to the second
one in the array (skip the root) */
/* Step 0: create an id_table with one bucket for every possible
* message-id and reference (nmsg + tref)
*/
construct_hash_table(&id_table, nmsg + tref, 1);
/* Step 1: link messages together */
ref_link_messages(msgdata, nmsg, &newnode, &id_table);
/* Step 2: find the root set (gather all of the orphan messages) */
rootset.nroot = 0;
hash_enumerate(&id_table, ref_gather_orphans, &rootset);
/* discard id_table */
free_hash_table(&id_table, NULL);
/* Step 3: prune tree of empty containers - get our deposit back :^) */
ref_prune_tree(rootset.root);
/* Step 4: sort the root set */
ref_sort_root(rootset.root);
/* Step 5: group root set by subject */
ref_group_subjects(rootset.root, rootset.nroot, &newnode);
/* Optionally search threads (to be used by REFERENCES derivatives) */
if (searchproc) index_thread_search(state, rootset.root, searchproc);
/* Step 6: sort threads */
if (sortcrit) index_thread_sort(rootset.root, sortcrit);
/* Output the threaded messages */
index_thread_print(state, rootset.root, usinguid);
/* free the thread array */
free(rootset.root);
/* free the msgdata array */
index_msgdata_free(msgdata, nmsg);
}
/*
* Thread a list of messages using the REFERENCES algorithm.
*/
static void index_thread_ref(struct index_state *state,
unsigned *msgno_list, unsigned int nmsg,
int usinguid)
{
static const struct sortcrit loadcrit[] =
{{ LOAD_IDS, 0, {{NULL,NULL}} },
{ SORT_SUBJECT, 0, {{NULL,NULL}} },
{ SORT_DATE, 0, {{NULL,NULL}} },
{ SORT_SEQUENCE, 0, {{NULL,NULL}} }};
static const struct sortcrit sortcrit[] =
{{ SORT_DATE, 0, {{NULL,NULL}} },
{ SORT_SEQUENCE, 0, {{NULL,NULL}} }};
_index_thread_ref(state, msgno_list, nmsg, loadcrit, NULL, sortcrit, usinguid);
}
/*
* NNTP specific stuff.
*/
EXPORTED char *index_get_msgid(struct index_state *state,
uint32_t msgno)
{
struct mailbox *mailbox = state->mailbox;
struct index_record record;
if (index_reload_record(state, msgno, &record))
return NULL;
return mailbox_cache_get_msgid(mailbox, &record);
}
static void massage_header(char *hdr)
{
int n = 0;
char *p, c;
for (p = hdr; *p; p++) {
if (*p == ' ' || *p == '\t' || *p == '\r') {
if (!n || *(p+1) == '\n') {
/* no leading or trailing whitespace */
continue;
}
/* replace with space */
c = ' ';
}
else if (*p == '\n') {
if (*(p+1) == ' ' || *(p+1) == '\t') {
/* folded header */
continue;
}
/* end of header */
break;
}
else
c = *p;
hdr[n++] = c;
}
hdr[n] = '\0';
}
EXPORTED extern struct nntp_overview *index_overview(struct index_state *state,
uint32_t msgno)
{
static struct nntp_overview over;
static char *env = NULL, *from = NULL, *hdr = NULL;
static int envsize = 0, fromsize = 0, hdrsize = 0;
int size;
char *envtokens[NUMENVTOKENS];
struct address addr = { NULL, NULL, NULL, NULL, NULL, NULL };
strarray_t refhdr = STRARRAY_INITIALIZER;
struct mailbox *mailbox = state->mailbox;
struct index_record record;
/* flush any previous data */
memset(&over, 0, sizeof(struct nntp_overview));
if (index_reload_record(state, msgno, &record))
return NULL;
if (mailbox_cacherecord(mailbox, &record))
return NULL; /* upper layers can cope! */
/* make a working copy of envelope; strip outer ()'s */
/* -2 -> don't include the size of the outer parens */
/* +1 -> leave space for NUL */
size = cacheitem_size(&record, CACHE_ENVELOPE) - 2 + 1;
if (envsize < size) {
envsize = size;
env = xrealloc(env, envsize);
}
/* +1 -> skip the leading paren */
strlcpy(env, cacheitem_base(&record, CACHE_ENVELOPE) + 1, size);
/* make a working copy of headers */
size = cacheitem_size(&record, CACHE_HEADERS);
if (hdrsize < size+2) {
hdrsize = size+100;
hdr = xrealloc(hdr, hdrsize);
}
memcpy(hdr, cacheitem_base(&record, CACHE_HEADERS), size);
hdr[size] = '\0';
parse_cached_envelope(env, envtokens, VECTOR_SIZE(envtokens));
over.uid = record.uid;
over.bytes = record.size;
over.lines = index_getlines(state, msgno);
over.date = envtokens[ENV_DATE];
over.msgid = envtokens[ENV_MSGID];
/* massage subject */
if ((over.subj = envtokens[ENV_SUBJECT]))
massage_header(over.subj);
/* build original From: header */
if (envtokens[ENV_FROM]) /* paranoia */
message_parse_env_address(envtokens[ENV_FROM], &addr);
if (addr.mailbox && addr.domain) { /* paranoia */
/* +3 -> add space for quotes and space */
/* +4 -> add space for < @ > NUL */
size = (addr.name ? strlen(addr.name) + 3 : 0) +
strlen(addr.mailbox) + strlen(addr.domain) + 4;
if (fromsize < size) {
fromsize = size;
from = xrealloc(from, fromsize);
}
from[0] = '\0';
if (addr.name) sprintf(from, "\"%s\" ", addr.name);
snprintf(from + strlen(from), fromsize - strlen(from),
"<%s@%s>", addr.mailbox, addr.domain);
over.from = from;
}
/* massage references */
strarray_append(&refhdr, "references");
message_pruneheader(hdr, &refhdr, 0);
strarray_fini(&refhdr);
if (*hdr) {
over.ref = hdr + 11; /* skip over header name */
massage_header(over.ref);
}
return &over;
}
EXPORTED extern char *index_getheader(struct index_state *state,
uint32_t msgno, char *hdr)
{
static struct buf staticbuf = BUF_INITIALIZER;
strarray_t headers = STRARRAY_INITIALIZER;
struct mailbox *mailbox = state->mailbox;
struct index_record record;
char *buf;
if (index_reload_record(state, msgno, &record))
return NULL;
/* see if the header is cached */
if (mailbox_cached_header(hdr) != BIT32_MAX &&
!mailbox_cacherecord(mailbox, &record)) {
buf_copy(&staticbuf, cacheitem_buf(&record, CACHE_HEADERS));
}
else {
/* uncached header */
struct buf msgbuf = BUF_INITIALIZER;
if (mailbox_map_record(mailbox, &record, &msgbuf))
return NULL;
buf_setcstr(&staticbuf, index_readheader(msgbuf.s, msgbuf.len, 0, record.header_size));
buf_free(&msgbuf);
}
strarray_append(&headers, hdr);
message_pruneheader(staticbuf.s, &headers, NULL);
strarray_fini(&headers);
buf = staticbuf.s;
if (*buf) {
buf += strlen(hdr) + 1; /* skip header: */
massage_header(buf);
}
return buf;
}
EXPORTED extern unsigned long index_getsize(struct index_state *state,
uint32_t msgno)
{
struct index_record record;
if (index_reload_record(state, msgno, &record))
return 0;
return record.size;
}
EXPORTED extern unsigned long index_getlines(struct index_state *state,
uint32_t msgno)
{
struct index_record record;
if (index_reload_record(state, msgno, &record))
return 0;
return record.content_lines;
}
EXPORTED const char *index_mboxname(const struct index_state *state)
{
if (!state) return NULL;
return state->mboxname;
}
EXPORTED int index_hasrights(const struct index_state *state, int rights)
{
return state->myrights & rights;
}
/*
* Parse a sequence into an array of sorted & merged ranges.
*/
static struct seqset *_parse_sequence(struct index_state *state,
const char *sequence, int usinguid)
{
unsigned maxval = usinguid ? state->last_uid : state->exists;
return seqset_parse(sequence, NULL, maxval);
}
static void appendsequencelist(struct index_state *state,
struct seqset **l,
char *sequence, int usinguid)
{
unsigned maxval = usinguid ? state->last_uid : state->exists;
seqset_append(l, sequence, maxval);
}
EXPORTED void freesequencelist(struct seqset *l)
{
seqset_free(l);
}
/*
* Create a new search program.
*/
EXPORTED struct searchargs *new_searchargs(const char *tag, int state,
struct namespace *namespace,
const char *userid,
struct auth_state *authstate,
int isadmin)
{
struct searchargs *sa;
sa = (struct searchargs *)xzmalloc(sizeof(struct searchargs));
sa->tag = tag;
sa->state = state;
/* default charset is US-ASCII which is always 0 */
sa->namespace = namespace;
sa->userid = userid;
sa->authstate = authstate;
sa->isadmin = isadmin;
return sa;
}
/*
* Free the searchargs 's'
*/
EXPORTED void freesearchargs(struct searchargs *s)
{
struct searchsub *sub, *n;
struct searchannot *sa;
if (!s) return;
freesequencelist(s->sequence);
freesequencelist(s->uidsequence);
freestrlist(s->from);
freestrlist(s->to);
freestrlist(s->cc);
freestrlist(s->bcc);
freestrlist(s->subject);
freestrlist(s->messageid);
freestrlist(s->listid);
freestrlist(s->contenttype);
freestrlist(s->body);
freestrlist(s->text);
freestrlist(s->header_name);
freestrlist(s->header);
freestrlist(s->folder);
while ((sa = s->annotations)) {
s->annotations = sa->next;
free(sa->entry);
free(sa->attrib);
buf_free(&sa->value);
free(sa);
}
for (sub = s->sublist; sub; sub = n) {
n = sub->next;
freesearchargs(sub->sub1);
freesearchargs(sub->sub2);
free(sub);
}
search_expr_free(s->root);
free(s);
}
+EXPORTED char *sortcrit_as_string(const struct sortcrit *sortcrit)
+{
+ struct buf b = BUF_INITIALIZER;
+ static const char * const key_names[] = {
+ "SEQUENCE", "ARRIVAL", "CC", "DATE",
+ "DISPLAYFROM", "DISPLAYTO", "FROM",
+ "SIZE", "SUBJECT", "TO", "ANNOTATION",
+ "MODSEQ", "UID", "HASFLAG", "CONVMODSEQ",
+ "CONVEXISTS", "CONVSIZE", "HASCONVFLAG",
+ "FOLDER"
+ };
+
+ for ( ; sortcrit->key ; sortcrit++) {
+ if (b.len)
+ buf_putc(&b, ' ');
+ if (sortcrit->flags & SORT_REVERSE)
+ buf_appendcstr(&b, "REVERSE ");
+
+ if (sortcrit->key < VECTOR_SIZE(key_names))
+ buf_appendcstr(&b, key_names[sortcrit->key]);
+ else
+ buf_printf(&b, "UNKNOWN%u", sortcrit->key);
+
+ switch (sortcrit->key) {
+ case SORT_ANNOTATION:
+ buf_printf(&b, " \"%s\" \"%s\"",
+ sortcrit->args.annot.entry,
+ *sortcrit->args.annot.userid ?
+ "value.priv" : "value.shared");
+ break;
+ }
+ }
+ return buf_release(&b);
+}
+
/*
* Free an array of sortcrit
*/
EXPORTED 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.userid);
break;
}
i++;
} while (s[i].key != SORT_SEQUENCE);
free(s);
}
diff --git a/imap/index.h b/imap/index.h
index 040e8e400..eb8b94eee 100644
--- a/imap/index.h
+++ b/imap/index.h
@@ -1,328 +1,329 @@
/* index.h -- Routines for dealing with the index file in the imapd
*
* Copyright (c) 1994-2008 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 legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@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.
*/
/* Header for internal usage of index.c + programs that make raw access
* to index files */
#ifndef INDEX_H
#define INDEX_H
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include "annotate.h" /* for strlist functionality */
#include "search_engines.h"
#include "message_guid.h"
#include "sequence.h"
#include "strarray.h"
/* Special "sort criteria" to load message-id and references/in-reply-to
* into msgdata array for threaders that need them.
*/
#define LOAD_IDS 256
struct message;
struct vanished_params {
unsigned long uidvalidity;
modseq_t modseq;
const char *match_seq;
const char *match_uid;
const char *sequence;
int uidvalidity_is_max;
};
struct index_init {
const char *userid;
struct auth_state *authstate;
struct protstream *out;
int examine_mode;
int qresync;
int select;
struct vanished_params vanished;
struct seqset *vanishedlist;
};
struct index_map {
modseq_t modseq;
modseq_t told_modseq;
uint32_t uid;
uint32_t recno;
uint32_t system_flags;
uint32_t user_flags[MAX_USER_FLAGS/32];
unsigned int isseen:1;
unsigned int isrecent:1;
};
struct index_state {
struct mailbox *mailbox;
unsigned num_records;
unsigned oldexists;
unsigned exists;
unsigned long last_uid;
uint32_t generation; /* to notice repacks */
uint32_t uidvalidity; /* to notice delete/recreate */
modseq_t highestmodseq;
modseq_t delayed_modseq;
struct index_map *map;
unsigned mapsize;
int internalseen;
int skipped_expunge;
int seen_dirty;
int examining;
int myrights;
unsigned numrecent;
unsigned numunseen;
unsigned firstnotseen;
char *flagname[MAX_USER_FLAGS];
char *userid;
char *mboxname;
struct protstream *out;
int qresync;
struct auth_state *authstate;
int want_expunged;
unsigned num_expunged;
};
struct copyargs {
struct copymsg *copymsg;
int nummsg;
int msgalloc;
};
struct search_folder;
typedef struct msgdata {
struct search_folder *folder; /* search folder (can be NULL) */
/* items from the index_record */
bit32 uid; /* UID for output purposes */
uint32_t msgno; /* message number */
conversation_id_t cid; /* conversation id */
strarray_t ref; /* array of references */
time_t sentdate; /* sent date & time of message
from Date: header (adjusted by time zone) */
time_t internaldate; /* internaldate */
size_t size; /* message size */
modseq_t modseq; /* modseq of record*/
bit32 hasflag; /* hasflag values (up to 32 of them) */
/* items from the conversations database */
modseq_t convmodseq; /* modseq of conversation */
uint32_t convexists; /* exists count of conversation */
uint32_t convsize; /* total size of messages in conversation */
bit32 hasconvflag; /* hasconvflag values (up to 32 of them) */
/* items from the cache record */
char *msgid; /* message ID */
char *listid; /* List-Id and Mailing-List fields */
char *contenttype; /* all MIME Content-Types except multipart */
char *cc; /* local-part of first "cc" address */
char *from; /* local-part of first "from" address */
char *to; /* local-part of first "to" address */
char *displayfrom; /* display-name of first "from" address */
char *displayto; /* display-name of first "to" address */
char *xsubj; /* extracted subject text */
unsigned xsubj_hash; /* hash of extracted subject text */
int is_refwd; /* is message a reply or forward? */
strarray_t annot; /* array of annotation attribute values
(stored in order of sortcrit) */
} MsgData;
typedef struct thread {
MsgData *msgdata; /* message data */
struct thread *parent; /* parent message */
struct thread *child; /* first child message */
struct thread *next; /* next sibling message */
} Thread;
typedef struct search_folder {
char *mboxname;
int id; /* index used for formatting output */
uint32_t uidvalidity;
MsgData **msgdata;
} SearchFolder;
struct rootset {
Thread *root;
unsigned nroot;
};
struct thread_algorithm {
const char *alg_name;
void (*threader)(struct index_state *state, unsigned *msgno_list,
unsigned int nmsg, int usinguid);
};
struct nntp_overview {
unsigned long uid;
char *subj;
char *from;
char *date;
char *msgid;
char *ref;
unsigned long bytes;
unsigned long lines;
};
enum index_warmup_flags
{
WARMUP_INDEX = (1<<0),
WARMUP_CONVERSATIONS = (1<<1),
WARMUP_ANNOTATIONS = (1<<2),
WARMUP_FOLDERSTATUS = (1<<3),
WARMUP_ALL = (~0),
};
/* non-locking, non-updating - just do a fetch on the state
* we already have */
void index_fetchresponses(struct index_state *state,
struct seqset *seq,
int usinguid,
const struct fetchargs *fetchargs,
int *fetchedsomething);
extern int index_fetch(struct index_state *state,
const char* sequence,
int usinguid,
const struct fetchargs* fetchargs,
int* fetchedsomething);
extern int index_store(struct index_state *state,
char *sequence,
struct storeargs *storeargs);
extern int index_run_annotator(struct index_state *state,
const char *sequence, int usinguid,
struct namespace *namespace, int isadmin);
extern int index_warmup(struct mboxlist_entry *, unsigned int warmup_flags);
extern int index_sort(struct index_state *state, const struct sortcrit *sortcrit,
const struct searchargs *searchargs, int usinguid);
extern int index_convsort(struct index_state *state, struct sortcrit *sortcrit,
struct searchargs *searchargs,
const struct windowargs * windowargs);
extern int index_convmultisort(struct index_state *state, struct sortcrit *sortcrit,
struct searchargs *searchargs,
const struct windowargs * windowargs);
extern int index_snippets(struct index_state *state,
const struct snippetargs *snippetargs,
struct searchargs *searchargs);
extern int index_convupdates(struct index_state *state, struct sortcrit *sortcrit,
struct searchargs *searchargs,
const struct windowargs * windowargs);
extern int index_thread(struct index_state *state, int algorithm,
struct searchargs *searchargs, int usinguid);
extern int index_search(struct index_state *state,
struct searchargs *searchargs,
int usinguid);
extern int index_scan(struct index_state *state,
const char *contents);
extern int index_copy(struct index_state *state,
char *sequence,
int usinguid,
char *name,
char **copyuidp,
int nolink,
struct namespace *namespace,
int isadmin,
int ismove,
int ignorequota);
extern int find_thread_algorithm(char *arg);
extern int index_open(const char *name, struct index_init *init,
struct index_state **stateptr);
extern void index_checkflags(struct index_state *state, int print, int dirty);
extern void index_select(struct index_state *state, struct index_init *init);
extern int index_status(struct index_state *state, struct statusdata *sdata);
extern void index_release(struct index_state *state);
extern void index_close(struct index_state **stateptr);
extern uint32_t index_finduid(struct index_state *state, uint32_t uid);
extern uint32_t index_getuid(struct index_state *state, uint32_t msgno);
extern void index_tellchanges(struct index_state *state, int canexpunge,
int printuid, int printmodseq);
extern modseq_t index_highestmodseq(struct index_state *state);
extern int index_check(struct index_state *state, int usinguid, int printuid);
extern struct seqset *index_vanished(struct index_state *state,
struct vanished_params *params);
extern int index_urlfetch(struct index_state *state, uint32_t msgno,
unsigned params, const char *section,
unsigned long start_octet, unsigned long octet_count,
struct protstream *pout, unsigned long *size);
extern char *index_get_msgid(struct index_state *state, uint32_t msgno);
extern struct message *index_get_message(struct index_state *state, uint32_t msgno);
extern struct nntp_overview *index_overview(struct index_state *state,
uint32_t msgno);
extern char *index_getheader(struct index_state *state, uint32_t msgno,
char *hdr);
extern unsigned long index_getsize(struct index_state *state, uint32_t msgno);
extern unsigned long index_getlines(struct index_state *state, uint32_t msgno);
extern int index_copy_remote(struct index_state *state, char *sequence,
int usinguid, struct protstream *pout);
struct searchargs *new_searchargs(const char *tag, int state,
struct namespace *namespace,
const char *userid,
struct auth_state *authstate,
int isadmin);
void freesequencelist(struct seqset *l);
void freesearchargs(struct searchargs *s);
+char *sortcrit_as_string(const struct sortcrit *sortcrit);
void freesortcrit(struct sortcrit *s);
extern int index_expunge(struct index_state *state, char *uidsequence,
int need_deleted);
extern int index_getsearchtext(struct message *,
struct search_text_receiver *receiver,
int snippet);
extern int index_getuidsequence(struct index_state *state,
struct searchargs *searchargs,
unsigned **uid_list);
extern const char *index_mboxname(const struct index_state *state);
extern int index_hasrights(const struct index_state *state, int rights);
#endif /* INDEX_H */

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 6, 1:12 AM (2 d, 17 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831810
Default Alt Text
(570 KB)

Event Timeline