/* sync_server.c -- Cyrus synchonization server
*
* 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.
*
* $Id: sync_server.c,v 1.34 2010/01/06 17:01:41 murch Exp $
*
* Original version written by David Carter <dpc22@cam.ac.uk>
* Rewritten and integrated into Cyrus by Ken Murchison <ken@oceana.com>
*/
#include <config.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <syslog.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "assert.h"
#include "acl.h"
#include "annotate.h"
#include "append.h"
#include "auth.h"
#include "dlist.h"
#include "duplicate.h"
#include "exitcodes.h"
#include "global.h"
#include "hash.h"
#include "imap_err.h"
#include "imparse.h"
#include "iptostring.h"
#include "mailbox.h"
#include "map.h"
#include "mboxlist.h"
#include "proc.h"
#include "prot.h"
#include "quota.h"
#include "retry.h"
#include "seen.h"
#include "spool.h"
#include "statuscache.h"
#include "sync_log.h"
#include "telemetry.h"
#include "tls.h"
#include "user.h"
#include "util.h"
#include "version.h"
#include "xmalloc.h"
#include "xstrlcat.h"
#include "xstrlcpy.h"
#include "message_guid.h"
#include "sync_support.h"
/*#include "cdb.h"*/
extern int optind;
extern char *optarg;
extern int opterr;
/* for config.c */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
static sasl_ssf_t extprops_ssf = 0;
#ifdef HAVE_SSL
static SSL *tls_conn;
#endif /* HAVE_SSL */
sasl_conn_t *sync_saslconn = NULL; /* the sasl connection context */
char *sync_userid = 0;
struct namespace sync_namespace;
struct namespace *sync_namespacep = &sync_namespace;
struct auth_state *sync_authstate = 0;
int sync_userisadmin = 0;
struct sockaddr_storage sync_localaddr, sync_remoteaddr;
int sync_haveaddr = 0;
char sync_clienthost[NI_MAXHOST*2+1] = "[local]";
struct protstream *sync_out = NULL;
struct protstream *sync_in = NULL;
static int sync_logfd = -1;
static int sync_starttls_done = 0;
static int sync_compress_done = 0;
/* commands that have specific names */
static void cmdloop(void);
static void cmd_authenticate(char *mech, char *resp);
static void cmd_starttls(void);
static void cmd_restart(struct sync_reserve_list **reserve_listp,
int realloc);
static void cmd_compress(char *alg);
/* generic commands - in dlist format */
static void cmd_get(struct dlist *kl);
static void cmd_apply(struct dlist *kl,
struct sync_reserve_list *reserve_list);
void usage(void);
void shut_down(int code) __attribute__ ((noreturn));
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);
static struct {
char *ipremoteport;
char *iplocalport;
sasl_ssf_t ssf;
char *authid;
} saslprops = {NULL,NULL,0,NULL};
/* the sasl proxy policy context */
static struct proxy_context sync_proxyctx = {
0, 1, &sync_authstate, &sync_userisadmin, NULL
};
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, (mysasl_cb_ft *) &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, (mysasl_cb_ft *) &mysasl_proxy_policy, (void*) &sync_proxyctx },
{ SASL_CB_CANON_USER, (mysasl_cb_ft *) &mysasl_canon_user, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
static void sync_reset(void)
{
proc_cleanup();
if (sync_in) {
prot_NONBLOCK(sync_in);
prot_fill(sync_in);
prot_free(sync_in);
}
if (sync_out) {
prot_flush(sync_out);
prot_free(sync_out);
}
sync_in = sync_out = NULL;
#ifdef HAVE_SSL
if (tls_conn) {
tls_reset_servertls(&tls_conn);
tls_conn = NULL;
}
#endif
cyrus_reset_stdio();
strcpy(sync_clienthost, "[local]");
if (sync_logfd != -1) {
close(sync_logfd);
sync_logfd = -1;
}
if (sync_userid != NULL) {
free(sync_userid);
sync_userid = NULL;
}
if (sync_authstate) {
auth_freestate(sync_authstate);
sync_authstate = NULL;
}
if (sync_saslconn) {
sasl_dispose(&sync_saslconn);
sync_saslconn = NULL;
}
sync_starttls_done = 0;
if(saslprops.iplocalport) {
free(saslprops.iplocalport);
saslprops.iplocalport = NULL;
}
if(saslprops.ipremoteport) {
free(saslprops.ipremoteport);
saslprops.ipremoteport = NULL;
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
saslprops.ssf = 0;
}
/*
* run once when process is forked;
* MUST NOT exit directly; must return with non-zero error code
*/
int service_init(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
int opt, r;
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);
while ((opt = getopt(argc, argv, "p:")) != EOF) {
switch(opt) {
case 'p': /* external protection */
extprops_ssf = atoi(optarg);
break;
default:
usage();
}
}
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(sync_namespacep, 1)) != 0) {
fatal(error_message(r), EC_CONFIG);
}
/* 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);
/* Initialize the annotatemore extention */
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
/* Open the statuscache so we can invalidate seen states */
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
statuscache_open(NULL);
}
return 0;
}
/*
* Issue the capability banner
*/
static void dobanner(void)
{
const char *mechlist;
int mechcount;
if (!sync_userid) {
if (sasl_listmech(sync_saslconn, NULL,
"* SASL ", " ", "\r\n",
&mechlist, NULL, &mechcount) == SASL_OK
&& mechcount > 0) {
prot_printf(sync_out, "%s", mechlist);
}
if (tls_enabled() && !sync_starttls_done) {
prot_printf(sync_out, "* STARTTLS\r\n");
}
#ifdef HAVE_ZLIB
if (!sync_compress_done && !sync_starttls_done) {
prot_printf(sync_out, "* COMPRESS DEFLATE\r\n");
}
#endif
}
prot_printf(sync_out,
"* OK %s Cyrus sync server %s\r\n",
config_servername, cyrus_version());
prot_flush(sync_out);
}
/*
* run for each accepted connection
*/
int service_main(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
struct protoent *proto;
socklen_t salen;
char localip[60], remoteip[60];
char hbuf[NI_MAXHOST];
int niflags;
sasl_security_properties_t *secprops = NULL;
signals_poll();
sync_in = prot_new(0, 0);
sync_out = prot_new(1, 1);
/* Force use of LITERAL+ so we don't need two way communications */
prot_setisclient(sync_in, 1);
prot_setisclient(sync_out, 1);
/* Find out name of client host */
salen = sizeof(sync_remoteaddr);
if (getpeername(0, (struct sockaddr *)&sync_remoteaddr, &salen) == 0 &&
(sync_remoteaddr.ss_family == AF_INET ||
sync_remoteaddr.ss_family == AF_INET6)) {
if (getnameinfo((struct sockaddr *)&sync_remoteaddr, salen,
hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) {
strncpy(sync_clienthost, hbuf, sizeof(hbuf));
strlcat(sync_clienthost, " ", sizeof(sync_clienthost));
sync_clienthost[sizeof(sync_clienthost)-30] = '\0';
} else {
sync_clienthost[0] = '\0';
}
niflags = NI_NUMERICHOST;
#ifdef NI_WITHSCOPEID
if (((struct sockaddr *)&sync_remoteaddr)->sa_family == AF_INET6)
niflags |= NI_WITHSCOPEID;
#endif
if (getnameinfo((struct sockaddr *)&sync_remoteaddr, salen, hbuf,
sizeof(hbuf), NULL, 0, niflags) != 0)
strlcpy(hbuf, "unknown", sizeof(hbuf));
strlcat(sync_clienthost, "[", sizeof(sync_clienthost));
strlcat(sync_clienthost, hbuf, sizeof(sync_clienthost));
strlcat(sync_clienthost, "]", sizeof(sync_clienthost));
salen = sizeof(sync_localaddr);
if (getsockname(0, (struct sockaddr *)&sync_localaddr, &salen) == 0) {
sync_haveaddr = 1;
}
/* other params should be filled in */
if (sasl_server_new("csync", config_servername, NULL, NULL, NULL,
NULL, 0, &sync_saslconn) != SASL_OK)
fatal("SASL failed initializing: sasl_server_new()",EC_TEMPFAIL);
/* will always return something valid */
secprops = mysasl_secprops(SASL_SEC_NOANONYMOUS);
if (sasl_setprop(sync_saslconn, SASL_SEC_PROPS, secprops) != SASL_OK)
fatal("Failed to set SASL property", EC_TEMPFAIL);
if (sasl_setprop(sync_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf) != SASL_OK)
fatal("Failed to set SASL property", EC_TEMPFAIL);
if(iptostring((struct sockaddr *)&sync_localaddr, salen,
localip, 60) == 0) {
sasl_setprop(sync_saslconn, SASL_IPLOCALPORT, localip);
saslprops.iplocalport = xstrdup(localip);
}
if(iptostring((struct sockaddr *)&sync_remoteaddr, salen,
remoteip, 60) == 0) {
if (sasl_setprop(sync_saslconn, SASL_IPREMOTEPORT, remoteip) != SASL_OK)
fatal("failed to set sasl property", EC_TEMPFAIL);
saslprops.ipremoteport = xstrdup(remoteip);
}
/* Disable Nagle's Algorithm => increase throughput
*
* http://en.wikipedia.org/wiki/Nagle's_algorithm
*/
if ((proto = getprotobyname("tcp")) != NULL) {
int on = 1;
if (setsockopt(1, proto->p_proto, TCP_NODELAY,
(void *) &on, sizeof(on)) != 0) {
syslog(LOG_ERR, "unable to setsocketopt(TCP_NODELAY): %m");
}
} else {
syslog(LOG_ERR, "unable to getprotobyname(\"tcp\"): %m");
}
} else {
/* we're not connected to an internet socket! */
strcpy(sync_clienthost, "[unix socket]");
sync_userid = xstrdup("cyrus");
sync_userisadmin = 1;
}
proc_register("sync_server", sync_clienthost, NULL, NULL);
#if 0
/* Set inactivity timer */
timeout = config_getint(IMAPOPT_TIMEOUT);
if (timeout < 3) timeout = 3;
prot_settimeout(sync_in, timeout*60);
#endif
prot_setflushonread(sync_in, sync_out);
sync_log_init();
if (!config_getswitch(IMAPOPT_SYNC_LOG_CHAIN))
sync_log_suppress();
dobanner();
cmdloop();
/* EXIT executed */
/* cleanup */
sync_reset();
return 0;
}
/* Called by service API to shut down the service */
void service_abort(int error)
{
shut_down(error);
}
void usage(void)
{
prot_printf(sync_out, "* usage: sync_server [-C <alt_config>]\r\n");
prot_flush(sync_out);
exit(EC_USAGE);
}
/*
* Cleanly shut down and exit
*/
void shut_down(int code)
{
in_shutdown = 1;
proc_cleanup();
if (config_getswitch(IMAPOPT_STATUSCACHE)) {
statuscache_close();
statuscache_done();
}
seen_done();
mboxlist_close();
mboxlist_done();
quotadb_close();
quotadb_done();
annotatemore_close();
annotatemore_done();
if (sync_in) {
prot_NONBLOCK(sync_in);
prot_fill(sync_in);
prot_free(sync_in);
}
if (sync_out) {
prot_flush(sync_out);
prot_free(sync_out);
}
#ifdef HAVE_SSL
tls_shutdown_serverengine();
#endif
cyrus_done();
exit(code);
}
void fatal(const char* s, int code)
{
static int recurse_code = 0;
if (recurse_code) {
/* We were called recursively. Just give up */
proc_cleanup();
exit(recurse_code);
}
recurse_code = code;
if (sync_out) {
prot_printf(sync_out, "* Fatal error: %s\r\n", s);
prot_flush(sync_out);
}
syslog(LOG_ERR, "Fatal error: %s", s);
shut_down(code);
}
/* 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("csync", config_servername,
NULL, NULL, NULL,
NULL, 0, conn);
if (ret != SASL_OK) return ret;
if (saslprops.ipremoteport)
ret = sasl_setprop(*conn, SASL_IPREMOTEPORT,
saslprops.ipremoteport);
if (ret != SASL_OK) return ret;
if (saslprops.iplocalport)
ret = sasl_setprop(*conn, SASL_IPLOCALPORT,
saslprops.iplocalport);
if (ret != SASL_OK) return ret;
secprops = mysasl_secprops(SASL_SEC_NOANONYMOUS);
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 cmdloop(void)
{
struct sync_reserve_list *reserve_list;
static struct buf cmd;
static struct buf arg1, arg2;
int c;
char *p;
struct dlist *kl;
syslog(LOG_DEBUG, "cmdloop(): startup");
reserve_list = sync_reserve_list_create(SYNC_MESSAGE_LIST_HASH_SIZE);
for (;;) {
prot_flush(sync_out);
/* Parse command name */
if ((c = getword(sync_in, &cmd)) == EOF)
break;
if (!cmd.s[0]) {
prot_printf(sync_out, "BAD Null command\r\n");
eatline(sync_in, c);
continue;
}
if (Uislower(cmd.s[0]))
cmd.s[0] = toupper((unsigned char) cmd.s[0]);
for (p = &cmd.s[1]; *p; p++) {
if (Uisupper(*p)) *p = tolower((unsigned char) *p);
}
/* Must be an admin */
if (sync_userid && !sync_userisadmin) goto noperm;
switch (cmd.s[0]) {
case 'A':
if (!strcmp(cmd.s, "Authenticate")) {
int haveinitresp = 0;
if (c != ' ') goto missingargs;
c = getword(sync_in, &arg1);
if (!imparse_isatom(arg1.s)) {
prot_printf(sync_out, "BAD Invalid mechanism\r\n");
eatline(sync_in, c);
continue;
}
if (c == ' ') {
haveinitresp = 1;
c = getword(sync_in, &arg2);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(sync_in);
if (c != '\n') goto extraargs;
if (sync_userid) {
prot_printf(sync_out, "BAD Already authenticated\r\n");
continue;
}
cmd_authenticate(arg1.s, haveinitresp ? arg2.s : NULL);
continue;
}
if (!sync_userid) goto nologin;
if (!strcmp(cmd.s, "Apply")) {
kl = sync_parseline(sync_in);
if (kl) {
cmd_apply(kl, reserve_list);
dlist_free(&kl);
}
else
prot_printf(sync_out, "BAD IMAP_PROTOCOL_ERROR Failed to parse APPLY line\r\n");
continue;
}
break;
case 'C':
if (!strcmp(cmd.s, "Compress")) {
if (c != ' ') goto missingargs;
c = getword(sync_in, &arg1);
if (c == '\r') c = prot_getc(sync_in);
if (c != '\n') goto extraargs;
cmd_compress(arg1.s);
continue;
}
break;
case 'G':
if (!sync_userid) goto nologin;
if (!strcmp(cmd.s, "Get")) {
kl = sync_parseline(sync_in);
if (kl) {
cmd_get(kl);
dlist_free(&kl);
}
else
prot_printf(sync_out, "BAD IMAP_PROTOCOL_ERROR Failed to parse GET line\r\n");
continue;
}
break;
case 'E':
if (!strcmp(cmd.s, "Exit")) {
if (c == '\r') c = prot_getc(sync_in);
if (c != '\n') goto extraargs;
prot_printf(sync_out, "OK Finished\r\n");
prot_flush(sync_out);
goto exit;
}
break;
case 'N':
if (!strcmp(cmd.s, "Noop")) {
if (c == '\r') c = prot_getc(sync_in);
if (c != '\n') goto extraargs;
prot_printf(sync_out, "OK Noop completed\r\n");
continue;
}
break;
case 'R':
if (!strcmp(cmd.s, "Restart")) {
if (c == '\r') c = prot_getc(sync_in);
if (c != '\n') goto extraargs;
/* just clear the GUID cache */
cmd_restart(&reserve_list, 1);
prot_printf(sync_out, "OK Restarting\r\n");
continue;
}
else if (!sync_userid) goto nologin;
break;
case 'S':
if (!strcmp(cmd.s, "Starttls") && tls_enabled()) {
if (c == '\r') c = prot_getc(sync_in);
if (c != '\n') goto extraargs;
/* XXX discard any input pipelined after STARTTLS */
prot_flush(sync_in);
/* if we've already done SASL fail */
if (sync_userid != NULL) {
prot_printf(sync_out,
"BAD Can't Starttls after authentication\r\n");
continue;
}
/* check if already did a successful tls */
if (sync_starttls_done == 1) {
prot_printf(sync_out,
"BAD Already did a successful Starttls\r\n");
continue;
}
cmd_starttls();
continue;
}
break;
}
prot_printf(sync_out, "BAD IMAP_PROTOCOL_ERROR Unrecognized command\r\n");
eatline(sync_in, c);
continue;
nologin:
prot_printf(sync_out, "NO Please authenticate first\r\n");
eatline(sync_in, c);
continue;
noperm:
prot_printf(sync_out, "NO %s\r\n",
error_message(IMAP_PERMISSION_DENIED));
eatline(sync_in, c);
continue;
missingargs:
prot_printf(sync_out, "BAD Missing required argument to %s\r\n", cmd.s);
eatline(sync_in, c);
continue;
extraargs:
prot_printf(sync_out, "BAD Unexpected extra arguments to %s\r\n", cmd.s);
eatline(sync_in, c);
continue;
}
exit:
cmd_restart(&reserve_list, 0);
}
static void cmd_authenticate(char *mech, char *resp)
{
int r, sasl_result;
sasl_ssf_t ssf;
char *ssfmsg = NULL;
const void *val;
int failedloginpause;
if (sync_userid) {
prot_printf(sync_out, "BAD Already authenticated\r\n");
return;
}
r = saslserver(sync_saslconn, mech, resp, "", "+ ", "",
sync_in, sync_out, &sasl_result, NULL);
if (r) {
const char *errorstring = NULL;
switch (r) {
case IMAP_SASL_CANCEL:
prot_printf(sync_out,
"BAD Client canceled authentication\r\n");
break;
case IMAP_SASL_PROTERR:
errorstring = prot_error(sync_in);
prot_printf(sync_out,
"NO Error reading client response: %s\r\n",
errorstring ? errorstring : "");
break;
default:
/* failed authentication */
errorstring = sasl_errstring(sasl_result, NULL, NULL);
syslog(LOG_NOTICE, "badlogin: %s %s [%s]",
sync_clienthost, mech, sasl_errdetail(sync_saslconn));
failedloginpause = config_getint(IMAPOPT_FAILEDLOGINPAUSE);
if (failedloginpause != 0) {
sleep(failedloginpause);
}
if (errorstring) {
prot_printf(sync_out, "NO %s\r\n", errorstring);
} else {
prot_printf(sync_out, "NO Error authenticating\r\n");
}
}
reset_saslconn(&sync_saslconn);
return;
}
/* successful authentication */
/* get the userid from SASL --- already canonicalized from
* mysasl_proxy_policy()
*/
sasl_result = sasl_getprop(sync_saslconn, SASL_USERNAME, &val);
if (sasl_result != SASL_OK) {
prot_printf(sync_out, "NO weird SASL error %d SASL_USERNAME\r\n",
sasl_result);
syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME",
sasl_result);
reset_saslconn(&sync_saslconn);
return;
}
sync_userid = xstrdup((const char *) val);
proc_register("sync_server", sync_clienthost, sync_userid, NULL);
syslog(LOG_NOTICE, "login: %s %s %s%s %s", sync_clienthost, sync_userid,
mech, sync_starttls_done ? "+TLS" : "", "User logged in");
sasl_getprop(sync_saslconn, SASL_SSF, &val);
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 (sync_starttls_done) {
switch(ssf) {
case 0: ssfmsg = "tls protection"; break;
case 1: ssfmsg = "tls plus integrity protection"; break;
default: ssfmsg = "tls plus privacy protection"; break;
}
} else {
switch(ssf) {
case 0: ssfmsg = "no protection"; break;
case 1: ssfmsg = "integrity protection"; break;
default: ssfmsg = "privacy protection"; break;
}
}
prot_printf(sync_out, "OK Success (%s)\r\n", ssfmsg);
prot_setsasl(sync_in, sync_saslconn);
prot_setsasl(sync_out, sync_saslconn);
/* Create telemetry log */
sync_logfd = telemetry_log(sync_userid, sync_in, sync_out, 0);
}
void printstring(const char *s __attribute__((unused)))
{
/* needed to link against annotate.o */
fatal("printstring() executed, but its not used for sync_server!",
EC_SOFTWARE);
}
#ifdef HAVE_SSL
static void cmd_starttls(void)
{
int result;
int *layerp;
sasl_ssf_t ssf;
char *auth_id;
if (sync_starttls_done == 1) {
prot_printf(sync_out, "NO %s\r\n",
"Already successfully executed STARTTLS");
return;
}
/* SASL and openssl have different ideas about whether ssf is signed */
layerp = (int *) &ssf;
result=tls_init_serverengine("csync",
5, /* depth to verify */
1, /* can client auth? */
1); /* TLS only? */
if (result == -1) {
syslog(LOG_ERR, "error initializing TLS");
prot_printf(sync_out, "NO %s\r\n", "Error initializing TLS");
return;
}
prot_printf(sync_out, "OK %s\r\n", "Begin TLS negotiation now");
/* must flush our buffers before starting tls */
prot_flush(sync_out);
result=tls_start_servertls(0, /* read */
1, /* write */
180, /* 3 minutes */
layerp,
&auth_id,
&tls_conn);
/* if error */
if (result==-1) {
prot_printf(sync_out, "NO Starttls failed\r\n");
syslog(LOG_NOTICE, "STARTTLS failed: %s", sync_clienthost);
return;
}
/* tell SASL about the negotiated layer */
result = sasl_setprop(sync_saslconn, SASL_SSF_EXTERNAL, &ssf);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
saslprops.ssf = ssf;
result = sasl_setprop(sync_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(sync_in, tls_conn);
prot_settls(sync_out, tls_conn);
sync_starttls_done = 1;
dobanner();
}
#else
static void cmd_starttls(void)
{
fatal("cmd_starttls() called, but no OpenSSL", EC_SOFTWARE);
}
#endif /* HAVE_SSL */
#ifdef HAVE_ZLIB
static void cmd_compress(char *alg)
{
if (sync_compress_done) {
prot_printf(sync_out, "NO Compression already active: %s\r\n", alg);
return;
}
if (strcasecmp(alg, "DEFLATE")) {
prot_printf(sync_out, "NO Unknown compression algorithm: %s\r\n", alg);
return;
}
if (ZLIB_VERSION[0] != zlibVersion()[0]) {
prot_printf(sync_out, "NO Error initializing %s "
"(incompatible zlib version)\r\n", alg);
return;
}
prot_printf(sync_out, "OK %s active\r\n", alg);
prot_flush(sync_out);
prot_setcompress(sync_in);
prot_setcompress(sync_out);
sync_compress_done = 1;
}
#else
static void cmd_compress(char *alg)
{
prot_printf(sync_out, "NO ZLIB not available\r\n");
}
#endif
/* ====================================================================== */
/* partition_list is simple linked list of names used by cmd_restart */
struct partition_list {
struct partition_list *next;
char *name;
};
static struct partition_list *
partition_list_add(char *name, struct partition_list *pl)
{
struct partition_list *p;
/* Is name already on list? */
for (p=pl; p; p = p->next) {
if (!strcmp(p->name, name))
return(pl);
}
/* Add entry to start of list and return new list */
p = xzmalloc(sizeof(struct partition_list));
p->next = pl;
p->name = xstrdup(name);
return(p);
}
static void
partition_list_free(struct partition_list *current)
{
while (current) {
struct partition_list *next = current->next;
free(current->name);
free(current);
current = next;
}
}
static void cmd_restart(struct sync_reserve_list **reserve_listp, int re_alloc)
{
struct sync_reserve *res;
struct sync_reserve_list *l = *reserve_listp;
struct sync_msgid *msg;
const char *fname;
int hash_size = l->hash_size;
struct partition_list *p, *pl = NULL;
for (res = l->head; res; res = res->next) {
for (msg = res->list->head; msg; msg = msg->next) {
pl = partition_list_add(res->part, pl);
fname = dlist_reserve_path(res->part, &msg->guid);
unlink(fname);
}
}
sync_reserve_list_free(reserve_listp);
/* Remove all <partition>/sync./<pid> directories referred to above */
for (p=pl; p ; p = p->next) {
static char buf[MAX_MAILBOX_PATH];
snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu",
config_partitiondir(p->name), (unsigned long)getpid());
rmdir(buf);
}
partition_list_free(pl);
if (re_alloc)
*reserve_listp = sync_reserve_list_create(hash_size);
else
*reserve_listp = NULL;
}
/* ====================================================================== */
void reserve_folder(const char *part, const char *mboxname,
struct sync_msgid_list *part_list)
{
struct mailbox *mailbox = NULL;
struct index_record record;
struct index_record record2;
int r;
struct sync_msgid *item;
const char *mailbox_msg_path, *stage_msg_path;
uint32_t recno;
/* Open and lock mailbox */
r = mailbox_open_irl(mboxname, &mailbox);
if (r) return;
for (recno = 1;
part_list->marked < part_list->count && recno <= mailbox->i.num_records;
recno++) {
if (mailbox_read_index_record(mailbox, recno, &record))
continue;
if (record.system_flags & FLAG_UNLINKED)
continue;
item = sync_msgid_lookup(part_list, &record.guid);
if (!item || item->mark)
continue;
/* Attempt to reserve this message */
mailbox_msg_path = mailbox_message_fname(mailbox, record.uid);
stage_msg_path = dlist_reserve_path(part, &record.guid);
/* check that the sha1 of the file on disk is correct */
memset(&record2, 0, sizeof(struct index_record));
r = message_parse(mailbox_msg_path, &record2);
if (r) {
syslog(LOG_ERR, "IOERROR: Unable to parse %s",
mailbox_msg_path);
continue;
}
if (!message_guid_equal(&record.guid, &record2.guid)) {
syslog(LOG_ERR, "IOERROR: GUID mismatch on parse for %s",
mailbox_msg_path);
continue;
}
if (mailbox_copyfile(mailbox_msg_path, stage_msg_path, 0) != 0) {
syslog(LOG_ERR, "IOERROR: Unable to link %s -> %s: %m",
mailbox_msg_path, stage_msg_path);
continue;
}
item->mark = 1;
part_list->marked++;
}
mailbox_close(&mailbox);
}
static int do_reserve(struct dlist *kl, struct sync_reserve_list *reserve_list)
{
struct message_guid tmp_guid;
struct sync_name_list *missing = sync_name_list_create();
struct sync_name_list *folder_names = sync_name_list_create();
struct sync_msgid_list *part_list;
struct sync_msgid *item;
struct sync_name *folder;
struct mboxlist_entry mbentry;
const char *partition = NULL;
struct dlist *ml;
struct dlist *gl;
struct dlist *i;
struct dlist *kout;
if (!dlist_getatom(kl, "PARTITION", &partition)) goto parse_err;
if (!dlist_getlist(kl, "MBOXNAME", &ml)) goto parse_err;
if (!dlist_getlist(kl, "GUID", &gl)) goto parse_err;
part_list = sync_reserve_partlist(reserve_list, partition);
for (i = gl->head; i; i = i->next) {
if (!i->sval || !message_guid_decode(&tmp_guid, i->sval))
goto parse_err;
sync_msgid_add(part_list, &tmp_guid);
}
/* need a list so we can mark items */
for (i = ml->head; i; i = i->next) {
sync_name_list_add(folder_names, i->sval);
}
for (folder = folder_names->head;
part_list->marked < part_list->count && folder;
folder = folder->next) {
if (mboxlist_lookup(folder->name, &mbentry, 0) ||
strcmp(mbentry.partition, partition))
continue; /* try folders on the same partition first! */
reserve_folder(partition, folder->name, part_list);
folder->mark = 1;
}
/* if we have other folders, check them now */
for (folder = folder_names->head;
part_list->marked < part_list->count && folder;
folder = folder->next) {
if (folder->mark)
continue;
reserve_folder(partition, folder->name, part_list);
}
/* check if we missed any */
kout = dlist_list(NULL, "MISSING");
for (i = gl->head; i; i = i->next) {
if (!message_guid_decode(&tmp_guid, i->sval))
continue;
item = sync_msgid_lookup(part_list, &tmp_guid);
if (item && !item->mark)
dlist_atom(kout, "GUID", i->sval);
}
if (kout->head)
sync_send_response(kout, sync_out);
dlist_free(&kout);
sync_name_list_free(&folder_names);
sync_name_list_free(&missing);
return 0;
parse_err:
sync_name_list_free(&folder_names);
sync_name_list_free(&missing);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
/* ====================================================================== */
static int do_unquota(struct dlist *kin)
{
return mboxlist_unsetquota(kin->sval);
}
static int do_quota(struct dlist *kin)
{
const char *root;
uint32_t limit;
if (!dlist_getatom(kin, "ROOT", &root))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum(kin, "LIMIT", &limit))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return mboxlist_setquota(root, limit, 1);
}
/* ====================================================================== */
static int mailbox_compare_update(struct mailbox *mailbox,
struct dlist *kr, int doupdate)
{
struct index_record mrecord;
struct index_record rrecord;
uint32_t recno = 1;
struct dlist *ki;
int r;
int i;
rrecord.uid = 0;
for (ki = kr->head; ki; ki = ki->next) {
r = parse_upload(ki, mailbox, &mrecord);
if (r) {
syslog(LOG_ERR, "Failed to parse uploaded record");
return r;
}
while (rrecord.uid < mrecord.uid) {
/* hit the end? Magic marker */
if (recno > mailbox->i.num_records) {
rrecord.uid = UINT32_MAX;
break;
}
/* read another record */
r = mailbox_read_index_record(mailbox, recno, &rrecord);
if (r) {
syslog(LOG_ERR, "Failed to read record %s %d",
mailbox->name, recno);
return r;
}
recno++;
}
/* found a match, check for updates */
if (rrecord.uid == mrecord.uid) {
/* GUID mismatch on a non-expunged record is an error straight away */
if (!(mrecord.system_flags & FLAG_EXPUNGED)) {
if (!message_guid_equal(&mrecord.guid, &rrecord.guid)) {
syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u",
mailbox->name, mrecord.uid);
return IMAP_MAILBOX_CRC;
}
if (rrecord.system_flags & FLAG_EXPUNGED) {
syslog(LOG_ERR, "SYNCERROR: expunged on replica %s %u",
mailbox->name, mrecord.uid);
return IMAP_MAILBOX_CRC;
}
}
/* higher modseq on the replica is an error */
if (rrecord.modseq > mrecord.modseq) {
syslog(LOG_ERR, "SYNCERROR: higher modseq on replica %s %u",
mailbox->name, mrecord.uid);
return IMAP_MAILBOX_CRC;
}
/* skip out on the first pass */
if (!doupdate) continue;
rrecord.modseq = mrecord.modseq;
rrecord.last_updated = mrecord.last_updated;
rrecord.internaldate = mrecord.internaldate;
rrecord.system_flags = (mrecord.system_flags & ~FLAG_UNLINKED) |
(rrecord.system_flags & FLAG_UNLINKED);
for (i = 0; i < MAX_USER_FLAGS/32; i++)
rrecord.user_flags[i] = mrecord.user_flags[i];
rrecord.silent = 1;
r = mailbox_rewrite_index_record(mailbox, &rrecord);
if (r) {
syslog(LOG_ERR, "IOERROR: failed to rewrite record %s %d",
mailbox->name, recno);
return r;
}
}
/* not found and less than LAST_UID, bogus */
else if (mrecord.uid <= mailbox->i.last_uid) {
/* Expunged, just skip it */
if (!(mrecord.system_flags & FLAG_EXPUNGED))
return IMAP_MAILBOX_CRC;
}
/* after LAST_UID, it's an append, that's OK */
else {
/* skip out on the first pass */
if (!doupdate) continue;
mrecord.silent = 1;
r = sync_append_copyfile(mailbox, &mrecord);
if (r) {
syslog(LOG_ERR, "IOERROR: failed to append file %s %d",
mailbox->name, recno);
return r;
}
}
}
return 0;
}
static int do_mailbox(struct dlist *kin)
{
/* fields from the request */
const char *uniqueid;
const char *partition;
const char *mboxname;
uint32_t last_uid;
modseq_t highestmodseq;
uint32_t recentuid;
time_t recenttime;
time_t last_appenddate;
time_t pop3_last_login;
uint32_t uidvalidity;
const char *acl;
const char *options_str;
uint32_t sync_crc;
uint32_t options;
struct mailbox *mailbox = NULL;
uint32_t newcrc;
struct dlist *kr;
int r;
if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "PARTITION", &partition))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum(kin, "LAST_UID", &last_uid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getmodseq(kin, "HIGHESTMODSEQ", &highestmodseq))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum(kin, "RECENTUID", &recentuid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "RECENTTIME", &recenttime))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LAST_APPENDDATE", &last_appenddate))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "POP3_LAST_LOGIN", &pop3_last_login))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum(kin, "SYNC_CRC", &sync_crc))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum(kin, "UIDVALIDITY", &uidvalidity))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "ACL", &acl))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "OPTIONS", &options_str))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getlist(kin, "RECORD", &kr))
return IMAP_PROTOCOL_BAD_PARAMETERS;
options = sync_parse_options(options_str);
r = mailbox_open_iwl(mboxname, &mailbox);
if (r == IMAP_MAILBOX_NONEXISTENT) {
r = mboxlist_createsync(mboxname, 0, partition,
sync_userid, sync_authstate,
options, uidvalidity, acl,
uniqueid, &mailbox);
}
if (r) {
syslog(LOG_ERR, "Failed to open mailbox %s to update", mboxname);
return r;
}
if (strcmp(mailbox->uniqueid, uniqueid)) {
syslog(LOG_ERR, "Mailbox uniqueid changed %s - retry", mboxname);
mailbox_close(&mailbox);
return IMAP_MAILBOX_MOVED;
}
/* skip out now, it's going to mismatch for sure! */
if (highestmodseq < mailbox->i.highestmodseq) {
syslog(LOG_ERR, "higher modseq on replica %s - "
MODSEQ_FMT " < " MODSEQ_FMT,
mboxname, highestmodseq, mailbox->i.highestmodseq);
mailbox_close(&mailbox);
return IMAP_MAILBOX_CRC;
}
if (last_uid < mailbox->i.last_uid) {
syslog(LOG_ERR, "higher last_uid on replica %s - %u < %u",
mboxname, last_uid, mailbox->i.last_uid);
mailbox_close(&mailbox);
return IMAP_MAILBOX_CRC;
}
if (strcmp(mailbox->acl, acl)) {
mailbox_set_acl(mailbox, acl, 0);
r = mboxlist_sync_setacls(mboxname, acl);
if (r) {
mailbox_close(&mailbox);
return r;
}
}
r = mailbox_compare_update(mailbox, kr, 0);
if (r) {
mailbox_close(&mailbox);
return r;
}
/* now we're committed to writing something no matter what happens! */
r = mailbox_compare_update(mailbox, kr, 1);
if (r) {
abort();
return r;
}
mailbox_index_dirty(mailbox);
assert(mailbox->i.last_uid <= last_uid);
mailbox->i.last_uid = last_uid;
mailbox->i.highestmodseq = highestmodseq;
mailbox->i.recentuid = recentuid;
mailbox->i.recenttime = recenttime;
mailbox->i.last_appenddate = last_appenddate;
mailbox->i.pop3_last_login = pop3_last_login;
/* mailbox->i.options = options; ... not really, there's unsyncable stuff in here */
if (mailbox->i.uidvalidity < uidvalidity) {
syslog(LOG_ERR, "%s uidvalidity higher on master, updating %u => %u",
mailbox->name, mailbox->i.uidvalidity, uidvalidity);
mailbox->i.uidvalidity = uidvalidity;
}
/* try re-calculating the CRC on mismatch... */
if (mailbox->i.sync_crc != sync_crc) {
mailbox_index_recalc(mailbox);
}
newcrc = mailbox->i.sync_crc;
mailbox_close(&mailbox);
/* check return value */
if (r) return r;
if (newcrc != sync_crc)
return IMAP_MAILBOX_CRC;
return 0;
}
/* ====================================================================== */
static int getannotation_cb(const char *mailbox __attribute__((unused)),
const char *entry, const char *userid,
struct annotation_data *attrib,
void *rock)
{
const char *mboxname = (char *)rock;
struct dlist *kl;
kl = dlist_new("ANNOTATION");
dlist_atom(kl, "MBOXNAME", mboxname);
dlist_atom(kl, "ENTRY", entry);
dlist_atom(kl, "USERID", userid);
dlist_atom(kl, "VALUE", attrib->value);
sync_send_response(kl, sync_out);
dlist_free(&kl);
return 0;
}
static int do_getannotation(struct dlist *kin)
{
const char *mboxname = kin->sval;
return annotatemore_findall(mboxname, "*", &getannotation_cb,
(void *)mboxname, NULL);
}
static void print_quota(struct quota *q)
{
struct dlist *kl;
kl = dlist_new("QUOTA");
dlist_atom(kl, "ROOT", q->root);
dlist_num(kl, "LIMIT", q->limit);
sync_send_response(kl, sync_out);
dlist_free(&kl);
}
static int quota_work(const char *root)
{
struct quota q;
q.root = root;
if (!quota_read(&q, NULL, 0))
print_quota(&q);
return 0;
}
static int do_getquota(struct dlist *kin)
{
return quota_work(kin->sval);
}
static int mailbox_cb(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct sync_name_list *qrl = (struct sync_name_list *)rock;
struct mailbox *mailbox = NULL;
struct dlist *kl = dlist_kvlist(NULL, "MAILBOX");
int r;
r = mailbox_open_irl(name, &mailbox);
/* doesn't exist? Probably not finished creating or removing yet */
if (r == IMAP_MAILBOX_NONEXISTENT) return 0;
if (r == IMAP_MAILBOX_RESERVED) return 0;
if (r) return r;
if (qrl && mailbox->quotaroot &&
!sync_name_lookup(qrl, mailbox->quotaroot))
sync_name_list_add(qrl, mailbox->quotaroot);
r = sync_mailbox(mailbox, NULL, NULL, kl, NULL, 0);
if (!r) sync_send_response(kl, sync_out);
dlist_free(&kl);
mailbox_close(&mailbox);
return r;
}
static int do_getfullmailbox(struct dlist *kin)
{
struct mailbox *mailbox = NULL;
struct dlist *kl = dlist_kvlist(NULL, "MAILBOX");
int r;
r = mailbox_open_irl(kin->sval, &mailbox);
if (r) return r;
r = sync_mailbox(mailbox, NULL, NULL, kl, NULL, 1);
if (!r) sync_send_response(kl, sync_out);
dlist_free(&kl);
mailbox_close(&mailbox);
return r;
}
static int do_getmailboxes(struct dlist *kin)
{
struct dlist *ki;
for (ki = kin->head; ki; ki = ki->next)
mailbox_cb(ki->sval, 0, 0, NULL);
return 0;
}
/* ====================================================================== */
static int print_seen(const char *uniqueid, struct seendata *sd,
void *rock __attribute__((unused)))
{
struct dlist *kl;
kl = dlist_new("SEEN");
dlist_atom(kl, "UNIQUEID", uniqueid);
dlist_date(kl, "LASTREAD", sd->lastread);
dlist_num(kl, "LASTUID", sd->lastuid);
dlist_date(kl, "LASTCHANGE", sd->lastchange);
dlist_atom(kl, "SEENUIDS", sd->seenuids);
sync_send_response(kl, sync_out);
dlist_free(&kl);
return 0;
}
static int user_seen(const char *userid)
{
struct seen *seendb = NULL;
/* no SEEN DB is OK, just return */
if (seen_open(userid, SEEN_SILENT, &seendb))
return 0;
seen_foreach(seendb, print_seen, NULL);
seen_close(&seendb);
return 0;
}
static int user_sub(const char *userid)
{
struct sync_name_list *list = sync_name_list_create();
struct sync_name *item;
struct dlist *kl;
mboxlist_allsubs(userid, addmbox_sub, list);
kl = dlist_list(NULL, "LSUB");
for (item = list->head; item; item = item->next) {
dlist_atom(kl, "MBOXNAME", item->name);
}
if (kl->head)
sync_send_response(kl, sync_out);
dlist_free(&kl);
sync_name_list_free(&list);
return 0;
}
static int user_sieve(const char *userid)
{
struct sync_sieve_list *sieve_list;
struct sync_sieve *sieve;
struct dlist *kl;
sieve_list = sync_sieve_list_generate(userid);
if (!sieve_list) return 0;
for (sieve = sieve_list->head; sieve; sieve = sieve->next) {
kl = dlist_new("SIEVE");
dlist_atom(kl, "FILENAME", sieve->name);
dlist_date(kl, "LAST_UPDATE", sieve->last_update);
dlist_num(kl, "ISACTIVE", sieve->active ? 1 : 0);
sync_send_response(kl, sync_out);
dlist_free(&kl);
}
sync_sieve_list_free(&sieve_list);
return 0;
}
static int user_meta(const char *userid)
{
user_seen(userid);
user_sub(userid);
user_sieve(userid);
return 0;
}
static int do_getmeta(struct dlist *kin)
{
return user_meta(kin->sval);
}
static int do_getuser(struct dlist *kin)
{
char buf[MAX_MAILBOX_PATH];
int r;
struct sync_name_list *quotaroots;
struct sync_name *qr;
const char *userid = kin->sval;
quotaroots = sync_name_list_create();
/* inbox */
((*sync_namespacep).mboxname_tointernal)(sync_namespacep, "INBOX",
userid, buf);
r = mailbox_cb(buf, 0, 0, quotaroots);
if (r) goto bail;
/* deleted namespace items if enabled */
if (mboxlist_delayed_delete_isenabled()) {
char deletedname[MAX_MAILBOX_BUFFER];
mboxname_todeleted(buf, deletedname, 0);
strlcat(deletedname, ".*", sizeof(deletedname));
r = (sync_namespace.mboxlist_findall)(sync_namespacep, deletedname,
sync_userisadmin,
userid, sync_authstate,
mailbox_cb, quotaroots);
if (r) goto bail;
}
/* And then all folders */
strlcat(buf, ".*", sizeof(buf));
r = ((*sync_namespacep).mboxlist_findall)(sync_namespacep, buf,
sync_userisadmin,
userid, sync_authstate,
mailbox_cb, quotaroots);
if (r) goto bail;
for (qr = quotaroots->head; qr; qr = qr->next) {
r = quota_work(qr->name);
if (r) goto bail;
}
r = user_meta(userid);
if (r) goto bail;
sync_log_user(userid);
bail:
sync_name_list_free("aroots);
return r;
}
/* ====================================================================== */
static int do_unmailbox(struct dlist *kin)
{
const char *mboxname = kin->sval;
/* Delete with admin priveleges */
return mboxlist_deletemailbox(mboxname, sync_userisadmin, sync_userid,
sync_authstate, 0, 0, 1);
}
static int do_rename(struct dlist *kin)
{
const char *oldmboxname;
const char *newmboxname;
const char *partition;
if (!dlist_getatom(kin, "OLDMBOXNAME", &oldmboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "NEWMBOXNAME", &newmboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "PARTITION", &partition))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return mboxlist_renamemailbox(oldmboxname, newmboxname, partition,
1, sync_userid, sync_authstate, 1, 1);
}
static int do_changesub(struct dlist *kin)
{
const char *mboxname;
const char *userid;
int add;
/* SUB or UNSUB */
add = strcmp(kin->name, "SUB") ? 0 : 1;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return mboxlist_changesub(mboxname, userid, sync_authstate, add, add);
}
/* ====================================================================== */
static int do_annotation(struct dlist *kin)
{
struct entryattlist *entryatts = NULL;
struct attvaluelist *attvalues = NULL;
const char *mboxname = NULL;
const char *entry = NULL;
const char *value = NULL;
const char *userid = NULL;
char *name = NULL;
int r;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "ENTRY", &entry))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "VALUE", &value))
return IMAP_PROTOCOL_BAD_PARAMETERS;
/* annotatemore_store() expects external mailbox names,
so translate the separator character */
name = xstrdup(mboxname);
mboxname_hiersep_toexternal(sync_namespacep, name, 0);
appendattvalue(&attvalues, *userid ? "value.priv" : "value.shared", value);
appendentryatt(&entryatts, entry, attvalues);
r = annotatemore_store(name, entryatts, sync_namespacep,
sync_userisadmin, userid, sync_authstate);
freeentryatts(entryatts);
free(name);
return r;
}
static int do_unannotation(struct dlist *kin)
{
struct entryattlist *entryatts = NULL;
struct attvaluelist *attvalues = NULL;
const char *mboxname = NULL;
const char *entry = NULL;
const char *userid = NULL;
char *name = NULL;
int r;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "ENTRY", &entry))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
/* annotatemore_store() expects external mailbox names,
so translate the separator character */
name = xstrdup(mboxname);
mboxname_hiersep_toexternal(sync_namespacep, name, 0);
appendattvalue(&attvalues, *userid ? "value.priv" : "value.shared", NULL);
appendentryatt(&entryatts, entry, attvalues);
r = annotatemore_store(name, entryatts, sync_namespacep,
sync_userisadmin, userid, sync_authstate);
freeentryatts(entryatts);
free(name);
return r;
}
static int do_sieve(struct dlist *kin)
{
const char *userid;
const char *filename;
time_t last_update;
const char *content;
size_t len;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LAST_UPDATE", &last_update))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getbuf(kin, "CONTENT", &content, &len))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_upload(userid, filename, last_update, content, len);
}
static int do_unsieve(struct dlist *kin)
{
const char *userid;
const char *filename;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_delete(userid, filename);
}
static int do_activate_sieve(struct dlist *kin)
{
const char *userid;
const char *filename;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_activate(userid, filename);
}
static int do_unactivate_sieve(struct dlist *kin)
{
const char *userid;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
return sync_sieve_deactivate(userid);
}
static int do_seen(struct dlist *kin)
{
int r;
struct seen *seendb = NULL;
struct seendata sd = SEENDATA_INITIALIZER;
const char *seenuids;
const char *userid;
const char *uniqueid;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LASTREAD", &sd.lastread))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum(kin, "LASTUID", &sd.lastuid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getdate(kin, "LASTCHANGE", &sd.lastchange))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "SEENUIDS", &seenuids))
return IMAP_PROTOCOL_BAD_PARAMETERS;
sd.seenuids = xstrdup(seenuids);
r = seen_open(userid, SEEN_CREATE, &seendb);
if (r) return r;
r = seen_write(seendb, uniqueid, &sd);
seen_close(&seendb);
seen_freedata(&sd);
return r;
}
static int do_unuser(struct dlist *kin)
{
struct sync_name_list *list = sync_name_list_create();
struct sync_name *item;
const char *userid = kin->sval;
char buf[MAX_MAILBOX_NAME];
int r = 0;
/* Nuke subscriptions */
mboxlist_allsubs(userid, addmbox_sub, list);
/* ignore failures here - the subs file gets deleted soon anyway */
for (item = list->head; item; item = item->next) {
mboxlist_changesub(item->name, userid, sync_authstate, 0, 0);
}
sync_name_list_free(&list);
/* Nuke normal folders */
list = sync_name_list_create();
(sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX",
userid, buf);
strlcat(buf, ".*", sizeof(buf));
r = (sync_namespacep->mboxlist_findall)(sync_namespacep, buf,
sync_userisadmin,
sync_userid, sync_authstate,
addmbox, (void *)list);
if (r) goto fail;
for (item = list->head; item; item = item->next) {
r = mboxlist_deletemailbox(item->name, sync_userisadmin,
sync_userid, sync_authstate, 0, 0, 1);
if (r) goto fail;
}
/* Nuke inbox (recursive nuke possible?) */
(sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX",
userid, buf);
r = mboxlist_deletemailbox(buf, sync_userisadmin, sync_userid,
sync_authstate, 0, 0, 1);
if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail;
r = user_deletedata((char *)userid, sync_userid, sync_authstate, 1);
fail:
sync_name_list_free(&list);
return r;
}
/* ====================================================================== */
static int do_fetchsieve(struct dlist *kin)
{
struct dlist *kl;
const char *userid;
const char *filename;
uint32_t size;
char *sieve;
if (!dlist_getatom(kin, "USERID", &userid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "FILENAME", &filename))
return IMAP_PROTOCOL_BAD_PARAMETERS;
sieve = sync_sieve_read(userid, filename, &size);
if (!sieve)
return IMAP_MAILBOX_NONEXISTENT;
kl = dlist_new("SIEVE");
dlist_atom(kl, "USERID", userid);
dlist_atom(kl, "FILENAME", filename);
dlist_buf(kl, "CONTENT", sieve, size);
sync_send_response(kl, sync_out);
dlist_free(&kl);
return 0;
}
/* NOTE - can't lock a mailbox here, because it could deadlock,
* so just pick the file out from under the hood */
static int do_fetch(struct dlist *kin)
{
const char *mboxname;
const char *partition;
const char *guid;
uint32_t uid;
const char *fname;
struct dlist *kl;
struct message_guid tmp_guid;
struct stat sbuf;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "PARTITION", &partition))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "GUID", &guid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getnum(kin, "UID", &uid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!message_guid_decode(&tmp_guid, guid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
fname = mboxname_datapath(partition, mboxname, uid);
if (stat(fname, &sbuf) == -1)
return IMAP_MAILBOX_NONEXISTENT;
kl = dlist_file(NULL, "MESSAGE", partition, &tmp_guid, sbuf.st_size, fname);
sync_send_response(kl, sync_out);
dlist_free(&kl);
return 0;
}
static int do_expunge(struct dlist *kin)
{
const char *mboxname;
const char *uniqueid;
struct dlist *ul;
struct dlist *ui;
struct mailbox *mailbox = NULL;
struct index_record record;
uint32_t recno;
int r = 0;
if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
return IMAP_PROTOCOL_BAD_PARAMETERS;
if (!dlist_getlist(kin, "UID", &ul))
return IMAP_PROTOCOL_BAD_PARAMETERS;
r = mailbox_open_iwl(mboxname, &mailbox);
if (r) goto done;
/* don't want to expunge the wrong mailbox! */
if (strcmp(mailbox->uniqueid, uniqueid)) {
r = IMAP_MAILBOX_MOVED;
goto done;
}
ui = ul->head;
for (recno = 1; recno <= mailbox->i.num_records; recno++) {
r = mailbox_read_index_record(mailbox, recno, &record);
if (r) goto done;
if (record.system_flags & FLAG_EXPUNGED) continue;
while (ui && ui->nval < record.uid) ui = ui->next;
if (!ui) break; /* no point continuing */
if (record.uid == ui->nval) {
record.system_flags |= FLAG_EXPUNGED;
record.silent = 1; /* so the next sync will succeed */
r = mailbox_rewrite_index_record(mailbox, &record);
if (r) goto done;
}
}
done:
mailbox_close(&mailbox);
return r;
}
static int do_upload(struct dlist *kin, struct sync_reserve_list *reserve_list)
{
struct sync_msgid_list *part_list;
struct dlist *ki;
struct sync_msgid *msgid;
for (ki = kin->head; ki; ki = ki->next) {
if (ki->type != DL_FILE)
continue;
part_list = sync_reserve_partlist(reserve_list, ki->part);
msgid = sync_msgid_lookup(part_list, &ki->gval);
if (!msgid)
msgid = sync_msgid_add(part_list, &ki->gval);
if (!msgid->mark) {
msgid->mark = 1;
part_list->marked++;
}
}
return 0;
}
static void print_response(int r)
{
switch (r) {
case 0:
prot_printf(sync_out, "OK success\r\n");
break;
case IMAP_INVALID_USER:
prot_printf(sync_out, "NO IMAP_INVALID_USER No Such User\r\n");
break;
case IMAP_MAILBOX_NONEXISTENT:
prot_printf(sync_out, "NO IMAP_MAILBOX_NONEXISTENT No Such Mailbox\r\n");
break;
case IMAP_MAILBOX_CRC:
prot_printf(sync_out, "NO IMAP_MAILBOX_CRC Checksum Failure\r\n");
break;
case IMAP_PROTOCOL_ERROR:
prot_printf(sync_out, "NO IMAP_PROTOCOL_ERROR Protocol error\r\n");
break;
case IMAP_PROTOCOL_BAD_PARAMETERS:
prot_printf(sync_out, "NO IMAP_PROTOCOL_BAD_PARAMETERS near %s\r\n", dlist_lastkey());
break;
default:
prot_printf(sync_out, "NO %s\r\n", error_message(r));
}
}
static void cmd_apply(struct dlist *kin, struct sync_reserve_list *reserve_list)
{
int r;
if (!strcmp(kin->name, "MESSAGE"))
r = do_upload(kin, reserve_list);
else if (!strcmp(kin->name, "EXPUNGE"))
r = do_expunge(kin);
/* dump protocol */
else if (!strcmp(kin->name, "ACTIVATE_SIEVE"))
r = do_activate_sieve(kin);
else if (!strcmp(kin->name, "ANNOTATION"))
r = do_annotation(kin);
else if (!strcmp(kin->name, "MAILBOX"))
r = do_mailbox(kin);
else if (!strcmp(kin->name, "QUOTA"))
r = do_quota(kin);
else if (!strcmp(kin->name, "SEEN"))
r = do_seen(kin);
else if (!strcmp(kin->name, "RENAME"))
r = do_rename(kin);
else if (!strcmp(kin->name, "RESERVE"))
r = do_reserve(kin, reserve_list);
else if (!strcmp(kin->name, "SIEVE"))
r = do_sieve(kin);
else if (!strcmp(kin->name, "SUB"))
r = do_changesub(kin);
/* "un"dump protocol ;) */
else if (!strcmp(kin->name, "UNACTIVATE_SIEVE"))
r = do_unactivate_sieve(kin);
else if (!strcmp(kin->name, "UNANNOTATION"))
r = do_unannotation(kin);
else if (!strcmp(kin->name, "UNMAILBOX"))
r = do_unmailbox(kin);
else if (!strcmp(kin->name, "UNQUOTA"))
r = do_unquota(kin);
else if (!strcmp(kin->name, "UNSIEVE"))
r = do_unsieve(kin);
else if (!strcmp(kin->name, "UNSUB"))
r = do_changesub(kin);
/* user is a special case that's not paired, there's no "upload user"
* as such - we just call the individual commands with their items */
else if (!strcmp(kin->name, "UNUSER"))
r = do_unuser(kin);
else
r = IMAP_PROTOCOL_ERROR;
print_response(r);
}
static void cmd_get(struct dlist *kin)
{
int r;
if (!strcmp(kin->name, "ANNOTATION"))
r = do_getannotation(kin);
else if (!strcmp(kin->name, "FETCH"))
r = do_fetch(kin);
else if (!strcmp(kin->name, "FETCH_SIEVE"))
r = do_fetchsieve(kin);
else if (!strcmp(kin->name, "FULLMAILBOX"))
r = do_getfullmailbox(kin);
else if (!strcmp(kin->name, "MAILBOXES"))
r = do_getmailboxes(kin);
else if (!strcmp(kin->name, "META"))
r = do_getmeta(kin);
else if (!strcmp(kin->name, "QUOTA"))
r = do_getquota(kin);
else if (!strcmp(kin->name, "USER"))
r = do_getuser(kin);
else
r = IMAP_PROTOCOL_ERROR;
print_response(r);
}