/* sync_client.c -- Cyrus synchronization client
*
* 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.
*
* 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_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sysexits.h>
#include <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
#include <netinet/tcp.h>
#include "global.h"
#include "append.h"
#include "mboxlist.h"
#include "mailbox.h"
#include "quota.h"
#include "xmalloc.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imapd.h"
#include "imap_proxy.h"
#include "util.h"
#include "prot.h"
#include "message_guid.h"
#include "sync_log.h"
#include "sync_support.h"
#include "cyr_lock.h"
#include "backend.h"
#include "xstrlcat.h"
#include "signals.h"
#include "cyrusdb.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
/* ====================================================================== */
/* Static global variables and support routines for sync_client */
extern char *optarg;
extern int optind;
static const char *servername = NULL;
static struct backend *sync_backend = NULL;
static struct protstream *sync_out = NULL;
static struct protstream *sync_in = NULL;
static struct buf tagbuf = BUF_INITIALIZER;
static struct namespace sync_namespace;
static unsigned flags = 0;
static int verbose = 0;
static int verbose_logging = 0;
static int connect_once = 0;
static int background = 0;
static int do_compress = 0;
static int no_copyback = 0;
static char *prev_userid;
static void shut_down(int code) __attribute__((noreturn));
static void shut_down(int code)
{
in_shutdown = 1;
seen_done();
cyrus_done();
exit(code);
}
static int usage(const char *name, const char *message)
{
if (message)
fprintf(stderr, "%s\n\n", message);
fprintf(stderr,
"Usage: %s -S <servername> [-C <alt_config>] [-r] [-v] mailbox...\n", name);
exit(EX_USAGE);
}
EXPORTED void fatal(const char *s, int code)
{
fprintf(stderr, "Fatal error: %s\n", s);
syslog(LOG_ERR, "Fatal error: %s", s);
exit(code);
}
#define report_verbose(...) do { \
if (verbose) printf(__VA_ARGS__); \
if (verbose_logging) syslog(LOG_INFO, __VA_ARGS__); \
} while(0)
/* ====================================================================== */
static int do_unuser(const char *userid)
{
const char *cmd = "UNUSER";
struct mailbox *mailbox = NULL;
struct dlist *kl;
int r;
/* nothing to do if there's no userid */
if (!userid || !userid[0]) {
syslog(LOG_WARNING, "ignoring attempt to %s() without userid", __func__);
return 0;
}
/* check local mailbox first */
char *inbox = mboxname_user_mbox(userid, NULL);
r = mailbox_open_irl(inbox, &mailbox);
/* only remove from server if there's no local mailbox */
if (r == IMAP_MAILBOX_NONEXISTENT) {
kl = dlist_setatom(NULL, cmd, userid);
sync_send_apply(kl, sync_out);
dlist_free(&kl);
r = sync_parse_response(cmd, sync_in, NULL);
if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
}
mailbox_close(&mailbox);
free(inbox);
return r;
}
/* ====================================================================== */
static int user_sub(const char *userid, const char *mboxname)
{
int r;
r = mboxlist_checksub(mboxname, userid);
switch (r) {
case CYRUSDB_OK:
return sync_set_sub(userid, mboxname, 1, sync_backend, flags);
case CYRUSDB_NOTFOUND:
return sync_set_sub(userid, mboxname, 0, sync_backend, flags);
default:
return r;
}
}
/* ====================================================================== */
static int do_unmailbox(const char *mboxname, struct backend *sync_be,
unsigned flags)
{
struct mailbox *mailbox = NULL;
int r;
/* XXX - this should be looking for an explicit tombstone record locally */
r = mailbox_open_irl(mboxname, &mailbox);
if (r == IMAP_MAILBOX_NONEXISTENT) {
r = sync_folder_delete(mboxname, sync_be, flags);
if (r) {
syslog(LOG_ERR, "folder_delete(): failed: %s '%s'",
mboxname, error_message(r));
}
}
mailbox_close(&mailbox);
return r;
}
/* ====================================================================== */
static void remove_meta(char *user, struct sync_action_list *list)
{
struct sync_action *action;
for (action = list->head ; action ; action = action->next) {
if (!strcmp(user, action->user)) {
action->active = 0;
}
}
}
/* ====================================================================== */
static int do_sync_mailboxes(struct sync_name_list *mboxname_list,
struct sync_action_list *user_list,
const char **channelp,
unsigned flags)
{
struct sync_name *mbox;
int r = 0;
if (mboxname_list->count) {
r = sync_do_mailboxes(mboxname_list, NULL, sync_backend, channelp, flags);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
if (mbox->mark) continue;
sync_log_channel_mailbox(*channelp, mbox->name);
report_verbose(" Deferred: MAILBOX %s\n", mbox->name);
}
r = 0;
}
else if (r) {
/* promote failed personal mailboxes to USER */
int nonuser = 0;
for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
/* done OK? Good :) */
if (mbox->mark)
continue;
char *userid = mboxname_to_userid(mbox->name);
if (userid) {
mbox->mark = 1;
sync_action_list_add(user_list, NULL, userid);
report_verbose(" Promoting: MAILBOX %s -> USER %s\n",
mbox->name, userid);
free(userid);
}
else
nonuser = 1; /* there was a non-user mailbox */
}
if (!nonuser) r = 0;
}
}
return r;
}
static int do_restart()
{
sync_send_restart(sync_out);
return sync_parse_response("RESTART", sync_in, NULL);
}
/*
* channelp = NULL => we're not processing a channel
* *channelp = NULL => we're processing the default channel
* *channelp = "foo" => we're processing the channel named "foo"
*/
static int do_sync(sync_log_reader_t *slr, const char **channelp)
{
struct sync_action_list *user_list = sync_action_list_create();
struct sync_action_list *unuser_list = sync_action_list_create();
struct sync_action_list *meta_list = sync_action_list_create();
struct sync_action_list *mailbox_list = sync_action_list_create();
struct sync_action_list *unmailbox_list = sync_action_list_create();
struct sync_action_list *quota_list = sync_action_list_create();
struct sync_action_list *annot_list = sync_action_list_create();
struct sync_action_list *seen_list = sync_action_list_create();
struct sync_action_list *sub_list = sync_action_list_create();
struct sync_name_list *mboxname_list = sync_name_list_create();
const char *args[3];
struct sync_action *action;
int r = 0;
while (1) {
r = sync_log_reader_getitem(slr, args);
if (r == EOF) break;
if (!strcmp(args[0], "USER"))
sync_action_list_add(user_list, NULL, args[1]);
else if (!strcmp(args[0], "UNUSER"))
sync_action_list_add(unuser_list, NULL, args[1]);
else if (!strcmp(args[0], "META"))
sync_action_list_add(meta_list, NULL, args[1]);
else if (!strcmp(args[0], "SIEVE"))
sync_action_list_add(meta_list, NULL, args[1]);
else if (!strcmp(args[0], "APPEND")) /* just a mailbox event */
sync_action_list_add(mailbox_list, args[1], NULL);
else if (!strcmp(args[0], "MAILBOX"))
sync_action_list_add(mailbox_list, args[1], NULL);
else if (!strcmp(args[0], "UNMAILBOX"))
sync_action_list_add(unmailbox_list, args[1], NULL);
else if (!strcmp(args[0], "QUOTA"))
sync_action_list_add(quota_list, args[1], NULL);
else if (!strcmp(args[0], "ANNOTATION"))
sync_action_list_add(annot_list, args[1], NULL);
else if (!strcmp(args[0], "SEEN"))
sync_action_list_add(seen_list, args[2], args[1]);
else if (!strcmp(args[0], "SUB"))
sync_action_list_add(sub_list, args[2], args[1]);
else if (!strcmp(args[0], "UNSUB"))
sync_action_list_add(sub_list, args[2], args[1]);
else
syslog(LOG_ERR, "Unknown action type: %s", args[0]);
}
/* Optimise out redundant clauses */
for (action = user_list->head; action; action = action->next) {
/* remove per-user items */
remove_meta(action->user, meta_list);
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
}
/* duplicate removal for unuser - we also strip all the user events */
for (action = unuser_list->head; action; action = action->next) {
/* remove per-user items */
remove_meta(action->user, meta_list);
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
/* unuser trumps user */
remove_meta(action->user, user_list);
}
for (action = meta_list->head; action; action = action->next) {
/* META action overrides any user SEEN or SUB/UNSUB action
for same user */
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
}
/* And then run tasks. */
for (action = quota_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_quota(action->name, sync_backend, flags);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_quota(*channelp, action->name);
report_verbose(" Deferred: QUOTA %s\n", action->name);
}
else if (r) {
/* XXX - bogus handling, should be user */
sync_action_list_add(mailbox_list, action->name, NULL);
report_verbose(" Promoting: QUOTA %s -> MAILBOX %s\n",
action->name, action->name);
}
}
for (action = annot_list->head; action; action = action->next) {
if (!action->active)
continue;
/* NOTE: ANNOTATION "" is a special case - it's a server
* annotation, hence the check for a character at the
* start of the name */
r = sync_do_annotation(action->name, sync_backend, flags);
if (!*action->name) continue;
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_annotation(*channelp, action->name);
report_verbose(" Deferred: ANNOTATION %s\n", action->name);
}
else if (r) {
/* XXX - bogus handling, should be ... er, something */
sync_action_list_add(mailbox_list, action->name, NULL);
report_verbose(" Promoting: ANNOTATION %s -> MAILBOX %s\n",
action->name, action->name);
}
}
for (action = seen_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_seen(action->user, action->name, sync_backend, flags);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_seen(*channelp, action->user, action->name);
report_verbose(" Deferred: SEEN %s %s\n",
action->user, action->name);
}
else if (r) {
char *userid = mboxname_to_userid(action->name);
if (userid && mboxname_isusermailbox(action->name, 1) && !strcmp(userid, action->user)) {
sync_action_list_add(user_list, NULL, action->user);
report_verbose(" Promoting: SEEN %s %s -> USER %s\n",
action->user, action->name, action->user);
} else {
sync_action_list_add(meta_list, NULL, action->user);
report_verbose(" Promoting: SEEN %s %s -> META %s\n",
action->user, action->name, action->user);
}
free(userid);
}
}
for (action = sub_list->head; action; action = action->next) {
if (!action->active)
continue;
r = user_sub(action->user, action->name);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_subscribe(*channelp, action->user, action->name);
report_verbose(" Deferred: SUB %s %s\n",
action->user, action->name);
}
else if (r) {
sync_action_list_add(meta_list, NULL, action->user);
report_verbose(" Promoting: SUB %s %s -> META %s\n",
action->user, action->name, action->user);
}
}
/* XXX - need to process this in sets of users at a time, such that we never split
* on a user boundary. This will require a data structure change to carry the list
* of mailboxes in per-user sets */
for (action = mailbox_list->head; action; action = action->next) {
if (!action->active)
continue;
sync_name_list_add(mboxname_list, action->name);
/* only do up to 1000 mailboxes at a time */
if (mboxname_list->count > 1000) {
syslog(LOG_NOTICE, "sync_mailboxes: doing 1000");
r = do_sync_mailboxes(mboxname_list, user_list, channelp, flags);
if (r) goto cleanup;
r = do_restart();
if (r) goto cleanup;
sync_name_list_free(&mboxname_list);
mboxname_list = sync_name_list_create();
}
}
r = do_sync_mailboxes(mboxname_list, user_list, channelp, flags);
if (r) goto cleanup;
r = do_restart();
if (r) goto cleanup;
/* XXX - is unmailbox used much anyway - we need to see if it's logged for a rename,
* e.g.
* RENAME A B:
* MAILBOX A
* MAILBOX B
* UNMAILBOX A
*
* suggestion: PROMOTE ALL UNMAILBOX on user accounts to USER foo
*/
for (action = unmailbox_list->head; action; action = action->next) {
if (!action->active)
continue;
r = do_unmailbox(action->name, sync_backend, flags);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_unmailbox(*channelp, action->name);
report_verbose(" Deferred: UNMAILBOX %s\n", action->name);
}
else if (r) goto cleanup;
}
for (action = meta_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_meta(action->user, sync_backend, flags);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_sieve(*channelp, action->user);
report_verbose(" Deferred: META %s\n", action->user);
}
else if (r == IMAP_INVALID_USER) {
goto cleanup;
}
else if (r) {
sync_action_list_add(user_list, NULL, action->user);
report_verbose(" Promoting: META %s -> USER %s\n",
action->user, action->user);
}
}
for (action = user_list->head; action; action = action->next) {
if (!action->active)
continue;
r = sync_do_user(action->user, NULL, sync_backend, channelp, flags);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_user(*channelp, action->user);
report_verbose(" Deferred: USER %s\n", action->user);
}
else if (r) goto cleanup;
r = do_restart();
if (r) goto cleanup;
}
for (action = unuser_list->head; action; action = action->next) {
if (!action->active)
continue;
r = do_unuser(action->user);
if (channelp && r == IMAP_MAILBOX_LOCKED) {
sync_log_channel_unuser(*channelp, action->user);
report_verbose(" Deferred: UNUSER %s\n", action->user);
}
else if (r) goto cleanup;
}
cleanup:
if (r) {
if (verbose)
fprintf(stderr, "Error in do_sync(): bailing out! %s\n", error_message(r));
syslog(LOG_ERR, "Error in do_sync(): bailing out! %s", error_message(r));
}
sync_action_list_free(&user_list);
sync_action_list_free(&unuser_list);
sync_action_list_free(&meta_list);
sync_action_list_free(&mailbox_list);
sync_action_list_free(&unmailbox_list);
sync_action_list_free("a_list);
sync_action_list_free(&annot_list);
sync_action_list_free(&seen_list);
sync_action_list_free(&sub_list);
sync_name_list_free(&mboxname_list);
return r;
}
static int do_sync_filename(const char *filename)
{
sync_log_reader_t *slr;
int r;
if ((filename == NULL) || !strcmp(filename, "-"))
slr = sync_log_reader_create_with_fd(0); /* STDIN */
else
slr = sync_log_reader_create_with_filename(filename);
r = sync_log_reader_begin(slr);
if (!r)
r = do_sync(slr, NULL);
sync_log_reader_end(slr);
sync_log_reader_free(slr);
return r;
}
/* ====================================================================== */
enum {
RESTART_NONE = 0,
RESTART_NORMAL,
RESTART_RECONNECT
};
static int do_daemon_work(const char *channel, const char *sync_shutdown_file,
unsigned long timeout, unsigned long min_delta,
int *restartp)
{
int r = 0;
time_t session_start;
time_t single_start;
int delta;
struct stat sbuf;
sync_log_reader_t *slr;
*restartp = RESTART_NONE;
slr = sync_log_reader_create_with_channel(channel);
session_start = time(NULL);
while (1) {
single_start = time(NULL);
signals_poll();
/* Check for shutdown file */
if (sync_shutdown_file && !stat(sync_shutdown_file, &sbuf)) {
unlink(sync_shutdown_file);
/* Have to exit with r == 0 or do_daemon() will call us again.
* The value of r is unknown from calls to sync_log_reader_begin() below.
*/
r = 0;
break;
}
/* See if its time to RESTART */
if ((timeout > 0) &&
((single_start - session_start) > (time_t) timeout)) {
*restartp = RESTART_NORMAL;
break;
}
r = sync_log_reader_begin(slr);
if (r) {
/* including specifically r == IMAP_AGAIN */
if (min_delta > 0) {
sleep(min_delta);
} else {
usleep(100000); /* 1/10th second */
}
continue;
}
/* Process the work log */
if ((r=do_sync(slr, &channel))) {
syslog(LOG_ERR,
"Processing sync log file %s failed: %s",
sync_log_reader_get_file_name(slr), error_message(r));
break;
}
r = sync_log_reader_end(slr);
if (r) break;
delta = time(NULL) - single_start;
if (((unsigned) delta < min_delta) && ((min_delta-delta) > 0))
sleep(min_delta-delta);
}
sync_log_reader_free(slr);
if (*restartp == RESTART_NORMAL) {
r = do_restart();
if (r) {
syslog(LOG_ERR, "sync_client RESTART failed: %s",
error_message(r));
} else {
syslog(LOG_INFO, "sync_client RESTART succeeded");
}
r = 0;
}
return(r);
}
static void replica_connect(const char *channel)
{
int wait;
sasl_callback_t *cb;
int timeout;
const char *port, *auth_status = NULL;
int try_imap;
cb = mysasl_callbacks(NULL,
sync_get_config(channel, "sync_authname"),
sync_get_config(channel, "sync_realm"),
sync_get_config(channel, "sync_password"));
/* get the right port */
port = sync_get_config(channel, "sync_port");
if (port) {
imap_csync_protocol.service = port;
csync_protocol.service = port;
}
try_imap = sync_get_switchconfig(channel, "sync_try_imap");
for (wait = 15;; wait *= 2) {
if (try_imap) {
sync_backend = backend_connect(sync_backend, servername,
&imap_csync_protocol, "", cb, &auth_status,
(verbose > 1 ? fileno(stderr) : -1));
if (sync_backend) {
if (sync_backend->capability & CAPA_REPLICATION) {
/* attach our IMAP tag buffer to our protstreams as userdata */
sync_backend->in->userdata = sync_backend->out->userdata = &tagbuf;
break;
}
else {
backend_disconnect(sync_backend);
sync_backend = NULL;
}
}
}
sync_backend = backend_connect(sync_backend, servername,
&csync_protocol, "", cb, NULL,
(verbose > 1 ? fileno(stderr) : -1));
if (sync_backend || auth_status || connect_once || wait > 1000) break;
fprintf(stderr,
"Can not connect to server '%s', retrying in %d seconds\n",
servername, wait);
sleep(wait);
}
free_callbacks(cb);
cb = NULL;
if (!sync_backend) {
fprintf(stderr, "Can not connect to server '%s'\n",
servername);
syslog(LOG_ERR, "Can not connect to server '%s'", servername);
_exit(1);
}
if (servername[0] != '/' && sync_backend->sock >= 0) {
tcp_disable_nagle(sync_backend->sock);
tcp_enable_keepalive(sync_backend->sock);
}
#ifdef HAVE_ZLIB
/* Does the backend support compression? */
if (CAPA(sync_backend, CAPA_COMPRESS)) {
prot_printf(sync_backend->out, "%s\r\n",
sync_backend->prot->u.std.compress_cmd.cmd);
prot_flush(sync_backend->out);
if (sync_parse_response("COMPRESS", sync_backend->in, NULL)) {
if (do_compress) fatal("Failed to enable compression, aborting", EX_SOFTWARE);
syslog(LOG_NOTICE, "Failed to enable compression, continuing uncompressed");
}
else {
prot_setcompress(sync_backend->in);
prot_setcompress(sync_backend->out);
}
}
else if (do_compress) fatal("Backend does not support compression, aborting", EX_SOFTWARE);
#endif
/* links to sockets */
sync_in = sync_backend->in;
sync_out = sync_backend->out;
if (verbose > 1) {
prot_setlog(sync_in, fileno(stderr));
prot_setlog(sync_out, fileno(stderr));
}
/* Set inactivity timer */
timeout = config_getduration(IMAPOPT_SYNC_TIMEOUT, 's');
if (timeout < 3) timeout = 3;
prot_settimeout(sync_in, timeout);
/* Force use of LITERAL+ so we don't need two way communications */
prot_setisclient(sync_in, 1);
prot_setisclient(sync_out, 1);
}
static void replica_disconnect(void)
{
backend_disconnect(sync_backend);
}
static void do_daemon(const char *channel, const char *sync_shutdown_file,
unsigned long timeout, unsigned long min_delta)
{
int r = 0;
int restart = 1;
signal(SIGPIPE, SIG_IGN); /* don't fail on server disconnects */
while (restart) {
replica_connect(channel);
r = do_daemon_work(channel, sync_shutdown_file,
timeout, min_delta, &restart);
if (r) {
/* See if we're still connected to the server.
* If we are, we had some type of error, so we exit.
* Otherwise, try reconnecting.
*/
if (!backend_ping(sync_backend, NULL)) restart = 1;
}
replica_disconnect();
}
}
static int do_mailbox(const char *mboxname, const char **channelp, unsigned flags)
{
struct sync_name_list *list = sync_name_list_create();
int r;
sync_name_list_add(list, mboxname);
r = sync_do_mailboxes(list, NULL, sync_backend, channelp, flags);
sync_name_list_free(&list);
return r;
}
static int cb_allmbox(const mbentry_t *mbentry, void *rock)
{
int r = 0;
const char **channelp = (const char **)rock;
char *userid = mboxname_to_userid(mbentry->name);
if (userid) {
/* skip deleted mailboxes only because the are out of order, and you would
* otherwise have to sync the user twice thanks to our naive logic */
if (mboxname_isdeletedmailbox(mbentry->name, NULL))
goto done;
/* only sync if we haven't just done the user */
if (strcmpsafe(userid, prev_userid)) {
r = sync_do_user(userid, NULL, sync_backend, channelp, flags);
if (r) {
if (verbose)
fprintf(stderr, "Error from do_user(%s): bailing out!\n", userid);
syslog(LOG_ERR, "Error in do_user(%s): bailing out!", userid);
goto done;
}
free(prev_userid);
prev_userid = xstrdup(userid);
}
}
else {
/* all shared mailboxes, including DELETED ones, sync alone */
/* XXX: batch in hundreds? */
r = do_mailbox(mbentry->name, channelp, flags);
if (r) {
if (verbose)
fprintf(stderr, "Error from do_user(%s): bailing out!\n", mbentry->name);
syslog(LOG_ERR, "Error in do_user(%s): bailing out!", mbentry->name);
goto done;
}
}
done:
free(userid);
return r;
}
/* ====================================================================== */
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, (mysasl_cb_ft *) &mysasl_config, NULL },
{ SASL_CB_CANON_USER, (mysasl_cb_ft *) &mysasl_canon_user, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
enum {
MODE_UNKNOWN = -1,
MODE_REPEAT,
MODE_USER,
MODE_ALLUSER,
MODE_MAILBOX,
MODE_META
};
int main(int argc, char **argv)
{
int opt, i = 0;
char *alt_config = NULL;
char *input_filename = NULL;
int r = 0;
int exit_rc = 0;
int mode = MODE_UNKNOWN;
int wait = 0;
int timeout = 600;
int min_delta = 0;
const char *channel = NULL;
const char *sync_shutdown_file = NULL;
const char *partition = NULL;
char buf[512];
FILE *file;
int len;
struct sync_name_list *mboxname_list;
setbuf(stdout, NULL);
while ((opt = getopt(argc, argv, "C:vlLS:F:f:w:t:d:n:rRumsozOAp:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'o': /* only try to connect once */
connect_once = 1;
break;
case 'v': /* verbose */
verbose++;
break;
case 'l': /* verbose Logging */
verbose_logging++;
break;
case 'L': /* local mailbox operations only */
flags |= SYNC_FLAG_LOCALONLY;
break;
case 'S': /* Socket descriptor for server */
servername = optarg;
break;
case 'F': /* Shutdown file */
sync_shutdown_file = optarg;
break;
case 'f': /* input_filename used by user and mailbox modes; OR
alternate sync_log_file used by single-run repeat mode */
input_filename = optarg;
break;
case 'n':
channel = optarg;
break;
case 'w':
wait = atoi(optarg);
break;
case 't':
timeout = atoi(optarg);
break;
case 'd':
min_delta = atoi(optarg);
break;
case 'r':
background = 1;
/* fallthrough */
case 'R':
if (mode != MODE_UNKNOWN)
usage("sync_client", "Mutually exclusive options defined");
mode = MODE_REPEAT;
break;
case 'A':
if (mode != MODE_UNKNOWN)
usage("sync_client", "Mutually exclusive options defined");
mode = MODE_ALLUSER;
break;
case 'u':
if (mode != MODE_UNKNOWN)
usage("sync_client", "Mutually exclusive options defined");
mode = MODE_USER;
break;
case 'm':
if (mode != MODE_UNKNOWN)
usage("sync_client", "Mutually exclusive options defined");
mode = MODE_MAILBOX;
break;
case 's':
if (mode != MODE_UNKNOWN)
usage("sync_client", "Mutually exclusive options defined");
mode = MODE_META;
break;
case 'z':
#ifdef HAVE_ZLIB
do_compress = 1;
#else
do_compress = 0;
fatal("Compress not available without zlib compiled in", EX_SOFTWARE);
#endif
break;
case 'O':
/* don't copy changes back from server */
no_copyback = 1;
break;
case 'p':
partition = optarg;
break;
default:
usage("sync_client", NULL);
}
}
if (mode == MODE_UNKNOWN)
usage("sync_client", "No replication mode specified");
if (verbose) flags |= SYNC_FLAG_VERBOSE;
if (verbose_logging) flags |= SYNC_FLAG_LOGGING;
if (no_copyback) flags |= SYNC_FLAG_NO_COPYBACK;
/* fork if required */
if (background && !input_filename && !getenv("CYRUS_ISDAEMON")) {
int pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid != 0) { /* parent */
exit(0);
}
}
cyrus_init(alt_config, "sync_client",
(verbose > 1 ? CYRUSINIT_PERROR : 0),
CONFIG_NEED_PARTITION_DATA);
/* get the server name if not specified */
if (!servername)
servername = sync_get_config(channel, "sync_host");
if (!servername)
fatal("sync_host not defined", EX_SOFTWARE);
/* Just to help with debugging, so we have time to attach debugger */
if (wait > 0) {
fprintf(stderr, "Waiting for %d seconds for gdb attach...\n", wait);
sleep(wait);
}
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&sync_namespace, 1)) != 0) {
fatal(error_message(r), EX_CONFIG);
}
mboxevent_setnamespace(&sync_namespace);
signals_set_shutdown(&shut_down);
signals_add_handlers(0);
/* load the SASL plugins */
global_sasl_init(1, 0, mysasl_cb);
switch (mode) {
case MODE_USER:
/* Open up connection to server */
replica_connect(channel);
if (input_filename) {
if ((file=fopen(input_filename, "r")) == NULL) {
syslog(LOG_NOTICE, "Unable to open %s: %m", input_filename);
shut_down(1);
}
while (fgets(buf, sizeof(buf), file)) {
/* Chomp, then ignore empty/comment lines. */
if (((len=strlen(buf)) > 0) && (buf[len-1] == '\n'))
buf[--len] = '\0';
if ((len == 0) || (buf[0] == '#'))
continue;
if (sync_do_user(buf, partition, sync_backend, NULL, flags)) {
if (verbose)
fprintf(stderr,
"Error from sync_do_user(%s): bailing out!\n",
buf);
syslog(LOG_ERR, "Error in sync_do_user(%s): bailing out!",
buf);
exit_rc = 1;
}
}
fclose(file);
} else for (i = optind; !r && i < argc; i++) {
if (sync_do_user(argv[i], partition, sync_backend, NULL, flags)) {
if (verbose)
fprintf(stderr, "Error from sync_do_user(%s): bailing out!\n",
argv[i]);
syslog(LOG_ERR, "Error in sync_do_user(%s): bailing out!", argv[i]);
exit_rc = 1;
}
}
replica_disconnect();
break;
case MODE_ALLUSER:
/* Open up connection to server */
replica_connect(channel);
if (mboxlist_allmbox(optind < argc ? argv[optind] : NULL, cb_allmbox, NULL, 0))
exit_rc = 1;
replica_disconnect();
break;
case MODE_MAILBOX:
/* Open up connection to server */
replica_connect(channel);
mboxname_list = sync_name_list_create();
if (input_filename) {
if ((file=fopen(input_filename, "r")) == NULL) {
syslog(LOG_NOTICE, "Unable to open %s: %m", input_filename);
shut_down(1);
}
while (fgets(buf, sizeof(buf), file)) {
/* Chomp, then ignore empty/comment lines. */
if (((len=strlen(buf)) > 0) && (buf[len-1] == '\n'))
buf[--len] = '\0';
if ((len == 0) || (buf[0] == '#'))
continue;
char *intname = mboxname_from_external(buf, &sync_namespace, NULL);
if (!sync_name_lookup(mboxname_list, intname))
sync_name_list_add(mboxname_list, intname);
free(intname);
}
fclose(file);
} else for (i = optind; i < argc; i++) {
char *intname = mboxname_from_external(argv[i], &sync_namespace, NULL);
if (!sync_name_lookup(mboxname_list, intname))
sync_name_list_add(mboxname_list, intname);
free(intname);
}
if (sync_do_mailboxes(mboxname_list, partition, sync_backend, NULL, flags)) {
if (verbose) {
fprintf(stderr,
"Error from sync_do_mailboxes(): bailing out!\n");
}
syslog(LOG_ERR, "Error in sync_do_mailboxes(): bailing out!");
exit_rc = 1;
}
sync_name_list_free(&mboxname_list);
replica_disconnect();
break;
case MODE_META:
/* Open up connection to server */
replica_connect(channel);
for (i = optind; i < argc; i++) {
if (sync_do_meta(argv[i], sync_backend, flags)) {
if (verbose) {
fprintf(stderr,
"Error from sync_do_meta(%s): bailing out!\n",
argv[i]);
}
syslog(LOG_ERR, "Error in sync_do_meta(%s): bailing out!",
argv[i]);
exit_rc = 1;
}
}
replica_disconnect();
break;
case MODE_REPEAT:
if (input_filename) {
/* Open up connection to server */
replica_connect(channel);
exit_rc = do_sync_filename(input_filename);
replica_disconnect();
}
else {
/* rolling replication */
if (!sync_shutdown_file)
sync_shutdown_file = sync_get_config(channel, "sync_shutdown_file");
if (!min_delta)
min_delta = sync_get_durationconfig(channel, "sync_repeat_interval", 's');
do_daemon(channel, sync_shutdown_file, timeout, min_delta);
}
break;
default:
if (verbose) fprintf(stderr, "Nothing to do!\n");
break;
}
buf_free(&tagbuf);
shut_down(exit_rc);
}