Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
443 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/imap/arbitron.c b/imap/arbitron.c
index ddb489470..99dc01875 100644
--- a/imap/arbitron.c
+++ b/imap/arbitron.c
@@ -1,538 +1,540 @@
/* arbitron.c -- program to report readership statistics
*
* Copyright (c) 1998, 2000 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: arbitron.c,v 1.42 2007/02/05 18:49:55 jeaton Exp $ */
+/* $Id: arbitron.c,v 1.43 2007/03/30 18:40:20 murch Exp $ */
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include "assert.h"
#include "global.h"
#include "exitcodes.h"
#include "hash.h"
#include "imap_err.h"
#include "mailbox.h"
#include "mpool.h"
#include "mboxlist.h"
#include "convert_code.h"
#include "seen.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
/* config.c stuff */
const int config_need_data = 0;
#define DB (config_seenstate_db)
#define SUBDB (config_subscription_db)
extern int optind;
extern char *optarg;
/* Maintain the mailbox list */
/* xxx it'd be nice to generate a subscriber list too */
struct user_list {
const char *user;
struct user_list *next;
};
struct arb_mailbox_data {
int nreaders;
struct user_list *readers;
int nsubscribers;
struct user_list *subscribers;
};
struct mpool *arb_pool;
hash_table mailbox_table, mboxname_table;
time_t report_start_time = -1, report_end_time, prune_time = 0;
int code = 0;
int dosubs = 1;
int dousers = 0;
int long_report = 0;
/* current namespace */
static struct namespace arb_namespace;
/* forward declarations */
void usage(void);
void run_users(void);
void make_report(char *key, void *data, void *rock);
void process_seen(const char *path, const char *user);
void process_subs(const char *path, const char *user);
int do_mailbox(const char *name, int matchlen, int maycreate, void *rock);
int main(int argc,char **argv)
{
int opt, r;
int report_days = 30;
int prune_months = 0;
char pattern[MAX_MAILBOX_NAME+1];
char *alt_config = NULL;
time_t now = time(0);
strcpy(pattern, "*");
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
report_end_time = now;
while ((opt = getopt(argc, argv, "C:oud:D:p:l")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'd':
if (report_start_time != -1) usage();
report_days = atoi(optarg);
if (report_days <= 0) usage();
break;
case 'D': {
unsigned month, day, year;
struct tm date;
if (strlen(optarg) < 8 ||
sscanf(optarg, "%02u%02u%04u", &month, &day, &year) < 3) {
usage();
}
memset(&date, 0, sizeof(struct tm));
date.tm_mon = month - 1;
date.tm_mday = day;
date.tm_year = year - 1900;
report_start_time = mktime(&date);
if (optarg[8] == ':' && strlen(optarg+9) == 8) {
if (sscanf(optarg+9, "%02u%02u%04u", &month, &day, &year) < 3) {
usage();
}
memset(&date, 0, sizeof(struct tm));
date.tm_mon = month - 1;
date.tm_mday = day;
date.tm_year = year - 1900;
report_end_time = mktime(&date);
}
break;
}
case 'o':
dosubs = 0;
break;
case 'u':
dousers = 1;
break;
case 'p':
prune_months = atoi(optarg);
if (prune_months <= 0) usage();
break;
case 'l':
long_report = dousers = 1;
break;
default:
usage();
}
}
/* Init Cyrus Backend Foo */
cyrus_init(alt_config, "arbitron", 0);
mboxlist_init(0);
mboxlist_open(NULL);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&arb_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
if (optind != argc) strlcpy(pattern, argv[optind], sizeof(pattern));
if (report_start_time == -1) {
report_start_time = now - (report_days*60*60*24);
}
if (prune_months) {
prune_time = now - (prune_months*60*60*24*31);
}
/* Allocate our shared memory pools */
arb_pool = new_mpool(0);
construct_hash_table(&mailbox_table, 2047, 1);
construct_hash_table(&mboxname_table, 2047, 1);
/* Translate any separators in mailboxname */
mboxname_hiersep_tointernal(&arb_namespace, pattern, 0);
/* Get the mailbox list */
fprintf(stderr, "Loading Mailboxes...");
(*arb_namespace.mboxlist_findall)(&arb_namespace, pattern, 1, 0, 0,
do_mailbox, NULL);
fprintf(stderr, "Done\nLoading Users");
/* Now do all the users */
run_users();
fprintf(stderr, "Done\n");
/* And print the report */
hash_enumerate(&mboxname_table, make_report, NULL);
/* Free Resources */
free_hash_table(&mailbox_table, NULL);
free_hash_table(&mboxname_table, NULL);
free_mpool(arb_pool);
mboxlist_close();
mboxlist_done();
cyrus_done();
return code;
}
void usage(void)
{
fprintf(stderr,
"usage: arbitron [-o] [-u] [-l] [-C alt_config] "
"[-d days | -D mmddyyy[:mmddyyyy]]\n"
" [-p months] [mboxpattern]\n");
exit(EC_USAGE);
}
int do_mailbox(const char *name, int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock __attribute__((unused)))
{
int r;
struct mailbox mbox;
r = mailbox_open_header(name, NULL, &mbox);
if(!r) {
struct arb_mailbox_data *d = mpool_malloc(arb_pool,
sizeof(struct arb_mailbox_data));
d->nreaders = 0;
d->nsubscribers = 0;
d->readers = NULL;
d->subscribers = NULL;
/* printf("inserting %s (key %s)\n", name, mbox.uniqueid); */
hash_insert(mbox.uniqueid, d, &mailbox_table);
hash_insert(name, d, &mboxname_table);
mailbox_close(&mbox);
}
return 0;
}
void run_users()
{
char prefix[MAX_MAILBOX_PATH+1],path[MAX_MAILBOX_PATH+1],
file[MAX_MAILBOX_PATH+1];
DIR *dirp, *dirq;
struct dirent *dirent1, *dirent2;
snprintf(prefix, sizeof(prefix), "%s%s", config_dir, FNAME_USERDIR);
dirp = opendir(prefix);
if(!dirp) {
fatal("can't open user directory", EC_SOFTWARE);
}
while((dirent1 = readdir(dirp)) != NULL) {
if(!strcmp(dirent1->d_name, ".") || !strcmp(dirent1->d_name,"..")) {
continue;
}
snprintf(path, sizeof(path), "%s%s", prefix, dirent1->d_name);
/* printf("trying %s\n",path); */
dirq = opendir(path);
if(dirq) {
fprintf(stderr, ".");
while(dirq && ((dirent2 = readdir(dirq)) != NULL)) {
size_t len;
if(!strcmp(dirent2->d_name, ".") ||
!strcmp(dirent2->d_name,"..")) {
continue;
}
len = strlen(dirent2->d_name);
/* 5 is magic number for strlen(".seen") and
4 is the magic number for strlen(".sub") */
if(len > 4) {
char *user = NULL;
snprintf(file, sizeof(file),
"%s/%s", path, dirent2->d_name);
/* printf("got file %s\n",file); */
if(len > 5 &&
!strcmp(dirent2->d_name + len - 5, ".seen")) {
if (dousers) {
user = mpool_strndup(arb_pool, dirent2->d_name,
len-5);
}
process_seen(file, user);
} else if (dosubs &&
!strcmp(dirent2->d_name + len - 4, ".sub")) {
if (dousers) {
user = mpool_strndup(arb_pool, dirent2->d_name,
len-4);
}
process_subs(file, user);
}
}
}
closedir(dirq);
}
}
closedir(dirp);
}
static int process_user_cb(void *rockp,
const char *key, int keylen,
const char *tmpdata __attribute__((unused)),
int tmpdatalen __attribute__((unused)))
{
/* Only called to do deletes */
/* printf("pruning entry\n"); */
DB->delete((struct db *)rockp, key, keylen, NULL, 0);
return 0;
}
/* We can cheat and do all we need to in this function */
static int process_user_p(void *rockp,
const char *key,
int keylen,
const char *data,
int datalen __attribute__((unused)))
{
int ret = 0;
long version, lastread;
char *p;
char buf[64];
struct arb_mailbox_data *mbox;
const char *user = (const char *) rockp;
/* remember that 'data' may not be null terminated ! */
version = strtol(data, &p, 10); data = p;
/* xxx not checking version */
lastread = strtol(data, &p, 10); data = p;
memcpy(buf, key, keylen);
buf[keylen] = '\0';
mbox = hash_lookup(buf, &mailbox_table);
if(mbox && lastread >= report_start_time &&
lastread <= report_end_time) {
/* printf("got %s\n", mbox->name); */
mbox->nreaders++;
if (user) {
struct user_list *u = mpool_malloc(arb_pool,
sizeof(struct user_list));
u->user = user;
u->next = mbox->readers;
mbox->readers = u;
}
}
/* Check for pruning even if mailbox isn't valid */
if(lastread < prune_time) {
ret = 1;
}
/* Only return true if we need to prune this guy */
return ret;
}
void process_seen(const char *path, const char *user)
{
int r;
struct db *tmp = NULL;
r = DB->open(path, 0, &tmp);
if(r) goto done;
DB->foreach(tmp, "", 0, process_user_p, process_user_cb,
(void *) user, NULL);
done:
if(tmp) DB->close(tmp);
}
static int process_subs_cb(void *rockp __attribute__((unused)),
const char *key __attribute__((unused)),
int keylen __attribute__((unused)),
const char *tmpdata __attribute__((unused)),
int tmpdatalen __attribute__((unused)))
{
return 0;
}
static int process_subs_p(void *rockp,
const char *key, int keylen,
const char *tmpdata __attribute__((unused)),
int tmpdatalen __attribute__((unused)))
{
struct arb_mailbox_data *mbox;
char buf[MAX_MAILBOX_NAME+1];
const char *user = (const char *) rockp;
memcpy(buf, key, keylen);
buf[keylen] = '\0';
/* printf("lookup %s\n", buf); */
mbox = hash_lookup(buf, &mboxname_table);
if(mbox) {
/* printf("got sub %s\n", buf); */
mbox->nsubscribers++;
if (user) {
struct user_list *u = mpool_malloc(arb_pool,
sizeof(struct user_list));
u->user = user;
u->next = mbox->subscribers;
mbox->subscribers = u;
}
}
return 0; /* never do callback */
}
void process_subs(const char *path, const char *user)
{
int r;
struct db *tmp = NULL;
r = SUBDB->open(path, 0, &tmp);
if(r) goto done;
SUBDB->foreach(tmp, "", 0, process_subs_p, process_subs_cb,
(void *) user, NULL);
done:
if(tmp) SUBDB->close(tmp);
}
void report_users(struct user_list *u)
{
char sep = ':';
while (u) {
printf("%c%s", sep, u->user);
sep = ',';
u = u->next;
}
}
void long_report_users(struct user_list *u, const char *mbox, char type)
{
char buf[100];
struct tm *tm;
while (u) {
printf("%s,%s,%c,", mbox, u->user, type);
tm = localtime(&report_start_time);
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", tm);
printf("%s,", buf);
tm = localtime(&report_end_time);
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", tm);
printf("%s\n", buf);
u = u->next;
}
}
void make_report(char *key, void *data, void *rock __attribute__((unused)))
{
struct arb_mailbox_data *mbox = (struct arb_mailbox_data *)data;
/* Skip underread user mailboxes */
if(!strncasecmp(key, "user.", 5) && mbox->nreaders <= 1)
return;
mboxname_hiersep_toexternal(&arb_namespace, key, 0);
if (long_report) {
long_report_users(mbox->readers, key, 'r');
long_report_users(mbox->subscribers, key, 's');
}
else {
printf("%s %d", key, mbox->nreaders);
if (dousers) report_users(mbox->readers);
if (dosubs) {
printf(" %d", mbox->nsubscribers);
if (dousers) report_users(mbox->subscribers);
}
printf("\n");
}
}
diff --git a/imap/ctl_cyrusdb.c b/imap/ctl_cyrusdb.c
index 65d811b0b..3b47fcbae 100644
--- a/imap/ctl_cyrusdb.c
+++ b/imap/ctl_cyrusdb.c
@@ -1,341 +1,343 @@
/* ctl_cyrusdb.c -- Program to perform operations common to all cyrus DBs
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
- * $Id: ctl_cyrusdb.c,v 1.28 2006/11/30 17:11:17 murch Exp $
+ * $Id: ctl_cyrusdb.c,v 1.29 2007/03/30 18:40:20 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "annotate.h"
#include "cyrusdb.h"
#include "duplicate.h"
#include "global.h"
#include "exitcodes.h"
#include "libcyr_cfg.h"
#include "mboxlist.h"
#include "seen.h"
#include "tls.h"
#include "util.h"
#include "xmalloc.h"
#define N(a) (sizeof(a) / sizeof(a[0]))
/* config.c stuff */
const int config_need_data = 0;
struct cyrusdb {
const char *name;
struct cyrusdb_backend **env;
int archive;
} dblist[] = {
{ FNAME_MBOXLIST, &config_mboxlist_db, 1 },
{ FNAME_QUOTADB, &config_quota_db, 1 },
{ FNAME_ANNOTATIONS, &config_annotation_db, 1 },
{ FNAME_DELIVERDB, &config_duplicate_db, 0 },
{ FNAME_TLSSESSIONS, &config_tlscache_db, 0 },
{ FNAME_PTSDB, &config_ptscache_db, 0 },
{ NULL, NULL, 0 }
};
static int compdb(const void *v1, const void *v2)
{
struct cyrusdb *db1 = (struct cyrusdb *) v1;
struct cyrusdb *db2 = (struct cyrusdb *) v2;
return (db1->env - db2->env);
}
void usage(void)
{
fprintf(stderr, "ctl_cyrusdb [-C <altconfig>] -c\n");
fprintf(stderr, "ctl_cyrusdb [-C <altconfig>] -r [-x]\n");
exit(-1);
}
/* Callback for use by recover_reserved */
static int fixmbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock __attribute__((unused)))
{
int mbtype;
int r;
/* Do an mboxlist_detail on the mailbox */
r = mboxlist_detail(name, &mbtype, NULL, NULL, NULL, NULL, NULL);
/* if it is MBTYPE_RESERVED, unset it & call mboxlist_delete */
if(!r && (mbtype & MBTYPE_RESERVE)) {
if(!r) {
r = mboxlist_deletemailbox(name, 1, NULL, NULL, 0, 0, 1);
if(r) {
/* log the error */
syslog(LOG_ERR,
"could not remove reserved mailbox '%s': %s",
name, error_message(r));
} else {
syslog(LOG_ERR,
"removed reserved mailbox '%s'",
name);
}
}
}
return 0;
}
void recover_reserved()
{
char pattern[2] = { '*', '\0' };
mboxlist_init(0);
mboxlist_open(NULL);
/* Need annotations.db for mboxlist_deletemailbox() */
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
/* build a list of mailboxes - we're using internal names here */
mboxlist_findall(NULL, pattern, 1, NULL,
NULL, fixmbox, NULL);
annotatemore_close();
annotatemore_done();
mboxlist_close();
mboxlist_done();
}
int main(int argc, char *argv[])
{
extern char *optarg;
int opt, r, r2;
char *alt_config = NULL;
int reserve_flag = 1;
enum { RECOVER, CHECKPOINT, NONE } op = NONE;
char dirname[1024], backup1[1024], backup2[1024];
char *archive_files[N(dblist)];
char *msg = "";
int i, j, rotated = 0;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
r = r2 = 0;
while ((opt = getopt(argc, argv, "C:rxc")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'r':
libcyrus_config_setint(CYRUSOPT_DB_INIT_FLAGS, CYRUSDB_RECOVER);
msg = "recovering cyrus databases";
if (op == NONE) op = RECOVER;
else usage();
break;
case 'c':
msg = "checkpointing cyrus databases";
if (op == NONE) op = CHECKPOINT;
else usage();
break;
case 'x':
reserve_flag = 0;
break;
default:
usage();
break;
}
}
if (op == NONE || (op != RECOVER && !reserve_flag)) {
usage();
/* NOTREACHED */
}
cyrus_init(alt_config, "ctl_cyrusdb", 0);
/* create the name of the db directory */
/* (used by backup directory names) */
strcpy(dirname, config_dir);
strcat(dirname, FNAME_DBDIR);
/* create the names of the backup directories */
strcpy(backup1, dirname);
strcat(backup1, ".backup1");
strcpy(backup2, dirname);
strcat(backup2, ".backup2");
syslog(LOG_NOTICE, "%s", msg);
/* sort dbenvs */
qsort(dblist, N(dblist)-1, sizeof(struct cyrusdb), &compdb);
memset(archive_files, 0, N(dblist) * sizeof(char*));
for (i = 0, j = 0; dblist[i].name != NULL; i++) {
/* if we need to archive this db, add it to the list */
if (dblist[i].archive) {
archive_files[j] = (char*) xmalloc(strlen(config_dir) +
strlen(dblist[i].name) + 1);
strcpy(archive_files[j], config_dir);
strcat(archive_files[j++], dblist[i].name);
}
/* deal with each dbenv once */
if (dblist[i].env == dblist[i+1].env) continue;
r = r2 = 0;
switch (op) {
case RECOVER:
break;
case CHECKPOINT:
r2 = (*(dblist[i].env))->sync();
if (r2) {
syslog(LOG_ERR, "DBERROR: sync %s: %s", dirname,
cyrusdb_strerror(r2));
fprintf(stderr,
"ctl_cyrusdb: unable to sync environment\n");
}
/* ARCHIVE */
r2 = 0;
if (!rotated) {
/* rotate the backup directories -- ONE time only */
char *tail;
DIR *dirp;
struct dirent *dirent;
tail = backup2 + strlen(backup2);
/* remove db.backup2 */
dirp = opendir(backup2);
strcat(tail++, "/");
if (dirp) {
while ((dirent = readdir(dirp)) != NULL) {
if (dirent->d_name[0] == '.') continue;
strcpy(tail, dirent->d_name);
unlink(backup2);
}
closedir(dirp);
}
tail[-1] = '\0';
r2 = rmdir(backup2);
/* move db.backup1 to db.backup2 */
if (r2 == 0 || errno == ENOENT)
r2 = rename(backup1, backup2);
/* make a new db.backup1 */
if (r2 == 0 || errno == ENOENT)
r2 = mkdir(backup1, 0755);
rotated = 1;
}
/* do the archive */
if (r2 == 0)
r2 = (*(dblist[i].env))->archive((const char**) archive_files,
backup1);
if (r2) {
syslog(LOG_ERR, "DBERROR: archive %s: %s", dirname,
cyrusdb_strerror(r2));
fprintf(stderr,
"ctl_cyrusdb: unable to archive environment\n");
}
break;
default:
break;
}
/* free the archive_list */
while (j > 0) {
free(archive_files[--j]);
archive_files[j] = NULL;
}
}
if(op == RECOVER && reserve_flag)
recover_reserved();
cyrus_done();
syslog(LOG_NOTICE, "done %s", msg);
exit(r || r2);
}
diff --git a/imap/ctl_deliver.c b/imap/ctl_deliver.c
index 84c17ca44..e62fa13c6 100644
--- a/imap/ctl_deliver.c
+++ b/imap/ctl_deliver.c
@@ -1,162 +1,164 @@
/* ctl_deliver.c -- Program to perform operations on duplicate delivery db
- * $Id: ctl_deliver.c,v 1.21 2006/11/30 17:11:17 murch Exp $
+ * $Id: ctl_deliver.c,v 1.22 2007/03/30 18:40:20 murch Exp $
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include "cyrusdb.h"
#include "duplicate.h"
#include "exitcodes.h"
#include "global.h"
#include "libcyr_cfg.h"
#include "util.h"
#include "xmalloc.h"
/* global state */
const int config_need_data = 0;
void usage(void)
{
fprintf(stderr,
"ctl_deliver [-C <altconfig>] -d [-f <dbfile>]\n");
exit(-1);
}
int main(int argc, char *argv[])
{
extern char *optarg;
int opt, r = 0;
char *alt_file = NULL;
char *alt_config = NULL;
char *days = NULL;
int flag = 0;
enum { DUMP, PRUNE, NONE } op = NONE;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:drE:f:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'd':
if (op == NONE) op = DUMP;
else usage();
break;
case 'f':
if (alt_file == NULL) alt_file = optarg;
else usage ();
break;
case 'E':
if (op == NONE) op = PRUNE;
else usage();
/* deprecated, but we still support it */
days = optarg;
break;
default:
usage();
break;
}
}
switch (op) {
case PRUNE: {
char buf[4096];
fprintf(stderr, "ctl_deliver -E is deprecated: "
"using cyr_expire -E instead\n");
r = snprintf(buf, sizeof(buf), "%s/cyr_expire", SERVICE_PATH);
if(r < 0 || r >= sizeof(buf)) {
fatal("cyr_expire command buffer not sufficiently big", EC_CONFIG);
}
if (alt_config)
execl(buf, buf, "-C", alt_config, "-E", days, NULL);
else
execl(buf, buf, "-E", days, NULL);
break;
}
case DUMP:
cyrus_init(alt_config, "ctl_deliver", 0);
if (duplicate_init(alt_file, flag) != 0) {
fprintf(stderr,
"ctl_deliver: unable to init duplicate delivery database\n");
exit(1);
}
printf("it is NOW: %d\n", (int) time(NULL));
printf("got %d entries\n", duplicate_dump(stdout));
r = 0;
duplicate_done();
cyrus_done();
break;
case NONE:
r = 2;
usage();
break;
}
return r;
}
diff --git a/imap/ctl_mboxlist.c b/imap/ctl_mboxlist.c
index bb80b822b..33c37eb31 100644
--- a/imap/ctl_mboxlist.c
+++ b/imap/ctl_mboxlist.c
@@ -1,754 +1,756 @@
/* ctl_mboxlist.c -- do DB related operations on mboxlist
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
-/* $Id: ctl_mboxlist.c,v 1.54 2007/03/06 13:56:00 murch Exp $ */
+/* $Id: ctl_mboxlist.c,v 1.55 2007/03/30 18:40:20 murch Exp $ */
/* currently doesn't catch signals; probably SHOULD */
#include <config.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#include <sasl/sasl.h>
#include "assert.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "global.h"
#include "libcyr_cfg.h"
#include "mboxlist.h"
#include "mupdate.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "sync_log.h"
/* config.c stuff */
const int config_need_data = 0;
extern int optind;
extern char *optarg;
const int PER_COMMIT = 1000;
enum mboxop { DUMP,
M_POPULATE,
RECOVER,
CHECKPOINT,
UNDUMP,
NONE };
struct dumprock {
enum mboxop op;
struct txn *tid;
const char *partition;
int purge;
mupdate_handle *h;
};
struct mb_node
{
char mailbox[MAX_MAILBOX_NAME+1];
char server[MAX_MAILBOX_NAME+1];
char *acl;
struct mb_node *next;
};
static struct mb_node *act_head = NULL, **act_tail = &act_head;
static struct mb_node *del_head = NULL;
static struct mb_node *wipe_head = NULL, *unflag_head = NULL;
/* assume the local copy is authoritative and that it should just overwrite
* mupdate */
static int local_authoritative = 0;
static int warn_only = 0;
/* For each mailbox that this guy gets called for, check that
* it is a mailbox that:
* a) mupdate server thinks *we* host
* -> Because we were called, this is the case, provided we
* -> gave the prefix parameter to the remote.
* b) we do not actually host
*
* if that's the case, enqueue a delete
* otherwise, we both agree that it exists, but we still need
* to verify that its info is up to date.
*/
static int mupdate_list_cb(struct mupdate_mailboxdata *mdata,
const char *cmd,
void *context __attribute__((unused)))
{
int ret;
/* the server thinks we have it, do we think we have it? */
ret = mboxlist_lookup(mdata->mailbox, NULL, NULL);
if(ret) {
struct mb_node *next;
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, mdata->mailbox, sizeof(next->mailbox));
next->next = del_head;
del_head = next;
} else {
/* we both agree that it exists */
/* throw it onto the back of the activate queue */
/* we may or may not need to send an update */
struct mb_node *next;
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, mdata->mailbox, sizeof(next->mailbox));
strlcpy(next->server, mdata->server, sizeof(next->server));
if(!strncmp(cmd, "MAILBOX", 7))
next->acl = xstrdup(mdata->acl);
*act_tail = next;
act_tail = &(next->next);
}
return 0;
}
static int dump_cb(void *rockp,
const char *key, int keylen,
const char *data, int datalen)
{
struct dumprock *d = (struct dumprock *) rockp;
int r;
char *p;
char *name, *part, *acl;
int mbtype;
/* \0 terminate 'name' */
name = xstrndup(key, keylen);
/* Get mailbox type */
mbtype = strtol(data, &p, 10);
p = strchr(data, ' ');
if (p == NULL) {
abort();
}
p++;
acl = strchr(p, ' ');
if (acl == NULL) {
abort();
}
/* grab 'part', \0 terminate */
part = xstrndup(p, acl - p);
/* \0 terminate 'acl' */
p = acl + 1;
acl = xstrndup(p, datalen - (p - data));
switch (d->op) {
case DUMP:
if(!d->partition || !strcmp(d->partition, part)) {
printf("%s\t%d %s\t%s\n", name, mbtype, part, acl);
if(d->purge) {
config_mboxlist_db->delete(mbdb, key, keylen, &(d->tid), 0);
}
}
break;
case M_POPULATE:
{
char *realpart = xmalloc(strlen(config_servername) + 1
+ strlen(part) + 1);
int skip_flag;
/* If it is marked MBTYPE_MOVING, and it DOES match the entry,
* we need to unmark it. If it does not match the entry in our
* list, then we assume that it successfully made the move and
* we delete it from the local disk */
/* realpart is 'hostname!partition' */
sprintf(realpart, "%s!%s", config_servername, part);
/* If they match, then we should check that we actually need
* to update it. If they *don't* match, then we believe that we
* need to send fresh data. There will be no point at which something
* is in the act_head list that we do not have locally, because that
* is a condition of being in the act_head list */
if(act_head && !strcmp(name, act_head->mailbox)) {
struct mb_node *tmp;
/* If this mailbox was moving, we want to unmark the movingness,
* since the MUPDATE server agreed that it lives here. */
/* (and later also force an mupdate push) */
if(mbtype & MBTYPE_MOVING) {
struct mb_node *next;
if(warn_only) {
printf("Remove remote flag on: %s\n", name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, name, sizeof(next->mailbox));
next->next = unflag_head;
unflag_head = next;
}
/* No need to update mupdate NOW, we'll get it when we
* untag the mailbox */
skip_flag = 1;
} else if(act_head->acl &&
!strcmp(realpart, act_head->server) &&
!strcmp(acl, act_head->acl)) {
/* Do not update if location does match, and there is an acl,
* and the acl matches */
skip_flag = 1;
} else {
skip_flag = 0;
}
/* in any case, free the node. */
if(act_head->acl) free(act_head->acl);
tmp = act_head;
act_head = act_head->next;
free(tmp);
} else {
/* if they do not match, do an explicit MUPDATE find on the
* mailbox, and if it is living somewhere else, delete the local
* data, if it is NOT living somewhere else, recreate it in
* mupdate */
struct mupdate_mailboxdata *unused_mbdata;
/* if this is okay, we found it (so it is on another host, since
* it wasn't in our list in this position) */
if(!local_authoritative &&
!mupdate_find(d->h, name, &unused_mbdata)) {
/* since it lives on another server, schedule it for a wipe */
struct mb_node *next;
if (config_mupdate_config !=
IMAP_ENUM_MUPDATE_CONFIG_UNIFIED) {
/* But not for a unified configuration */
if(warn_only) {
printf("Remove Local Mailbox: %s\n", name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, name, sizeof(next->mailbox));
next->next = wipe_head;
wipe_head = next;
}
}
skip_flag = 1;
} else {
/* Check that it isn't flagged moving */
if(mbtype & MBTYPE_MOVING) {
/* it's flagged moving, we'll fix it later (and
* push it then too) */
struct mb_node *next;
if(warn_only) {
printf("Remove remote flag on: %s\n", name);
} else {
next = xzmalloc(sizeof(struct mb_node));
strlcpy(next->mailbox, name, sizeof(next->mailbox));
next->next = unflag_head;
unflag_head = next;
}
/* No need to update mupdate now, we'll get it when we
* untag the mailbox */
skip_flag = 1;
} else {
/* we should just push the change to mupdate now */
skip_flag = 0;
}
}
}
if(skip_flag) {
free(realpart);
break;
}
if(warn_only) {
printf("Force Activate: %s\n", name);
free(realpart);
break;
}
r = mupdate_activate(d->h,name,realpart,acl);
free(realpart);
if(r == MUPDATE_NOCONN) {
fprintf(stderr, "permanant failure storing '%s'\n", name);
return IMAP_IOERROR;
} else if (r == MUPDATE_FAIL) {
fprintf(stderr,
"temporary failure storing '%s' (update continuing)",
name);
}
break;
}
default: /* yikes ! */
abort();
break;
}
free(name);
free(part);
free(acl);
return 0;
}
/* Resyncing with mupdate:
*
* If it is local and not present on mupdate at all, push to mupdate.
* If it is local and present on mupdate for another host, delete local mailbox
* If it is local and present on mupdate but with incorrect partition/acl,
* update mupdate.
* If it is not local and present on mupdate for this host, delete it from
* mupdate.
*/
void do_dump(enum mboxop op, const char *part, int purge)
{
struct dumprock d;
int ret;
char buf[8192];
assert(op == DUMP || op == M_POPULATE);
assert(op == DUMP || !purge);
assert(op == DUMP || !part);
d.op = op;
d.partition = part;
d.purge = purge;
d.tid = NULL;
if(op == M_POPULATE) {
ret = mupdate_connect(NULL, NULL, &(d.h), NULL);
if(ret) {
fprintf(stderr, "couldn't connect to mupdate server\n");
exit(1);
}
/* now we need a list of what the remote thinks we have
* To generate it, ask for a prefix of '<our hostname>!',
* (to ensure we get exactly our hostname) */
snprintf(buf, sizeof(buf), "%s!", config_servername);
ret = mupdate_list(d.h, mupdate_list_cb, buf, NULL);
if(ret) {
fprintf(stderr, "couldn't do LIST command on mupdate server\n");
exit(1);
}
/* Run pending mupdate deletes */
while(del_head) {
struct mb_node *me = del_head;
del_head = del_head->next;
if(warn_only) {
printf("Remove from MUPDATE: %s\n", me->mailbox);
} else {
ret = mupdate_delete(d.h, me->mailbox);
if(ret) {
fprintf(stderr,
"couldn't mupdate delete %s\n", me->mailbox);
exit(1);
}
}
free(me);
}
}
/* Dump Database */
config_mboxlist_db->foreach(mbdb, "", 0, NULL, &dump_cb, &d, NULL);
if(d.tid) {
config_mboxlist_db->commit(mbdb, d.tid);
d.tid = NULL;
}
if(op == M_POPULATE) {
/* Remove MBTYPE_MOVING flags (unflag_head) */
while(unflag_head) {
struct mb_node *me = unflag_head;
int type;
char *part, *acl, *newpart;
unflag_head = unflag_head->next;
ret = mboxlist_detail(me->mailbox, &type, NULL, NULL,
&part, &acl, NULL);
if(ret) {
fprintf(stderr,
"couldn't perform lookup to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
/* Reset the partition! */
newpart = strchr(part, '!');
if(!newpart) newpart = part;
else newpart++;
ret = mboxlist_update(me->mailbox, type & ~MBTYPE_MOVING,
newpart, acl, 1);
if(ret) {
fprintf(stderr,
"couldn't perform update to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
/* force a push to mupdate */
snprintf(buf, sizeof(buf), "%s!%s", config_servername, part);
ret = mupdate_activate(d.h, me->mailbox, buf, acl);
if(ret) {
fprintf(stderr,
"couldn't perform mupdatepush to un-remote-flag %s\n",
me->mailbox);
exit(1);
}
free(me);
}
/* Delete local mailboxes where needed (wipe_head) */
while(wipe_head) {
struct mb_node *me = wipe_head;
wipe_head = wipe_head->next;
ret = mboxlist_deletemailbox(me->mailbox, 1, "", NULL, 0, 1, 1);
if(!ret) sync_log_mailbox(me->mailbox);
if(ret) {
fprintf(stderr, "couldn't delete defunct mailbox %s\n",
me->mailbox);
exit(1);
}
free(me);
}
/* Done with mupdate */
mupdate_disconnect(&(d.h));
sasl_done();
}
return;
}
void do_undump(void)
{
int r = 0;
char buf[16384];
int line = 0;
char last_commit[MAX_MAILBOX_NAME];
char *key=NULL, *data=NULL;
int keylen, datalen;
int untilCommit = PER_COMMIT;
struct txn *tid = NULL;
last_commit[0] = '\0';
while (fgets(buf, sizeof(buf), stdin)) {
char *name, *partition, *acl;
char *p;
int mbtype = 0, tries = 0;
line++;
name = buf;
for (p = buf; *p && *p != '\t'; p++) ;
if (!*p) {
fprintf(stderr, "line %d: no partition found\n", line);
continue;
}
*p++ = '\0';
if (isdigit((int) *p)) {
/* new style dump */
mbtype = strtol(p, &p, 10);
/* skip trailing space */
if (*p == ' ') p++;
}
partition = p;
for (; *p && *p != '\t'; p++) ;
if (!*p) {
fprintf(stderr, "line %d: no acl found\n", line);
continue;
}
*p++ = '\0';
acl = p;
/* chop off the newline */
for (; *p && *p != '\r' && *p != '\n'; p++) ;
*p++ = '\0';
if (strlen(name) > MAX_MAILBOX_NAME) {
fprintf(stderr, "line %d: mailbox name too long\n", line);
continue;
}
if (strlen(partition) >= MAX_PARTITION_LEN) {
fprintf(stderr, "line %d: partition name too long\n", line);
continue;
}
key = name; keylen = strlen(key);
data = mboxlist_makeentry(mbtype, partition, acl); datalen = strlen(data);
tries = 0;
retry:
r = config_mboxlist_db->store(mbdb, key, keylen, data, datalen, &tid);
switch (r) {
case 0:
break;
case CYRUSDB_AGAIN:
if (tries++ < 5) {
fprintf(stderr, "warning: DB_LOCK_DEADLOCK; retrying\n");
goto retry;
}
fprintf(stderr, "error: too many deadlocks, aborting\n");
break;
default:
r = IMAP_IOERROR;
break;
}
free(data);
if(--untilCommit == 0) {
/* commit */
r = config_mboxlist_db->commit(mbdb, tid);
if(r) break;
tid = NULL;
untilCommit = PER_COMMIT;
strlcpy(last_commit,key,sizeof(last_commit));
}
if (r) break;
}
if(!r && tid) {
/* commit the last transaction */
r=config_mboxlist_db->commit(mbdb, tid);
}
if (r) {
if(tid) config_mboxlist_db->abort(mbdb, tid);
fprintf(stderr, "db error: %s\n", cyrusdb_strerror(r));
if(key) fprintf(stderr, "was processing mailbox: %s\n", key);
if(last_commit[0]) fprintf(stderr, "last commit was at: %s\n",
last_commit);
else fprintf(stderr, "no commits\n");
}
return;
}
void usage(void)
{
fprintf(stderr, "DUMP:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -d [-x] [-f filename] [-p partition]\n");
fprintf(stderr, "UNDUMP:\n");
fprintf(stderr,
" ctl_mboxlist [-C <alt_config>] -u [-f filename]"
" [< mboxlist.dump]\n");
fprintf(stderr, "MUPDATE populate:\n");
fprintf(stderr, " ctl_mboxlist [-C <alt_config>] -m [-a] [-w] [-f filename]\n");
exit(1);
}
int main(int argc, char *argv[])
{
const char *partition = NULL;
char *mboxdb_fname = NULL;
int dopurge = 0;
int opt;
enum mboxop op = NONE;
char *alt_config = NULL;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:awmdurcxf:p:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'r':
/* deprecated, but we still support it */
fprintf(stderr, "ctl_mboxlist -r is deprecated: "
"use ctl_cyrusdb -r instead\n");
syslog(LOG_WARNING, "ctl_mboxlist -r is deprecated: "
"use ctl_cyrusdb -r instead");
if (op == NONE) op = RECOVER;
else usage();
break;
case 'c':
/* deprecated, but we still support it */
fprintf(stderr, "ctl_mboxlist -c is deprecated: "
"use ctl_cyrusdb -c instead\n");
syslog(LOG_WARNING, "ctl_mboxlist -c is deprecated: "
"use ctl_cyrusdb -c instead");
if (op == NONE) op = CHECKPOINT;
else usage();
break;
case 'f':
if (!mboxdb_fname) {
mboxdb_fname = optarg;
} else {
usage();
}
break;
case 'd':
if (op == NONE) op = DUMP;
else usage();
break;
case 'u':
if (op == NONE) op = UNDUMP;
else usage();
break;
case 'm':
if (op == NONE) op = M_POPULATE;
else usage();
break;
case 'p':
partition = optarg;
break;
case 'x':
dopurge = 1;
break;
case 'a':
local_authoritative = 1;
break;
case 'w':
warn_only = 1;
break;
default:
usage();
break;
}
}
if(op != M_POPULATE && (local_authoritative || warn_only)) usage();
if(op != DUMP && partition) usage();
if(op != DUMP && dopurge) usage();
if(op == RECOVER) {
syslog(LOG_NOTICE, "running mboxlist recovery");
libcyrus_config_setint(CYRUSOPT_DB_INIT_FLAGS, CYRUSDB_RECOVER);
}
cyrus_init(alt_config, "ctl_mboxlist", 0);
global_sasl_init(1,0,NULL);
switch (op) {
case RECOVER:
/* this was done by the call to cyrus_init via libcyrus */
syslog(LOG_NOTICE, "done running mboxlist recovery");
break;
case CHECKPOINT:
syslog(LOG_NOTICE, "checkpointing mboxlist");
mboxlist_init(MBOXLIST_SYNC);
mboxlist_done();
break;
case DUMP:
case M_POPULATE:
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
quotadb_init(0);
quotadb_open(NULL);
do_dump(op, partition, dopurge);
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
break;
case UNDUMP:
mboxlist_init(0);
mboxlist_open(mboxdb_fname);
quotadb_init(0);
quotadb_open(NULL);
do_undump();
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
break;
default:
usage();
cyrus_done();
return 1;
}
cyrus_done();
return 0;
}
diff --git a/imap/cyr_expire.c b/imap/cyr_expire.c
index 009cb424d..201baef32 100644
--- a/imap/cyr_expire.c
+++ b/imap/cyr_expire.c
@@ -1,372 +1,374 @@
/* cyr_expire.c -- Program to expire deliver.db entries and messages
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: cyr_expire.c,v 1.10 2007/02/05 18:41:46 jeaton Exp $
+ * $Id: cyr_expire.c,v 1.11 2007/03/30 18:40:20 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include "annotate.h"
#include "cyrusdb.h"
#include "duplicate.h"
#include "exitcodes.h"
#include "global.h"
#include "hash.h"
#include "libcyr_cfg.h"
#include "mboxlist.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
/* global state */
const int config_need_data = 0;
void usage(void)
{
fprintf(stderr,
"cyr_expire [-C <altconfig>] -E <days> [-X <expunge-days>] [-v]\n");
exit(-1);
}
struct expire_rock {
struct hash_table *table;
enum enum_value expunge_mode;
time_t expire_mark;
time_t expunge_mark;
unsigned long mailboxes;
unsigned long messages;
unsigned long deleted;
int verbose;
};
/*
* mailbox_expunge() callback to expunge expired articles.
*/
static int expire_cb(struct mailbox *mailbox __attribute__((unused)),
void *rock, char *index, int expunge_flags)
{
struct expire_rock *erock = (struct expire_rock *) rock;
bit32 tstamp;
erock->messages++;
/* if we're cleaning up expunge, delete by expunge time */
if (expunge_flags & EXPUNGE_CLEANUP) {
tstamp = ntohl(*((bit32 *)(index+OFFSET_LAST_UPDATED)));
if (tstamp < erock->expunge_mark) {
erock->deleted++;
return 1;
}
} else {
/* otherwise, we're expiring messages by sent date */
tstamp = ntohl(*((bit32 *)(index+OFFSET_SENTDATE)));
if (tstamp < erock->expire_mark) {
erock->deleted++;
return 1;
}
}
return 0;
}
/*
* mboxlist_findall() callback function to:
* - expire messages from mailboxes,
* - build a hash table of mailboxes in which we expired messages,
* - and perform a cleanup of expunged messages
*/
int expire(char *name, int matchlen, int maycreate __attribute__((unused)),
void *rock)
{
struct expire_rock *erock = (struct expire_rock *) rock;
char buf[MAX_MAILBOX_NAME+1] = "", *p;
struct annotation_data attrib;
int r, domainlen = 0;
int mbtype;
char *path, *mpath;
/* Skip remote mailboxes */
r = mboxlist_detail(name, &mbtype, &path, &mpath, NULL, NULL, NULL);
if (r) {
if (erock->verbose) {
printf("error looking up %s: %s\n", name, error_message(r));
}
return 1;
}
if (mbtype & MBTYPE_REMOTE) return 0;
if (config_virtdomains && (p = strchr(name, '!')))
domainlen = p - name + 1;
strncpy(buf, name, matchlen);
buf[matchlen] = '\0';
/* see if we need to expire messages.
* since mailboxes inherit /vendor/cmu/cyrus-imapd/expire,
* we need to iterate all the way up to "" (server entry)
*/
while (1) {
r = annotatemore_lookup(buf, "/vendor/cmu/cyrus-imapd/expire", "",
&attrib);
if (r || /* error */
attrib.value || /* found an entry */
!buf[0] || /* done recursing */
!strcmp(buf+domainlen, "user")) { /* server entry doesn't apply
to personal mailboxes */
break;
}
p = strrchr(buf, '.'); /* find parent mailbox */
if (p && (p - buf > domainlen)) /* don't split subdomain */
*p = '\0';
else if (!buf[domainlen]) /* server entry */
buf[0] = '\0';
else /* domain entry */
buf[domainlen] = '\0';
}
if (!r && (attrib.value ||
erock->expunge_mode != IMAP_ENUM_EXPUNGE_MODE_IMMEDIATE)) {
struct mailbox mailbox;
int doclose = 0;
int expunge_flags = 0;
if (!attrib.value &&
erock->expunge_mode != IMAP_ENUM_EXPUNGE_MODE_IMMEDIATE) {
/* ONLY cleanup mailbox of expunged messages.
Try to short circuit additional I/O by checking
for presence of cyrus.expunge.
*/
char fnamebuf[MAX_MAILBOX_PATH+1];
struct stat sbuf;
if (mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_EXPUNGE)) {
strlcpy(fnamebuf, mpath, sizeof(fnamebuf));
} else {
strlcpy(fnamebuf, path, sizeof(fnamebuf));
}
strlcat(fnamebuf, FNAME_EXPUNGE_INDEX, sizeof(fnamebuf));
if (stat(fnamebuf, &sbuf)) return 0;
expunge_flags |= EXPUNGE_CLEANUP;
}
/* Open/lock header */
r = mailbox_open_header(name, 0, &mailbox);
if (!r && mailbox.header_fd != -1) {
doclose = 1;
(void) mailbox_lock_header(&mailbox);
mailbox.header_lock_count = 1;
}
/* Attempt to open/lock index */
if (!r) r = mailbox_open_index(&mailbox);
if (!r) {
(void) mailbox_lock_index(&mailbox);
mailbox.index_lock_count = 1;
}
if (r) {
/* mailbox corrupt/nonexistent -- skip it */
syslog(LOG_WARNING, "unable to open/lock mailbox %s", name);
if (doclose) mailbox_close(&mailbox);
return 0;
}
erock->mailboxes++;
erock->expire_mark = 0;
if (attrib.value) {
/* add mailbox to table */
unsigned long expire_days = strtoul(attrib.value, NULL, 10);
time_t *expire_mark = (time_t *) xmalloc(sizeof(time_t));
*expire_mark = expire_days ?
time(0) - (expire_days * 60 * 60 * 24) : 0 /* never */ ;
hash_insert(name, (void *) expire_mark, erock->table);
if (erock->verbose) {
fprintf(stderr,
"expiring messages in %s older than %ld days\n",
name, expire_days);
}
erock->expire_mark = *expire_mark;
expunge_flags |= EXPUNGE_FORCE;
}
r = mailbox_expunge(&mailbox, expire_cb, erock, expunge_flags);
mailbox_close(&mailbox);
}
if (r) {
syslog(LOG_WARNING, "failure expiring %s: %s", name, error_message(r));
}
/* Even if we had a problem with one mailbox, continue with the others */
return 0;
}
int main(int argc, char *argv[])
{
extern char *optarg;
int opt, r = 0, expire_days = 0, expunge_days = 0;
char *alt_config = NULL;
char buf[100];
struct hash_table expire_table;
struct expire_rock erock;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
/* zero the expire_rock */
memset(&erock, 0, sizeof(erock));
while ((opt = getopt(argc, argv, "C:E:X:v")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'E':
if (expire_days) usage();
expire_days = atoi(optarg);
break;
case 'X':
if (expunge_days) usage();
expunge_days = atoi(optarg);
break;
case 'v':
erock.verbose++;
break;
default:
usage();
break;
}
}
if (!expire_days) usage();
cyrus_init(alt_config, "cyr_expire", 0);
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
mboxlist_init(0);
mboxlist_open(NULL);
/* open the quota db, we'll need it for expunge */
quotadb_init(0);
quotadb_open(NULL);
if (duplicate_init(NULL, 0) != 0) {
fprintf(stderr,
"cyr_expire: unable to init duplicate delivery database\n");
exit(1);
}
/* xxx better way to determine a size for this table? */
construct_hash_table(&expire_table, 10000, 1);
/* expire messages from mailboxes,
* build a hash table of mailboxes in which we expired messages,
* and perform a cleanup of expunged messages
*/
erock.table = &expire_table;
erock.expunge_mode = config_getenum(IMAPOPT_EXPUNGE_MODE);
erock.expunge_mark = time(0) - (expunge_days * 60 * 60 * 24);
if (erock.verbose &&
erock.expunge_mode != IMAP_ENUM_EXPUNGE_MODE_IMMEDIATE) {
fprintf(stderr,
"expunging deleted messages in mailboxes older than %d days\n",
expunge_days);
}
strlcpy(buf, "*", sizeof(buf));
mboxlist_findall(NULL, buf, 1, 0, 0, &expire, &erock);
syslog(LOG_NOTICE, "expunged %lu out of %lu messages from %lu mailboxes",
erock.deleted, erock.messages, erock.mailboxes);
if (erock.verbose) {
fprintf(stderr, "\nexpunged %lu out of %lu messages from %lu mailboxes\n",
erock.deleted, erock.messages, erock.mailboxes);
}
/* purge deliver.db entries of expired messages */
r = duplicate_prune(expire_days, &expire_table);
free_hash_table(&expire_table, free);
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
annotatemore_close();
annotatemore_done();
duplicate_done();
cyrus_done();
exit(r);
}
diff --git a/imap/cyrdump.c b/imap/cyrdump.c
index 89e667b14..c783e4657 100644
--- a/imap/cyrdump.c
+++ b/imap/cyrdump.c
@@ -1,357 +1,359 @@
-/* $Id: cyrdump.c,v 1.18 2007/02/05 18:41:46 jeaton Exp $
+/* $Id: cyrdump.c,v 1.19 2007/03/30 18:40:20 murch Exp $
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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 <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <string.h>
/* cyrus includes */
#include "assert.h"
#include "exitcodes.h"
#include "global.h"
#include "imapd.h"
#include "imap_err.h"
#include "imapurl.h"
#include "mailbox.h"
#include "mboxlist.h"
#include "sysexits.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
/* config.c stuff */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
int verbose = 0;
static int dump_me(char *name, int matchlen, int maycreate, void *rock);
static void print_seq(const char *tag, const char *attrib,
unsigned *seq, int n);
int usage(const char *name);
/* available from elsewhere */
int index_getuidsequence(struct mailbox *mailbox,
struct searchargs *searchargs,
unsigned **uid_list);
/* current namespace */
static struct namespace dump_namespace;
int imapd_exists;
struct protstream *imapd_out = NULL;
struct auth_state *imapd_authstate = NULL;
char *imapd_userid = NULL;
int imapd_condstore_client = 0;
struct incremental_record {
int incruid;
};
int main(int argc, char *argv[])
{
int option;
char buf[MAX_MAILBOX_PATH+1];
int i, r;
char *alt_config = NULL;
struct incremental_record irec;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EX_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((option = getopt(argc, argv, "v")) != EOF) {
switch (option) {
case 'v':
verbose++;
break;
case 'C': /* alt config file */
alt_config = optarg;
break;
default:
usage(argv[0]);
break;
}
}
if (optind == argc) {
usage(argv[0]);
}
cyrus_init(alt_config, "dump", 0);
mboxlist_init(0);
mboxlist_open(NULL);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&dump_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
irec.incruid = 0;
for (i = optind; i < argc; i++) {
strlcpy(buf, argv[i], sizeof(buf));
/* Translate any separators in mailboxname */
mboxname_hiersep_tointernal(&dump_namespace, buf,
config_virtdomains ?
strcspn(buf, "@") : 0);
(*dump_namespace.mboxlist_findall)(&dump_namespace, buf, 1, 0, 0,
dump_me, &irec);
}
mboxlist_close();
mboxlist_done();
cyrus_done();
return 0;
}
int usage(const char *name)
{
fprintf(stderr, "usage: %s [-v] [mboxpattern ...]\n", name);
exit(EC_USAGE);
}
static void generate_boundary(char *boundary, size_t size)
{
assert(size >= 100);
snprintf(boundary, size, "dump-%ld-%ld-%ld",
(long) getpid(), (long) time(NULL), (long) rand());
}
static int dump_me(char *name, int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)), void *rock)
{
int r;
struct mailbox m;
char boundary[128];
struct imapurl url;
char imapurl[MAX_MAILBOX_PATH];
struct incremental_record *irec = (struct incremental_record *) rock;
struct searchargs searchargs;
unsigned *uids;
unsigned *uidseq;
int i, n, numuids;
memset(&m, 0, sizeof(struct mailbox));
r = mailbox_open_header(name, 0, &m);
if (r) {
if (verbose) {
printf("error opening %s: %s\n", name, error_message(r));
}
return 0;
}
r = mailbox_open_index(&m);
if (!r) r = mailbox_lock_pop(&m);
if (r) {
if (verbose) {
printf("error locking index %s: %s\n", name, error_message(r));
}
mailbox_close(&m);
return 0;
}
mailbox_read_index_header(&m);
index_operatemailbox(&m);
generate_boundary(boundary, sizeof(boundary));
printf("Content-Type: multipart/related; boundary=\"%s\"\n\n", boundary);
printf("--%s\n", boundary);
printf("Content-Type: text/xml\n");
printf("IMAP-Dump-Version: 0\n");
printf("\n");
printf("<imapdump uniqueid=\"%s\">\n", m.uniqueid);
memset(&url, 0, sizeof(struct imapurl));
url.server = config_servername;
url.mailbox = m.name;
imapurl_toURL(imapurl, &url);
printf(" <mailbox-url>%s</mailbox-url>\n", imapurl);
printf(" <incremental-uid>%d</incremental-uid>\n", irec->incruid);
printf(" <nextuid>%ld</nextuid>\n", m.last_uid + 1);
printf("\n");
memset(&searchargs, 0, sizeof(struct searchargs));
numuids = index_getuidsequence(&m, &searchargs, &uids);
print_seq("uidlist", NULL, uids, numuids);
printf("\n");
printf(" <flags>\n");
searchargs.system_flags_set = FLAG_ANSWERED;
n = index_getuidsequence(&m, &searchargs, &uidseq);
print_seq("flag", "name=\"\\Answered\" user=\"*\"", uidseq, n);
if (uidseq) free(uidseq);
searchargs.system_flags_set = FLAG_DELETED;
n = index_getuidsequence(&m, &searchargs, &uidseq);
print_seq("flag", "name=\"\\Deleted\" user=\"*\"", uidseq, n);
if (uidseq) free(uidseq);
searchargs.system_flags_set = FLAG_DRAFT;
n = index_getuidsequence(&m, &searchargs, &uidseq);
print_seq("flag", "name=\"\\Draft\" user=\"*\"", uidseq, n);
if (uidseq) free(uidseq);
searchargs.system_flags_set = FLAG_FLAGGED;
n = index_getuidsequence(&m, &searchargs, &uidseq);
print_seq("flag", "name=\"\\Flagged\" user=\"*\"", uidseq, n);
if (uidseq) free(uidseq);
printf(" </flags>\n");
printf("</imapdump>\n");
for (i = 0; i < numuids; i++) {
const char *base;
unsigned long len;
if (uids[i] < irec->incruid) {
/* already dumped this message */
/* xxx could do binary search to get to the first
undumped uid */
continue;
}
printf("\n--%s\n", boundary);
printf("Content-Type: message/rfc822\n");
printf("Content-ID: %d\n", uids[i]);
printf("\n");
r = mailbox_map_message(&m, uids[i], &base, &len);
if (r) {
if (verbose) {
printf("error mapping message %d: %s\n", uids[i],
error_message(r));
}
break;
}
fwrite(base, 1, len, stdout);
mailbox_unmap_message(&m, uids[i], &base, &len);
}
printf("\n--%s--\n", boundary);
index_closemailbox(&m);
mailbox_close(&m);
return 0;
}
static void print_seq(const char *tag, const char *attrib,
unsigned *seq, int n)
{
int i;
printf(" <%s%s%s>", tag, attrib ? " " : "", attrib ? attrib : "");
for (i = 0; i < n; i++) {
printf("%u ", seq[i]);
}
printf("</%s>\n", tag);
}
#if 0
char *p, *str;
int str_sz;
int run_start = 0;
int first_time = 1;
p = str = (char *) xmalloc(sizeof(char) * 1024);
str_sz = 1024;
run_start = msgno_list[0];
for (i = 1; i < n; i++) {
if (msgno_list[i] == msgno_list[i-1] + 1) {
/* on a run */
continue;
}
if (first_time) {
first_time = 0;
} else {
*p++ = ',';
}
if (run_start != msgno_list[i-1]) {
/* non-trivial run */
p += sprintf(p, "%d:%d", run_start, msgno_list[i-1]);
} else {
/* singleton */
p += sprintf(p, "%d", msgno_list[i-1]);
}
if (p > (str + str_sz - 20)) {
/* running out of room */
int x;
x = p - str;
str = (char *) xrealloc(str, str_sz *= 2);
p = str + x;
}
run_start = msgno_list[i];
}
/* now handle the last entry */
if (!first_time) {
*p++ = ',';
}
if (run_start != msgno_list[i-1]) {
sprintf(p, "%d:%d", run_start, msgno_list[i-1]);
} else {
sprintf(p, "%d", msgno_list[i-1]);
}
return str;
#endif
void printastring(const char *s __attribute__((unused)))
{
fatal("printastring not implemented in cyrdump", EC_SOFTWARE);
}
diff --git a/imap/delivered_prune.c b/imap/delivered_prune.c
index c810f0ecf..c406969f1 100644
--- a/imap/delivered_prune.c
+++ b/imap/delivered_prune.c
@@ -1,122 +1,124 @@
/* cyr_expire.c -- Program to expire deliver.db entries and messages
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: delivered_prune.c,v 1.2 2006/11/30 17:11:17 murch Exp $
+ * $Id: delivered_prune.c,v 1.3 2007/03/30 18:40:20 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include "annotate.h"
#include "cyrusdb.h"
#include "duplicate.h"
#include "exitcodes.h"
#include "global.h"
#include "hash.h"
#include "libcyr_cfg.h"
#include "mboxlist.h"
#include "util.h"
#include "xmalloc.h"
/* global state */
const int config_need_data = 0;
void usage(void)
{
fprintf(stderr,
"cyr_expire [-C <altconfig>] -E <days>\n");
exit(-1);
}
int main(int argc, char *argv[])
{
extern char *optarg;
int opt, r = 0, expire_days = 0;
char *alt_config = NULL;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:E:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'E':
if (expire_days) usage();
expire_days = atoi(optarg);
break;
default:
usage();
break;
}
}
if (!expire_days) usage();
cyrus_init(alt_config, "delivered_expire", 0);
if (duplicate_init(NULL, 0) != 0) {
fprintf(stderr,
"cyr_expire: unable to init duplicate delivery database\n");
exit(1);
}
/* purge deliver.db entries of expired messages */
r = duplicate_prune(expire_days, NULL);
duplicate_done();
cyrus_done();
exit(r);
}
diff --git a/imap/fetchnews.c b/imap/fetchnews.c
index 84d842cfe..f648f7b0e 100644
--- a/imap/fetchnews.c
+++ b/imap/fetchnews.c
@@ -1,621 +1,623 @@
/* fetchnews.c -- Program to pull new articles from a peer and push to server
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: fetchnews.c,v 1.16 2006/11/30 17:11:17 murch Exp $
+ * $Id: fetchnews.c,v 1.17 2007/03/30 18:40:20 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include "cyrusdb.h"
#include "exitcodes.h"
#include "global.h"
#include "gmtoff.h"
#include "lock.h"
#include "prot.h"
#include "xmalloc.h"
/* global state */
const int config_need_data = 0;
#define FNAME_NEWSRCDB "/fetchnews.db"
#define DB (&cyrusdb_flat)
static struct db *newsrc_db = NULL;
static int newsrc_dbopen = 0;
/* must be called after cyrus_init */
int newsrc_init(char *fname, int myflags __attribute__((unused)))
{
char buf[1024];
int r = 0;
if (r != 0)
syslog(LOG_ERR, "DBERROR: init %s: %s", buf,
cyrusdb_strerror(r));
else {
char *tofree = NULL;
/* create db file name */
if (!fname) {
fname = xmalloc(strlen(config_dir)+sizeof(FNAME_NEWSRCDB));
tofree = fname;
strcpy(fname, config_dir);
strcat(fname, FNAME_NEWSRCDB);
}
r = DB->open(fname, CYRUSDB_CREATE, &newsrc_db);
if (r != 0)
syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
cyrusdb_strerror(r));
else
newsrc_dbopen = 1;
if (tofree) free(tofree);
}
return r;
}
int newsrc_done(void)
{
int r = 0;
if (newsrc_dbopen) {
r = DB->close(newsrc_db);
if (r) {
syslog(LOG_ERR, "DBERROR: error closing fetchnews.db: %s",
cyrusdb_strerror(r));
}
newsrc_dbopen = 0;
}
return r;
}
void usage(void)
{
fprintf(stderr,
"fetchnews [-C <altconfig>] [-s <server>] [-n] [-y] [-w <wildmat>] [-f <tstamp file>]\n"
" [-a <authname> [-p <password>]] <peer>\n");
exit(-1);
}
int init_net(const char *host, char *port,
struct protstream **in, struct protstream **out)
{
int sock = -1, err;
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
if ((err = getaddrinfo(host, port, &hints, &res0)) != 0) {
syslog(LOG_ERR, "getaddrinfo(%s, %s) failed: %m", host, port);
return -1;
}
for (res = res0; res; res = res->ai_next) {
if ((sock = socket(res->ai_family, res->ai_socktype,
res->ai_protocol)) < 0)
continue;
if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0)
break;
close(sock);
sock = -1;
}
freeaddrinfo(res0);
if(sock < 0) {
syslog(LOG_ERR, "connect(%s:%s) failed: %m", host, port);
return -1;
}
*in = prot_new(sock, 0);
*out = prot_new(sock, 1);
prot_setflushonread(*in, *out);
return sock;
}
int fetch(char *msgid, int bymsgid,
struct protstream *pin, struct protstream *pout,
struct protstream *sin, struct protstream *sout,
int *rejected, int *accepted, int *failed)
{
char buf[4096];
/* see if we want this article */
prot_printf(sout, "IHAVE %s\r\n", msgid);
if (!prot_fgets(buf, sizeof(buf), sin)) {
syslog(LOG_ERR, "IHAVE terminated abnormally");
return -1;
}
else if (strncmp("335", buf, 3)) {
/* don't want it */
(*rejected)++;
return 0;
}
/* fetch the article */
if (bymsgid)
prot_printf(pout, "ARTICLE %s\r\n", msgid);
else
prot_printf(pout, "ARTICLE\r\n");
if (!prot_fgets(buf, sizeof(buf), pin)) {
syslog(LOG_ERR, "ARTICLE terminated abnormally");
return -1;
}
else if (strncmp("220", buf, 3)) {
/* doh! the article doesn't exist, terminate IHAVE */
prot_printf(sout, ".\r\n");
}
else {
/* store the article */
while (prot_fgets(buf, sizeof(buf), pin)) {
if (buf[0] == '.') {
if (buf[1] == '\r' && buf[2] == '\n') {
/* End of message */
prot_printf(sout, ".\r\n");
break;
}
else if (buf[1] != '.') {
/* Add missing dot-stuffing */
prot_putc('.', sout);
}
}
do {
/* look for malformed lines with NUL CR LF */
if (buf[strlen(buf)-1] != '\n' &&
strlen(buf)+2 < sizeof(buf)-1 &&
buf[strlen(buf)+2] == '\n') {
strlcat(buf, "\r\n", sizeof(buf));
}
prot_printf(sout, "%s", buf);
} while (buf[strlen(buf)-1] != '\n' &&
prot_fgets(buf, sizeof(buf), pin));
}
if (buf[0] != '.') {
syslog(LOG_ERR, "ARTICLE terminated abnormally");
return -1;
}
}
/* see how we did */
if (!prot_fgets(buf, sizeof(buf), sin)) {
syslog(LOG_ERR, "IHAVE terminated abnormally");
return -1;
}
else if (!strncmp("235", buf, 3))
(*accepted)++;
else
(*failed)++;
return 0;
}
#define RESP_GROW 100
#define BUFFERSIZE 4096
int main(int argc, char *argv[])
{
extern char *optarg;
int opt;
char *alt_config = NULL, *port = "119";
const char *peer = NULL, *server = "localhost", *wildmat = "*";
char *authname = NULL, *password = NULL;
int psock = -1, ssock = -1;
struct protstream *pin, *pout, *sin, *sout;
char buf[BUFFERSIZE];
char sfile[1024] = "";
int fd = -1, i, n, offered, rejected, accepted, failed;
time_t stamp;
char **resp = NULL;
int newnews = 1;
char *datefmt = "%y%m%d %H%M%S";
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:s:w:f:a:p:ny")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 's': /* server */
server = xstrdup(optarg);
if ((port = strchr(server, ':')))
*port++ = '\0';
else
port = "119";
break;
case 'w': /* wildmat */
wildmat = optarg;
break;
case 'f': /* timestamp file */
snprintf(sfile, sizeof(sfile), optarg);
break;
case 'a': /* authname */
authname = optarg;
break;
case 'p': /* password */
password = optarg;
break;
case 'n': /* no newnews */
newnews = 0;
break;
case 'y': /* newsserver is y2k compliant */
datefmt = "%Y%m%d %H%M%S";
break;
default:
usage();
/* NOTREACHED */
}
}
if (argc - optind < 1) {
usage();
/* NOTREACHED */
}
peer = argv[optind++];
cyrus_init(alt_config, "fetchnews", 0);
/* connect to the peer */
/* xxx configurable port number? */
if ((psock = init_net(peer, "119", &pin, &pout)) < 0) {
fprintf(stderr, "connection to %s failed\n", peer);
cyrus_done();
exit(-1);
}
/* read the initial greeting */
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("20", buf, 2)) {
syslog(LOG_ERR, "peer not available");
goto quit;
}
if (authname) {
/* authenticate to peer */
/* XXX this should be modified to support SASL and STARTTLS */
prot_printf(pout, "AUTHINFO USER %s\r\n", authname);
if (!prot_fgets(buf, sizeof(buf), pin)) {
syslog(LOG_ERR, "AUTHINFO USER terminated abnormally");
goto quit;
}
else if (!strncmp("381", buf, 3)) {
/* password required */
if (!password)
password = getpass("Please enter the password: ");
if (!password) {
fprintf(stderr, "failed to get password\n");
goto quit;
}
prot_printf(pout, "AUTHINFO PASS %s\r\n", password);
if (!prot_fgets(buf, sizeof(buf), pin)) {
syslog(LOG_ERR, "AUTHINFO PASS terminated abnormally");
goto quit;
}
}
if (strncmp("281", buf, 3)) {
/* auth failed */
goto quit;
}
}
/* change to reader mode - not always necessary, so ignore result */
prot_printf(pout, "MODE READER\r\n");
prot_fgets(buf, sizeof(buf), pin);
if (newnews) {
struct tm ctime, *ptime;
/* fetch the server's current time */
prot_printf(pout, "DATE\r\n");
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("111 ", buf, 4)) {
syslog(LOG_ERR, "error fetching DATE");
goto quit;
}
/* parse and normalize the server time */
memset(&ctime, 0, sizeof(struct tm));
sscanf(buf+4, "%4d%02d%02d%02d%02d%02d",
&ctime.tm_year, &ctime.tm_mon, &ctime.tm_mday,
&ctime.tm_hour, &ctime.tm_min, &ctime.tm_sec);
ctime.tm_year -= 1900;
ctime.tm_mon--;
ctime.tm_isdst = -1;
/* read the previous timestamp */
if (!sfile[0]) {
char oldfile[1024];
snprintf(sfile, sizeof(sfile), "%s/fetchnews.stamp", config_dir);
/* upgrade from the old stamp filename to the new */
snprintf(oldfile, sizeof(oldfile), "%s/newsstamp", config_dir);
rename(oldfile, sfile);
}
if ((fd = open(sfile, O_RDWR | O_CREAT, 0644)) == -1) {
syslog(LOG_ERR, "can not open %s", sfile);
goto quit;
}
if (lock_nonblocking(fd) == -1) {
syslog(LOG_ERR, "can not lock %s: %m", sfile);
goto quit;
}
if (read(fd, &stamp, sizeof(stamp)) < sizeof(stamp)) {
/* XXX do something better here */
stamp = 0;
}
/* ask for new articles */
if (stamp) stamp -= 180; /* adjust back 3 minutes */
ptime = gmtime(&stamp);
ptime->tm_isdst = -1;
strftime(buf, sizeof(buf), datefmt, ptime);
prot_printf(pout, "NEWNEWS %s %s GMT\r\n", wildmat, buf);
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("230", buf, 3)) {
syslog(LOG_ERR, "peer doesn't support NEWNEWS");
newnews = 0;
}
/* prepare server's current time as new timestamp */
stamp = mktime(&ctime);
/* adjust for local timezone
XXX We need to do this because we use gmtime() above.
We can't change this, otherwise we'd be incompatible
with an old localtime timestamp.
*/
stamp += gmtoff_of(&ctime, stamp);
}
if (!newnews) {
prot_printf(pout, "LIST ACTIVE %s\r\n", wildmat);
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("215", buf, 3)) {
syslog(LOG_ERR, "peer doesn't support LIST ACTIVE");
goto quit;
}
}
/* process the NEWNEWS/LIST ACTIVE list */
n = 0;
while (prot_fgets(buf, sizeof(buf), pin)) {
if (buf[0] == '.') break;
if (!(n % RESP_GROW)) { /* time to alloc more */
resp = (char **)
xrealloc(resp, (n + RESP_GROW) * sizeof(char *));
}
resp[n++] = xstrdup(buf);
}
if (buf[0] != '.') {
syslog(LOG_ERR, "%s terminated abnormally",
newnews ? "NEWNEWS" : "LIST ACTIVE");
goto quit;
}
if (!n) {
/* nothing matches our wildmat */
goto quit;
}
/* connect to the server */
if ((ssock = init_net(server, port, &sin, &sout)) < 0) {
fprintf(stderr, "connection to %s failed\n", server);
goto quit;
}
/* read the initial greeting */
if (!prot_fgets(buf, sizeof(buf), sin) || strncmp("20", buf, 2)) {
syslog(LOG_ERR, "server not available");
goto quit;
}
/* fetch and store articles */
offered = rejected = accepted = failed = 0;
if (newnews) {
/* response is a list of msgids */
for (i = 0; i < n; i++) {
/* find the end of the msgid */
*(strrchr(resp[i], '>') + 1) = '\0';
offered++;
if (fetch(resp[i], 1, pin, pout, sin, sout,
&rejected, &accepted, &failed)) {
goto quit;
}
}
/* write the current timestamp */
lseek(fd, 0, SEEK_SET);
if (write(fd, &stamp, sizeof(stamp)) < sizeof(stamp))
syslog(LOG_ERR, "error writing %s", sfile);
lock_unlock(fd);
close(fd);
}
else {
char group[BUFFERSIZE], msgid[BUFFERSIZE], lastbuf[50];
const char *data;
unsigned long low, high, last, cur;
int start;
int datalen;
struct txn *tid = NULL;
newsrc_init(NULL, 0);
/*
* response is a list of groups.
* select each group, and STAT each article we haven't seen yet.
*/
for (i = 0; i < n; i++) {
/* parse the LIST ACTIVE response */
sscanf(resp[i], "%s %lu %lu", group, &high, &low);
last = 0;
if (!DB->fetchlock(newsrc_db, group, strlen(group),
&data, &datalen, &tid)) {
last = strtoul(data, NULL, 10);
}
if (high <= last) continue;
/* select the group */
prot_printf(pout, "GROUP %s\r\n", group);
if (!prot_fgets(buf, sizeof(buf), pin)) {
syslog(LOG_ERR, "GROUP terminated abnormally");
continue;
}
else if (strncmp("211", buf, 3)) break;
for (start = 1, cur = low > last ? low : ++last;; cur++) {
if (start) {
/* STAT the first article we haven't seen */
prot_printf(pout, "STAT %lu\r\n", cur);
} else {
/* continue with the NEXT article */
prot_printf(pout, "NEXT\r\n");
}
if (!prot_fgets(buf, sizeof(buf), pin)) {
syslog(LOG_ERR, "STAT/NEXT terminated abnormally");
cur--;
break;
}
if (!strncmp("223", buf, 3)) {
/* parse the STAT/NEXT response */
sscanf(buf, "223 %lu %s", &cur, msgid);
/* find the end of the msgid */
*(strrchr(msgid, '>') + 1) = '\0';
if (fetch(msgid, 0, pin, pout, sin, sout,
&rejected, &accepted, &failed)) {
cur--;
break;
}
offered++;
start = 0;
}
/* have we reached the highwater mark? */
if (cur >= high) break;
}
snprintf(lastbuf, sizeof(lastbuf), "%lu", cur);
DB->store(newsrc_db, group, strlen(group),
lastbuf, strlen(lastbuf)+1, &tid);
}
if (tid) DB->commit(newsrc_db, tid);
newsrc_done();
}
syslog(LOG_NOTICE,
"fetchnews: %s offered %d; %s rejected %d, accepted %d, failed %d",
peer, offered, server, rejected, accepted, failed);
quit:
if (psock >= 0) {
prot_printf(pout, "QUIT\r\n");
prot_flush(pout);
/* Flush the incoming buffer */
prot_NONBLOCK(pin);
prot_fill(pin);
/* close/free socket & prot layer */
close(psock);
prot_free(pin);
prot_free(pout);
}
if (ssock >= 0) {
prot_printf(sout, "QUIT\r\n");
prot_flush(sout);
/* Flush the incoming buffer */
prot_NONBLOCK(sin);
prot_fill(sin);
/* close/free socket & prot layer */
close(psock);
prot_free(sin);
prot_free(sout);
}
cyrus_done();
return 0;
}
diff --git a/imap/ipurge.c b/imap/ipurge.c
index 887a2f863..72fa5abf9 100644
--- a/imap/ipurge.c
+++ b/imap/ipurge.c
@@ -1,368 +1,370 @@
/*
* ipurge
*
* delete mail from cyrus imap mailbox or partition
* based on date (or size?)
*
* includes support for ISPN virtual host extensions
*
- * $Id: ipurge.c,v 1.26 2006/11/30 17:11:18 murch Exp $
+ * $Id: ipurge.c,v 1.27 2007/03/30 18:40:20 murch Exp $
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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 <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <string.h>
#include <netinet/in.h>
/* cyrus includes */
#include "global.h"
#include "sysexits.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "mboxlist.h"
/* config.c stuff */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
/* globals for getopt routines */
extern char *optarg;
extern int optind;
extern int opterr;
extern int optopt;
/* globals for callback functions */
int days = -1;
int size = -1;
int exact = -1;
int pattern = -1;
int skipflagged = 0;
int datemode = OFFSET_SENTDATE;
int invertmatch = 0;
/* for statistical purposes */
typedef struct mbox_stats_s {
int total; /* total including those deleted */
int total_bytes;
int deleted;
int deleted_bytes;
} mbox_stats_t;
/* current namespace */
static struct namespace purge_namespace;
int verbose = 1;
int forceall = 0;
int purge_me(char *, int, int);
int purge_check(struct mailbox *, void *, char *, int);
int usage(char *name);
void print_stats(mbox_stats_t *stats);
int main (int argc, char *argv[]) {
int option; /* getopt() returns an int */
char buf[MAX_MAILBOX_PATH+1];
char *alt_config = NULL;
int r;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EX_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((option = getopt(argc, argv, "C:hxd:b:k:m:fsXi")) != EOF) {
switch (option) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'd': {
if (optarg == 0) {
usage(argv[0]);
}
days = atoi(optarg) * 86400 /* nominal # of seconds in a 'day' */;
} break;
case 'b': {
if (optarg == 0) {
usage(argv[0]);
}
size = atoi(optarg);
} break;
case 'k': {
if (optarg == 0) {
usage(argv[0]);
}
size = atoi(optarg) * 1024; /* make it bytes */
} break;
case 'm': {
if (optarg == 0) {
usage(argv[0]);
}
size = atoi(optarg) * 1048576; /* 1024 * 1024 */
} break;
case 'x' : {
exact = 1;
} break;
case 'f' : {
forceall = 1;
} break;
case 's' : {
skipflagged = 1;
} break;
case 'X' : {
datemode = OFFSET_INTERNALDATE;
} break;
case 'i' : {
invertmatch = 1;
} break;
case 'h':
default: usage(argv[0]);
}
}
if ((days == -1 ) && (size == -1)) {
printf("One of these must be specified -d, -b -k, -m\n");
usage(argv[0]);
}
cyrus_init(alt_config, "ipurge", 0);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&purge_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
mboxlist_init(0);
mboxlist_open(NULL);
/* open the quota db, we'll need it for expunge */
quotadb_init(0);
quotadb_open(NULL);
if (optind == argc) { /* do the whole partition */
strcpy(buf, "*");
(*purge_namespace.mboxlist_findall)(&purge_namespace, buf, 1, 0, 0,
purge_me, NULL);
} else {
for (; optind < argc; optind++) {
strncpy(buf, argv[optind], MAX_MAILBOX_NAME);
/* Translate any separators in mailboxname */
mboxname_hiersep_tointernal(&purge_namespace, buf,
config_virtdomains ?
strcspn(buf, "@") : 0);
(*purge_namespace.mboxlist_findall)(&purge_namespace, buf, 1, 0, 0,
purge_me, NULL);
}
}
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
cyrus_done();
return 0;
}
int
usage(char *name) {
printf("usage: %s [-f] [-s] [-C <alt_config>] [-x] [-X] [-i] {-d days | -b bytes|-k Kbytes|-m Mbytes}\n\t[mboxpattern1 ... [mboxpatternN]]\n", name);
printf("\tthere are no defaults and at least one of -d, -b, -k, -m\n\tmust be specified\n");
printf("\tif no mboxpattern is given %s works on all mailboxes\n", name);
printf("\t -x specifies an exact match for days or size\n");
printf("\t -f force also to delete mail below user.* and INBOX.*\n");
printf("\t -s skip over messages that are flagged.\n");
printf("\t -X use delivery time instead of date header for date matches.\n");
printf("\t -i invert match logic: -x means not equal, date is for newer, size is for smaller.\n");
exit(0);
}
/* we don't check what comes in on matchlen and maycreate, should we? */
int purge_me(char *name, int matchlen __attribute__((unused)),
int maycreate __attribute__((unused))) {
struct mailbox the_box;
int error;
mbox_stats_t stats;
if( ! forceall ) {
/* DON'T purge INBOX* and user.* */
if (!strncasecmp(name,"INBOX",5) || mboxname_isusermailbox(name, 0))
return 0;
}
memset(&stats, '\0', sizeof(mbox_stats_t));
if (verbose) {
char mboxname[MAX_MAILBOX_NAME+1];
/* Convert internal name to external */
(*purge_namespace.mboxname_toexternal)(&purge_namespace, name,
"cyrus", mboxname);
printf("Working on %s...\n", mboxname);
}
error = mailbox_open_header(name, 0, &the_box);
if (error != 0) { /* did we find it? */
syslog(LOG_ERR, "Couldn't find %s, check spelling", name);
return error;
}
if (the_box.header_fd != -1) {
(void) mailbox_lock_header(&the_box);
}
the_box.header_lock_count = 1;
error = mailbox_open_index(&the_box);
if (error != 0) {
mailbox_close(&the_box);
syslog(LOG_ERR, "Couldn't open mailbox index for %s", name);
return error;
}
(void) mailbox_lock_index(&the_box);
the_box.index_lock_count = 1;
mailbox_expunge(&the_box, purge_check, &stats, EXPUNGE_FORCE);
mailbox_close(&the_box);
print_stats(&stats);
return 0;
}
void deleteit(bit32 msgsize, mbox_stats_t *stats)
{
stats->deleted++;
stats->deleted_bytes += msgsize;
}
/* thumbs up routine, checks date & size and returns yes or no for deletion */
/* 0 = no, 1 = yes */
int purge_check(struct mailbox *mailbox __attribute__((unused)),
void *deciderock, char *buf,
int expunge_flags __attribute__((unused)))
{
time_t my_time;
mbox_stats_t *stats = (mbox_stats_t *) deciderock;
bit32 senttime;
bit32 msgsize;
bit32 flagged;
senttime = ntohl(*((bit32 *)(buf + datemode)));
msgsize = ntohl(*((bit32 *)(buf + OFFSET_SIZE)));
flagged = ntohl(*((bit32 *)(buf + OFFSET_SYSTEM_FLAGS))) & FLAG_FLAGGED;
stats->total++;
stats->total_bytes += msgsize;
if (skipflagged && flagged)
return 0;
if (exact == 1) {
if (days >= 0) {
my_time = time(0);
/* printf("comparing %ld :: %ld\n", my_time, the_record->sentdate); */
if (((my_time - (time_t) senttime)/86400) == (days/86400)) {
if (invertmatch) return 0;
deleteit(msgsize, stats);
return 1;
} else {
if (!invertmatch) return 0;
deleteit(msgsize, stats);
return 1;
}
}
if (size >= 0) {
/* check size */
if (msgsize == size) {
if (invertmatch) return 0;
deleteit(msgsize, stats);
return 1;
} else {
if (!invertmatch) return 0;
deleteit(msgsize, stats);
return 1;
}
}
return 0;
} else {
if (days >= 0) {
my_time = time(0);
/* printf("comparing %ld :: %ld\n", my_time, the_record->sentdate); */
if (!invertmatch && ((my_time - (time_t) senttime) > days)) {
deleteit(msgsize, stats);
return 1;
}
if (invertmatch && ((my_time - (time_t) senttime) < days)) {
deleteit(msgsize, stats);
return 1;
}
}
if (size >= 0) {
/* check size */
if (!invertmatch && (msgsize > size)) {
deleteit(msgsize, stats);
return 1;
}
if (invertmatch && (msgsize < size)) {
deleteit(msgsize, stats);
return 1;
}
}
return 0;
}
}
void print_stats(mbox_stats_t *stats)
{
printf("total messages \t\t %d\n",stats->total);
printf("total bytes \t\t %d\n",stats->total_bytes);
printf("Deleted messages \t\t %d\n",stats->deleted);
printf("Deleted bytes \t\t %d\n",stats->deleted_bytes);
printf("Remaining messages\t\t %d\n",stats->total - stats->deleted);
printf("Remaining bytes \t\t %d\n",
stats->total_bytes - stats->deleted_bytes);
}
diff --git a/imap/make_md5.c b/imap/make_md5.c
index 173c7aa23..2a5b94c7c 100644
--- a/imap/make_md5.c
+++ b/imap/make_md5.c
@@ -1,923 +1,924 @@
#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 <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include "global.h"
#include "assert.h"
#include "mboxlist.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "acl.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "md5global.h"
#include "md5.h"
/*#include "cdb.h"*/
/* global state */
const int config_need_data = 0;
extern char *optarg;
extern int optind;
/* Stuff to make index.c link */
int imapd_exists;
struct protstream *imapd_out = NULL;
struct auth_state *imapd_authstate = NULL;
char *imapd_userid = NULL;
void printastring(const char *s)
{
fatal("not implemented", EC_SOFTWARE);
}
void printstring(const char *s)
{
fatal("not implemented", EC_SOFTWARE);
}
/* end stuff to make index.c link */
static int verbose = 0;
static void shut_down(int code) __attribute__((noreturn));
static void shut_down(int code)
{
seen_done();
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
exit(code);
}
static int usage(const char *name)
{
fprintf(stderr,
"usage: %s [-C <alt_config>] [-d] [-k <count>] [-v]"
" [-m <offset>] [-M <modulo>] user...\n",
name);
exit(EC_USAGE);
}
void fatal(const char* s, int code)
{
fprintf(stderr, "make_md5: %s\n", s);
exit(code);
}
/* ====================================================================== */
struct md5_node {
struct md5_node *next;
int active;
unsigned long uid;
unsigned char md5_msg[16];
unsigned char md5_cache[16];
};
struct md5_mailbox {
struct md5_mailbox *next;
char *name;
char *uniqueid;
struct md5_node *head;
struct md5_node *tail;
unsigned long count;
int active;
};
struct md5_mailbox_list {
struct md5_mailbox *head;
struct md5_mailbox *tail;
unsigned long count;
int dirty;
};
static void
md5_mailbox_free(struct md5_mailbox *list)
{
struct md5_node *current, *next;
for (current = list->head; current ; current = next) {
next = current->next;
free(current);
}
free(list->name);
free(list->uniqueid);
free(list);
}
static void
md5_mailbox_rename(struct md5_mailbox *mailbox, char *name)
{
free(mailbox->name);
mailbox->name = xstrdup(name);
}
static struct md5_node *
md5_mailbox_add(struct md5_mailbox *list,
unsigned long uid,
unsigned char md5_msg[16],
unsigned char md5_cache[16],
int active)
{
struct md5_node *new = xmalloc(sizeof(struct md5_node));
struct md5_node *current, *last;
new->next = NULL;
new->uid = uid;
new->active = active;
memcpy(&new->md5_msg, md5_msg, 16);
memcpy(&new->md5_cache, md5_cache, 16);
list->count++;
if (list->head == NULL) {
/* Add to empty list */
list->head = list->tail = new;
return(new);
}
assert(list->tail != NULL);
if (list->tail->uid < uid) {
/* Add to end of list */
/* This is the common case as UIDs assigned in ascending order */
list->tail = list->tail->next = new;
return(new);
}
assert(list->head != NULL);
if (uid < list->head->uid) {
new->next = list->head; /* Add to start of list */
list->head = new;
return(new);
}
current = list->head;
do {
last = current;
current = current->next;
} while (current && (uid > current->uid));
if (current && (uid < current->uid)) {
new->next = current; /* Insert between last and current */
last->next = new;
return(new);
}
return(current);
}
static struct md5_mailbox_list *
md5_mailbox_list_create(void)
{
struct md5_mailbox_list *list = xmalloc(sizeof(struct md5_mailbox_list));
list->head = NULL;
list->tail = NULL;
list->count = 0;
list->dirty = 0;
return(list);
}
static void
md5_mailbox_list_free(struct md5_mailbox_list *list)
{
struct md5_mailbox *current, *next;
for (current = list->head; current ; current = next) {
next = current->next;
md5_mailbox_free(current);
}
free(list);
}
static struct md5_mailbox *
md5_mailbox_list_add(struct md5_mailbox_list *list, char *name, char *uniqueid)
{
struct md5_mailbox *new = xzmalloc(sizeof(struct md5_mailbox));
struct md5_mailbox *current, *last;
list->count++;
new->next = NULL;
new->name = xstrdup(name);
new->uniqueid = xstrdup(uniqueid);
new->head = NULL;
new->tail = NULL;
new->count = 0;
new->active = 0;
if (list->head == NULL) {
/* Add to empty list */
list->head = list->tail = new;
return(new);
}
assert(list->tail != NULL);
if (strcmp(list->tail->uniqueid, uniqueid) < 0) {
/* Add to end of list */
/* This is the common case as folders sorted in ascending order */
list->tail = list->tail->next = new;
return(new);
}
assert(list->head != NULL);
if (strcmp(list->head->uniqueid, uniqueid) > 0) {
new->next = list->head; /* Add to start of list */
list->head = new;
return(new);
}
current = list->head;
do {
last = current;
current = current->next;
} while (current && (strcmp(uniqueid, current->uniqueid) > 0));
if (!current)
return(NULL);
if (!strcmp(uniqueid, current->uniqueid)) {
if (strcmp(current->name, name) != 0) {
free(current->name);
current->name = xstrdup(name);
}
return(current);
}
/* Insert between last and current */
new->next = current;
last->next = new;
return(new);
}
static struct md5_mailbox *
md5_mailbox_list_find(struct md5_mailbox_list *list, char *uniqueid)
{
struct md5_mailbox *mailbox;
for (mailbox = list->head ; mailbox ; mailbox = mailbox->next) {
if (!strcmp(mailbox->uniqueid, uniqueid))
return(mailbox);
}
return(NULL);
}
static int
md5_mailbox_list_check_deleted(struct md5_mailbox_list *list)
{
struct md5_mailbox *mailbox;
for (mailbox = list->head ; mailbox ; mailbox = mailbox->next) {
if (!mailbox->active) {
list->dirty = 1;
return(1);
}
}
return(0);
}
static int
md5_parse(unsigned char md5[16], char *s)
{
int i;
char c;
if (strlen(s) != 32)
return(0);
for (i = 0 ; i < 16 ; i++) {
c = *s++;
if ((c >= '0') && (c <= '9'))
md5[i] = (c - '0') * 16;
else if ((c >= 'a') && (c <= 'z'))
md5[i] = (c - 'a' + 10) * 16;
else if ((c >= 'A') && (c <= 'Z'))
md5[i] = (c - 'A' + 10) * 16;
else
return(0);
c = *s++;
if ((c >= '0') && (c <= '9'))
md5[i] += (c - '0');
else if ((c >= 'a') && (c <= 'z'))
md5[i] += (c - 'a' + 10);
else if ((c >= 'A') && (c <= 'Z'))
md5[i] += (c - 'A' + 10);
else
return(0);
}
return(1);
}
static int
md5_mailbox_list_read(struct md5_mailbox_list *list, char *name)
{
struct md5_mailbox *current = NULL;
FILE *file;
char buf[MAX_MAILBOX_NAME+2];
unsigned char md5_msg[16];
unsigned char md5_cache[16];
int len;
int lineno = 0;
unsigned long uid;
char *mboxname, *uniqueid, *s;
if ((file=fopen(name, "r")) == NULL)
return(0);
while (fgets(buf, sizeof(buf), file)) {
++lineno;
if (((len=strlen(buf)) > 0) && (buf[len-1] == '\n'))
buf[--len] = '\0';
if ((buf[0] == '#') || (buf[0] == '\0'))
continue;
if (buf[0] != ' ') {
/* "%s %s", mboxname, uniqueid. mboxname may contain spaces */
mboxname = buf;
uniqueid = strrchr(buf, ' ');
if ((uniqueid == NULL) || ((uniqueid - mboxname) < 6))
goto parse_err;
*uniqueid++ = '\0';
current = md5_mailbox_list_add(list, mboxname, uniqueid);
} else {
if (!(current && (s = strtok(buf, "\t ")) && (uid = atoi(s)) &&
(s = strtok(NULL, "\t ")) && md5_parse(md5_msg, s) &&
(s = strtok(NULL, "\t ")) && md5_parse(md5_cache, s)))
goto parse_err;
md5_mailbox_add(current, uid, md5_msg, md5_cache, 0);
}
}
fclose(file);
return(0);
parse_err:
syslog(LOG_ERR, "Invalid format input file %s at line %d",
name, lineno);
fclose(file);
return(IMAP_IOERROR);
}
static int
md5_mailbox_list_write(struct md5_mailbox_list *list, char *name)
{
struct md5_mailbox *mailbox;
struct md5_node *node;
FILE *file;
int i;
file = fopen(name, "w");
if (file == NULL && errno == ENOENT) {
if (cyrus_mkdir(name, 0750) == 0) {
file = fopen(name, "w");
}
}
if (file == NULL)
return(IMAP_IOERROR);
for (mailbox = list->head ; mailbox ; mailbox = mailbox->next) {
if (!mailbox->active)
continue;
fprintf(file, "%s %s\n", mailbox->name, mailbox->uniqueid);
for (node = mailbox->head ; node ; node = node->next) {
if (!node->active)
continue;
fprintf(file, " %lu: ", node->uid);
for (i = 0 ; i < 16 ; i++)
fprintf(file, "%-2.2x", node->md5_msg[i]);
fprintf(file, " ");
for (i = 0 ; i < 16 ; i++)
fprintf(file, "%-2.2x", node->md5_cache[i]);
fprintf(file, "\n");
}
}
fclose(file);
return(0);
}
/* ====================================================================== */
/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The
resulting message digest number will be written into the 16 bytes
beginning at RESBLOCK. */
static void *
md5_buffer (const char *buffer, size_t len, void *resblock)
{
MD5_CTX ctx;
MD5Init(&ctx);
MD5Update(&ctx, buffer, len);
MD5Final(resblock, &ctx);
return resblock;
}
/* Compute MD5 message digest for bytes read from STREAM. The
resulting message digest number will be written into the 16 bytes
beginning at RESBLOCK. */
static int
md5_stream (FILE *stream, void *resblock)
{
const char *base = NULL;
unsigned long len = 0;
map_refresh(fileno(stream), 1, &base, &len, MAP_UNKNOWN_LEN, "msg", NULL);
md5_buffer(base, len, resblock);
map_free(&base, &len);
return 0;
}
static int
md5_single(char *name, int matchlen, int maycreate, void *rock)
{
struct mailbox m;
int r = 0;
unsigned long msgno;
struct index_record record;
unsigned char md5_msg[16], md5_cache[16];
char buf[MAX_MAILBOX_PATH+1];
FILE *file;
struct md5_mailbox_list *md5_mailbox_list;
struct md5_mailbox *md5_mailbox;
struct md5_node *md5_node;
unsigned long cache_offset;
unsigned long cache_size;
if (verbose > 1)
printf(" %s\n", name);
md5_mailbox_list = (struct md5_mailbox_list *)rock;
/* First we have to jump through hoops to open the mailbox and its
Cyrus index. */
memset(&m, 0, sizeof(struct mailbox));
/* Garbage collect live cache file */
if (!r) r = mailbox_open_header(name, 0, &m);
if (r) {
syslog(LOG_NOTICE, "error opening %s: %s\n", name, error_message(r));
return(r);
}
if (!r) r = mailbox_open_index(&m);
if (r) {
syslog(LOG_NOTICE, "error opening %s: %s\n", name, error_message(r));
goto bail;
}
if (!(md5_mailbox=md5_mailbox_list_find(md5_mailbox_list, m.uniqueid))) {
/* New mailbox */
md5_mailbox = md5_mailbox_list_add(md5_mailbox_list, name, m.uniqueid);
md5_mailbox_list->dirty = 1;
}
if (!md5_mailbox) {
syslog(LOG_NOTICE, "Failed to create md5_mailbox_list for %s", name);
goto bail;
}
if (strcmp(md5_mailbox->name, m.name) != 0) {
/* Renamed mailbox */
md5_mailbox_rename(md5_mailbox, m.name);
md5_mailbox_list->dirty = 1;
}
md5_mailbox->active = 1;
md5_node = md5_mailbox->head;
for (msgno = 1 ; msgno <= m.exists ; msgno++) {
if ((r=mailbox_read_index_record(&m, msgno, &record))) {
syslog(LOG_ERR, "IOERROR: %s failed to read index record %lu/%lu",
m.name, msgno, m.exists);
r = IMAP_IOERROR;
goto bail;
}
if (record.uid == 0) {
syslog(LOG_ERR, "IOERROR: %s zero index record %lu/%lu",
m.name, msgno, m.exists);
r = IMAP_IOERROR;
goto bail;
}
/* Skip over UIDs in md5_mailbox which have now been deleted
* (but record fact that md5 list should be updated for this user) */
while (md5_node && (md5_node->uid < record.uid)) {
md5_mailbox_list->dirty = 1; /* Need to write out new MD5 list */
md5_node->active = 0;
md5_node = md5_node->next;
}
/* Check whether MD5 value already exists for this UID */
if (md5_node && (md5_node->uid == record.uid)) {
md5_node->active = 1;
md5_node = md5_node->next;
continue;
}
snprintf(buf, sizeof(buf), "%s/%lu.", m.path, record.uid);
if (!(file=fopen(buf, "r"))) {
syslog(LOG_ERR, "IOERROR: %s failed to open msg UID %lu",
m.name, record.uid);
r = IMAP_IOERROR;
goto bail;
}
if (md5_stream(file, md5_msg) != 0) {
syslog(LOG_ERR, "IOERROR: %s failed to md5 msg UID %lu",
m.name, record.uid);
r = IMAP_IOERROR;
fclose(file);
goto bail;
}
cache_offset = record.cache_offset;
cache_size = mailbox_cache_size(&m, msgno);
if (!md5_buffer(m.cache_base+cache_offset, cache_size, md5_cache)) {
syslog(LOG_ERR, "IOERROR: %s failed to md5 msg cache UID %lu",
m.name, record.uid);
r = IMAP_IOERROR;
goto bail;
}
md5_mailbox_add(md5_mailbox, record.uid, md5_msg, md5_cache, 1);
md5_mailbox_list->dirty = 1; /* Need to write out new MD5 list */
fclose(file);
}
/* Check for deletions at end of the folder */
if (md5_node)
md5_mailbox_list->dirty = 1; /* Need to write out new MD5 list */
bail:
#if 0
mailbox_unlock_expire(&m);
#endif
mailbox_close(&m);
return(r);
}
/* ====================================================================== */
/* If uid_set and uid_modulo non-zero, use existing database entry for all
* but given tranche of users. That tranche gets regenerated from scratch */
static int
use_existing_data(char *user, int uid_set, int uid_modulo, int uid_fd)
{
char buf[64];
unsigned long len;
int uid;
if ((uid_modulo == 0) || (uid_fd < 0))
return(1);
#if 0 /* XXX make sure we're not the replica */
if (cdb_seek(uid_fd, (unsigned char *)user, strlen(user), &len) != 1)
return(1);
#endif
if ((len >= sizeof(buf)) || (read(uid_fd, buf, len) != len))
return(1);
if ((uid = atoi(buf)) == 0)
return(1);
return ((uid_set == (uid % uid_modulo)) ? 0 : 1);
}
static int
do_user(const char *md5_dir, char *user, struct namespace *namespacep,
int uid_set, int uid_modulo, int uid_fd)
{
char buf[MAX_MAILBOX_PATH+1];
char buf2[MAX_MAILBOX_PATH+1];
int r = 0;
int regenerate = 0;
struct md5_mailbox_list *md5_mailbox_list = md5_mailbox_list_create();
imapd_userid = user;
imapd_authstate = auth_newstate(imapd_userid);
if (use_existing_data(user, uid_set, uid_modulo, uid_fd)) {
snprintf(buf, sizeof(buf)-1, "%s/%c/%s", md5_dir, user[0], user);
r = md5_mailbox_list_read(md5_mailbox_list, buf);
if (r) {
syslog(LOG_NOTICE, "Failed to read mailbox list for %s", user);
md5_mailbox_list_free(md5_mailbox_list);
return(r);
}
if (verbose > 0)
printf("Make_MD5: %s\n", user);
} else {
regenerate = 1;
if (verbose > 0)
printf("Make_MD5: %s (regenerating)\n", user);
}
/* Index inbox */
snprintf(buf, sizeof(buf)-1, "user.%s", user);
md5_single(buf, 0, 0, md5_mailbox_list);
/* And then all folders */
snprintf(buf, sizeof(buf)-1, "user.%s.*", user);
r = (namespacep->mboxlist_findall)(namespacep, buf, 0,
imapd_userid, imapd_authstate,
md5_single, md5_mailbox_list);
if (r) {
syslog(LOG_NOTICE, "Failed to enumerate mailboxes for %s", user);
md5_mailbox_list_free(md5_mailbox_list);
return(r);
}
auth_freestate(imapd_authstate);
/* If mailbox have been deleted, we need to rewrite */
if (md5_mailbox_list->dirty ||
md5_mailbox_list_check_deleted(md5_mailbox_list)) {
snprintf(buf, sizeof(buf)-1, "%s/%c/%s-NEW", md5_dir, user[0], user);
md5_mailbox_list_write(md5_mailbox_list, buf);
snprintf(buf, sizeof(buf)-1, "%s/%c/%s-NEW", md5_dir, user[0], user);
snprintf(buf2, sizeof(buf2)-1, "%s/%c/%s", md5_dir, user[0], user);
if (rename(buf, buf2) < 0) {
syslog(LOG_NOTICE, "Failed to rename %s -> %s", buf, buf2);
md5_mailbox_list_free(md5_mailbox_list);
return(IMAP_IOERROR);
}
}
if (regenerate)
syslog(LOG_NOTICE, "Done make_md5 for %s (regenerated)", user);
else
syslog(LOG_NOTICE, "Done make_md5 for %s", user);
md5_mailbox_list_free(md5_mailbox_list);
return(0);
}
/* ====================================================================== */
static unsigned long md5_children = 0;
static void
md5_child_reaper()
{
int status;
pid_t child;
do {
child = waitpid(0, &status, WNOHANG);
if ((child > 0) && (md5_children > 0))
md5_children--;
} while (child > 0);
}
static int
md5_signal_child_init(void (*fn)())
{
struct sigaction act, oact;
sigemptyset(&act.sa_mask);
act.sa_handler = fn;
act.sa_flags = 0;
if (sigaction(SIGCHLD, &act, &oact) == 0)
return(1);
fprintf(stderr, "[os_signal_child_init()] sigaction() failed: %s",
strerror(errno));
return(0);
}
/* ====================================================================== */
int
main(int argc, char **argv)
{
int opt;
char *alt_config = NULL;
char *input_file = NULL;
const char *md5_dir = NULL;
const char *uid_file = NULL;
int uid_fd = (-1);
int uid_set = 0;
int uid_modulo = 0;
int r = 0;
int i;
int max_children = 0;
pid_t pid;
struct namespace md5_namespace;
char buf[512];
FILE *file;
int len;
- if(geteuid() == 0)
- fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
setbuf(stdout, NULL);
while ((opt = getopt(argc, argv, "C:D:k:f:m:M:v")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'D': /* MD5 directory */
md5_dir = optarg;
break;
case 'f': /* Input file */
input_file = optarg;
break;
case 'k': /* Concurrent threads */
max_children = atoi(optarg);
break;
case 'm': /* Together with -M process fraction of users */
uid_set = atoi(optarg);
break;
case 'M': /* Together with -m process fraction of users */
uid_modulo = atoi(optarg);
break;
case 'v': /* verbose */
verbose++;
break;
default:
usage("make_md5");
}
}
/* Set up default bounds if no command line options provided */
cyrus_init(alt_config, "make_md5", 0);
syslog(LOG_NOTICE, "Generating MD5 checksums for mailboxes");
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&md5_namespace, 1)) != 0) {
fatal(error_message(r), EC_CONFIG);
}
mboxlist_init(0);
mboxlist_open(NULL);
mailbox_initialize();
/* open the quota db, we'll need it for real work */
quotadb_init(0);
quotadb_open(NULL);
signals_set_shutdown(&shut_down);
signals_add_handlers(0);
if (!input_file && (optind == argc)) {
fprintf(stderr, "please specify user to MD5\n");
shut_down(1);
}
if (!md5_dir) md5_dir = config_getstring(IMAPOPT_MD5_DIR);
if (!md5_dir)
md5_dir = xstrdup("/var/imap/md5");
if (((uid_file = config_getstring(IMAPOPT_MD5_USER_MAP)) != NULL) &&
((uid_fd=open(uid_file, O_RDONLY)) < 0)) {
syslog(LOG_NOTICE, "Failed to open uid file %s: %m\n", uid_file);
shut_down(1);
}
if (max_children == 0) {
/* Simple case */
if (input_file) {
if ((file=fopen(input_file, "r")) == NULL) {
syslog(LOG_NOTICE, "Unable to open %s: %m", input_file);
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 (do_user(md5_dir, buf, &md5_namespace,
uid_set, uid_modulo, uid_fd)) {
syslog(LOG_NOTICE, "Error make_md5 %s: %m", buf);
shut_down(1);
}
}
fclose(file);
} else for (i = optind; i < argc; i++) {
if (do_user(md5_dir, argv[i], &md5_namespace,
uid_set, uid_modulo, uid_fd)) {
syslog(LOG_NOTICE, "Error make_md5 %s: %m", argv[i]);
shut_down(1);
}
}
syslog(LOG_NOTICE, "Done MD5 checksums for mailboxes");
shut_down(0);
}
/* Enable child handler */
if (!md5_signal_child_init(md5_child_reaper)) {
fprintf(stderr, "Couldn't initialise child reaper\n");
exit(1);
}
if (input_file) {
if ((file=fopen(input_file, "r")) == NULL) {
syslog(LOG_NOTICE, "Unable to open %s: %m", input_file);
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;
while (md5_children == max_children) /* Concurrency limit */
pause();
if ((pid = fork()) < 0) {
fprintf(stderr, "Fork failed.\n");
shut_down(1);
}
if (pid == 0) {
/* Child process */
do_user(md5_dir, buf, &md5_namespace,
uid_set, uid_modulo, uid_fd);
_exit(0);
}
md5_children++; /* Parent process */
}
fclose(file);
} else for (i = optind; i < argc; i++) {
while (md5_children == max_children) /* Concurrency limit */
pause();
if ((pid = fork()) < 0) {
fprintf(stderr, "Fork failed.\n");
shut_down(1);
}
if (pid == 0) {
/* Child process */
do_user(md5_dir, argv[i], &md5_namespace,
uid_set, uid_modulo, uid_fd);
_exit(0);
}
md5_children++; /* Parent process */
}
/* Wait forall children to finish */
while (md5_children > 0)
pause();
syslog(LOG_NOTICE, "Finished generating MD5 checksums for mailboxes");
shut_down(0);
}
diff --git a/imap/mbexamine.c b/imap/mbexamine.c
index a7fba9473..e07647007 100644
--- a/imap/mbexamine.c
+++ b/imap/mbexamine.c
@@ -1,400 +1,402 @@
/* mbexamine.c -- examine the contents of a mailbox index and cache
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: mbexamine.c,v 1.13 2007/02/05 18:41:47 jeaton Exp $ */
+/* $Id: mbexamine.c,v 1.14 2007/03/30 18:40:20 murch Exp $ */
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <stdlib.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "acl.h"
#include "assert.h"
#include "bsearch.h"
#include "convert_code.h"
#include "exitcodes.h"
#include "index.h"
#include "global.h"
#include "imap_err.h"
#include "imparse.h"
#include "mailbox.h"
#include "message.h"
#include "mboxname.h"
#include "mboxlist.h"
#include "seen.h"
#include "retry.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
extern int optind;
extern char *optarg;
/* current namespace */
static struct namespace recon_namespace;
/* config.c stuff */
const int config_need_data = 0;
/* forward declarations */
int do_examine(char *name, int matchlen, int maycreate, void *rock);
void usage(void);
void shut_down(int code);
int code = 0;
int wantuid = 0;
int wantvalue = 0;
int main(int argc, char **argv)
{
int opt, i, r;
char buf[MAX_MAILBOX_PATH+1];
char *alt_config = NULL;
-/* if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); */
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
/* Ensure we're up-to-date on the index file format */
assert(INDEX_HEADER_SIZE == (OFFSET_SPARE4+4));
assert(INDEX_RECORD_SIZE == (OFFSET_MODSEQ+4));
while ((opt = getopt(argc, argv, "C:u:s:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'u':
if(wantvalue) usage();
wantuid = 1;
wantvalue = atoi(optarg);
break;
case 's':
if(wantvalue) usage();
wantvalue = atoi(optarg);
break;
default:
usage();
}
}
cyrus_init(alt_config, "mbexamine", 0);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&recon_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
mboxlist_init(0);
mboxlist_open(NULL);
signals_set_shutdown(&shut_down);
signals_add_handlers(0);
if (optind == argc) {
strlcpy(buf, "*", sizeof(buf));
(*recon_namespace.mboxlist_findall)(&recon_namespace, buf, 1, 0, 0,
do_examine, NULL);
}
for (i = optind; i < argc; i++) {
/* Handle virtdomains and separators in mailboxname */
(*recon_namespace.mboxname_tointernal)(&recon_namespace, argv[i],
NULL, buf);
(*recon_namespace.mboxlist_findall)(&recon_namespace, buf, 1, 0,
0, do_examine, NULL);
}
mboxlist_close();
mboxlist_done();
exit(code);
}
void usage(void)
{
fprintf(stderr,
"usage: mbexamine [-C <alt_config>] [-s seqnum] mailbox...\n"
" mbexamine [-C <alt_config>] [-u uid] mailbox...\n");
exit(EC_USAGE);
}
/*
* mboxlist_findall() callback function to examine a mailbox
*/
int do_examine(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock __attribute__((unused)))
{
int i,r = 0;
int flag = 0;
char ext_name_buf[MAX_MAILBOX_PATH+1];
struct mailbox mailbox;
const char *index_base;
long int start_offset, record_size;
signals_poll();
/* Convert internal name to external */
(*recon_namespace.mboxname_toexternal)(&recon_namespace, name,
"cyrus", ext_name_buf);
printf("Examining %s...\n", ext_name_buf);
/* Open/lock header */
r = mailbox_open_header(name, 0, &mailbox);
if (r) {
return r;
}
if (mailbox.header_fd != -1) {
(void) mailbox_lock_header(&mailbox);
}
mailbox.header_lock_count = 1;
if (chdir(mailbox.path) == -1) {
r = IMAP_IOERROR;
goto done;
}
/* Attempt to open/lock index */
r = mailbox_open_index(&mailbox);
if (r) {
goto done;
} else {
(void) mailbox_lock_index(&mailbox);
}
mailbox.index_lock_count = 1;
printf(" Mailbox Header Info:\n");
printf(" Path to mailbox: %s\n", mailbox.path);
printf(" Mailbox ACL: %s\n", mailbox.acl); /* xxx parse */
printf(" Unique ID: %s\n", mailbox.uniqueid);
printf(" User Flags: ");
for(i=0;i<MAX_USER_FLAGS;i++) {
if(!mailbox.flagname[i]) break;
printf("%s ", mailbox.flagname[i]);
}
if(i==0) printf("[none]");
printf("\n");
printf("\n Index Header Info:\n");
printf(" Generation Number: %d\n", mailbox.generation_no);
printf(" Format: ");
switch(mailbox.format) {
case MAILBOX_FORMAT_NORMAL:
printf("NORMAL");
break;
case MAILBOX_FORMAT_NETNEWS:
printf("NET NEWS");
break;
default:
printf("UNKNOWN");
}
printf("\n");
printf(" Minor Version: %d\n", mailbox.minor_version);
printf(" Header Size: %ld bytes Record Size: %ld bytes\n",
mailbox.start_offset, mailbox.record_size);
printf(" Number of Messages: %lu Mailbox Size: " UQUOTA_T_FMT " bytes\n",
mailbox.exists, mailbox.quota_mailbox_used);
printf(" Last Append Date: (%ld) %s", mailbox.last_appenddate,
ctime(&mailbox.last_appenddate));
printf(" UIDValidity: %ld Last UID: %ld\n", mailbox.uidvalidity,
mailbox.last_uid);
printf(" Deleted: %ld Answered: %ld Flagged: %ld\n",
mailbox.deleted, mailbox.answered, mailbox.flagged);
if (mailbox.minor_version >= 4) {
printf(" Mailbox Options:");
if (!mailbox.options) {
printf(" NONE");
} else {
if (mailbox.options & OPT_POP3_NEW_UIDL) {
printf(" POP3_NEW_UIDL");
}
if (mailbox.options & OPT_IMAP_CONDSTORE) {
printf(" IMAP_CONDSTORE");
}
}
printf("\n");
}
printf(" Last POP3 Login: (%ld) %s", mailbox.pop3_last_login,
ctime((const long *) &mailbox.pop3_last_login));
if (mailbox.minor_version >= 8) {
printf(" Highest Mod Sequence: " MODSEQ_FMT "\n",
mailbox.highestmodseq);
}
printf("\n Message Info:\n");
index_base = mailbox.index_base;
start_offset = mailbox.start_offset;
record_size = mailbox.record_size;
for(i=1; i<=mailbox.exists; i++) {
const char *cacheitem;
int j;
if(wantvalue) {
if(!wantuid) {
if(i != wantvalue) continue;
} else {
if(UID(i) != wantvalue) continue;
}
flag = 1;
}
printf("%06d> UID:%08d INT_DATE:%d SENTDATE:%d SIZE:%-6d\n",
i, UID(i), INTERNALDATE(i), SENTDATE(i),
SIZE(i));
printf(" > HDRSIZE:%-6d LASTUPD :%ld SYSFLAGS:%08X",
HEADER_SIZE(i), LAST_UPDATED(i), SYSTEM_FLAGS(i));
if (mailbox.minor_version >= 5)
printf(" LINES:%-6d", CONTENT_LINES(i));
if (mailbox.minor_version >= 6)
printf(" CACHEVER:%-6d", CACHE_VERSION(i));
if (mailbox.minor_version >= 8) {
printf(" MODSEQ:" MODSEQ_FMT, MODSEQ(i));
}
printf("\n");
printf(" > USERFLAGS:");
for(j=(MAX_USER_FLAGS/32)-1; j>=0; j--) {
printf(" %08X", USER_FLAGS(i,j));
}
printf("\n");
cacheitem = mailbox.cache_base + CACHE_OFFSET(i);
printf(" Envel>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf("BdyStr>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf(" Body>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
#if 0
/* xxx print out machine-readable bodystructure? */
printf(" Sects>\n");
#endif
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf("CacHdr>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf(" From>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf(" To>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf(" Cc>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf(" Bcc>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
printf("Subjct>{%d}%s\n", CACHE_ITEM_LEN(cacheitem),
cacheitem + CACHE_ITEM_SIZE_SKIP);
if(flag) break;
}
if(wantvalue && !flag) {
printf("Desired message not found\n");
}
done:
mailbox_close(&mailbox);
return r;
}
/*
* Cleanly shut down and exit
*/
void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
mboxlist_close();
mboxlist_done();
exit(code);
}
diff --git a/imap/mbpath.c b/imap/mbpath.c
index 39aafcfff..5d39386d3 100644
--- a/imap/mbpath.c
+++ b/imap/mbpath.c
@@ -1,165 +1,167 @@
/* mbpath.c -- help the sysadmin to find the path matching the mailbox
*
- * $Id: mbpath.c,v 1.20 2006/11/30 17:11:19 murch Exp $
+ * $Id: mbpath.c,v 1.21 2007/03/30 18:40:20 murch Exp $
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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.
*
*/
-/* static char _rcsid[] = "$Id: mbpath.c,v 1.20 2006/11/30 17:11:19 murch Exp $"; */
+/* static char _rcsid[] = "$Id: mbpath.c,v 1.21 2007/03/30 18:40:20 murch Exp $"; */
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/param.h>
#include "acl.h"
#include "util.h"
#include "auth.h"
#include "prot.h"
#include "imparse.h"
#include "lock.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "mboxlist.h"
extern int optind;
extern char *optarg;
/* current namespace */
static struct namespace mbpath_namespace;
/* config.c stuff */
const int config_need_data = 0;
static int
usage(void) {
fprintf(stderr,"usage: mbpath [-C <alt_config>] [-q] [-s] [-m] <mailbox name>...\n");
fprintf(stderr,"\t-q\tquietly drop any error messages\n");
fprintf(stderr,"\t-s\tstop on error\n");
fprintf(stderr,"\t-m\toutput the path to the metadata files (if different from the message files)\n");
exit(-1);
}
int
main(int argc, char **argv)
{
char *path, *mpath;
int rc, i, quiet = 0, stop_on_error=0, metadata=0;
int opt; /* getopt() returns an int */
char *alt_config = NULL;
char buf[MAX_MAILBOX_PATH+1];
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:qsm")) != EOF) {
switch(opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'q':
quiet = 1;
break;
case 's':
stop_on_error = 1;
break;
case 'm':
metadata = 1;
break;
default:
usage();
}
}
cyrus_init(alt_config, "mbpath", 0);
if ((rc = mboxname_init_namespace(&mbpath_namespace, 1)) != 0) {
fatal(error_message(rc), -1);
}
mboxlist_init(0);
mboxlist_open(NULL);
for (i = optind; i < argc; i++) {
(void)memset(&path, 0, sizeof(path));
/* Translate mailboxname */
(*mbpath_namespace.mboxname_tointernal)(&mbpath_namespace, argv[i], NULL, buf);
if ((rc = mboxlist_detail(buf, NULL, &path, &mpath,
NULL, NULL, NULL)) == 0) {
if (metadata && mpath) printf("%s\n", mpath);
else printf("%s\n", path);
} else {
if (!quiet && (rc == IMAP_MAILBOX_NONEXISTENT)) {
fprintf(stderr, "Invalid mailbox name: %s\n", argv[i]);
}
if (stop_on_error) {
if (quiet) {
fatal("", -1);
} else {
fatal("Error in processing mailbox. Stopping\n", -1);
}
}
}
}
mboxlist_close();
mboxlist_done();
cyrus_done();
return 0;
}
-/* $Header: /mnt/data/cyrus/cvsroot/src/cyrus/imap/mbpath.c,v 1.20 2006/11/30 17:11:19 murch Exp $ */
+/* $Header: /mnt/data/cyrus/cvsroot/src/cyrus/imap/mbpath.c,v 1.21 2007/03/30 18:40:20 murch Exp $ */
diff --git a/imap/quota.c b/imap/quota.c
index 3d5b267aa..bf777e336 100644
--- a/imap/quota.c
+++ b/imap/quota.c
@@ -1,566 +1,568 @@
/* quota.c -- program to report/reconstruct quotas
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: quota.c,v 1.67 2007/02/05 18:41:48 jeaton Exp $ */
+/* $Id: quota.c,v 1.68 2007/03/30 18:40:20 murch Exp $ */
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "assert.h"
#include "cyrusdb.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "mboxlist.h"
#include "mboxname.h"
#include "quota.h"
#include "convert_code.h"
#include "util.h"
extern int optind;
extern char *optarg;
/* current namespace */
static struct namespace quota_namespace;
/* config.c stuff */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
struct fix_rock {
char *domain;
struct txn **tid;
unsigned long change_count;
};
struct quotaentry {
struct quota quota;
int refcount;
int deleted;
uquota_t newused;
};
/* forward declarations */
void usage(void);
void reportquota(void);
int buildquotalist(char *domain, char **roots, int nroots,
struct fix_rock *frock);
int fixquota_mailbox(char *name, int matchlen, int maycreate, void *rock);
int fixquota(struct fix_rock *frock);
int fixquota_fixroot(struct mailbox *mailbox, char *root);
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count);
#define QUOTAGROW 300
struct quotaentry *quota;
int quota_num = 0, quota_alloc = 0;
int firstquota = 0;
int redofix = 0;
int partial;
int main(int argc,char **argv)
{
int opt;
int fflag = 0;
int r, code = 0;
char *alt_config = NULL, *domain = NULL;
struct fix_rock frock;
struct txn *tid = NULL;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:d:f")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'd':
domain = optarg;
break;
case 'f':
fflag = 1;
break;
default:
usage();
}
}
cyrus_init(alt_config, "quota", 0);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&quota_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
/*
* Lock mailbox list to prevent mailbox creation/deletion
* during work
*/
mboxlist_init(0);
mboxlist_open(NULL);
quotadb_init(0);
quotadb_open(NULL);
if (!r) {
frock.domain = domain;
frock.tid = &tid;
frock.change_count = 0;
r = buildquotalist(domain, argv+optind, argc-optind,
fflag ? &frock : NULL);
}
if (!r && fflag) {
partial = argc-optind;
r = fixquota(&frock);
}
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
if (r) code = convert_code(r);
else reportquota();
cyrus_done();
return code;
}
void usage(void)
{
fprintf(stderr,
"usage: quota [-C <alt_config>] [-d <domain>] [-f] [prefix]...\n");
exit(EC_USAGE);
}
void errmsg(const char *fmt, const char *arg, int err)
{
char buf[1024];
int len;
len = snprintf(buf, sizeof(buf), fmt, arg);
if (len < sizeof(buf))
len += snprintf(buf+len, sizeof(buf)-len, ": %s", error_message(err));
if ((err == IMAP_IOERROR) && (len < sizeof(buf)))
len += snprintf(buf+len, sizeof(buf)-len, ": %%m");
syslog(LOG_ERR, buf);
fprintf(stderr, "%s\n", buf);
}
/*
* A matching mailbox was found, process it.
*/
static int found_match(char *name, int matchlen, int maycreate, void *frock)
{
int r;
if (quota_num == quota_alloc) {
/* Create new qr list entry */
quota_alloc += QUOTAGROW;
quota = (struct quotaentry *)
xrealloc((char *)quota, quota_alloc * sizeof(struct quotaentry));
memset(&quota[quota_num], 0, QUOTAGROW * sizeof(struct quotaentry));
}
/* See if the mailbox name corresponds to a quotaroot */
quota[quota_num].quota.root = name;
do {
r = quota_read(&quota[quota_num].quota, NULL, 0);
} while (r == IMAP_AGAIN);
switch (r) {
case 0:
/* Its a quotaroot! */
quota[quota_num++].quota.root = xstrdup(name);
break;
case IMAP_QUOTAROOT_NONEXISTENT:
if (!frock || !quota_num ||
strncmp(name, quota[quota_num-1].quota.root,
strlen(quota[quota_num-1].quota.root))) {
/* Its not a quotaroot, and either we're not fixing quotas,
or its not part of the most recent quotaroot */
return 0;
}
break;
default:
return r;
break;
}
if (frock) {
/* Recalculate the quota (we need the subfolders too!) */
r = fixquota_mailbox(name, matchlen, maycreate, frock);
}
return r;
}
/*
* Build the list of quota roots in 'quota'
*/
int buildquotalist(char *domain, char **roots, int nroots,
struct fix_rock *frock)
{
int i, r;
char buf[MAX_MAILBOX_NAME+1], *tail;
size_t domainlen = 0;
buf[0] = '\0';
tail = buf;
if (domain) {
domainlen = snprintf(buf, sizeof(buf), "%s!", domain);
tail += domainlen;
}
/*
* Walk through all given pattern(s) and resolve them to all
* matching mailbox names. Call found_match() for every mailbox
* name found. If no pattern is given, assume "*".
*/
i = 0;
do {
if (nroots > 0) {
/* Translate separator in quotaroot.
*
* We do this directly instead of using the mboxname_tointernal()
* function pointer because we know that we are using the internal
* namespace and so we don't have to allocate a buffer for the
* translated name.
*/
mboxname_hiersep_tointernal(&quota_namespace, roots[i], 0);
strlcpy(tail, roots[i], sizeof(buf) - domainlen);
}
else {
strlcpy(tail, "*", sizeof(buf) - domainlen);
}
i++;
r = (*quota_namespace.mboxlist_findall)(&quota_namespace, buf, 1, 0, 0,
&found_match, frock);
if (r < 0) {
errmsg("failed building quota list for '%s'", buf, IMAP_IOERROR);
return IMAP_IOERROR;
}
} while (i < nroots);
return 0;
}
/*
* Account for mailbox 'name' when fixing the quota roots
*/
int fixquota_mailbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
int r;
struct mailbox mailbox;
int i, len, thisquota, thisquotalen;
struct fix_rock *frock = (struct fix_rock *) rock;
char *p, *domain = frock->domain;
/* make sure the domains match */
if (domain &&
(!(p = strchr(name, '!')) || (p - name) != strlen(domain) ||
strncmp(name, domain, p - name))) {
return 0;
}
while (firstquota < quota_num &&
strncmp(name, quota[firstquota].quota.root,
strlen(quota[firstquota].quota.root)) > 0) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
if (r) return r;
}
thisquota = -1;
thisquotalen = 0;
for (i = firstquota;
i < quota_num && strcmp(name, quota[i].quota.root) >= 0; i++) {
len = strlen(quota[i].quota.root);
if (!strncmp(name, quota[i].quota.root, len) &&
(!name[len] || name[len] == '.' ||
(domain && name[len-1] == '!'))) {
quota[i].refcount++;
if (len > thisquotalen) {
thisquota = i;
thisquotalen = len;
}
}
}
if (partial && thisquota == -1) return 0;
r = mailbox_open_header(name, 0, &mailbox);
if (r) errmsg("failed opening header for mailbox '%s'", name, r);
else {
if (thisquota == -1) {
if (mailbox.quota.root) {
r = fixquota_fixroot(&mailbox, (char *)0);
}
}
else {
if (!mailbox.quota.root ||
strcmp(mailbox.quota.root, quota[thisquota].quota.root) != 0) {
r = fixquota_fixroot(&mailbox, quota[thisquota].quota.root);
}
if (!r) {
r = mailbox_open_index(&mailbox);
if (r) errmsg("failed opening index for mailbox '%s'", name, r);
}
if (!r) quota[thisquota].newused += mailbox.quota_mailbox_used;
}
mailbox_close(&mailbox);
}
if (r) {
/* mailbox error of some type, commit what we have */
quota_commit(frock->tid);
*(frock->tid) = NULL;
}
return r;
}
int fixquota_fixroot(struct mailbox *mailbox,
char *root)
{
int r;
redofix = 1;
r = mailbox_lock_header(mailbox);
if (r) {
errmsg("failed locking header for mailbox '%s'", mailbox->name, r);
return r;
}
printf("%s: quota root %s --> %s\n", mailbox->name,
mailbox->quota.root ? mailbox->quota.root : "(none)",
root ? root : "(none)");
if (mailbox->quota.root) free(mailbox->quota.root);
if (root) {
mailbox->quota.root = xstrdup(root);
}
else {
mailbox->quota.root = 0;
}
r = mailbox_write_header(mailbox);
(void) mailbox_unlock_header(mailbox);
if (r) errmsg("failed writing header for mailbox '%s'", mailbox->name, r);
return r;
}
/*
* Finish fixing up a quota root
*/
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count)
{
int r = 0;
if (!quota[thisquota].refcount) {
if (!quota[thisquota].deleted++) {
printf("%s: removed\n", quota[thisquota].quota.root);
r = quota_delete(&quota[thisquota].quota, tid);
if (r) {
errmsg("failed deleting quotaroot '%s'",
quota[thisquota].quota.root, r);
return r;
}
(*count)++;
free(quota[thisquota].quota.root);
quota[thisquota].quota.root = NULL;
}
return 0;
}
if (quota[thisquota].quota.used != quota[thisquota].newused) {
/* re-read the quota with the record locked */
r = quota_read(&quota[thisquota].quota, tid, 1);
if (r) {
errmsg("failed reading quotaroot '%s'",
quota[thisquota].quota.root, r);
return r;
}
(*count)++;
}
if (quota[thisquota].quota.used != quota[thisquota].newused) {
printf("%s: usage was " UQUOTA_T_FMT ", now " UQUOTA_T_FMT "\n",
quota[thisquota].quota.root,
quota[thisquota].quota.used, quota[thisquota].newused);
quota[thisquota].quota.used = quota[thisquota].newused;
r = quota_write(&quota[thisquota].quota, tid);
if (r) {
errmsg("failed writing quotaroot '%s'",
quota[thisquota].quota.root, r);
return r;
}
(*count)++;
}
/* commit the transaction every 100 changes */
if (*count && !(*count % 100)) {
quota_commit(tid);
*tid = NULL;
}
return r;
}
/*
* Fix all the quota roots
*/
int fixquota(struct fix_rock *frock)
{
int i, r = 0;
while (!r && redofix) {
while (!r && firstquota < quota_num) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
}
redofix = 0;
firstquota = 0;
/*
* Loop over all qr entries and recalculate the quota.
* We need the subfolders too!
*/
for (i = 0; !r && i < quota_num; i++) {
r = (*quota_namespace.mboxlist_findall)(&quota_namespace,
quota[i].quota.root,
1, 0, 0, fixquota_mailbox,
frock);
}
}
while (!r && firstquota < quota_num) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
}
if (!r && *(frock->tid)) quota_commit(frock->tid);
return 0;
}
/*
* Print out the quota report
*/
void reportquota(void)
{
int i;
char buf[MAX_MAILBOX_PATH+1];
printf(" Quota %% Used Used Root\n");
for (i = 0; i < quota_num; i++) {
if (quota[i].deleted) continue;
if (quota[i].quota.limit > 0) {
printf(" %7d " QUOTA_REPORT_FMT , quota[i].quota.limit,
((quota[i].quota.used / QUOTA_UNITS) * 100) / quota[i].quota.limit);
}
else if (quota[i].quota.limit == 0) {
printf(" 0 ");
}
else {
printf(" ");
}
/* Convert internal name to external */
(*quota_namespace.mboxname_toexternal)(&quota_namespace,
quota[i].quota.root,
"cyrus", buf);
printf(" " QUOTA_REPORT_FMT " %s\n",
quota[i].quota.used / QUOTA_UNITS, buf);
}
}
diff --git a/imap/reconstruct.c b/imap/reconstruct.c
index 244ab7d23..35bc4cc4f 100644
--- a/imap/reconstruct.c
+++ b/imap/reconstruct.c
@@ -1,1314 +1,1316 @@
/* reconstruct.c -- program to reconstruct a mailbox
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: reconstruct.c,v 1.91 2007/02/09 13:18:42 murch Exp $ */
+/* $Id: reconstruct.c,v 1.92 2007/03/30 18:40:20 murch Exp $ */
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <stdlib.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "acl.h"
#include "assert.h"
#include "bsearch.h"
#include "imparse.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "map.h"
#include "message.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "global.h"
#include "mboxname.h"
#include "mboxlist.h"
#include "quota.h"
#include "seen.h"
#include "retry.h"
#include "convert_code.h"
#include "util.h"
#include "sync_log.h"
extern int optind;
extern char *optarg;
struct discovered {
char *name;
struct discovered *next;
};
struct uniqmailid {
char * uniqmbxid;
char *uniqname;
struct uniqmailid *uniqnext;
};
struct uniqmailid *uniqmid_head;
/* current namespace */
static struct namespace recon_namespace;
/* config.c stuff */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
/* forward declarations */
void do_mboxlist(void);
int do_reconstruct(char *name, int matchlen, int maycreate, void *rock);
int reconstruct(char *name, struct discovered *l);
void usage(void);
char * getmailname (char * mailboxname);
struct uniqmailid * add_uniqid (char * mailboxname, char * mailboxid);
struct uniqmailid * find_uniqid (char * mailboxname, char * mailboxid);
extern cyrus_acl_canonproc_t mboxlist_ensureOwnerRights;
int code = 0;
int keepflag = 0;
int syncflag = 0;
int main(int argc, char **argv)
{
int opt, i, r;
int rflag = 0;
int mflag = 0;
int fflag = 0;
int xflag = 0;
char buf[MAX_MAILBOX_PATH+1];
char mbbuf[MAX_MAILBOX_PATH+1];
struct discovered head;
char *alt_config = NULL;
char *start_part = NULL;
const char *start_part_path = NULL, *start_part_mpath = NULL, *path;
memset(&head, 0, sizeof(head));
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
/* Ensure we're up-to-date on the index file format */
assert(INDEX_HEADER_SIZE == (OFFSET_SPARE4+4));
assert(INDEX_RECORD_SIZE == (OFFSET_MODSEQ+4));
while ((opt = getopt(argc, argv, "C:kp:rmfsx")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'k':
keepflag = 1;
break;
case 'p':
start_part = optarg;
break;
case 'r':
rflag = 1;
break;
case 'm':
mflag = 1;
break;
case 'f':
fflag = 1;
break;
case 's':
syncflag = 1;
break;
case 'x':
xflag = 1;
break;
default:
usage();
}
}
cyrus_init(alt_config, "reconstruct", 0);
global_sasl_init(1,0,NULL);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&recon_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
if (syncflag)
sync_log_init();
if(start_part) {
/* Get partition's path */
start_part_path = config_partitiondir(start_part);
if (!start_part_path) {
fatal(error_message(IMAP_PARTITION_UNKNOWN), EC_USAGE);
}
start_part_mpath = config_metapartitiondir(start_part);
}
if (mflag) {
if (rflag || fflag || optind != argc) {
cyrus_done();
usage();
}
do_mboxlist();
}
mboxlist_init(0);
mboxlist_open(NULL);
quotadb_init(0);
quotadb_open(NULL);
mailbox_reconstructmode();
/* Deal with nonexistent mailboxes */
if (start_part) {
/* We were handed a mailbox that does not exist currently */
if(optind == argc) {
fprintf(stderr,
"When using -p, you must specify a mailbox to attempt to reconstruct.");
exit(EC_USAGE);
}
/* do any of the mailboxes exist in mboxlist already? */
/* Do they look like mailboxes? */
for (i = optind; i < argc; i++) {
struct stat sbuf;
if(strchr(argv[i],'%') || strchr(argv[i],'*')) {
fprintf(stderr, "Using wildcards with -p is not supported.\n");
exit(EC_USAGE);
}
/* Translate mailboxname */
(*recon_namespace.mboxname_tointernal)(&recon_namespace, argv[i],
NULL, buf);
/* Does it exist */
do {
r = mboxlist_lookup(buf, NULL, NULL);
} while (r == IMAP_AGAIN);
if(r != IMAP_MAILBOX_NONEXISTENT) {
fprintf(stderr,
"Mailbox %s already exists. Cannot specify -p.\n",
argv[i]);
exit(EC_USAGE);
}
/* Does the suspected path *look* like a mailbox? */
path = (start_part_mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_HEADER)) ?
start_part_mpath : start_part_path;
mailbox_hash_mbox(mbbuf, sizeof(mbbuf), path, buf);
strlcat(mbbuf, FNAME_HEADER, sizeof(mbbuf));
if(stat(mbbuf, &sbuf) < 0) {
fprintf(stderr,
"%s does not appear to be a mailbox (no %s).\n",
argv[i], mbbuf);
exit(EC_USAGE);
}
}
/* None of them exist. Create them. */
for (i = optind; i < argc; i++) {
/* Translate mailboxname */
(*recon_namespace.mboxname_tointernal)(&recon_namespace, argv[i],
NULL, buf);
r = mboxlist_createmailbox(buf, 0, start_part, 1,
"cyrus", NULL, 0, 0, !xflag);
if(r) {
fprintf(stderr, "could not create %s\n", argv[i]);
}
}
}
/* Normal Operation */
if (optind == argc) {
if (rflag) {
fprintf(stderr, "please specify a mailbox to recurse from\n");
cyrus_done();
exit(EC_USAGE);
}
assert(!rflag);
strlcpy(buf, "*", sizeof(buf));
(*recon_namespace.mboxlist_findall)(&recon_namespace, buf, 1, 0, 0,
do_reconstruct, NULL);
}
for (i = optind; i < argc; i++) {
char *domain = NULL;
/* save domain */
if (config_virtdomains) domain = strchr(argv[i], '@');
strlcpy(buf, argv[i], sizeof(buf));
/* Translate any separators in mailboxname */
mboxname_hiersep_tointernal(&recon_namespace, buf,
config_virtdomains ?
strcspn(buf, "@") : 0);
/* reconstruct the first mailbox/pattern */
(*recon_namespace.mboxlist_findall)(&recon_namespace, buf, 1, 0,
0, do_reconstruct,
fflag ? &head : NULL);
if (rflag) {
/* build a pattern for submailboxes */
/* XXX mboxlist_findall() is destructive and removes domain */
strlcat(buf, ".*", sizeof(buf));
/* append the domain */
if (domain) strlcat(buf, domain, sizeof(buf));
/* reconstruct the submailboxes */
(*recon_namespace.mboxlist_findall)(&recon_namespace, buf, 1, 0,
0, do_reconstruct,
fflag ? &head : NULL);
}
}
/* examine our list to see if we discovered anything */
while (head.next) {
struct discovered *p;
int r = 0;
p = head.next;
head.next = p->next;
/* create p (database only) and reconstruct it */
/* partition is defined by the parent mailbox */
r = mboxlist_createmailbox(p->name, 0, NULL, 1,
"cyrus", NULL, 0, 0, !xflag);
if (!r) {
do_reconstruct(p->name, strlen(p->name), 0, &head);
} else {
fprintf(stderr, "createmailbox %s: %s\n",
p->name, error_message(r));
}
/* may have added more things into our list */
free(p->name);
free(p);
}
mboxlist_close();
mboxlist_done();
quotadb_close();
quotadb_done();
cyrus_done();
return code;
}
void usage(void)
{
fprintf(stderr,
"usage: reconstruct [-C <alt_config>] [-p partition] [-ksrfx] mailbox...\n");
fprintf(stderr, " reconstruct [-C <alt_config>] -m\n");
exit(EC_USAGE);
}
int compare_uid(const void *a, const void *b)
{
return *(unsigned long *)a - *(unsigned long *)b;
}
#define UIDGROW 300
/*
* mboxlist_findall() callback function to reconstruct a mailbox
*/
int
do_reconstruct(char *name,
int matchlen,
int maycreate __attribute__((unused)),
void *rock)
{
int r;
char buf[MAX_MAILBOX_PATH+1];
static char lastname[MAX_MAILBOX_PATH+1] = "";
signals_poll();
/* don't repeat */
if (matchlen == strlen(lastname) &&
!strncmp(name, lastname, matchlen)) return 0;
if(matchlen >= sizeof(lastname))
matchlen = sizeof(lastname) - 1;
strncpy(lastname, name, matchlen);
lastname[matchlen] = '\0';
r = reconstruct(lastname, rock);
if (r) {
com_err(name, r, (r == IMAP_IOERROR) ? error_message(errno) : NULL);
code = convert_code(r);
} else {
/* Convert internal name to external */
(*recon_namespace.mboxname_toexternal)(&recon_namespace, lastname,
NULL, buf);
printf("%s\n", buf);
}
return 0;
}
int
reconstruct_expunge (char * path, struct mailbox * mailbox,
unsigned long **expuid, int *expuid_num)
{
char fnamebuf[MAX_MAILBOX_PATH+1];
char newfnamebuf[MAX_MAILBOX_PATH+1];
struct stat sbuf;
char buf[((INDEX_HEADER_SIZE > INDEX_RECORD_SIZE) ?
INDEX_HEADER_SIZE : INDEX_RECORD_SIZE)];
int expunge_fd;
FILE *fexpunge;
const char *index_base = NULL;
unsigned long index_len = 0; /* mapped size */
int format;
int minor_version;
unsigned long start_offset;
unsigned long record_size;
unsigned long exists;
unsigned long uid;
char msgfname[MAILBOX_FNAME_LEN+1];
unsigned msgno;
const char *p;
int n;
unsigned long *expuid_array = NULL;
int expuid_idx = 0;
int expuid_alloc = 0;
int expmsg;
*expuid = NULL;
*expuid_num = 0;
/* Open and validate the cyrus.expunge file */
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_EXPUNGE_INDEX, sizeof(fnamebuf));
if (stat(fnamebuf, &sbuf) == -1 ||
sbuf.st_size <= OFFSET_LEAKED_CACHE ||
(expunge_fd = open(fnamebuf, O_RDONLY, 0666)) == -1) {
unlink(fnamebuf);
return (0);
}
/* Map the file */
map_refresh(expunge_fd, 1, &index_base,
&index_len, sbuf.st_size, "expunge", mailbox->name);
format = ntohl(*((bit32 *)(index_base+OFFSET_FORMAT)));
minor_version = ntohl(*((bit32 *)(index_base+OFFSET_MINOR_VERSION)));
start_offset = ntohl(*((bit32 *)(index_base+OFFSET_START_OFFSET)));
record_size = ntohl(*((bit32 *)(index_base+OFFSET_RECORD_SIZE)));
exists = ntohl(*((bit32 *)(index_base+OFFSET_EXISTS)));
/* Sanity check the header as best we can */
if ((format != 0) || (exists == 0) ||
(minor_version == 0) || (minor_version > MAILBOX_MINOR_VERSION) ||
(start_offset == 0) || (start_offset > INDEX_HEADER_SIZE) ||
(record_size == 0) || (record_size > INDEX_RECORD_SIZE) ||
(sbuf.st_size < (start_offset + exists * record_size))) {
syslog(LOG_ERR, "Unable to verify header - deleting: %s", fnamebuf);
close (expunge_fd);
map_free(&index_base, &index_len);
unlink(fnamebuf);
return (0);
}
/*
* verify the message files exist -
* optionally deleting message files
*/
p = index_base + start_offset;
for (msgno = 1; msgno <= exists; msgno++, p += record_size) {
uid = ntohl(*((bit32 *)(p + OFFSET_UID)));
/* Sanity check UID */
if (uid == 0) {
syslog(LOG_ERR, "IOERROR: %s zero expunge record %u/%lu",
fnamebuf, msgno, exists);
break;
}
mailbox_message_get_fname(mailbox, uid, msgfname, sizeof(msgfname));
if (keepflag == 0) {
unlink(msgfname);
continue;
}
if ( (stat (msgfname, &sbuf)) != 0) {
syslog (LOG_ERR, "IOERROR: %m while stat\'ing message %s", msgfname);
continue;
}
if (sbuf.st_size == 0) {
/* Zero-length message file--blow it away */
unlink(msgfname);
continue;
}
if (expuid_idx == expuid_alloc) {
expuid_alloc += UIDGROW;
expuid_array = (unsigned long *) xrealloc((char *)*expuid,
expuid_alloc * sizeof(unsigned long));
}
expuid_array[expuid_idx] = uid;
expuid_idx++;
}
*expuid = expuid_array;
*expuid_num = expuid_idx;
if (keepflag == 0) {
map_free(&index_base, &index_len);
close (expunge_fd);
unlink(fnamebuf);
return (0);
}
/*
* If the number of message files in the old expunge file
* is not equal to what we just counted,
* we'll rewrite the expunge file.
* If we found no message files -- blow away cyrus.expunge
*/
if (exists == expuid_idx) {
close(expunge_fd);
map_free(&index_base, &index_len);
} else if ( expuid_idx == 0) {
close(expunge_fd);
map_free(&index_base, &index_len);
unlink (fnamebuf);
} else {
/* we must rewrite the cyrus.expunge file */
strcpy (newfnamebuf, fnamebuf);
strcat (newfnamebuf, ".NEW");
fexpunge = fopen(newfnamebuf, "w+");
if (fexpunge == NULL) {
syslog (LOG_ERR, "IOERROR: %m while creating: %s", newfnamebuf);
close(expunge_fd);
map_free(&index_base, &index_len);
free(expuid);
return IMAP_IOERROR;
}
/*
* verify the message files exist -
* optionally deleting message files
*/
p = index_base + start_offset;
for (msgno = 1; msgno <= exists; msgno++, p += record_size) {
unsigned long fileuid = ntohl(*((bit32 *)(p + OFFSET_UID)));
for (expmsg = 0; expmsg < expuid_idx; expmsg++) {
if (fileuid == expuid_array [expmsg]) {
n = fwrite(buf, 1, INDEX_RECORD_SIZE, fexpunge);
if (n != INDEX_RECORD_SIZE) {
syslog (LOG_ERR,
"IOERROR: %m writing cyrus.expunge record: %s", newfnamebuf);
close(expunge_fd);
map_free(&index_base, &index_len);
free(expuid);
fclose(fexpunge);
return IMAP_IOERROR;
}
break;
}
}
}
rewind (fexpunge);
*((bit32 *)(buf+OFFSET_EXISTS)) = htonl(*expuid_num);
n = fwrite(buf, 1, INDEX_HEADER_SIZE, fexpunge);
if (n != INDEX_HEADER_SIZE || ferror(fexpunge) ) {
syslog (LOG_ERR, "IOERROR: %m writing expunge header: %s", newfnamebuf);
close(expunge_fd);
map_free(&index_base, &index_len);
free(expuid);
fclose(fexpunge);
return IMAP_IOERROR;
}
close(expunge_fd);
map_free(&index_base, &index_len);
unlink(fnamebuf);
fchmod (fileno (fexpunge),
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |S_IWOTH);
fclose(fexpunge);
if (rename(newfnamebuf, fnamebuf)) {
free(expuid);
return IMAP_IOERROR;
}
}
if (expuid_idx > 0) {
qsort((char *)expuid_array, expuid_idx, sizeof(expuid_array), compare_uid);
}
return (0);
}
char *
getmailname (char * mailboxname)
{
static char namebuf[MAX_MAILBOX_PATH + 1];
static int namebuflen;
char * pname;
strlcpy (namebuf, mailboxname, sizeof (namebuf));
pname = strchr (namebuf, '.');
if (pname) {
pname = strchr(pname + 1, '.');
if (pname)
*pname = '\0';
}
return (namebuf);
}
struct uniqmailid *
find_uniqid ( char * mailboxname, char * mailboxid)
{
struct uniqmailid *puniq;
char * nameptr;
nameptr = getmailname (mailboxname);
for (puniq = uniqmid_head; puniq != NULL; puniq = puniq->uniqnext) {
if (strcmp (puniq->uniqmbxid, mailboxid) == 0) {
if (strcmp (puniq->uniqname, nameptr) == 0) {
return (puniq);
}
}
}
return NULL;
}
struct uniqmailid *
add_uniqid ( char * mailboxname, char * mailboxid)
{
struct uniqmailid *puniq;
char *pboxname;
pboxname = getmailname (mailboxname);
puniq = xmalloc (sizeof (struct uniqmailid));
puniq->uniqmbxid = xstrdup(mailboxid);
puniq->uniqname = xstrdup(pboxname);
puniq->uniqnext = uniqmid_head;
uniqmid_head = puniq;
return (puniq);
}
/*
* Reconstruct the single mailbox named 'name'
*/
int reconstruct(char *name, struct discovered *found)
{
char buf[((INDEX_HEADER_SIZE > INDEX_RECORD_SIZE) ?
INDEX_HEADER_SIZE : INDEX_RECORD_SIZE)];
char quota_root[MAX_MAILBOX_PATH+1];
bit32 valid_user_flags[MAX_USER_FLAGS/32];
struct mailbox mailbox;
int r = 0;
int i, n, hasquota, flag;
int format = MAILBOX_FORMAT_NORMAL;
char *p;
char fnamebuf[MAX_MAILBOX_PATH+1], newfnamebuf[MAX_MAILBOX_PATH+1];
FILE *newindex, *msgfile;
DIR *dirp;
struct dirent *dirent;
struct stat sbuf;
int newcache_fd;
unsigned long *uid;
int uid_num, uid_alloc;
unsigned long *expuid;
int expuid_num;
int expmsg;
int msg, old_msg = 0;
int new_exists = 0,
new_answered = 0,
new_flagged = 0,
new_deleted = 0;
char *list_acl, *list_part;
int list_type;
uquota_t new_quota = 0;
struct index_record message_index, old_index;
static struct index_record zero_index;
struct body *body = NULL;
char *mypath, *mympath, *mypart, *myacl;
int mytype;
char mbpath[MAX_MAILBOX_PATH+1], *path;
int expunge_found;
char unique_buf[32];
/* Start by looking up current data in mailbox list */
r = mboxlist_detail(name, &mytype, &mypath, &mympath,
&mypart, &myacl, NULL);
if(r) return r;
/* stat for header, if it is not there, we need to create it
* note that we do not want to wind up with a fully-open mailbox,
* so we will re-open. */
path = (mympath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_HEADER)) ?
mympath : mypath;
snprintf(mbpath, sizeof(mbpath), "%s%s", path, FNAME_HEADER);
if(stat(mbpath, &sbuf) == -1) {
/* Header doesn't exist, create it! */
r = mailbox_create(name, mypart, myacl, NULL,
((mytype & MBTYPE_NETNEWS) ?
MAILBOX_FORMAT_NETNEWS :
MAILBOX_FORMAT_NORMAL), NULL);
if(r) return r;
}
/* Now open just the header (it will hopefully be valid) */
r = mailbox_open_header(name, 0, &mailbox);
if (r) return r;
if (mailbox.header_fd != -1) {
(void) mailbox_lock_header(&mailbox);
}
mailbox.header_lock_count = 1;
if (chdir(mailbox.path) == -1) {
return IMAP_IOERROR;
}
/* Fix quota root */
hasquota = quota_findroot(quota_root, sizeof(quota_root), mailbox.name);
if (mailbox.quota.root) free(mailbox.quota.root);
if (hasquota) {
mailbox.quota.root = xstrdup(quota_root);
}
else {
mailbox.quota.root = 0;
}
/* Validate user flags */
for (i = 0; i < MAX_USER_FLAGS/32; i++) {
valid_user_flags[i] = 0;
}
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if (!mailbox.flagname[flag]) continue;
if ((flag && !mailbox.flagname[flag-1]) ||
!imparse_isatom(mailbox.flagname[flag])) {
free(mailbox.flagname[flag]);
mailbox.flagname[flag] = 0;
}
valid_user_flags[flag/32] |= 1<<(flag&31);
}
/* Verify ACL and update mboxlist if needed */
r = mailbox_read_header_acl(&mailbox);
if (r) return r;
r = mboxlist_detail(name, &list_type, NULL, NULL,
&list_part, &list_acl, NULL);
if (r) return r;
if(strcmp(list_acl, mailbox.acl)) {
r = mboxlist_update(name, list_type, list_part, mailbox.acl, 0);
}
if(r) return r;
/* Attempt to open/lock index */
r = mailbox_open_index(&mailbox);
if (r) {
mailbox.exists = 0;
mailbox.last_uid = 0;
mailbox.last_appenddate = 0;
mailbox.uidvalidity = time(0);
/* If we can't read the index, assume new UIDL so that stupid clients
will retrieve all of the messages in the mailbox. */
mailbox.options = OPT_POP3_NEW_UIDL;
mailbox.highestmodseq = 1;
}
else {
(void) mailbox_lock_index(&mailbox);
}
mailbox.index_lock_count = 1;
mailbox.pop3_last_login = 0;
/* Create new index/cache files */
path = (mailbox.mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_INDEX)) ?
mailbox.mpath : mailbox.path;
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_INDEX, sizeof(fnamebuf));
strlcat(fnamebuf, ".NEW", sizeof(fnamebuf));
newindex = fopen(fnamebuf, "w+");
if (!newindex) {
mailbox_close(&mailbox);
return IMAP_IOERROR;
}
path = (mailbox.mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_CACHE)) ?
mailbox.mpath : mailbox.path;
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_CACHE, sizeof(fnamebuf));
strlcat(fnamebuf, ".NEW", sizeof(fnamebuf));
newcache_fd = open(fnamebuf, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (newcache_fd == -1) {
fclose(newindex);
mailbox_close(&mailbox);
return IMAP_IOERROR;
}
memset(buf, 0, sizeof(buf));
*((bit32 *)(buf+OFFSET_GENERATION_NO)) = htonl(mailbox.generation_no + 1);
fwrite(buf, 1, INDEX_HEADER_SIZE, newindex);
retry_write(newcache_fd, buf, sizeof(bit32));
if (reconstruct_expunge ( path, &mailbox, &expuid, &expuid_num) != 0) {
fclose(newindex);
mailbox_close(&mailbox);
return IMAP_IOERROR;
}
/* Find all message files in directory */
uid = (unsigned long *) xmalloc(UIDGROW * sizeof(unsigned long));
uid_num = 0;
uid_alloc = UIDGROW;
dirp = opendir(".");
if (!dirp) {
fclose(newindex);
close(newcache_fd);
mailbox_close(&mailbox);
free(uid);
return IMAP_IOERROR;
}
while ((dirent = readdir(dirp))!=NULL) {
if (!isdigit((int) (dirent->d_name[0])) || dirent->d_name[0] == '0')
continue;
p = dirent->d_name;
msg = 0;
while (isdigit((int) *p)) {
msg = msg * 10 + *p++ - '0';
}
if (*p++ != '.') continue;
if (*p) continue;
if (uid_num == uid_alloc) {
uid_alloc += UIDGROW;
uid = (unsigned long *)
xrealloc((char *)uid, uid_alloc * sizeof(unsigned long));
}
uid[uid_num] = msg;
uid_num++;
}
closedir(dirp);
qsort((char *)uid, uid_num, sizeof(*uid), compare_uid);
/* Put each message file in the new index/cache */
old_msg = 0;
old_index.uid = 0;
mailbox.format = format;
if (mailbox.cache_fd) close(mailbox.cache_fd);
mailbox.cache_fd = newcache_fd;
for (msg = 0; msg < uid_num; msg++) {
char msgfname[MAILBOX_FNAME_LEN+1];
message_index = zero_index;
message_index.uid = uid[msg];
mailbox_message_get_fname(&mailbox, uid[msg], msgfname, sizeof(msgfname));
msgfile = fopen(msgfname, "r");
if (!msgfile) {
fprintf(stderr, "reconstruct: fopen() failed for '%s' [error=%d] -- skipping.\n",
msgfname, errno);
continue;
}
if (fstat(fileno(msgfile), &sbuf)) {
fclose(msgfile);
continue;
}
if (sbuf.st_size == 0) {
/* Zero-length message file--blow it away */
fclose(msgfile);
unlink(msgfname);
continue;
}
/* Check if this message is on the expunge list */
expunge_found = 0;
for (expmsg = 0; expmsg < expuid_num; expmsg++) {
if (uid[msg] == expuid [expmsg]) {
expunge_found = 1;
break;
}
if (uid[msg] > expuid [expmsg]) {
break;
}
}
if ( expunge_found == 0 ) {
/* Find old index record, if it exists */
while (old_msg < mailbox.exists && old_index.uid < uid[msg]) {
if (mailbox_read_index_record(&mailbox, ++old_msg, &old_index)) {
old_index.uid = 0;
}
}
if (old_index.uid == uid[msg]) {
/* Use data in old index file, subject to validity checks */
message_index.internaldate = old_index.internaldate;
message_index.modseq = old_index.modseq;
/* This should never happen, but bugs in 2.3.4 and 2.3.5
* could have left modseq blank. If so, update it */
if (!message_index.modseq) message_index.modseq = 1;
message_index.system_flags = old_index.system_flags &
(FLAG_ANSWERED|FLAG_FLAGGED|FLAG_DELETED|FLAG_DRAFT);
for (i = 0; i < MAX_USER_FLAGS/32; i++) {
message_index.user_flags[i] =
old_index.user_flags[i] & valid_user_flags[i];
}
/* Copy across MessageUUID if confident that data on disk */
message_uuid_copy(&message_index.uuid, &old_index.uuid);
}
else {
/* Message file write time is good estimate of internaldate */
message_index.internaldate = sbuf.st_mtime;
/* If we are recovering a message, assume new UIDL
so that stupid clients will retrieve this message */
mailbox.options |= OPT_POP3_NEW_UIDL;
/* Wipe the Message UUID */
message_uuid_set_null(&message_index.uuid);
/* If we are recovering a message, reset MODSEQ */
message_index.modseq = 1;
}
message_index.last_updated = time(0);
if (message_index.modseq > mailbox.highestmodseq) {
mailbox.highestmodseq = message_index.modseq;
}
}
if (((r = message_parse_file(msgfile, NULL, NULL, &body)) != 0) ||
((r = message_create_record(&mailbox, &message_index, body)) != 0)) {
fclose(msgfile);
fclose(newindex);
mailbox_close(&mailbox);
free(uid);
if (expuid_num) {
free(expuid);
}
return r;
}
fclose(msgfile);
if (body) message_free_body(body);
if (expunge_found == 0) {
/* Write out new entry in index file */
mailbox_index_record_to_buf(&message_index, buf);
n = fwrite(buf, 1, INDEX_RECORD_SIZE, newindex);
if (n != INDEX_RECORD_SIZE) {
fclose(newindex);
mailbox_close(&mailbox);
free(uid);
if (expuid_num) {
free(expuid);
}
return IMAP_IOERROR;
}
new_exists++;
if (message_index.system_flags & FLAG_ANSWERED) new_answered++;
if (message_index.system_flags & FLAG_FLAGGED) new_flagged++;
if (message_index.system_flags & FLAG_DELETED) new_deleted++;
new_quota += message_index.size;
}
}
if (expuid_num) {
free (expuid);
}
if (body) free(body);
/* Write out new index file header */
rewind(newindex);
if (uid_num && mailbox.last_uid < uid[uid_num-1]) {
mailbox.last_uid = uid[uid_num-1] + 100;
}
if (mailbox.last_appenddate == 0 || mailbox.last_appenddate > time(0)) {
mailbox.last_appenddate = time(0);
}
if (mailbox.uidvalidity == 0 || mailbox.uidvalidity > time(0)) {
mailbox.uidvalidity = time(0);
}
free(uid);
*((bit32 *)(buf+OFFSET_GENERATION_NO)) = htonl(mailbox.generation_no + 1);
*((bit32 *)(buf+OFFSET_FORMAT)) = htonl(mailbox.format);
*((bit32 *)(buf+OFFSET_MINOR_VERSION)) = htonl(MAILBOX_MINOR_VERSION);
*((bit32 *)(buf+OFFSET_START_OFFSET)) = htonl(INDEX_HEADER_SIZE);
*((bit32 *)(buf+OFFSET_RECORD_SIZE)) = htonl(INDEX_RECORD_SIZE);
*((bit32 *)(buf+OFFSET_EXISTS)) = htonl(new_exists);
*((bit32 *)(buf+OFFSET_LAST_APPENDDATE)) = htonl(mailbox.last_appenddate);
*((bit32 *)(buf+OFFSET_LAST_UID)) = htonl(mailbox.last_uid);
/* quotas may be 64bit now */
#ifdef HAVE_LONG_LONG_INT
*((bit64 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonll(new_quota);
#else
/* zero the unused 32bits */
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonl(0);
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED)) = htonl(new_quota);
#endif
*((bit32 *)(buf+OFFSET_POP3_LAST_LOGIN)) = htonl(mailbox.pop3_last_login);
*((bit32 *)(buf+OFFSET_UIDVALIDITY)) = htonl(mailbox.uidvalidity);
*((bit32 *)(buf+OFFSET_DELETED)) = htonl(new_deleted);
*((bit32 *)(buf+OFFSET_ANSWERED)) = htonl(new_answered);
*((bit32 *)(buf+OFFSET_FLAGGED)) = htonl(new_flagged);
*((bit32 *)(buf+OFFSET_MAILBOX_OPTIONS)) = htonl(mailbox.options);
*((bit32 *)(buf+OFFSET_LEAKED_CACHE)) = htonl(0);
#ifdef HAVE_LONG_LONG_INT
*((bit64 *)(buf+OFFSET_HIGHESTMODSEQ_64)) = htonll(mailbox.highestmodseq);
#else
/* zero the unused 32bits */
*((bit32 *)(buf+OFFSET_HIGHESTMODSEQ_64)) = htonl(0);
*((bit32 *)(buf+OFFSET_HIGHESTMODSEQ)) = htonl(mailbox.highestmodseq);
#endif
*((bit32 *)(buf+OFFSET_SPARE0)) = htonl(0); /* RESERVED */
*((bit32 *)(buf+OFFSET_SPARE1)) = htonl(0); /* RESERVED */
*((bit32 *)(buf+OFFSET_SPARE2)) = htonl(0); /* RESERVED */
*((bit32 *)(buf+OFFSET_SPARE3)) = htonl(0); /* RESERVED */
*((bit32 *)(buf+OFFSET_SPARE4)) = htonl(0); /* RESERVED */
n = fwrite(buf, 1, INDEX_HEADER_SIZE, newindex);
fflush(newindex);
if (n != INDEX_HEADER_SIZE || ferror(newindex)
|| fsync(fileno(newindex)) || fsync(newcache_fd)) {
fclose(newindex);
mailbox_close(&mailbox);
return IMAP_IOERROR;
}
/* validate uniqueid */
if (!mailbox.uniqueid) {
/* this may change uniqueid, but if it does, nothing we can do
about it */
mailbox_make_uniqueid(mailbox.name, mailbox.uidvalidity, unique_buf,
sizeof(unique_buf));
mailbox.uniqueid = xstrdup(unique_buf);
} else {
if (find_uniqid (mailbox.name, mailbox.uniqueid) != NULL ) {
mailbox_make_uniqueid(mailbox.name, mailbox.uidvalidity, unique_buf,
sizeof(unique_buf));
free (mailbox.uniqueid);
mailbox.uniqueid = xstrdup(unique_buf);
}
}
if (add_uniqid (mailbox.name, mailbox.uniqueid) == NULL) {
syslog (LOG_ERR, "Failed adding mailbox: %s unique id: %s\n",
mailbox.name, mailbox.uniqueid );
}
/* Write header */
r = mailbox_write_header(&mailbox);
if (r) {
mailbox_close(&mailbox);
return r;
}
/* Rename new index/cache file in place */
path = (mailbox.mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_INDEX)) ?
mailbox.mpath : mailbox.path;
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_INDEX, sizeof(fnamebuf));
strlcpy(newfnamebuf, fnamebuf, sizeof(newfnamebuf));
strlcat(newfnamebuf, ".NEW", sizeof(fnamebuf));
if (rename(newfnamebuf, fnamebuf)) {
fclose(newindex);
mailbox_close(&mailbox);
return IMAP_IOERROR;
}
path = (mailbox.mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_CACHE)) ?
mailbox.mpath : mailbox.path;
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_CACHE, sizeof(fnamebuf));
strlcpy(newfnamebuf, fnamebuf, sizeof(newfnamebuf));
strlcat(newfnamebuf, ".NEW", sizeof(newfnamebuf));
if (rename(newfnamebuf, fnamebuf)) {
fclose(newindex);
mailbox_close(&mailbox);
return IMAP_IOERROR;
}
fclose(newindex);
r = seen_reconstruct(&mailbox, (time_t)0, (time_t)0, (int (*)())0, (void *)0);
if (syncflag) {
sync_log_mailbox(mailbox.name);
}
mailbox_close(&mailbox);
if (found) {
if (mympath &&
(config_metapartition_files & IMAP_ENUM_METAPARTITION_FILES_HEADER) &&
chdir(mympath) == -1) {
return IMAP_IOERROR;
}
/* we recurse down this directory to see if there's any mailboxes
under this not in the mailboxes database */
dirp = opendir(".");
while ((dirent = readdir(dirp)) != NULL) {
struct discovered *new;
/* mailbox directories never have a dot in them */
if (strchr(dirent->d_name, '.')) continue;
if (stat(dirent->d_name, &sbuf) < 0) continue;
if (!S_ISDIR(sbuf.st_mode)) continue;
/* ok, we found a directory that doesn't have a dot in it;
is there a cyrus.header file? */
snprintf(fnamebuf, sizeof(fnamebuf), "%s%s",
dirent->d_name, FNAME_HEADER);
if (stat(fnamebuf, &sbuf) < 0) continue;
/* ok, we have a real mailbox directory */
snprintf(fnamebuf, sizeof(fnamebuf), "%s.%s",
name, dirent->d_name);
/* does fnamebuf exist as a mailbox in mboxlist? */
do {
r = mboxlist_lookup(fnamebuf, NULL, NULL);
} while (r == IMAP_AGAIN);
if (!r) continue; /* mailbox exists; it'll be reconstructed
with a -r */
if (r != IMAP_MAILBOX_NONEXISTENT) break; /* erg? */
else r = 0; /* reset error condition */
printf("discovered %s\n", fnamebuf);
new = (struct discovered *) xmalloc(sizeof(struct discovered));
new->name = strdup(fnamebuf);
new->next = found->next;
found->next = new;
}
closedir(dirp);
}
return r;
}
/* XXX What is the stuff below used for? Do we need to metadata-ize it? */
/* List of directories to scan for mailboxes */
struct todo {
char *name;
char *path;
char *partition;
struct todo *next;
} *todo_head = 0, **todo_tail = &todo_head;
void
todo_append(name, path, partition)
char *name;
char *path;
char *partition;
{
struct todo *newentry;
newentry = (struct todo *)xmalloc(sizeof(struct todo));
newentry->name = name;
newentry->path = path;
newentry->partition = partition;
newentry->next = 0;
*todo_tail = newentry;
todo_tail = &newentry->next;
}
void
todo_append_hashed(char *name, char *path, char *partition)
{
DIR *dirp;
struct dirent *dirent;
dirp = opendir(path);
if (!dirp) {
fprintf(stderr, "reconstruct: couldn't open partition %s: %s\n",
partition, strerror(errno));
} else while ((dirent = readdir(dirp))!=NULL) {
struct todo *newentry;
if (strchr(dirent->d_name, '.')) {
continue;
}
newentry = (struct todo *)xmalloc(sizeof(struct todo));
newentry->name = xstrdup(name);
newentry->path = xmalloc(strlen(path) +
strlen(dirent->d_name) + 2);
sprintf(newentry->path, "%s/%s", path, dirent->d_name);
newentry->partition = partition;
newentry->next = 0;
*todo_tail = newentry;
todo_tail = &newentry->next;
}
}
char *cleanacl(char *acl, char *mboxname)
{
char owner[MAX_MAILBOX_NAME+1];
cyrus_acl_canonproc_t *aclcanonproc = 0;
char *p;
char *newacl;
char *identifier;
char *rights;
/* Rebuild ACL */
if ((p = mboxname_isusermailbox(mboxname, 0))) {
strlcpy(owner, p, sizeof(owner));
p = strchr(owner, '.');
if (p) *p = '\0';
aclcanonproc = mboxlist_ensureOwnerRights;
}
newacl = xstrdup("");
if (aclcanonproc) {
cyrus_acl_set(&newacl, owner, ACL_MODE_SET, ACL_ALL,
(cyrus_acl_canonproc_t *)0, (void *)0);
}
for (;;) {
identifier = acl;
rights = strchr(acl, '\t');
if (!rights) break;
*rights++ = '\0';
acl = strchr(rights, '\t');
if (!acl) break;
*acl++ = '\0';
cyrus_acl_set(&newacl, identifier, ACL_MODE_SET,
cyrus_acl_strtomask(rights), aclcanonproc,
(void *)owner);
}
return newacl;
}
/*
* Reconstruct the mailboxes list.
*/
void do_mboxlist(void)
{
fprintf(stderr, "reconstructing mailboxes.db currently not supported\n");
exit(EC_USAGE);
}
diff --git a/imap/squatter.c b/imap/squatter.c
index 1d8a0cb62..d9701da26 100644
--- a/imap/squatter.c
+++ b/imap/squatter.c
@@ -1,579 +1,580 @@
/* squatter.c -- SQUAT-based message indexing tool
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: squatter.c,v 1.17 2007/02/05 18:41:48 jeaton Exp $
+ * $Id: squatter.c,v 1.18 2007/03/30 18:40:20 murch Exp $
*/
/*
This is the tool that creates SQUAT indexes for Cyrus mailboxes.
SQUAT index files are organised as follows:
There is (at most) one index file for each Cyrus mailbox, named
"cyrus.squat", stored in the mailbox directory.
Source documents are named 'xUID' where UID is the numeric UID of a
message and x is a character denoting a part of the message: 'f' ==
FROM, 't' == TO, 'b' == BCC, 'c' == CC, 's' == SUBJECT, 'h' == other
headers, 'm' == the body. So, a messge with UID 331 could give rise
to several source documents named "f331", "t331", "b331", "c331",
"s331", "h331" and "m331".
There is also a special source document named "validity.N" where N
is the validitity nonce for the mailbox. We use this to detect when
the UIDs have been renumbered since we created the index (in which
case the index is useless and is ignored).
This tool creates new indexes for one or more mailboxes. (We do not
support incremental updates to an index yet.) The index is created
in "cyrus.squat.tmp" and then, if creation was successful, it is
atomically renamed to "cyrus.squat". This guarantees that we don't
interfere with anyone who has the old index open.
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include "annotate.h"
#include "assert.h"
#include "mboxlist.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "acl.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "squat.h"
#include "imapd.h"
/* global state */
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
extern char *optarg;
extern int optind;
/* current namespace */
static struct namespace squat_namespace;
/* Stuff to make index.c link */
int imapd_exists;
struct protstream *imapd_out = NULL;
struct auth_state *imapd_authstate = NULL;
char *imapd_userid = NULL;
int imapd_condstore_client = 0;
void printastring(const char *s __attribute__((unused)))
{
fatal("not implemented", EC_SOFTWARE);
}
/* end stuff to make index.c link */
/* These stats are gathered 1) per mailbox and 2) for the whole operation. */
typedef struct {
int indexed_bytes; /* How many bytes of processed message text
have we indexed? */
int indexed_messages; /* How many messages have we indexed? */
int index_size; /* How many bytes is the index using? */
time_t start_time; /* When did this operation start? */
time_t end_time; /* When did it end? */
} SquatStats;
const int SKIP_FUZZ = 60;
static int verbose = 0;
static int mailbox_count = 0;
static int skip_unmodified = 0;
static SquatStats total_stats;
static void start_stats(SquatStats* stats) {
stats->index_size = 0;
stats->indexed_bytes = 0;
stats->indexed_messages = 0;
stats->start_time = time(NULL);
}
static void stop_stats(SquatStats* stats) {
stats->end_time = time(NULL);
}
static void print_stats(FILE* out, SquatStats* stats) {
fprintf(out, "Indexed %d messages (%d bytes) "
"into %d index bytes in %d seconds\n",
stats->indexed_messages, stats->indexed_bytes,
stats->index_size, (int) (stats->end_time - stats->start_time));
}
static int usage(const char *name)
{
fprintf(stderr,
"usage: %s [-C <alt_config>] [-r] [-s] [-a] [-v] [mailbox...]\n",
name);
exit(EC_USAGE);
}
static void fatal_syserror(const char* s)
{
perror(s);
exit(99);
}
static void fatal_squat_error(const char* s)
{
int err = squat_get_last_error();
switch (err) {
case SQUAT_ERR_OUT_OF_MEMORY:
fprintf(stderr, "SQUAT: Out of memory (%s)\n", s);
break;
case SQUAT_ERR_SYSERR:
perror(s);
break;
default:
/* There are other error codes, but they only apply for searching,
not index construction */
fprintf(stderr, "SQUAT: Unknown error %d (%s)\n", err, s);
}
exit(98);
}
typedef struct {
SquatStats* mailbox_stats;
SquatIndex* index;
struct mailbox* mailbox;
} SquatReceiverData;
/* Cyrus passes the text to index in here, after it has canonicalized
the text. We figure out what source document the text belongs to,
and update the index. */
static void search_text_receiver(int uid, int part, int cmd,
char const* text, int text_len, void* rock) {
SquatReceiverData* d = (SquatReceiverData*)rock;
if ((cmd & SEARCHINDEX_CMD_BEGINPART) != 0) {
char buf[100];
char part_char;
/* Figure out what the name of the source document is going to be. */
switch (part) {
case SEARCHINDEX_PART_FROM: part_char = 'f'; break;
case SEARCHINDEX_PART_TO: part_char = 't'; break;
case SEARCHINDEX_PART_CC: part_char = 'c'; break;
case SEARCHINDEX_PART_BCC: part_char = 'b'; break;
case SEARCHINDEX_PART_SUBJECT: part_char = 's'; break;
case SEARCHINDEX_PART_HEADERS: part_char = 'h'; break;
default:
assert(0);
case SEARCHINDEX_PART_BODY:
part_char = 'm';
d->mailbox_stats->indexed_messages++;
total_stats.indexed_messages++;
break;
}
snprintf(buf, sizeof(buf), "%c%d", part_char, uid);
/* don't index document parts that are going to be empty (or too
short to search) */
if ((cmd & SEARCHINDEX_CMD_ENDPART) != 0
&& ((cmd & SEARCHINDEX_CMD_APPENDPART) == 0
|| text_len < SQUAT_WORD_SIZE)) {
if (verbose > 2) {
printf("Skipping tiny document part '%s' (size %d)\n", buf,
(cmd & SEARCHINDEX_CMD_APPENDPART) == 0 ? 0 : text_len);
}
return;
}
if (verbose > 2) {
printf("Opening document part '%s'\n", buf);
}
if (squat_index_open_document(d->index, buf) != SQUAT_OK) {
fatal_squat_error("Writing index");
}
}
if ((cmd & SEARCHINDEX_CMD_APPENDPART) != 0) {
if (verbose > 3) {
printf("Writing %d bytes into message %d\n", text_len, uid);
}
if (squat_index_append_document(d->index, text, text_len) != SQUAT_OK) {
fatal_squat_error("Writing index data");
}
d->mailbox_stats->indexed_bytes += text_len;
total_stats.indexed_bytes += text_len;
}
if ((cmd & SEARCHINDEX_CMD_ENDPART) != 0) {
if (squat_index_close_document(d->index) != SQUAT_OK) {
fatal_squat_error("Writing index update");
}
}
}
/* Let SQUAT tell us what's going on in the expensive
squat_index_finish function. */
static void stats_callback(void* closure __attribute__((unused)),
SquatStatsEvent* params) {
switch (params->generic.type) {
case SQUAT_STATS_COMPLETED_INITIAL_CHAR:
if (verbose > 1) {
if (params->completed_initial_char.num_words > 0) {
printf("Processing index character %d, %d total words, "
"temp file size is %d\n",
params->completed_initial_char.completed_char,
params->completed_initial_char.num_words,
params->completed_initial_char.temp_file_size);
}
}
break;
default:
; /* do nothing */
}
}
/* This is called once for each mailbox we're told to index. */
static int index_me(char *name, int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock) {
struct mailbox m;
int r;
SquatStats stats;
SquatReceiverData data;
char squat_file_name[MAX_MAILBOX_PATH+1], *path;
char new_file_name[MAX_MAILBOX_PATH+1];
int fd;
SquatOptions options;
struct stat squat_file_info;
struct stat index_file_info;
char uid_validity_buf[30];
char extname[MAX_MAILBOX_NAME+1];
int use_annot = *((int *) rock);
int mbtype;
/* Convert internal name to external */
(*squat_namespace.mboxname_toexternal)(&squat_namespace, name,
NULL, extname);
/* Skip remote mailboxes */
r = mboxlist_detail(name, &mbtype, NULL, NULL, NULL, NULL, NULL);
if (r) {
if (verbose) {
printf("error opening looking up %s: %s\n",
extname, error_message(r));
}
return 1;
}
if (mbtype & MBTYPE_REMOTE) return 0;
/* make sure the mailbox (or an ancestor) has
/vendor/cmu/cyrus-imapd/squat set to "true" */
if (use_annot) {
char buf[MAX_MAILBOX_NAME+1] = "", *p;
struct annotation_data attrib;
int domainlen = 0;
if (config_virtdomains && (p = strchr(name, '!')))
domainlen = p - name + 1;
strlcpy(buf, name, sizeof(buf));
/* since mailboxes inherit /vendor/cmu/cyrus-imapd/squat,
we need to iterate all the way up to "" (server entry) */
while (1) {
r = annotatemore_lookup(buf, "/vendor/cmu/cyrus-imapd/squat", "",
&attrib);
if (r || /* error */
attrib.value || /* found an entry */
!buf[0]) { /* done recursing */
break;
}
p = strrchr(buf, '.'); /* find parent mailbox */
if (p && (p - buf > domainlen)) /* don't split subdomain */
*p = '\0';
else if (!buf[domainlen]) /* server entry */
buf[0] = '\0';
else /* domain entry */
buf[domainlen] = '\0';
}
if (r || !attrib.value || strcasecmp(attrib.value, "true"))
return 0;
}
data.mailbox_stats = &stats;
data.mailbox = &m;
/* First we have to jump through hoops to open the mailbox and its
Cyrus index. */
memset(&m, 0, sizeof(struct mailbox));
r = mailbox_open_header(name, 0, &m);
if (r) {
if (verbose) {
printf("error opening %s: %s\n", extname, error_message(r));
}
return 1;
}
r = mailbox_open_index(&m);
if (!r) r = mailbox_lock_pop(&m);
if (r) {
if (verbose) {
printf("error locking index %s: %s\n", extname, error_message(r));
}
mailbox_close(&m);
return 1;
}
path = (m.mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_SQUAT)) ?
m.mpath : m.path;
snprintf(squat_file_name, sizeof(squat_file_name),
"%s%s", path, FNAME_SQUAT_INDEX);
/* process only changed mailboxes if skip option delected. */
if (skip_unmodified &&
!fstat(m.index_fd, &index_file_info) &&
!stat(squat_file_name, &squat_file_info)) {
if (SKIP_FUZZ + index_file_info.st_mtime <
squat_file_info.st_mtime) {
syslog(LOG_DEBUG, "skipping mailbox %s", extname);
if (verbose > 0) {
printf("Skipping mailbox %s\n", extname);
}
mailbox_close(&m);
return 0;
}
}
strlcpy(new_file_name, squat_file_name, sizeof(new_file_name));
strlcat(new_file_name, ".NEW", sizeof(new_file_name));
syslog(LOG_INFO, "indexing mailbox %s... ", extname);
if (verbose > 0) {
printf("Indexing mailbox %s... ", extname);
}
if ((fd = open(new_file_name,
O_CREAT | O_TRUNC | O_WRONLY, S_IREAD | S_IWRITE))
< 0) {
fatal_syserror("Unable to create temporary index file");
}
options.option_mask = SQUAT_OPTION_TMP_PATH | SQUAT_OPTION_STATISTICS;
options.tmp_path = m.path;
options.stats_callback = stats_callback;
options.stats_callback_closure = NULL;
data.index = squat_index_init(fd, &options);
if (data.index == NULL) {
fatal_squat_error("Initializing index");
}
/* write an empty document at the beginning to record the validity
nonce */
snprintf(uid_validity_buf, sizeof(uid_validity_buf),
"validity.%ld", m.uidvalidity);
if (squat_index_open_document(data.index, uid_validity_buf) != SQUAT_OK
|| squat_index_close_document(data.index) != SQUAT_OK) {
fatal_squat_error("Writing index");
}
start_stats(&stats);
mailbox_read_index_header(&m);
index_operatemailbox(&m);
index_getsearchtext(&m, search_text_receiver, &data);
index_closemailbox(&m);
mailbox_close(&m);
mailbox_count++;
if (squat_index_finish(data.index) != SQUAT_OK) {
fatal_squat_error("Closing index");
}
/* Check how big the resulting file is */
if (fstat(fd, &index_file_info) < 0) {
fatal_syserror("Unable to stat temporary index file");
}
stats.index_size = index_file_info.st_size;
total_stats.index_size += index_file_info.st_size;
if (close(fd) < 0) {
fatal_syserror("Unable to complete writing temporary index file");
}
/* OK, we successfully created the index under the temporary file name.
Let's rename it to make it the real index. */
if (rename(new_file_name, squat_file_name) < 0) {
fatal_syserror("Unable to rename temporary index file");
}
stop_stats(&stats);
if (verbose > 0) {
print_stats(stdout, &stats);
}
return 0;
}
int main(int argc, char **argv)
{
int opt;
char *alt_config = NULL;
int rflag = 0, use_annot = 0;
int i;
char buf[MAX_MAILBOX_PATH+1];
int r;
- if(geteuid() == 0)
- fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
setbuf(stdout, NULL);
while ((opt = getopt(argc, argv, "C:rsav")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'v': /* verbose */
verbose++;
break;
case 'r': /* recurse */
rflag = 1;
break;
case 's': /* skip unmodifed */
skip_unmodified = 1;
break;
case 'a': /* use /squat annotation */
use_annot = 1;
break;
default:
usage("squatter");
}
}
cyrus_init(alt_config, "squatter", 0);
syslog(LOG_NOTICE, "indexing mailboxes");
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&squat_namespace, 1)) != 0) {
fatal(error_message(r), EC_CONFIG);
}
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
mboxlist_init(0);
mboxlist_open(NULL);
mailbox_initialize();
start_stats(&total_stats);
if (optind == argc) {
if (rflag) {
fprintf(stderr, "please specify a mailbox to recurse from\n");
exit(EC_USAGE);
}
assert(!rflag);
strlcpy(buf, "*", sizeof(buf));
(*squat_namespace.mboxlist_findall)(&squat_namespace, buf, 1,
0, 0, index_me, &use_annot);
}
for (i = optind; i < argc; i++) {
/* Translate any separators in mailboxname */
(*squat_namespace.mboxname_tointernal)(&squat_namespace, argv[i],
NULL, buf);
index_me(buf, 0, 0, &use_annot);
if (rflag) {
strlcat(buf, ".*", sizeof(buf));
(*squat_namespace.mboxlist_findall)(&squat_namespace, buf, 1,
0, 0, index_me, &use_annot);
}
}
if (verbose > 0 && mailbox_count > 1) {
stop_stats(&total_stats);
printf("Total over all mailboxes: ");
print_stats(stdout, &total_stats);
}
syslog(LOG_NOTICE, "done indexing mailboxes");
seen_done();
mboxlist_close();
mboxlist_done();
annotatemore_close();
annotatemore_done();
cyrus_done();
return 0;
}
diff --git a/imap/sync_client.c b/imap/sync_client.c
index 849c45fe9..fee7a6cd0 100644
--- a/imap/sync_client.c
+++ b/imap/sync_client.c
@@ -1,3594 +1,3595 @@
/* sync_client.c -- Cyrus synchonization client
*
* Copyright (c) 1998-2005 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Original version written by David Carter <dpc22@cam.ac.uk>
* Rewritten and integrated into Cyrus by Ken Murchison <ken@oceana.com>
*
- * $Id: sync_client.c,v 1.7 2007/03/27 19:29:56 murch Exp $
+ * $Id: sync_client.c,v 1.8 2007/03/30 18:40:20 murch Exp $
*/
#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 <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <ctype.h>
#include "global.h"
#include "assert.h"
#include "mboxlist.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "quota.h"
#include "xmalloc.h"
#include "acl.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imapd.h"
#include "imparse.h"
#include "util.h"
#include "prot.h"
#include "sync_support.h"
#include "sync_commit.h"
#include "lock.h"
#include "backend.h"
/* signal to config.c */
const int config_need_data = 0; /* YYY */
/* Stuff to make index.c link */
int imapd_exists;
struct protstream *imapd_out = NULL;
struct auth_state *imapd_authstate = NULL;
char *imapd_userid = NULL;
void printastring(const char *s)
{
fatal("not implemented", EC_SOFTWARE);
}
void printstring(const char *s)
{
fatal("not implemented", EC_SOFTWARE);
}
/* end stuff to make index.c link */
/* ====================================================================== */
/* Static global variables and support routines for sync_client */
extern char *optarg;
extern int optind;
static struct protstream *toserver = NULL;
static struct protstream *fromserver = NULL;
/* List/Hash of messageIDs that are available on server */
static struct sync_msgid_list *msgid_onserver = NULL;
static struct namespace sync_namespace;
static struct auth_state *sync_authstate = NULL;
static int verbose = 0;
static int verbose_logging = 0;
static int connect_once = 0;
static int do_meta(char *user);
static void shut_down(int code) __attribute__((noreturn));
static void shut_down(int code)
{
seen_done();
annotatemore_close();
annotatemore_done();
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
exit(code);
}
static int usage(const char *name)
{
fprintf(stderr,
"usage: %s -S <servername> [-C <alt_config>] [-r] [-v] mailbox...\n", name);
exit(EC_USAGE);
}
void fatal(const char *s, int code)
{
fprintf(stderr, "sync_client: %s\n", s);
exit(code);
}
/* ====================================================================== */
static int send_lock()
{
prot_printf(toserver, "LOCK\r\n");
prot_flush(toserver);
return(sync_parse_code("LOCK", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int send_unlock()
{
int r = 0;
int c = ' ';
static struct buf token; /* BSS */
prot_printf(toserver, "UNLOCK\r\n");
prot_flush(toserver);
r = sync_parse_code("UNLOCK", fromserver, SYNC_PARSE_NOEAT_OKLINE, NULL);
if (r) return(r);
if ((c = getword(fromserver, &token)) != ' ') {
eatline(fromserver, c);
syslog(LOG_ERR, "Garbage on Unlock response");
return(IMAP_PROTOCOL_ERROR);
}
eatline(fromserver, c);
/* Clear out msgid_on_server list if server restarted */
if (!strcmp(token.s, "[RESTART]")) {
int hash_size = msgid_onserver->hash_size;
sync_msgid_list_free(&msgid_onserver);
msgid_onserver = sync_msgid_list_create(hash_size);
syslog(LOG_INFO, "UNLOCK: received RESTART");
}
return(0);
}
/* ====================================================================== */
/* Routines relevant to reserve operation */
/* Find the messages that we will want to upload from this mailbox,
* flag messages that are already available at the server end */
static int find_reserve_messages(struct mailbox *mailbox,
struct sync_msg_list *msg_list,
struct sync_msgid_list *server_msgid_list,
struct sync_msgid_list *reserve_msgid_list)
{
struct sync_msg *msg;
struct index_record record;
unsigned long msgno;
int r;
if (mailbox->exists == 0)
return(0);
msg = msg_list->head;
for (msgno = 1 ; msgno <= mailbox->exists ; msgno++) {
r = mailbox_read_index_record(mailbox, msgno, &record);
if (r) {
syslog(LOG_ERR,
"IOERROR: reading index entry for nsgno %lu of %s: %m",
record.uid, mailbox->name);
return(IMAP_IOERROR);
}
if (msg && ((msg->uid < record.uid) ||
((msg->uid == record.uid) &&
message_uuid_compare(&msg->uuid, &record.uuid)))) {
msg = msg->next;
continue;
}
/* Want to upload this message; does the server have a copy? */
if (sync_msgid_lookup(server_msgid_list, &record.uuid))
sync_msgid_add(reserve_msgid_list, &record.uuid);
}
return(0);
}
static int reserve_all_messages(struct mailbox *mailbox,
struct sync_msgid_list *server_msgid_list,
struct sync_msgid_list *reserve_msgid_list)
{
struct index_record record;
unsigned long msgno;
int r;
if (mailbox->exists == 0)
return(0);
for (msgno = 1 ; msgno <= mailbox->exists ; msgno++) {
r = mailbox_read_index_record(mailbox, msgno, &record);
if (r) {
syslog(LOG_ERR,
"IOERROR: reading index entry for nsgno %lu of %s: %m",
record.uid, mailbox->name);
return(IMAP_IOERROR);
}
/* Want to upload this message; does the server have a copy? */
if (sync_msgid_lookup(server_msgid_list, &record.uuid))
sync_msgid_add(reserve_msgid_list, &record.uuid);
}
return(0);
}
/* Count numbers of instances on server of each MessageID that we will
* want to copy */
static int count_reserve_messages(struct sync_folder *server_folder,
struct sync_msgid_list *reserve_msgid_list)
{
struct sync_msg_list *msglist = server_folder->msglist;
struct sync_msg *msg;
struct sync_msgid *msgid;
for (msg = msglist->head ; msg ; msg = msg->next) {
if ((msgid=sync_msgid_lookup(reserve_msgid_list, &msg->uuid)))
msgid->count++;
}
return(0);
}
static int reserve_check_folder(struct sync_msgid_list *reserve_msgid_list,
struct sync_folder *folder)
{
struct sync_msg *msg;
struct sync_msgid *msgid;
for (msg = folder->msglist->head ; msg ; msg = msg->next) {
msgid = sync_msgid_lookup(reserve_msgid_list, &msg->uuid);
if (msgid && !msgid->reserved)
return(1);
}
return(0);
}
static int reserve_folder(struct sync_msgid_list *reserve_msgid_list,
struct sync_folder *folder)
{
struct sync_msg *msg;
struct sync_msgid *msgid;
static struct buf arg;
int r = 0, unsolicited, c;
prot_printf(toserver, "RESERVE ");
sync_printastring(toserver, folder->name);
for (msg = folder->msglist->head ; msg ; msg = msg->next) {
msgid = sync_msgid_lookup(reserve_msgid_list, &msg->uuid);
if (msgid && !msgid->reserved) {
/* Attempt to Reserve message in this folder */
prot_printf(toserver, " ");
sync_printastring(toserver, message_uuid_text(&msgid->uuid));
}
}
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r = sync_parse_code("RESERVE", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited);
/* Parse response to record successfully reserved messages */
while (!r && unsolicited) {
struct message_uuid tmp_uuid;
c = getword(fromserver, &arg);
if (c == '\r')
c = prot_getc(fromserver);
if (c != '\n') {
syslog(LOG_ERR, "Illegal response to RESERVE: %s", arg.s);
sync_eatlines_unsolicited(fromserver, c);
return(IMAP_PROTOCOL_ERROR);
}
if (!message_uuid_from_text(&tmp_uuid, arg.s)) {
syslog(LOG_ERR, "Illegal response to RESERVE: %s", arg.s);
sync_eatlines_unsolicited(fromserver, c);
return(IMAP_PROTOCOL_ERROR);
}
if ((msgid = sync_msgid_lookup(reserve_msgid_list, &tmp_uuid))) {
msgid->reserved = 1;
reserve_msgid_list->reserved++;
sync_msgid_add(msgid_onserver, &tmp_uuid);
} else
syslog(LOG_ERR, "RESERVE: Unexpected response MessageID %s in %s",
arg.s, folder->name);
r = sync_parse_code("RESERVE", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited);
}
return(r);
}
struct reserve_sort_item {
struct sync_folder *folder;
unsigned long count;
};
static int reserve_folder_compare(const void *v1, const void *v2)
{
struct reserve_sort_item *s1 = (struct reserve_sort_item *)v1;
struct reserve_sort_item *s2 = (struct reserve_sort_item *)v2;
return(s1->count - s2->count);
}
static int reserve_messages(struct sync_folder_list *client_list,
struct sync_folder_list *server_list,
int *vanishedp)
{
struct sync_msgid_list *server_msgid_list = NULL;
struct sync_msgid_list *reserve_msgid_list = NULL;
struct sync_folder *folder, *folder2;
struct sync_msg *msg;
struct sync_msgid *msgid;
struct reserve_sort_item *reserve_sort_list = 0;
int reserve_sort_count;
int r = 0;
int mailbox_open = 0;
int count;
int i;
struct mailbox m;
server_msgid_list = sync_msgid_list_create(SYNC_MSGID_LIST_HASH_SIZE);
reserve_msgid_list = sync_msgid_list_create(SYNC_MSGID_LIST_HASH_SIZE);
/* Generate fast lookup hash of all MessageIDs available on server */
for (folder = server_list->head ; folder ; folder = folder->next) {
for (msg = folder->msglist->head ; msg ; msg = msg->next) {
if (!sync_msgid_lookup(server_msgid_list, &msg->uuid))
sync_msgid_add(server_msgid_list, &msg->uuid);
}
}
/* Find messages we want to upload that are available on server */
for (folder = client_list->head ; folder ; folder = folder->next) {
/* Quietly skip over folders that have already been processed */
if (folder->mark) continue;
folder->id = NULL;
folder->acl = NULL;
r = mailbox_open_header(folder->name, 0, &m);
/* Quietly skip over folders which have been deleted since we
started working (but record fact in case caller cares) */
if (r == IMAP_MAILBOX_NONEXISTENT) {
(*vanishedp)++;
r = 0;
continue;
}
/* Quietly ignore objects that we don't have access to.
* Includes directory stubs, which have not underlying cyrus.*
* files in the filesystem */
if (r == IMAP_PERMISSION_DENIED) {
r = 0;
continue;
}
if (!r) mailbox_open = 1;
if (!r) r = mailbox_open_index(&m);
if (r) {
if (mailbox_open) mailbox_close(&m);
syslog(LOG_ERR, "IOERROR: %s", error_message(r));
goto bail;
}
folder->id = xstrdup(m.uniqueid);
folder->acl = xstrdup(m.acl);
if ((folder2=sync_folder_lookup(server_list, m.uniqueid)))
find_reserve_messages(&m, folder2->msglist,
server_msgid_list,
reserve_msgid_list);
else
reserve_all_messages(&m,
server_msgid_list,
reserve_msgid_list);
mailbox_close(&m);
}
if (reserve_msgid_list->count == 0) {
r = 0; /* Nothing to do */
goto bail;
}
/* Generate instance count for messages available on server */
for (folder = server_list->head ; folder ; folder = folder->next)
count_reserve_messages(folder, reserve_msgid_list);
/* Add all folders which have unique copies of messages to reserve list
* (as they will definitely be needed) */
for (folder = server_list->head ; folder ; folder = folder->next) {
for (msg = folder->msglist->head ; msg ; msg = msg->next) {
msgid = sync_msgid_lookup(reserve_msgid_list, &msg->uuid);
if (msgid && (msgid->count == 1)) {
reserve_folder(reserve_msgid_list, folder);
folder->reserve = 1;
break;
}
}
}
/* Record all folders with unreserved messages and sort them so the
* folder with most unreserved messages in first */
reserve_sort_list
= xmalloc(server_list->count*sizeof(struct reserve_sort_item));
/* Count messages we will be able to reserve from each folder on server */
reserve_sort_count = 0;
for (folder = server_list->head; folder ; folder=folder->next) {
if (folder->reserve) continue;
for (count = 0, msg = folder->msglist->head ; msg ; msg = msg->next) {
msgid = sync_msgid_lookup(reserve_msgid_list, &msg->uuid);
if (msgid && !msgid->reserved)
count++;
}
if (count > 0) {
reserve_sort_list[reserve_sort_count].folder = folder;
reserve_sort_list[reserve_sort_count].count = count;
reserve_sort_count++;
}
}
/* Sort folders (folder with most reservable messages first) */
if (reserve_sort_count > 0)
qsort(reserve_sort_list, reserve_sort_count,
sizeof(struct reserve_sort_item), reserve_folder_compare);
/* Work through folders until all messages reserved or no more */
for (i=0; i < reserve_sort_count ; i++) {
folder = reserve_sort_list[i].folder;
if (reserve_check_folder(reserve_msgid_list, folder))
reserve_folder(reserve_msgid_list, folder);
if (reserve_msgid_list->reserved == reserve_msgid_list->count)
break;
}
bail:
sync_msgid_list_free(&server_msgid_list);
sync_msgid_list_free(&reserve_msgid_list);
if (reserve_sort_list) free(reserve_sort_list);
return(r);
}
static int folders_get_uniqueid(struct sync_folder_list *client_list,
int *vanishedp)
{
struct sync_folder *folder;
int r = 0;
int mailbox_open = 0;
struct mailbox m;
/* Find messages we want to upload that are available on server */
for (folder = client_list->head ; folder ; folder = folder->next) {
/* Quietly skip over folders that have already been processed */
if (folder->mark) continue;
folder->id = NULL;
folder->acl = NULL;
r = mailbox_open_header(folder->name, 0, &m);
/* Quietly skip over folders which have been deleted since we
started working (but record fact in case caller cares) */
if (r == IMAP_MAILBOX_NONEXISTENT) {
(*vanishedp)++;
r = 0;
continue;
}
/* Quietly ignore objects that we don't have access to.
* Includes directory stubs, which have not underlying cyrus.*
* files in the filesystem */
if (r == IMAP_MAILBOX_NONEXISTENT) {
r = 0;
continue;
}
if (!r) mailbox_open = 1;
if (!r) r = mailbox_open_index(&m);
if (r) {
if (mailbox_open) mailbox_close(&m);
syslog(LOG_ERR, "IOERROR: %s", error_message(r));
return(r);
}
folder->id = xstrdup(m.uniqueid);
folder->acl = xstrdup(m.acl);
mailbox_close(&m);
}
return(0);
}
/* ====================================================================== */
static int user_reset(char *user)
{
prot_printf(toserver, "RESET ");
sync_printastring(toserver, user);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("RESET", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
/* ====================================================================== */
static int folder_select(char *name, char *myuniqueid,
unsigned long *lastuidp)
{
int r, c;
static struct buf uniqueid;
static struct buf lastuid;
prot_printf(toserver, "SELECT ");
sync_printastring(toserver, name);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r = sync_parse_code("SELECT", fromserver, SYNC_PARSE_NOEAT_OKLINE, NULL);
if (r) return(r);
if ((c = getword(fromserver, &uniqueid)) != ' ') {
eatline(fromserver, c);
syslog(LOG_ERR, "Garbage on Select response");
return(IMAP_PROTOCOL_ERROR);
}
c = getword(fromserver, &lastuid);
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') {
eatline(fromserver, c);
syslog(LOG_ERR, "Garbage on Select response");
return(IMAP_PROTOCOL_ERROR);
}
if (strcmp(uniqueid.s, myuniqueid) != 0)
return(IMAP_MAILBOX_MOVED);
if (lastuidp) *lastuidp = sync_atoul(lastuid.s);
return(0);
}
static int folder_create(char *name, char *part, char *uniqueid, char *acl,
unsigned long options, unsigned long uidvalidity)
{
prot_printf(toserver, "CREATE ");
sync_printastring(toserver, name);
prot_printf(toserver, " ");
sync_printastring(toserver, part);
prot_printf(toserver, " ");
sync_printastring(toserver, uniqueid);
prot_printf(toserver, " ");
sync_printastring(toserver, acl);
prot_printf(toserver, " %d %lu %lu\r\n", 0, options, uidvalidity);
prot_flush(toserver);
return(sync_parse_code("CREATE", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int folder_rename(char *oldname, char *newname)
{
prot_printf(toserver, "RENAME ");
sync_printastring(toserver, oldname);
prot_printf(toserver, " ");
sync_printastring(toserver, newname);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("RENAME", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int folder_delete(char *name)
{
prot_printf(toserver, "DELETE ");
sync_printastring(toserver, name);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("DELETE", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int user_addsub(char *user, char *name)
{
if (verbose)
printf("ADDSUB %s %s\n", user, name);
if (verbose_logging)
syslog(LOG_INFO, "ADDSUB %s %s", user, name);
prot_printf(toserver, "ADDSUB ");
sync_printastring(toserver, user);
prot_printf(toserver, " ");
sync_printastring(toserver, name);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("ADDSUB", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int user_delsub(char *user, char *name)
{
if (verbose)
printf("DELSUB %s %s\n", user, name);
if (verbose_logging)
syslog(LOG_INFO, "DELSUB %s %s", user, name);
prot_printf(toserver, "DELSUB ");
sync_printastring(toserver, user);
prot_printf(toserver, " ");
sync_printastring(toserver, name);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("DELSUB", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int folder_setacl(char *name, char *acl)
{
prot_printf(toserver, "SETACL ");
sync_printastring(toserver, name);
prot_printf(toserver, " ");
sync_printastring(toserver, acl);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("SETACL", fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int folder_setannotation(char *name, char *entry, char *userid,
char *value)
{
prot_printf(toserver, "SETANNOTATION ");
sync_printastring(toserver, name);
prot_printf(toserver, " ");
sync_printastring(toserver, entry);
prot_printf(toserver, " ");
sync_printastring(toserver, userid);
prot_printf(toserver, " ");
if (value) sync_printastring(toserver, value);
else prot_printf(toserver, "NIL");
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("SETANNOTATION", fromserver,
SYNC_PARSE_EAT_OKLINE, NULL));
}
/* ====================================================================== */
static int sieve_upload(char *user, char *name, unsigned long last_update)
{
char *s, *sieve;
unsigned long size;
if (!(sieve = sync_sieve_read(user, name, &size))) {
return(IMAP_IOERROR);
}
prot_printf(toserver, "UPLOAD_SIEVE ");
sync_printastring(toserver, user);
prot_printf(toserver, " ");
sync_printastring(toserver, name);
prot_printf(toserver, " %lu {%lu+}\r\n", last_update, size);
s = sieve;
while (size) {
prot_putc(*s, toserver);
s++;
size--;
}
prot_printf(toserver,"\r\n");
free(sieve);
prot_flush(toserver);
return(sync_parse_code("UPLOAD_SIEVE",
fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
return(1);
}
static int sieve_delete(char *user, char *name)
{
prot_printf(toserver, "DELETE_SIEVE ");
sync_printastring(toserver, user);
prot_printf(toserver, " ");
sync_printastring(toserver, name);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("DELETE_SIEVE",
fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int sieve_activate(char *user, char *name)
{
prot_printf(toserver, "ACTIVATE_SIEVE ");
sync_printastring(toserver, user);
prot_printf(toserver, " ");
sync_printastring(toserver, name);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("ACTIVATE_SIEVE",
fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
static int sieve_deactivate(char *user)
{
prot_printf(toserver, "DEACTIVATE_SIEVE ");
sync_printastring(toserver, user);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("DEACTIVATE_SIEVE",
fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
/* ====================================================================== */
static int update_quota_work(struct quota *client, struct quota *server)
{
int r;
if ((r = quota_read(client, NULL, 0))) {
syslog(LOG_INFO, "Warning: failed to read quotaroot %s: %s",
client->root, error_message(r));
return(0);
}
if (server && (client->limit == server->limit))
return(0);
prot_printf(toserver, "SETQUOTA ");
sync_printastring(toserver, client->root);
prot_printf(toserver, " %d\r\n", client->limit);
prot_flush(toserver);
return(sync_parse_code("SETQUOTA",fromserver,SYNC_PARSE_EAT_OKLINE,NULL));
}
/* ====================================================================== */
static void create_flags_lookup(int table[], char *client[], char *server[])
{
int i, j;
/* Rather unfortunate O(n^2) loop, where 0 <= n <= 128
* However n (number of active user defined flags is typically small:
* (client[i] == NULL) test should make this much closer to O(n).
*/
for (i = 0 ; i < MAX_USER_FLAGS ; i++) {
table[i] = (-1);
if (client[i] == NULL)
continue;
for (j = 0 ; j < MAX_USER_FLAGS ; j++) {
if (server[j] && !strcmp(client[i], server[j])) {
table[i] = j;
break;
}
}
}
}
static int check_flags(struct mailbox *mailbox, struct sync_msg_list *list,
int flag_lookup_table[])
{
struct sync_msg *msg;
unsigned long msgno;
struct index_record record;
int cflag, sflag, cvalue, svalue;
msg = list->head;
for (msgno = 1; msg && (msgno <= mailbox->exists) ; msgno++) {
mailbox_read_index_record(mailbox, msgno, &record);
/* Skip msgs on client missing on server (will upload later) */
if (record.uid < msg->uid)
continue;
/* Skip over messages recorded on server which are missing on client
* (either will be expunged or have been expunged already) */
while (msg && (record.uid > msg->uid))
msg = msg->next;
if (!(msg && (record.uid == msg->uid)))
continue;
/* Got a message on client which has same UID as message on server
* Work out if system and user flags match */
if (record.system_flags != msg->flags.system_flags)
return(1);
for (cflag = 0; cflag < MAX_USER_FLAGS; cflag++) {
if (mailbox->flagname[cflag] == NULL)
continue;
cvalue = svalue = 0;
if (record.user_flags[cflag/32] & (1<<(cflag&31)))
cvalue = 1;
if (((sflag = flag_lookup_table[cflag]) >= 0) &&
(msg->flags.user_flags[sflag/32] & 1<<(sflag&31)))
svalue = 1;
if (cvalue != svalue)
return(1);
}
}
return(0);
}
static int update_flags(struct mailbox *mailbox, struct sync_msg_list *list,
int flag_lookup_table[])
{
struct sync_msg *msg;
unsigned long msgno;
struct index_record record;
int flags_printed, flag;
int cflag, sflag, cvalue, svalue;
int update;
int have_update = 0;
msg = list->head;
for (msgno = 1; msg && (msgno <= mailbox->exists) ; msgno++) {
mailbox_read_index_record(mailbox, msgno, &record);
/* Skip msgs on client missing on server (will upload later) */
if (record.uid < msg->uid)
continue;
/* Skip over messages recorded on server which are missing on client
* (either will be expunged or have been expunged already) */
while (msg && (record.uid > msg->uid))
msg = msg->next;
if (!(msg && (record.uid == msg->uid)))
continue;
/* Got a message on client which has same UID as message on server
* Work out if system and user flags match */
update = 0;
if (record.system_flags != msg->flags.system_flags) {
update = 1;
} else for (cflag = 0; cflag < MAX_USER_FLAGS; cflag++) {
if (mailbox->flagname[cflag] == NULL)
continue;
cvalue = svalue = 0;
if (record.user_flags[cflag/32] & (1<<(cflag&31)))
cvalue = 1;
if (((sflag = flag_lookup_table[cflag]) >= 0) &&
(msg->flags.user_flags[sflag/32] & 1<<(sflag&31)))
svalue = 1;
if (cvalue != svalue) {
update = 1;
break;
}
}
if (!update)
continue;
if (!have_update) {
prot_printf(toserver, "SETFLAGS");
have_update = 1;
}
prot_printf(toserver, " %lu (", record.uid);
flags_printed = 0;
if (record.system_flags & FLAG_DELETED)
sync_flag_print(toserver, &flags_printed,"\\deleted");
if (record.system_flags & FLAG_ANSWERED)
sync_flag_print(toserver, &flags_printed,"\\answered");
if (record.system_flags & FLAG_FLAGGED)
sync_flag_print(toserver,&flags_printed, "\\flagged");
if (record.system_flags & FLAG_DRAFT)
sync_flag_print(toserver,&flags_printed, "\\draft");
for (flag = 0 ; flag < MAX_USER_FLAGS ; flag++) {
if (mailbox->flagname[flag] &&
(record.user_flags[flag/32] & (1<<(flag&31)) ))
sync_flag_print(toserver, &flags_printed,
mailbox->flagname[flag]);
}
prot_printf(toserver, ")");
}
if (!have_update)
return(0);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("SETFLAGS",fromserver,SYNC_PARSE_EAT_OKLINE,NULL));
}
/* ====================================================================== */
static int check_expunged(struct mailbox *mailbox, struct sync_msg_list *list)
{
struct sync_msg *msg = list->head;
unsigned long msgno = 1;
struct index_record record;
for (msgno = 1; msg && (msgno <= mailbox->exists) ; msgno++) {
mailbox_read_index_record(mailbox, msgno, &record);
/* Skip msgs on client missing on server (will upload later) */
if (record.uid < msg->uid)
continue;
/* Message on server doesn't exist on client: need expunge */
if (record.uid > msg->uid)
return(1);
/* UIDs match => exist on client and server */
msg = msg->next;
}
return((msg) ? 1 : 0); /* Remaining messages on server: expunge needed */
}
static int expunge(struct mailbox *mailbox, struct sync_msg_list *list)
{
struct sync_msg *msg = list->head;
unsigned long msgno = 1;
struct index_record record;
int count = 0;
for (msgno = 1; msg && (msgno <= mailbox->exists) ; msgno++) {
mailbox_read_index_record(mailbox, msgno, &record);
/* Skip msgs on client missing on server (will upload later) */
if (record.uid < msg->uid)
continue;
/* Expunge messages on server which do not exist on client */
while (msg && (record.uid > msg->uid)) {
if (count++ == 0)
prot_printf(toserver, "EXPUNGE");
prot_printf(toserver, " %lu", msg->uid);
msg = msg->next;
}
/* Skip messages which exist on both client and server */
if (msg && (record.uid == msg->uid))
msg = msg->next;
}
/* Expunge messages on server which do not exist on client */
while (msg) {
if (count++ == 0)
prot_printf(toserver, "EXPUNGE");
prot_printf(toserver, " %lu", msg->uid);
msg = msg->next;
}
if (count == 0)
return(0);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("EXPUNGE",fromserver,SYNC_PARSE_EAT_OKLINE,NULL));
}
/* ====================================================================== */
/* Check whether there are any messages to upload in this folder */
static int check_upload_messages(struct mailbox *mailbox,
struct sync_msg_list *list)
{
struct sync_msg *msg;
struct index_record record;
unsigned long msgno;
if (mailbox->exists == 0)
return(0);
/* Find out whether server is missing any messages */
if ((msg = list->head) == NULL)
return(1);
for (msgno = 1 ; msgno <= mailbox->exists ; msgno++) {
if (mailbox_read_index_record(mailbox, msgno, &record))
return(1); /* Attempt upload, report error then */
/* Skip over messages recorded on server which are missing on client
* (either will be expunged or have been expunged already) */
while (msg && (record.uid > msg->uid))
msg = msg->next;
if (msg && (record.uid == msg->uid) &&
message_uuid_compare(&record.uuid, &msg->uuid)) {
msg = msg->next; /* Ignore exact match */
continue;
}
/* Found a message on the client which doesn't exist on the server */
return(1);
}
return (msgno <= mailbox->exists);
}
/* Upload missing messages from folders (uses UPLOAD COPY where possible) */
static int upload_message_work(struct mailbox *mailbox,
unsigned long msgno,
struct index_record *record)
{
unsigned long cache_size;
int flags_printed = 0;
int r = 0, flag, need_body;
static unsigned long sequence = 1;
const char *msg_base = NULL;
unsigned long msg_size = 0;
/* Protocol for PARSED items:
* C: PARSED <msgid> <uid>
* <internaldate> <sent-date> <last-updated> <modseq> <flags>
* <hdr size> <content_lines>
* <cache literal (includes cache size!)>
* <msg literal (includes msg size!)>
*/
/* Protocol for COPY items:
* C: COPY <msgid> <uid>
* <internaldate> <sent-date> <last-updated> <modseq> <flags>
*/
if (sync_msgid_lookup(msgid_onserver, &record->uuid)) {
prot_printf(toserver, " COPY");
need_body = 0;
} else {
sync_msgid_add(msgid_onserver, &record->uuid);
prot_printf(toserver, " PARSED");
need_body = 1;
}
prot_printf(toserver, " %s %lu %lu %lu %lu " MODSEQ_FMT " (",
message_uuid_text(&record->uuid),
record->uid, record->internaldate,
record->sentdate, record->last_updated, record->modseq);
flags_printed = 0;
if (record->system_flags & FLAG_DELETED)
sync_flag_print(toserver, &flags_printed, "\\deleted");
if (record->system_flags & FLAG_ANSWERED)
sync_flag_print(toserver, &flags_printed, "\\answered");
if (record->system_flags & FLAG_FLAGGED)
sync_flag_print(toserver, &flags_printed, "\\flagged");
if (record->system_flags & FLAG_DRAFT)
sync_flag_print(toserver, &flags_printed, "\\draft");
for (flag = 0 ; flag < MAX_USER_FLAGS ; flag++) {
if (mailbox->flagname[flag] &&
(record->user_flags[flag/32] & (1<<(flag&31)) ))
sync_flag_print(toserver,
&flags_printed, mailbox->flagname[flag]);
}
prot_printf(toserver, ")");
if (need_body) {
/* Server doesn't have this message yet */
cache_size = mailbox_cache_size(mailbox, msgno);
if (cache_size == 0) {
syslog(LOG_ERR,
"upload_messages(): Empty cache entry for msgno %lu",
msgno);
return(IMAP_INTERNAL);
}
r = mailbox_map_message(mailbox, record->uid, &msg_base, &msg_size);
if (r) {
syslog(LOG_ERR, "IOERROR: opening message file %lu of %s: %m",
record->uid, mailbox->name);
return(IMAP_IOERROR);
}
prot_printf(toserver, " %lu %lu %lu {%lu+}\r\n",
record->header_size, record->content_lines,
record->cache_version, cache_size);
prot_write(toserver,
(char *)(mailbox->cache_base + record->cache_offset),
cache_size);
prot_printf(toserver, "{%lu+}\r\n", msg_size);
prot_write(toserver, (char *)msg_base, msg_size);
mailbox_unmap_message(mailbox, record->uid, &msg_base, &msg_size);
sequence++;
}
return(r);
}
static int upload_messages_list(struct mailbox *mailbox,
struct sync_msg_list *list)
{
unsigned long msgno = 1;
int r = 0;
struct index_record record;
struct sync_msg *msg;
int count;
int c = ' ';
static struct buf token; /* BSS */
int max_count = config_getint(IMAPOPT_SYNC_BATCH_SIZE);
if (max_count <= 0) max_count = INT_MAX;
if (chdir(mailbox->path)) {
syslog(LOG_ERR, "Couldn't chdir to %s: %s",
mailbox->path, strerror(errno));
return(IMAP_IOERROR);
}
repeatupload:
msg = list->head;
for (count = 0; count < max_count && msgno <= mailbox->exists ; msgno++) {
r = mailbox_read_index_record(mailbox, msgno, &record);
if (r) {
syslog(LOG_ERR,
"IOERROR: reading index entry for nsgno %lu of %s: %m",
record.uid, mailbox->name);
return(IMAP_IOERROR);
}
/* Skip over messages recorded on server which are missing on client
* (either will be expunged or have been expunged already) */
while (msg && (record.uid > msg->uid))
msg = msg->next;
if (msg && (record.uid == msg->uid) &&
message_uuid_compare(&record.uuid, &msg->uuid)) {
msg = msg->next; /* Ignore exact match */
continue;
}
if (count++ == 0)
prot_printf(toserver, "UPLOAD %lu %lu",
mailbox->last_uid, mailbox->last_appenddate);
/* Message with this UUID exists on client but not server */
if ((r=upload_message_work(mailbox, msgno, &record)))
return(r);
if (msg && (msg->uid == record.uid)) /* Overwritten on server */
msg = msg->next;
}
if (count == 0)
return(r);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r = sync_parse_code("UPLOAD", fromserver, SYNC_PARSE_NOEAT_OKLINE, NULL);
if (r) return(r);
if ((c = getword(fromserver, &token)) != ' ') {
eatline(fromserver, c);
syslog(LOG_ERR, "Garbage on Upload response");
return(IMAP_PROTOCOL_ERROR);
}
eatline(fromserver, c);
/* Clear out msgid_on_server list if server restarted */
if (!strcmp(token.s, "[RESTART]")) {
int hash_size = msgid_onserver->hash_size;
sync_msgid_list_free(&msgid_onserver);
msgid_onserver = sync_msgid_list_create(hash_size);
syslog(LOG_INFO, "UPLOAD: received RESTART");
}
/* don't overload the server with too many uploads at once! */
if (count >= max_count) {
syslog(LOG_INFO, "UPLOAD: hit %d uploads at msgno %d", count, msgno);
goto repeatupload;
}
return(0);
}
static int upload_messages_from(struct mailbox *mailbox,
unsigned long old_last_uid)
{
unsigned long msgno;
int r = 0;
struct index_record record;
int count = 0;
int c = ' ';
static struct buf token; /* BSS */
if (chdir(mailbox->path)) {
syslog(LOG_ERR, "Couldn't chdir to %s: %s",
mailbox->path, strerror(errno));
return(IMAP_IOERROR);
}
for (msgno = 1 ; msgno <= mailbox->exists ; msgno++) {
r = mailbox_read_index_record(mailbox, msgno, &record);
if (r) {
syslog(LOG_ERR,
"IOERROR: reading index entry for nsgno %lu of %s: %m",
record.uid, mailbox->name);
return(IMAP_IOERROR);
}
if (record.uid <= old_last_uid)
continue;
if (count++ == 0)
prot_printf(toserver, "UPLOAD %lu %lu",
mailbox->last_uid, mailbox->last_appenddate);
if ((r=upload_message_work(mailbox, msgno, &record)))
return(r);
}
if (count == 0)
return(r);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r = sync_parse_code("UPLOAD", fromserver, SYNC_PARSE_NOEAT_OKLINE, NULL);
if (r) return(r);
if ((c = getword(fromserver, &token)) != ' ') {
eatline(fromserver, c);
syslog(LOG_ERR, "Garbage on Upload response");
return(IMAP_PROTOCOL_ERROR);
}
eatline(fromserver, c);
/* Clear out msgid_on_server list if server restarted */
if (!strcmp(token.s, "[RESTART]")) {
int hash_size = msgid_onserver->hash_size;
sync_msgid_list_free(&msgid_onserver);
msgid_onserver = sync_msgid_list_create(hash_size);
syslog(LOG_INFO, "UPLOAD: received RESTART");
}
return(0);
}
/* upload_messages() null operations still requires UIDLAST update */
static int update_uidlast(struct mailbox *mailbox)
{
prot_printf(toserver, "UIDLAST %lu %lu\r\n",
mailbox->last_uid, mailbox->last_appenddate);
prot_flush(toserver);
return(sync_parse_code("UIDLAST",fromserver, SYNC_PARSE_EAT_OKLINE, NULL));
}
/* ====================================================================== */
static int do_seen(char *user, char *name)
{
int r = 0;
struct mailbox m;
struct seen *seendb;
time_t lastread, lastchange;
unsigned int last_recent_uid;
char *seenuid = NULL;
if (verbose)
printf("SEEN %s %s\n", user, name);
if (verbose_logging)
syslog(LOG_INFO, "SEEN %s %s", user, name);
r = mailbox_open_header(name, 0, &m);
if (r) return(r);
r = seen_open(&m, user, 0, &seendb);
if (!r) {
r = seen_read(seendb, &lastread, &last_recent_uid,
&lastchange, &seenuid);
seen_close(seendb);
}
if (r) {
syslog(LOG_ERR, "Failed to read seendb (%s, %s): %s",
user, m.name, error_message(r));
goto bail;
}
/* Update seen list */
prot_printf(toserver, "SETSEEN ");
sync_printastring(toserver, user);
prot_printf(toserver, " ");
sync_printastring(toserver, m.name);
prot_printf(toserver, " %lu %lu %lu ",
lastread, last_recent_uid, lastchange);
sync_printastring(toserver, seenuid);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r = sync_parse_code("SETSEEN",fromserver,SYNC_PARSE_EAT_OKLINE,NULL);
bail:
mailbox_close(&m);
if (seenuid) free(seenuid);
return r;
}
/* ====================================================================== */
static int do_append(char *name)
{
struct mailbox m;
int r = 0;
int mailbox_open = 0;
int selected = 0;
unsigned long last_uid = 0;
struct index_record record;
if (verbose)
printf("APPEND %s\n", name);
if (verbose_logging)
syslog(LOG_INFO, "APPEND %s", name);
if ((r = mailbox_open_header(name, 0, &m)))
goto bail;
mailbox_open = 1;
if ((r = mailbox_open_index(&m)))
goto bail;
if ((r = folder_select(name, m.uniqueid, &last_uid)))
goto bail;
selected = 1;
if ((r = mailbox_read_index_record(&m, m.exists, &record)))
goto bail;
if ((record.uid > last_uid) && (r=upload_messages_from(&m, last_uid)))
goto bail;
bail:
if (mailbox_open) mailbox_close(&m);
return(r);
}
/* ====================================================================== */
static int do_acl(char *name)
{
int r = 0;
struct mailbox m;
if (verbose)
printf("SETACL %s\n", name);
if (verbose_logging)
syslog(LOG_INFO, "SETACL: %s", name);
r = mailbox_open_header(name, 0, &m);
if (r) return r;
r = folder_setacl(m.name, m.acl);
mailbox_close(&m);
return(r);
}
static int do_quota(char *name)
{
int r = 0;
struct quota quota;
if (verbose)
printf("SETQUOTA %s\n", name);
if (verbose_logging)
syslog(LOG_INFO, "SETQUOTA: %s", name);
quota.root = name;
r = update_quota_work(&quota, NULL);
return(r);
}
static int add_annot(const char *mailbox __attribute__((unused)),
const char *entry, const char *userid,
struct annotation_data *attrib, void *rock)
{
struct sync_annot_list *l = (struct sync_annot_list *) rock;
sync_annot_list_add(l, entry, userid, attrib->value);
return 0;
}
static int do_annotation(char *name)
{
int unsolicited, c, r = 0;
static struct buf entry, userid, value;
struct sync_annot_list *server_list = sync_annot_list_create();
prot_printf(toserver, "LIST_ANNOTATIONS ");
sync_printastring(toserver, name);
prot_printf(toserver, "\r\n", name);
prot_flush(toserver);
r=sync_parse_code("LIST_ANNOTATIONS", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited);
while (!r && unsolicited) {
if ((c = getastring(fromserver, toserver, &entry)) != ' ') {
syslog(LOG_ERR,
"LIST_ANNOTATIONS: Invalid type %d response from server: %s",
unsolicited, entry.s);
sync_eatlines_unsolicited(fromserver, c);
r = IMAP_PROTOCOL_ERROR;
break;
}
if ((c = getastring(fromserver, toserver, &userid)) != ' ') {
syslog(LOG_ERR,
"LIST_ANNOTATIONS: Invalid type %d response from server: %s",
unsolicited, userid.s);
sync_eatlines_unsolicited(fromserver, c);
r = IMAP_PROTOCOL_ERROR;
break;
}
c = getastring(fromserver, toserver, &value);
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') {
syslog(LOG_ERR,
"LIST_ANNOTATIONS: Invalid type %d response from server: %s",
unsolicited, value.s);
sync_eatlines_unsolicited(fromserver, c);
r = IMAP_PROTOCOL_ERROR;
break;
}
sync_annot_list_add(server_list, entry.s, userid.s, value.s);
r = sync_parse_code("LIST_ANNOTATIONS", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited);
}
if (!r) {
struct sync_annot_list *client_list = sync_annot_list_create();
struct sync_annot_item *c, *s;
int n;
annotatemore_findall(name, "*", &add_annot, client_list, NULL);
/* both lists are sorted, so we work our way through the lists
top-to-bottom and determine what we need to do based on order */
for (c = client_list->head,
s = server_list->head; c || s; c = c ? c->next : NULL) {
if (!s) n = -1; /* add all client annotations */
else if (!c) n = 1; /* remove all server annotations */
else if ((n = strcmp(c->entry, s->entry)) == 0)
n = strcmp(c->userid, s->userid);
if (n > 0) {
/* remove server annotations until we reach or pass the
current client annotation, or we reach the end of the
server list */
do {
if ((r = folder_setannotation(name, s->entry, s->userid,
NULL))) {
goto bail;
}
s = s->next;
if (!s) n = -1; /* end of server list, we're done */
else if (!c) n = 1; /* remove all server annotations */
else if ((n = strcmp(c->entry, s->entry)) == 0)
n = strcmp(c->userid, s->userid);
} while (n > 0);
}
if (n == 0) {
/* already have the annotation, but is the value different? */
if (strcmp(c->value, s->value) != 0) n = -1;
s = s->next;
}
if (c && n < 0) {
/* add the current client annotation */
if ((r = folder_setannotation(name, c->entry, c->userid,
c->value))) {
goto bail;
}
}
}
bail:
sync_annot_list_free(&client_list);
}
sync_annot_list_free(&server_list);
return(r);
}
/* ====================================================================== */
/* Caller should acquire expire lock before opening mailbox index:
* gives us readonly snapshot of mailbox for duration of upload
*/
static int do_mailbox_work(struct mailbox *mailbox,
struct sync_msg_list *list, int just_created,
char *uniqueid)
{
unsigned int last_recent_uid;
time_t lastread, lastchange;
struct seen *seendb;
char *seenuid;
int r = 0;
int selected = 0;
int flag_lookup_table[MAX_USER_FLAGS];
create_flags_lookup(flag_lookup_table,
mailbox->flagname, list->meta.flagname);
if (check_flags(mailbox, list, flag_lookup_table)) {
if (!selected &&
(r=folder_select(mailbox->name, mailbox->uniqueid, NULL)))
return(r);
selected = 1;
if ((r=update_flags(mailbox, list, flag_lookup_table)))
goto bail;
}
if (check_expunged(mailbox, list)) {
if (!selected &&
(r=folder_select(mailbox->name, mailbox->uniqueid, NULL)))
goto bail;
selected = 1;
if ((r=expunge(mailbox, list)))
goto bail;
}
if (check_upload_messages(mailbox, list)) {
if (!selected &&
(r=folder_select(mailbox->name, mailbox->uniqueid, NULL)))
goto bail;
selected = 1;
if ((r=upload_messages_list(mailbox, list)))
goto bail;
} else if (just_created || (list->last_uid != mailbox->last_uid)) {
if (!selected &&
(r=folder_select(mailbox->name, mailbox->uniqueid, NULL)))
goto bail;
selected = 1;
if ((r=update_uidlast(mailbox)))
goto bail;
}
bail:
return(r);
}
/* ====================================================================== */
int do_folders(struct sync_folder_list *client_list,
struct sync_folder_list *server_list,
int *vanishedp,
int do_contents,
int doing_user)
{
struct mailbox m;
int r = 0, mailbox_open = 0;
struct sync_rename_list *rename_list = sync_rename_list_create();
struct sync_folder *folder, *folder2;
*vanishedp = 0;
if (do_contents) {
/* Attempt to reserve messages on server that we would overwise have
* to upload from client */
if ((r = reserve_messages(client_list, server_list, vanishedp)))
goto bail;
} else {
/* Just need to check whether folders exist, get uniqueid */
if ((r = folders_get_uniqueid(client_list, vanishedp)))
goto bail;
}
/* Tag folders on server which still exist on the client. Anything
* on the server which remains untagged can be deleted immediately */
for (folder = client_list->head ; folder ; folder = folder->next) {
/* Quietly skip over folders that have already been processed */
if (folder->mark) continue;
if (folder->id &&
(folder2 = sync_folder_lookup(server_list, folder->id))) {
folder2->mark = 1;
if (strcmp(folder->name , folder2->name) != 0)
sync_rename_list_add(rename_list,
folder->id, folder2->name, folder->name);
}
}
/* Delete folders on server which no longer exist on client */
for (folder = server_list->head ; folder ; folder = folder->next) {
if (!folder->mark && ((r=folder_delete(folder->name)) != 0))
goto bail;
}
/* Need to rename folders in an order which avoids dependancy conflicts
* following isn't wildly efficient, but rename_list will typically be
* short and contain few dependancies. Algorithm is to simply pick a
* rename operation which has no dependancy and repeat until done */
while (rename_list->done < rename_list->count) {
int rename_success = 0;
struct sync_rename_item *item, *item2;
for (item = rename_list->head; item; item = item->next) {
if (item->done) continue;
item2 = sync_rename_lookup(rename_list, item->newname);
if (item2 && !item2->done) continue;
/* Found unprocessed item which should rename cleanly */
if ((r = folder_rename(item->oldname, item->newname))) {
syslog(LOG_ERR, "do_folders(): failed to rename: %s -> %s ",
item->oldname, item->newname);
goto bail;
}
rename_list->done++;
item->done = 1;
rename_success = 1;
}
if (!rename_success) {
/* Scanned entire list without a match */
syslog(LOG_ERR,
"do_folders(): failed to order folders correctly");
r = IMAP_PROTOCOL_ERROR;
goto bail;
}
}
for (folder = client_list->head ; folder ; folder = folder->next) {
/* Quietly skip over folders that have already been processed */
if (folder->mark || !folder->id) continue;
r = mailbox_open_header(folder->name, 0, &m);
/* Deal with folders deleted since start of function call. Likely
* cause concurrent rename/delete: caller may need countermeaures
* (e.g: lock out imapds for a few seconds and then retry)
*/
if (r == IMAP_MAILBOX_NONEXISTENT) {
(*vanishedp)++;
folder2 = sync_folder_lookup(server_list, folder->id);
if (folder2 && folder2->mark) {
if ((r=folder_delete(folder2->name))) goto bail;
folder2->mark = 0;
}
continue;
}
/* Quietly ignore objects that we don't have access to.
* Includes directory stubs, which have not underlying cyrus.*
* files in the filesystem */
if (r == IMAP_PERMISSION_DENIED) {
r = 0;
continue;
}
if (!r) mailbox_open = 1;
if (!r) r = mailbox_open_index(&m);
if (r) {
if (mailbox_open) mailbox_close(&m);
syslog(LOG_ERR, "IOERROR: Failed to open %s: %s",
folder->name, error_message(r));
r = IMAP_IOERROR;
goto bail;
}
if ((folder2=sync_folder_lookup(server_list, folder->id))) {
if (strcmp(folder2->id, m.uniqueid) != 0) {
/* Folder UniqueID has changed under our feet: force resync */
char *part;
if ((r=folder_delete(folder2->name)))
goto bail;
if ((r=mboxlist_detail(m.name,NULL,NULL,NULL,&part,NULL,NULL))
|| (r=folder_create(m.name,part,m.uniqueid,m.acl,m.options,
m.uidvalidity)))
goto bail;
if (!r && m.quota.root && !strcmp(m.name, m.quota.root))
r = update_quota_work(&m.quota, &folder2->quota);
if (!r) r = do_annotation(m.name);
if (!r && do_contents) {
struct sync_msg_list *folder_msglist;
/* 0L, 0L Forces last_uid and seendb push as well */
folder_msglist = sync_msg_list_create(m.flagname, 0);
r = do_mailbox_work(&m, folder_msglist, 1, m.uniqueid);
sync_msg_list_free(&folder_msglist);
}
} else {
/* Deal with existing folder */
if (!(folder2->acl && !strcmp(m.acl, folder2->acl)))
r = folder_setacl(folder->name, m.acl);
if ((folder2->options ^ m.options) & OPT_IMAP_CONDSTORE) {
r = folder_setannotation(m.name,
"/vendor/cmu/cyrus-imapd/condstore",
"",
(m.options & OPT_IMAP_CONDSTORE) ?
"true" : "false");
}
if (!r && m.quota.root && !strcmp(m.name, m.quota.root))
r = update_quota_work(&m.quota, &folder2->quota);
if (!r) r = do_annotation(m.name);
if (!r && do_contents)
r = do_mailbox_work(&m, folder2->msglist, 0, m.uniqueid);
}
} else {
char *userid, *part;
/* Need to create fresh folder on server */
if ((r=mboxlist_detail(m.name,NULL,NULL,NULL,&part,NULL,NULL)) ||
(r=folder_create(m.name,part,m.uniqueid,m.acl,m.options,
m.uidvalidity)))
goto bail;
if (!r && m.quota.root && !strcmp(m.name, m.quota.root))
r = update_quota_work(&m.quota, NULL);
if (!r) r = do_annotation(m.name);
if (!r && do_contents) {
struct sync_msg_list *folder_msglist;
/* 0L, 0L Forces last_uid and seendb push as well */
folder_msglist = sync_msg_list_create(m.flagname, 0);
r = do_mailbox_work(&m, folder_msglist, 1, m.uniqueid);
sync_msg_list_free(&folder_msglist);
}
if (!r && !doing_user && (userid = mboxname_isusermailbox(m.name, 1)))
r = do_meta(userid);
}
if (r) goto bail;
/* Mark folder as processed */
folder->mark = 1;
client_list->count--;
mailbox_close(&m);
mailbox_open = 0;
}
bail:
if (mailbox_open) mailbox_close(&m);
sync_rename_list_free(&rename_list);
return(r);
}
/* ====================================================================== */
/* Generate sync_folder_list including all msg information from
list of client folders */
int do_mailboxes_work(struct sync_folder_list *client_list,
struct sync_folder_list *server_list)
{
struct sync_folder *folder = NULL;
int c = ' ', r = 0;
int unsolicited_type;
struct sync_msg *msg = NULL;
static struct buf id;
static struct buf acl;
static struct buf name;
static struct buf lastuid;
static struct buf options;
static struct buf arg;
struct quota quota, *quotap;
prot_printf(toserver, "MAILBOXES");
for (folder = client_list->head ; folder; folder = folder->next) {
/* Quietly skip over folders that have already been processed */
if (folder->mark) continue;
prot_printf(toserver, " ");
sync_printastring(toserver, folder->name);
}
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r = sync_parse_code("MAILBOXES", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited_type);
while (!r && (unsolicited_type > 0)) {
switch (unsolicited_type) {
case 2:
/* New folder */
if ((c = getword(fromserver, &id)) != ' ')
goto parse_err;
if ((c = getastring(fromserver, toserver, &name)) != ' ')
goto parse_err;
if ((c = getastring(fromserver, toserver, &acl)) != ' ')
goto parse_err;
if ((c = getastring(fromserver, toserver, &lastuid)) != ' ')
goto parse_err;
c = getastring(fromserver, toserver, &options);
quotap = NULL;
if (c == ' ') {
c = getword(fromserver, &arg);
quota.limit = atoi(arg.s);
quotap = &quota;
}
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') goto parse_err;
if (!imparse_isnumber(lastuid.s)) goto parse_err;
folder = sync_folder_list_add(server_list, id.s, name.s, acl.s,
sync_atoul(options.s), quotap);
folder->msglist = sync_msg_list_create(NULL, sync_atoul(lastuid.s));
break;
case 1:
/* New message in current folder */
if (folder == NULL) goto parse_err; /* No current folder */
msg = sync_msg_list_add(folder->msglist);
if (((c = getword(fromserver, &arg)) != ' ') ||
((msg->uid = sync_atoul(arg.s)) == 0)) goto parse_err;
if (((c = getword(fromserver, &arg)) != ' ')) goto parse_err;
if (!message_uuid_from_text(&msg->uuid, arg.s))
goto parse_err;
c = sync_getflags(fromserver, &msg->flags, &folder->msglist->meta);
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') goto parse_err;
break;
default:
goto parse_err;
}
r = sync_parse_code("MAILBOXES", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited_type);
}
return(r);
parse_err:
syslog(LOG_ERR,
"MAILBOXES: Invalid unsolicited response type %d from server: %s",
unsolicited_type, arg.s);
sync_eatlines_unsolicited(fromserver, c);
return(IMAP_PROTOCOL_ERROR);
}
/* ====================================================================== */
static int do_mailboxes(struct sync_folder_list *client_folder_list)
{
struct sync_folder_list *server_folder_list = sync_folder_list_create();
int r = 0;
int vanished = 0;
struct sync_folder *folder;
if (verbose) {
printf("MAILBOXES");
for (folder = client_folder_list->head; folder ; folder = folder->next) {
/* Quietly skip over folders that have already been processed */
if (folder->mark) continue;
printf(" %s", folder->name);
}
printf("\n");
}
if (verbose_logging) {
for (folder = client_folder_list->head; folder ; folder = folder->next) {
/* Quietly skip over folders that have already been processed */
if (folder->mark) continue;
syslog(LOG_INFO, "MAILBOX %s", folder->name);
}
}
/* Worthwhile doing mailboxes even in case of single mailbox:
* catches duplicate messages in single folder. Only cost is that
* mailbox at server end is opened twice: once for do_mailboxes_work(),
* once for do_folders() */
if (!r) r = do_mailboxes_work(client_folder_list,
server_folder_list);
if (!r) r = do_folders(client_folder_list, server_folder_list,
&vanished, 1, 0);
sync_folder_list_free(&server_folder_list);
return(r);
}
/* ====================================================================== */
static int addmbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct sync_folder_list *list = (struct sync_folder_list *) rock;
int mbtype;
mboxlist_detail(name, &mbtype, NULL, NULL, NULL, NULL, NULL);
if (!(mbtype & (MBTYPE_RESERVE | MBTYPE_MOVING | MBTYPE_REMOTE))) {
sync_folder_list_add(list, NULL, name, NULL, 0, NULL);
}
return(0);
}
static int addmbox_sub(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct sync_folder_list *list = (struct sync_folder_list *) rock;
sync_folder_list_add(list, name, name, NULL, 0, NULL);
return(0);
}
/* ====================================================================== */
int do_mailbox_preload(struct sync_folder *folder)
{
struct mailbox m;
int r = 0;
unsigned long msgno;
struct index_record record;
int lastuid = 0;
if ((r=mailbox_open_header(folder->name, 0, &m)))
return(r);
if (!r) r = mailbox_open_index(&m);
/* Quietly preload data from index */
for (msgno = 1 ; msgno <= m.exists; msgno++) {
mailbox_read_index_record(&m, msgno, &record);
/* Fairly pointless, just to ensure that compiler doesn't
optimise loop away somehow */
if (record.uid <= lastuid)
syslog(LOG_ERR, "cmd_status_work_sub(): UIDs out of order!");
}
mailbox_close(&m);
return r;
}
int do_user_preload(char *user)
{
char buf[MAX_MAILBOX_NAME+1];
int r = 0;
struct sync_folder_list *client_list = sync_folder_list_create();
struct sync_folder *folder;
/* Generate full list of folders on client side */
(sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX",
user, buf);
addmbox(buf, 0, 0, (void *)client_list);
strlcat(buf, ".*", sizeof(buf));
r = (sync_namespace.mboxlist_findall)(&sync_namespace, buf, 1,
user, NULL, addmbox,
(void *)client_list);
if (r) {
syslog(LOG_ERR, "IOERROR: %s", error_message(r));
sync_folder_list_free(&client_list);
return(r);
}
for (folder = client_list->head ; folder ; folder = folder->next) {
r = do_mailbox_preload(folder);
if (r) break;
}
sync_folder_list_free(&client_list);
return(r);
}
int do_user_main(char *user, struct sync_folder_list *server_list,
int *vanishedp)
{
char buf[MAX_MAILBOX_NAME+1];
int r = 0;
struct sync_folder_list *client_list = sync_folder_list_create();
/* Generate full list of folders on client side */
(sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX",
user, buf);
addmbox(buf, 0, 0, (void *)client_list);
strlcat(buf, ".*", sizeof(buf));
r = (sync_namespace.mboxlist_findall)(&sync_namespace, buf, 1,
user, NULL, addmbox,
(void *)client_list);
if (r) {
syslog(LOG_ERR, "IOERROR: %s", error_message(r));
return(r);
}
return(do_folders(client_list, server_list, vanishedp, 1, 1));
}
int do_user_sub(char *user, struct sync_folder_list *server_list)
{
int r = 0;
struct sync_folder_list *client_list = sync_folder_list_create();
struct sync_folder *c, *s;
int n;
char buf[MAX_MAILBOX_NAME+1];
/* Includes subsiduary nodes automatically */
r = (sync_namespace.mboxlist_findsub)(&sync_namespace, "*", 1,
user, NULL, addmbox_sub,
(void *)client_list, 1);
if (r) {
syslog(LOG_ERR, "IOERROR: %s", error_message(r));
goto bail;
}
/* both lists are sorted, so we work our way through the lists
top-to-bottom and determine what we need to do based on order */
for (c = client_list->head,
s = server_list->head; c || s; c = c ? c->next : NULL) {
if (!s) n = -1; /* add all client subscriptions */
else if (!c) n = 1; /* remove all server subscriptions */
else n = strcmp(c->name, s->name);
if (n > 0) {
/* remove server subscriptions until we reach or pass the
current client subscription, or we reach the end of the
server list */
do {
(sync_namespace.mboxname_tointernal)(&sync_namespace, s->name,
user, buf);
if ((r = user_delsub(user, buf))) goto bail;
s = s->next;
if (!s) n = -1; /* end of server list, we're done */
else if (!c) n = 1; /* remove all server subscriptions */
else n = strcmp(c->name, s->name);
} while (n > 0);
}
if (n == 0) {
/* already subscribed, skip it */
s = s->next;
}
else if (c && n < 0) {
/* add the current client subscription */
if ((r = user_addsub(user, c->name))) goto bail;
}
}
bail:
sync_folder_list_free(&client_list);
return(r);
}
static int do_user_seen(char *user)
{
char *seen_file = seen_getpath(user);
int filefd;
const char *base;
unsigned long len;
struct stat sbuf;
/* map file */
filefd = open(seen_file, O_RDONLY, 0666);
if (filefd == -1) {
if (errno == ENOENT) {
/* its ok if it doesn't exist */
free(seen_file);
return 0;
}
syslog(LOG_ERR, "IOERROR: open on %s: %m", seen_file);
return IMAP_SYS_ERROR;
}
if (fstat(filefd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m", seen_file);
fatal("can't fstat seen db", EC_OSFILE);
}
base = NULL;
len = 0;
map_refresh(filefd, 1, &base, &len, sbuf.st_size, seen_file, NULL);
close(filefd);
free(seen_file);
/* Update seen db */
prot_printf(toserver, "SETSEEN_ALL ");
sync_printastring(toserver, user);
prot_printf(toserver, " {%lu+}\r\n", len);
prot_write(toserver, base, len);
map_free(&base, &len);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(sync_parse_code("SETSEEN_ALL",fromserver,SYNC_PARSE_EAT_OKLINE,NULL));
}
int do_user_sieve(char *user, struct sync_sieve_list *server_list)
{
int r = 0;
struct sync_sieve_list *client_list;
struct sync_sieve_item *item, *item2;
int client_active = 0;
int server_active = 0;
if ((client_list = sync_sieve_list_generate(user)) == NULL) {
syslog(LOG_ERR, "Unable to list sieve scripts for %s", user);
return(IMAP_IOERROR);
}
/* Upload missing and out of date scripts */
for (item = client_list->head ; item ; item = item->next) {
if ((item2 = sync_sieve_lookup(server_list, item->name))) {
item2->mark = 1;
if ((item2->last_update < item->last_update) &&
(r=sieve_upload(user, item->name, item->last_update)))
goto bail;
} else if ((r=sieve_upload(user, item->name, item->last_update)))
goto bail;
}
/* Delete scripts which no longer exist on the client */
server_active = 0;
for (item = server_list->head ; item ; item = item->next) {
if (item->mark) {
if (item->active) server_active = 1;
} else if ((r=sieve_delete(user, item->name)))
goto bail;
}
/* Change active script if necessary */
client_active = 0;
for (item = client_list->head ; item ; item = item->next) {
if (!item->active)
continue;
client_active = 1;
if (!((item2 = sync_sieve_lookup(server_list, item->name)) &&
(item2->active))) {
if ((r = sieve_activate(user, item->name)))
goto bail;
server_active = 1;
}
break;
}
if (!r && !client_active && server_active)
r = sieve_deactivate(user);
bail:
sync_sieve_list_free(&client_list);
return(r);
}
/* do_user() separated into two parts so that we can start process
* asynchronously, come back and parse result when local list generated */
int do_user_start(char *user)
{
prot_printf(toserver, "USER ");
sync_printastring(toserver, user);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
return(0);
}
int do_user_parse(char *user,
struct sync_folder_list *server_list,
struct sync_folder_list *server_sub_list,
struct sync_sieve_list *server_sieve_list)
{
int r = 0;
int c = ' ';
int active = 0;
int unsolicited_type;
static struct buf id;
static struct buf name;
static struct buf time;
static struct buf flag;
static struct buf acl;
static struct buf lastuid;
static struct buf options;
static struct buf arg;
struct sync_folder *folder = NULL;
struct sync_msg *msg = NULL;
struct quota quota, *quotap;
r = sync_parse_code("USER", fromserver,
SYNC_PARSE_NOEAT_OKLINE, &unsolicited_type);
/* Unpleasant: translate remote access error into "please reset me" */
if (r == IMAP_MAILBOX_NONEXISTENT)
return(0);
while (!r && (unsolicited_type > 0)) {
switch (unsolicited_type) {
case 4:
/* New Sieve script */
c = getastring(fromserver, toserver, &name);
if (c != ' ') goto parse_err;
c = getastring(fromserver, toserver, &time);
if (c == ' ') {
c = getastring(fromserver, toserver, &flag);
if (!strcmp(flag.s, "*"))
active = 1;
} else
active = 0;
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') goto parse_err;
sync_sieve_list_add(server_sieve_list,
name.s, atoi(time.s), active);
break;
case 3:
/* New subscription */
c = getastring(fromserver, toserver, &name);
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') goto parse_err;
sync_folder_list_add(server_sub_list, name.s, name.s, NULL, 0, NULL);
break;
case 2:
/* New folder */
if ((c = getword(fromserver, &id)) != ' ')
goto parse_err;
if ((c = getastring(fromserver, toserver, &name)) != ' ')
goto parse_err;
if ((c = getastring(fromserver, toserver, &acl)) != ' ')
goto parse_err;
if ((c = getastring(fromserver, toserver, &lastuid)) != ' ')
goto parse_err;
c = getastring(fromserver, toserver, &options);
quotap = NULL;
if (c == ' ') {
c = getword(fromserver, &arg);
quota.limit = atoi(arg.s);
quotap = &quota;
}
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') goto parse_err;
if (!imparse_isnumber(lastuid.s)) goto parse_err;
folder = sync_folder_list_add(server_list, id.s, name.s, acl.s,
sync_atoul(options.s), quotap);
folder->msglist = sync_msg_list_create(NULL, sync_atoul(lastuid.s));
break;
case 1:
/* New message in current folder */
if (folder == NULL) goto parse_err; /* No current folder */
msg = sync_msg_list_add(folder->msglist);
if (((c = getword(fromserver, &arg)) != ' ') ||
((msg->uid = sync_atoul(arg.s)) == 0)) goto parse_err;
if (((c = getword(fromserver, &arg)) != ' ')) goto parse_err;
if (!message_uuid_from_text(&msg->uuid, arg.s))
goto parse_err;
c = sync_getflags(fromserver, &msg->flags, &folder->msglist->meta);
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') goto parse_err;
break;
default:
goto parse_err;
}
r = sync_parse_code("USER", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited_type);
}
return(r);
parse_err:
syslog(LOG_ERR, "USER: Invalid type %d response from server",
unsolicited_type);
sync_eatlines_unsolicited(fromserver, c);
return(IMAP_PROTOCOL_ERROR);
}
int do_user_work(char *user, int *vanishedp)
{
char buf[MAX_MAILBOX_NAME+1];
int r = 0, mailbox_open = 0;
struct sync_folder_list *server_list = sync_folder_list_create();
struct sync_folder_list *server_sub_list = sync_folder_list_create();
struct sync_sieve_list *server_sieve_list = sync_sieve_list_create();
struct mailbox m;
struct sync_folder *folder2;
if (verbose)
printf("USER %s\n", user);
if (verbose_logging)
syslog(LOG_INFO, "USER %s", user);
(sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX",
user, buf);
r = mailbox_open_header(buf, 0, &m);
if (r == IMAP_MAILBOX_NONEXISTENT) {
/* user has been removed, RESET server */
r = user_reset(user);
goto bail;
}
if (!r) mailbox_open = 1;
if (!r) r = mailbox_open_index(&m);
if (r) {
if (mailbox_open) mailbox_close(&m);
syslog(LOG_ERR, "IOERROR: Failed to open %s: %s",
buf, error_message(r));
r = IMAP_IOERROR;
goto bail;
}
/* Get server started */
do_user_start(user);
/* Preload data at client end while server is working */
do_user_preload(user);
r = do_user_parse(user, server_list, server_sub_list, server_sieve_list);
if (r) {
sync_folder_list_free(&server_list);
sync_folder_list_free(&server_sub_list);
return(r);
}
/* Reset target account entirely if uniqueid of inbox doesn't match
* (Most likely reason is that source account has been replaced)
* Also if mailbox doesn't exist at all on target.
*/
if (((folder2 = sync_folder_lookup_byname(server_list, m.name)) == NULL) ||
(strcmp(m.uniqueid, folder2->id) != 0)) {
r = user_reset(user);
/* Reset local copies */
sync_folder_list_free(&server_list);
sync_folder_list_free(&server_sub_list);
server_list = sync_folder_list_create();
server_sub_list = sync_folder_list_create();
}
mailbox_close(&m);
if (!r) r = do_user_main(user, server_list, vanishedp);
if (!r) r = do_user_sub(user, server_sub_list);
if (!r) r = do_user_seen(user);
if (!r) r = do_user_sieve(user, server_sieve_list);
bail:
sync_folder_list_free(&server_list);
sync_folder_list_free(&server_sub_list);
sync_sieve_list_free(&server_sieve_list);
return(r);
}
static int do_user(char *user)
{
struct sync_lock lock;
int r = 0;
int vanished = 0;
/* Most of the time we don't need locking here: rename (the only
* complicated case) is pretty rare, especially in the middle of the
* night, which is when most of this will be going on */
r = do_user_work(user, &vanished);
/* Complication: monthly folder rotation causes rapid rename+create.
*
* mailbox_open_header() and mailbox_open_index() bail out with
* IMAP_MAILBOX_BADFORMAT if they try to open a mailbox which is
* currently in the process of being created. This is a nasty race
* condition which imapd just ignores (presumably on the principle that
* rapid rename+create+select would be very rare in normal use).
*
* We could solve this problem by putting a sync_lock() around
* _every_ single replication operation, but this is tedious and would
* probably involve quite a lot of overhead. As an experiment
* catch IMAP_MAILBOX_BADFORMAT and have another go while locking out
* user access to the mboxlist.
*/
if (r == IMAP_MAILBOX_BADFORMAT)
syslog(LOG_ERR,
"do_user() IMAP_MAILBOX_BADFORMAT: retrying with snapshot");
if ((r == IMAP_MAILBOX_BADFORMAT) || (vanished > 0)) {
/* (vanished > 0): If we lost a folder in transit, lock the user
* out of mboxlist for a few seconds while we retry. Will be a NOOP
* if folder actually was deleted during do_user_work run.
* Following just protects us against folder rename smack in the
* middle of night or manual sys. admin inspired sync run */
r = do_user_work(user, &vanished);
}
return(r);
}
/* ====================================================================== */
static int do_meta_sub(char *user)
{
int unsolicited, c, r = 0;
static struct buf name;
struct sync_folder_list *server_list = sync_folder_list_create();
prot_printf(toserver, "LSUB ");
sync_printastring(toserver, user);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r=sync_parse_code("LSUB",fromserver, SYNC_PARSE_EAT_OKLINE, &unsolicited);
while (!r && unsolicited) {
c = getastring(fromserver, toserver, &name);
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') {
syslog(LOG_ERR, "LSUB: Invalid type %d response from server: %s",
unsolicited, name.s);
sync_eatlines_unsolicited(fromserver, c);
r = IMAP_PROTOCOL_ERROR;
break;
}
sync_folder_list_add(server_list, name.s, name.s, NULL, 0, NULL);
r = sync_parse_code("LSUB", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited);
}
if (!r) r = do_user_sub(user, server_list);
sync_folder_list_free(&server_list);
return(r);
}
static int do_meta_sieve(char *user)
{
int unsolicited, c, r = 0;
static struct buf name;
static struct buf time;
static struct buf flag;
struct sync_sieve_list *server_list = sync_sieve_list_create();
int active = 0;
prot_printf(toserver, "LIST_SIEVE ");
sync_printastring(toserver, user);
prot_printf(toserver, "\r\n");
prot_flush(toserver);
r=sync_parse_code("LIST_SIEVE",
fromserver, SYNC_PARSE_EAT_OKLINE, &unsolicited);
while (!r && unsolicited) {
c = getastring(fromserver, toserver, &name);
if (c != ' ') {
syslog(LOG_ERR,
"LIST_SIEVE: Invalid name response from server: %s",
name.s);
sync_eatlines_unsolicited(fromserver, c);
r = IMAP_PROTOCOL_ERROR;
break;
}
c = getastring(fromserver, toserver, &time);
if (c == ' ') {
c = getastring(fromserver, toserver, &flag);
if (!strcmp(flag.s, "*"))
active = 1;
} else
active = 0;
if (c == '\r') c = prot_getc(fromserver);
if (c != '\n') {
syslog(LOG_ERR,
"LIST_SIEVE: Invalid flag response from server: %s",
flag.s);
sync_eatlines_unsolicited(fromserver, c);
r = IMAP_PROTOCOL_ERROR;
break;
}
sync_sieve_list_add(server_list, name.s, atoi(time.s), active);
r = sync_parse_code("LIST_SIEVE", fromserver,
SYNC_PARSE_EAT_OKLINE, &unsolicited);
}
if (r) {
sync_sieve_list_free(&server_list);
return(IMAP_IOERROR);
}
r = do_user_sieve(user, server_list);
sync_sieve_list_free(&server_list);
return(r);
}
static int do_sieve(char *user)
{
int r = 0;
r = do_meta_sieve(user);
return(r);
}
static int do_meta(char *user)
{
int r = 0;
if (verbose)
printf("META %s\n", user);
if (verbose_logging)
syslog(LOG_INFO, "META %s", user);
if (!r) r = do_meta_sub(user);
if (!r) r = do_user_seen(user);
if (!r) r = do_meta_sieve(user);
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 void remove_folder(char *name, struct sync_action_list *list,
int chk_child)
{
struct sync_action *action;
size_t len = strlen(name);
for (action = list->head ; action ; action = action->next) {
if (!strncmp(name, action->name, len) &&
((action->name[len] == '\0') ||
(chk_child && (action->name[len] == '.')))) {
action->active = 0;
}
}
}
/* ====================================================================== */
#define SYNC_MAILBOX_RETRIES 3
static int do_sync(const char *filename)
{
struct sync_user_list *user_folder_list = sync_user_list_create();
struct sync_user *user;
struct sync_action_list *user_list = sync_action_list_create();
struct sync_action_list *meta_list = sync_action_list_create();
struct sync_action_list *sieve_list = sync_action_list_create();
struct sync_action_list *mailbox_list= sync_action_list_create();
struct sync_action_list *append_list = sync_action_list_create();
struct sync_action_list *acl_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_action_list *unsub_list = sync_action_list_create();
struct sync_folder_list *folder_list = sync_folder_list_create();
static struct buf type, arg1, arg2;
char *arg1s, *arg2s;
char *userid;
struct sync_action *action;
int c;
int fd;
struct protstream *input;
int r = 0;
if ((filename == NULL) || !strcmp(filename, "-"))
fd = 0;
else {
if ((fd = open(filename, O_RDWR)) < 0) {
syslog(LOG_ERR, "Failed to open %s: %m", filename);
return(IMAP_IOERROR);
}
if (lock_blocking(fd) < 0) {
syslog(LOG_ERR, "Failed to lock %s: %m", filename);
return(IMAP_IOERROR);
}
}
input = prot_new(fd, 0);
while (1) {
if ((c = getword(input, &type)) == EOF)
break;
/* Ignore blank lines */
if (c == '\r') c = prot_getc(input);
if (c == '\n')
continue;
if (c != ' ') {
syslog(LOG_ERR, "Invalid input");
eatline(input, c);
continue;
}
if ((c = getastring(input, 0, &arg1)) == EOF)
break;
arg1s = arg1.s;
if (c == ' ') {
if ((c = getastring(input, 0, &arg2)) == EOF)
break;
arg2s = arg2.s;
} else
arg2s = NULL;
if (c == '\r') c = prot_getc(input);
if (c != '\n') {
syslog(LOG_ERR, "Garbage at end of input line");
eatline(input, c);
continue;
}
ucase(type.s);
if (!strcmp(type.s, "USER"))
sync_action_list_add(user_list, NULL, arg1s);
else if (!strcmp(type.s, "META"))
sync_action_list_add(meta_list, NULL, arg1s);
else if (!strcmp(type.s, "SIEVE"))
sync_action_list_add(meta_list, NULL, arg1s);
else if (!strcmp(type.s, "MAILBOX"))
sync_action_list_add(mailbox_list, arg1s, NULL);
else if (!strcmp(type.s, "APPEND"))
sync_action_list_add(append_list, arg1s, NULL);
else if (!strcmp(type.s, "ACL"))
sync_action_list_add(acl_list, arg1s, NULL);
else if (!strcmp(type.s, "QUOTA"))
sync_action_list_add(quota_list, arg1s, NULL);
else if (!strcmp(type.s, "ANNOTATION"))
sync_action_list_add(annot_list, arg1s, NULL);
else if (!strcmp(type.s, "SEEN"))
sync_action_list_add(seen_list, arg2s, arg1s);
else if (!strcmp(type.s, "SUB"))
sync_action_list_add(sub_list, arg2s, arg1s);
else if (!strcmp(type.s, "UNSUB"))
sync_action_list_add(unsub_list, arg2s, arg1s);
else
syslog(LOG_ERR, "Unknown action type: %s", type.s);
}
/* Optimise out redundant clauses */
for (action = user_list->head ; action ; action = action->next) {
char inboxname[MAX_MAILBOX_NAME+1];
/* USER action overrides any MAILBOX, APPEND, ACL, QUOTA, ANNOTATION action on
any of the user's mailboxes or any META, SIEVE, SEEN, SUB, UNSUB
action for same user */
(sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX",
action->user, inboxname);
remove_folder(inboxname, mailbox_list, 1);
remove_folder(inboxname, append_list, 1);
remove_folder(inboxname, acl_list, 1);
remove_folder(inboxname, quota_list, 1);
remove_folder(inboxname, annot_list, 1);
remove_meta(action->user, meta_list);
remove_meta(action->user, sieve_list);
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
remove_meta(action->user, unsub_list);
}
for (action = meta_list->head ; action ; action = action->next) {
/* META action overrides any user SIEVE, SEEN, SUB, UNSUB action
for same user */
remove_meta(action->user, sieve_list);
remove_meta(action->user, seen_list);
remove_meta(action->user, sub_list);
remove_meta(action->user, unsub_list);
}
for (action = mailbox_list->head ; action ; action = action->next) {
/* MAILBOX action overrides any APPEND, ACL, QUOTA, ANNOTATION action
on same mailbox */
remove_folder(action->name, append_list, 0);
remove_folder(action->name, acl_list, 0);
remove_folder(action->name, quota_list, 0);
remove_folder(action->name, annot_list, 0);
}
/* Create a lock for our transaction */
if ((r = send_lock())) goto cleanup;
/* And then run tasks. */
for (action = append_list->head ; action ; action = action->next) {
if (!action->active)
continue;
if (do_append(action->name)) {
sync_action_list_add(mailbox_list, action->name, NULL);
if (verbose) {
printf(" Promoting: APPEND %s -> MAILBOX %s\n",
action->name, action->name);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: APPEND %s -> MAILBOX %s",
action->name, action->name);
}
}
}
for (action = acl_list->head ; action ; action = action->next) {
if (action->active && do_acl(action->name)) {
sync_action_list_add(mailbox_list, action->name, NULL);
if (verbose) {
printf(" Promoting: ACL %s -> MAILBOX %s\n",
action->name, action->name);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: ACL %s -> MAILBOX %s",
action->name, action->name);
}
}
}
for (action = quota_list->head ; action ; action = action->next) {
if (action->active && do_quota(action->name)) {
sync_action_list_add(mailbox_list, action->name, NULL);
if (verbose) {
printf(" Promoting: QUOTA %s -> MAILBOX %s\n",
action->name, action->name);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: QUOTA %s -> MAILBOX %s",
action->name, action->name);
}
}
}
for (action = annot_list->head ; action ; action = action->next) {
if (action->active && do_annotation(action->name) && *action->name) {
sync_action_list_add(mailbox_list, action->name, NULL);
if (verbose) {
printf(" Promoting: ANNOTATION %s -> MAILBOX %s\n",
action->name, action->name);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: ANNOTATION %s -> MAILBOX %s",
action->name, action->name);
}
}
}
for (action = sieve_list->head ; action ; action = action->next) {
if (action->active && do_sieve(action->user)) {
sync_action_list_add(meta_list, NULL, action->user);
if (verbose) {
printf(" Promoting: SIEVE %s -> META %s\n",
action->user, action->user);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: SIEVE %s -> META %s",
action->user, action->user);
}
}
}
for (action = seen_list->head ; action ; action = action->next) {
if (action->active && do_seen(action->user, action->name)) {
char *userid = mboxname_isusermailbox(action->name, 1);
if (userid && !strcmp(userid, action->user)) {
sync_action_list_add(user_list, NULL, action->user);
if (verbose) {
printf(" Promoting: SEEN %s %s -> USER %s\n",
action->user, action->name, action->user);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: SEEN %s %s -> USER %s",
action->user, action->name, action->user);
}
} else {
sync_action_list_add(meta_list, NULL, action->user);
if (verbose) {
printf(" Promoting: SEEN %s %s -> META %s\n",
action->user, action->name, action->user);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: SEEN %s %s -> META %s",
action->user, action->name, action->user);
}
}
}
}
for (action = sub_list->head ; action ; action = action->next) {
if (action->active && user_addsub(action->user, action->name)) {
sync_action_list_add(meta_list, NULL, action->user);
if (verbose) {
printf(" Promoting: SUB %s %s -> META %s\n",
action->user, action->name, action->user);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: SUB %s %s -> META %s",
action->user, action->name, action->name);
}
}
}
for (action = unsub_list->head ; action ; action = action->next) {
if (action->active && user_delsub(action->user, action->name)) {
sync_action_list_add(meta_list, NULL, action->user);
if (verbose) {
printf(" Promoting: UNSUB %s %s -> META %s\n",
action->user, action->name, action->user);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: UNSUB %s %s -> META %s",
action->user, action->name, action->name);
}
}
}
for (action = mailbox_list->head ; action ; action = action->next) {
if (!action->active)
continue;
sync_folder_list_add(folder_list, NULL, action->name, NULL, 0, NULL);
}
if (folder_list->count) {
int n = 0;
do {
sleep(n*2); /* XXX should this be longer? */
r = do_mailboxes(folder_list);
if (r) {
/* promote failed personal mailboxes to USER */
struct sync_folder *folder;
char *userid, *p;
for (folder = folder_list->head; folder && folder->mark;
folder = folder->next);
if (folder &&
(userid = xstrdup(mboxname_isusermailbox(folder->name, 0)))) {
if ((p = strchr(userid, '.'))) *p = '\0';
folder->mark = 1;
if (--folder_list->count == 0) r = 0;
sync_action_list_add(user_list, NULL, userid);
if (verbose) {
printf(" Promoting: MAILBOX %s -> USER %s\n",
folder->name, userid);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: MAILBOX %s -> USER %s",
folder->name, userid);
}
free(userid);
}
}
} while (r && (++n < SYNC_MAILBOX_RETRIES));
if (r) goto bail;
}
for (action = meta_list->head ; action ; action = action->next) {
if (action->active && (r=do_meta(action->user))) {
if (r == IMAP_INVALID_USER) goto bail;
sync_action_list_add(user_list, NULL, action->user);
if (verbose) {
printf(" Promoting: META %s -> USER %s\n",
action->user, action->user);
}
if (verbose_logging) {
syslog(LOG_INFO, " Promoting: META %s -> USER %s",
action->user, action->user);
}
}
}
for (action = user_list->head ; action ; action = action->next) {
int n = 0;
do {
sleep(n*2); /* XXX should this be longer? */
r = do_user(action->user);
} while (r && (++n < SYNC_MAILBOX_RETRIES));
if (r) goto bail;
}
bail:
send_unlock();
cleanup:
if (r) {
if (verbose)
fprintf(stderr, "Error in do_sync(): bailing out!\n");
syslog(LOG_ERR, "Error in do_sync(): bailing out!");
}
sync_user_list_free(&user_folder_list);
sync_action_list_free(&user_list);
sync_action_list_free(&meta_list);
sync_action_list_free(&sieve_list);
sync_action_list_free(&mailbox_list);
sync_action_list_free(&append_list);
sync_action_list_free(&acl_list);
sync_action_list_free(&quota_list);
sync_action_list_free(&annot_list);
sync_action_list_free(&seen_list);
sync_action_list_free(&sub_list);
sync_action_list_free(&unsub_list);
sync_folder_list_free(&folder_list);
prot_free(input);
close(fd);
return(r);
}
/* ====================================================================== */
int do_daemon_work(const char *sync_log_file, const char *sync_shutdown_file,
unsigned long timeout, unsigned long min_delta,
int *restartp)
{
int r = 0;
char *work_file_name;
time_t session_start;
time_t single_start;
int delta;
struct stat sbuf;
*restartp = 0;
work_file_name = xmalloc(strlen(sync_log_file)+20);
snprintf(work_file_name, strlen(sync_log_file)+20,
"%s-%d", sync_log_file, getpid());
session_start = time(NULL);
while (1) {
single_start = time(NULL);
if (sync_shutdown_file && !stat(sync_shutdown_file, &sbuf)) {
unlink(sync_shutdown_file);
break;
}
if ((timeout > 0) && ((single_start - session_start) > timeout)) {
*restartp = 1;
break;
}
if (stat(sync_log_file, &sbuf) < 0) {
if (min_delta > 0) {
sleep(min_delta);
} else {
usleep(100000); /* 1/10th second */
}
continue;
}
if (rename(sync_log_file, work_file_name) < 0) {
syslog(LOG_ERR, "Rename %s -> %s failed: %m",
sync_log_file, work_file_name);
exit(1);
}
if ((r=do_sync(work_file_name)))
return(r);
if (unlink(work_file_name) < 0) {
syslog(LOG_ERR, "Unlink %s failed: %m", work_file_name);
exit(1);
}
delta = time(NULL) - single_start;
if ((delta < min_delta) && ((min_delta-delta) > 0))
sleep(min_delta-delta);
}
free(work_file_name);
if (*restartp == 0)
return(0);
prot_printf(toserver, "RESTART\r\n");
prot_flush(toserver);
r = sync_parse_code("RESTART", fromserver, SYNC_PARSE_EAT_OKLINE, NULL);
if (r)
syslog(LOG_ERR, "sync_client RESTART failed");
else
syslog(LOG_INFO, "sync_client RESTART succeeded");
return(r);
}
struct backend *replica_connect(struct backend *be, const char *servername,
sasl_callback_t *cb)
{
int wait;
for (wait = 15;; wait *= 2) {
be = backend_connect(be, servername, &protocol[PROTOCOL_CSYNC],
"", cb, NULL);
if (be || connect_once || wait > 1000) break;
fprintf(stderr,
"Can not connect to server '%s', retrying in %d seconds\n",
servername, wait);
sleep(wait);
}
if (!be) {
fprintf(stderr, "Can not connect to server '%s'\n",
servername);
_exit(1);
}
return be;
}
void do_daemon(const char *sync_log_file, const char *sync_shutdown_file,
unsigned long timeout, unsigned long min_delta,
struct backend *be, sasl_callback_t *cb)
{
int r = 0;
pid_t pid;
int status;
int restart;
/* for a child so we can release from master */
if ((pid=fork()) < 0)
fatal("fork failed", EC_SOFTWARE);
if (pid != 0) { /* parent */
cyrus_done();
exit(0);
}
/* child */
if (timeout == 0) {
do_daemon_work(sync_log_file, sync_shutdown_file,
timeout, min_delta, &restart);
return;
}
do {
if ((pid=fork()) < 0)
fatal("fork failed", EC_SOFTWARE);
if (pid == 0) {
if (be->sock == -1) {
/* Reopen up connection to server */
be = replica_connect(be, be->hostname, cb);
if (!be) {
fprintf(stderr, "Can not connect to server '%s'\n",
be->hostname);
_exit(1);
}
/* XXX hack. should just pass 'be' around */
fromserver = be->in;
toserver = be->out;
}
r = do_daemon_work(sync_log_file, sync_shutdown_file,
timeout, min_delta, &restart);
if (r) _exit(1);
if (restart) _exit(EX_TEMPFAIL);
_exit(0);
}
if (waitpid(pid, &status, 0) < 0)
fatal("waitpid failed", EC_SOFTWARE);
backend_disconnect(be);
} while (WIFEXITED(status) && (WEXITSTATUS(status) == EX_TEMPFAIL));
}
/* ====================================================================== */
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, &mysasl_config, NULL },
{ SASL_CB_CANON_USER, &mysasl_canon_user, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
enum {
MODE_UNKNOWN = -1,
MODE_REPEAT,
MODE_USER,
MODE_MAILBOX,
MODE_SIEVE
};
int main(int argc, char **argv)
{
int opt, i = 0;
char *alt_config = NULL;
char *input_filename = NULL;
const char *servername = NULL;
int r = 0;
int exit_rc = 0;
int mode = MODE_UNKNOWN;
int wait = 0;
int timeout = 600;
int min_delta = 0;
const char *sync_host = NULL;
char sync_log_file[MAX_MAILBOX_PATH+1];
const char *sync_shutdown_file = NULL;
char buf[512];
FILE *file;
int len;
struct backend *be = NULL;
sasl_callback_t *cb;
/* Global list */
msgid_onserver = sync_msgid_list_create(SYNC_MSGID_LIST_HASH_SIZE);
- if(geteuid() == 0)
- fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
setbuf(stdout, NULL);
while ((opt = getopt(argc, argv, "C:vlS:F:f:w:t:d:rumso")) != 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 '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 'w':
wait = atoi(optarg);
break;
case 't':
timeout = atoi(optarg);
break;
case 'd':
min_delta = atoi(optarg);
break;
case 'r':
if (mode != MODE_UNKNOWN)
fatal("Mutually exclusive options defined", EC_USAGE);
mode = MODE_REPEAT;
break;
case 'u':
if (mode != MODE_UNKNOWN)
fatal("Mutually exclusive options defined", EC_USAGE);
mode = MODE_USER;
break;
case 'm':
if (mode != MODE_UNKNOWN)
fatal("Mutually exclusive options defined", EC_USAGE);
mode = MODE_MAILBOX;
break;
case 's':
if (mode != MODE_UNKNOWN)
fatal("Mutually exclusive options defined", EC_USAGE);
mode = MODE_SIEVE;
break;
default:
usage("sync_client");
}
}
if (mode == MODE_UNKNOWN)
fatal("No replication mode specified", EC_USAGE);
cyrus_init(alt_config, "sync_client", 0);
if (!servername &&
!(servername = config_getstring(IMAPOPT_SYNC_HOST))) {
fatal("sync_host not defined", EC_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), EC_CONFIG);
}
/* open the mboxlist, we'll need it for real work */
mboxlist_init(0);
mboxlist_open(NULL);
mailbox_initialize();
/* open the quota db, we'll need it for real work */
quotadb_init(0);
quotadb_open(NULL);
/* open the annotation db */
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
signals_set_shutdown(&shut_down);
signals_add_handlers(0);
/* load the SASL plugins */
global_sasl_init(1, 0, mysasl_cb);
cb = mysasl_callbacks(NULL,
config_getstring(IMAPOPT_SYNC_AUTHNAME),
config_getstring(IMAPOPT_SYNC_REALM),
config_getstring(IMAPOPT_SYNC_PASSWORD));
/* Open up connection to server */
be = replica_connect(NULL, servername, cb);
if (!be) {
fprintf(stderr, "Can not connect to server '%s'\n", servername);
exit(1);
}
/* XXX hack. should just pass 'be' around */
fromserver = be->in;
toserver = be->out;
switch (mode) {
case MODE_USER:
if (input_filename) {
if ((file=fopen(input_filename, "r")) == NULL) {
syslog(LOG_NOTICE, "Unable to open %s: %m", input_filename);
shut_down(1);
}
while (!r && 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 ((r = send_lock())) {
if (verbose) {
fprintf(stderr,
"Error from send_lock(): bailing out!\n");
}
exit_rc = 1;
}
else {
if (do_user(buf)) {
if (verbose)
fprintf(stderr,
"Error from do_user(%s): bailing out!\n",
buf);
syslog(LOG_ERR, "Error in do_user(%s): bailing out!",
buf);
exit_rc = 1;
}
send_unlock();
}
}
fclose(file);
} else for (i = optind; !r && i < argc; i++) {
if ((r = send_lock())) {
if (verbose) {
fprintf(stderr,
"Error from send_lock(): bailing out!\n");
}
exit_rc = 1;
}
else {
if (do_user(argv[i])) {
if (verbose)
fprintf(stderr, "Error from do_user(%s): bailing out!\n",
argv[1]);
syslog(LOG_ERR, "Error in do_user(%s): bailing out!", argv[i]);
exit_rc = 1;
}
send_unlock();
}
}
break;
case MODE_MAILBOX:
{
struct sync_folder_list *folder_list = sync_folder_list_create();
struct sync_user *user;
char *s, *t;
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_folder_lookup_byname(folder_list, argv[i]))
sync_folder_list_add(folder_list,
NULL, argv[i], NULL, 0, NULL);
}
fclose(file);
} else for (i = optind; i < argc; i++) {
if (!sync_folder_lookup_byname(folder_list, argv[i]))
sync_folder_list_add(folder_list, NULL, argv[i], NULL, 0, NULL);
}
if ((r = send_lock())) {
if (verbose) {
fprintf(stderr,
"Error from send_lock(): bailing out!\n");
}
syslog(LOG_ERR, "Error in send_lock(): bailing out!");
exit_rc = 1;
} else {
if (do_mailboxes(folder_list)) {
if (verbose) {
fprintf(stderr,
"Error from do_mailboxes(): bailing out!\n");
}
syslog(LOG_ERR, "Error in do_mailboxes(): bailing out!");
exit_rc = 1;
}
send_unlock();
}
sync_folder_list_free(&folder_list);
}
break;
case MODE_SIEVE:
for (i = optind; !r && i < argc; i++) {
if ((r = send_lock())) {
if (verbose) {
fprintf(stderr,
"Error from send_lock(): bailing out!\n");
}
syslog(LOG_ERR, "Error in send_lock(): bailing out!");
exit_rc = 1;
}
else {
if (do_sieve(argv[i])) {
if (verbose) {
fprintf(stderr,
"Error from do_sieve(%s): bailing out!\n",
argv[i]);
}
syslog(LOG_ERR, "Error in do_sieve(%s): bailing out!",
argv[i]);
exit_rc = 1;
}
send_unlock();
}
}
break;
case MODE_REPEAT:
if (input_filename) {
exit_rc = do_sync(input_filename);
}
else {
strlcpy(sync_log_file, config_dir, sizeof(sync_log_file));
strlcat(sync_log_file, "/sync/log", sizeof(sync_log_file));
if (!sync_shutdown_file)
sync_shutdown_file = config_getstring(IMAPOPT_SYNC_SHUTDOWN_FILE);
if (!min_delta)
min_delta = config_getint(IMAPOPT_SYNC_REPEAT_INTERVAL);
do_daemon(sync_log_file, sync_shutdown_file, timeout, min_delta,
be, cb);
}
break;
default:
if (verbose) fprintf(stderr, "Nothing to do!\n");
break;
}
sync_msgid_list_free(&msgid_onserver);
backend_disconnect(be);
quit:
shut_down(exit_rc);
}
diff --git a/imap/sync_reset.c b/imap/sync_reset.c
index c2f5714fa..b6ca5f8b8 100644
--- a/imap/sync_reset.c
+++ b/imap/sync_reset.c
@@ -1,384 +1,385 @@
/* sync_reset.c -- Remove a user account from a replica system
*
* Copyright (c) 1998-2005 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Original version written by David Carter <dpc22@cam.ac.uk>
* Rewritten and integrated into Cyrus by Ken Murchison <ken@oceana.com>
*
- * $Id: sync_reset.c,v 1.2 2006/11/30 17:11:20 murch Exp $
+ * $Id: sync_reset.c,v 1.3 2007/03/30 18:40:21 murch Exp $
*/
#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 <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <ctype.h>
#include <sys/resource.h>
#include "global.h"
#include "assert.h"
#include "mboxlist.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "acl.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imparse.h"
#include "util.h"
#include "xmalloc.h"
#include "retry.h"
#include "imapd.h"
#include "user.h"
#include "sync_support.h"
#include "sync_commit.h"
/*#include "cdb.h"*/
/* global state */
const int config_need_data = 0;
/* Stuff to make index.c link */
int imapd_exists;
struct protstream *imapd_out = NULL;
struct auth_state *imapd_authstate = NULL;
char *imapd_userid = NULL;
void printastring(const char *s)
{
fatal("not implemented", EC_SOFTWARE);
}
void printstring(const char *s)
{
fatal("not implemented", EC_SOFTWARE);
}
/* end stuff to make index.c link */
/* ====================================================================== */
/* Static global variables and support routines for sync_server */
extern char *optarg;
extern int optind;
static struct namespace sync_namespace;
static struct namespace *sync_namespacep = &sync_namespace;
static struct auth_state *sync_authstate = NULL;
static char *sync_userid = NULL;
static int verbose = 0;
static void shut_down(int code) __attribute__((noreturn));
static void shut_down(int code)
{
if (sync_userid) free(sync_userid);
if (sync_authstate) auth_freestate(sync_authstate);
seen_done();
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
exit(code);
}
static int usage(const char *name)
{
fprintf(stderr,
"usage: %s [-C <alt_config>] [-v] [-f] user...\n", name);
exit(EC_USAGE);
}
void fatal(const char* s, int code)
{
fprintf(stderr, "sync_server: %s\n", s);
exit(code);
}
/* ====================================================================== */
static int
user_master_is_local(char *user)
{
int rc = 0;
#if 0 /* XXX make sure we're not the replica */
const char *filename;
unsigned long len;
int fd;
filename = config_getstring(IMAPOPT_SYNC_MASTER_MAP);
if ((fd = open(filename, O_RDONLY)) < 0)
return(0); /* Couldn't open */
rc = cdb_seek(fd, (unsigned char *)user, strlen(user), &len);
close(fd);
#endif
/* rc: -1 => error, 0 => lookup failed, 1 => lookup suceeded */
return(rc == 1);
}
/* ====================================================================== */
static int
addmbox_full(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct sync_folder_list *list = (struct sync_folder_list *) rock;
/* List all mailboxes, including directories and deleted items */
sync_folder_list_add(list, name, name, NULL, 0, NULL);
return(0);
}
static int
addmbox_sub(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
struct sync_folder_list *list = (struct sync_folder_list *) rock;
sync_folder_list_add(list, name, name, NULL, 0, NULL);
return(0);
}
/* ====================================================================== */
static int
reset_single(struct sync_lock *lock, char *user)
{
struct sync_folder_list *list = NULL;
struct sync_folder *item;
char buf[MAX_MAILBOX_NAME+1];
int r = 0;
static int md5_dir_set = 0;
static const char *md5_dir = NULL;
if (!md5_dir_set) {
md5_dir = config_getstring(IMAPOPT_MD5_DIR);
md5_dir_set = 1;
}
if (verbose > 1)
fprintf(stderr, " RESET %s\n", user);
if (user_master_is_local(user)) {
fprintf(stderr, "Attempt to update master for %s\n", user);
return(IMAP_INVALID_USER);
}
if ((r = sync_lock(lock))) {
fprintf(stderr, "Failed to lock: %s\n", error_message(r));
return(r);
}
if (sync_userid) free(sync_userid);
if (sync_authstate) auth_freestate(sync_authstate);
sync_userid = xstrdup(user);
sync_authstate = auth_newstate(sync_userid);
/* Nuke subscriptions */
list = sync_folder_list_create();
snprintf(buf, sizeof(buf)-1, "user.%s.*", user);
r = (sync_namespacep->mboxlist_findsub)(sync_namespacep, buf, 0,
user, sync_authstate, addmbox_sub,
(void *)list, 0);
if (r) goto fail;
for (item = list->head ; item ; item = item->next) {
r = mboxlist_changesub(item->name, sync_userid, sync_authstate, 0, 0);
if (r) goto fail;
}
sync_folder_list_free(&list);
#if 0
/* Nuke DELETED folders */
list = sync_folder_list_create();
snprintf(buf, sizeof(buf)-1, "user.%s.^DELETED.*", user);
r = (sync_namespacep->mboxlist_findall)(sync_namespacep, buf, 0,
user, sync_authstate, addmbox_full,
(void *)list);
if (r) goto fail;
for (item = list->head ; item ; item = item->next) {
r=mboxlist_deletemailbox(item->name, 1, NULL, sync_authstate, 1, 0, 0);
if (r) goto fail;
}
sync_folder_list_free(&list);
#endif
/* Nuke normal folders */
list = sync_folder_list_create();
snprintf(buf, sizeof(buf)-1, "user.%s.*", user);
r = (sync_namespacep->mboxlist_findall)(sync_namespacep, buf, 0,
user, sync_authstate, addmbox_full,
(void *)list);
if (r) goto fail;
for (item = list->head ; item ; item = item->next) {
r=mboxlist_deletemailbox(item->name, 1, NULL, sync_authstate, 1, 0, 0);
if (r) goto fail;
}
sync_folder_list_free(&list);
/* Nuke inbox (recursive nuke possible?) */
snprintf(buf, sizeof(buf)-1, "user.%s", user);
r = mboxlist_deletemailbox(buf, 1, "cyrus", sync_authstate, 1, 0, 0);
if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail;
if ((r=user_deletedata(user, sync_userid, sync_authstate, 1)))
goto fail;
/* Nuke md5 database entry (not the end of the world if it fails) */
if (md5_dir) {
snprintf(buf, sizeof(buf)-1, "%s/%c/%s", md5_dir, user[0], user);
unlink(buf);
}
sync_unlock(lock);
return(0);
fail:
sync_unlock(lock);
if (list)
sync_folder_list_free(&list);
fprintf(stderr, "Failed to reset account %s: %s\n",
sync_userid, error_message(r));
return(r);
}
/* ====================================================================== */
int
main(int argc, char **argv)
{
int opt;
char *alt_config = NULL;
int r = 0;
int force = 0;
struct sync_lock lock;
int i;
sync_lock_reset(&lock);
- if(geteuid() == 0)
- fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
setbuf(stdout, NULL);
while ((opt = getopt(argc, argv, "C:vf")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'v': /* verbose */
verbose++;
break;
case 'f': /* force: confirm option */
force++;
break;
default:
usage("sync_reset");
}
}
/* Set up default bounds if no command line options provided */
cyrus_init(alt_config, "sync_reset", 0);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(sync_namespacep, 1)) != 0) {
fatal(error_message(r), EC_CONFIG);
}
/* open the mboxlist and quotadb, we'll need them for real work */
mboxlist_init(0);
mboxlist_open(NULL);
quotadb_init(0);
quotadb_open(NULL);
mailbox_initialize();
signals_set_shutdown(&shut_down);
signals_add_handlers(0);
if (!force) {
fprintf(stderr, "Usage: sync_reset -f user user user ...\n");
fprintf(stderr, " -f [force] is obligitory for safety\n");
shut_down(0);
}
for (i = optind; i < argc; i++) {
if (reset_single(&lock, argv[i])) {
fprintf(stderr, "Bailing out!\n");
break;
}
}
shut_down(0);
}
diff --git a/imap/syncnews.c b/imap/syncnews.c
index 04c54abaa..8db196981 100644
--- a/imap/syncnews.c
+++ b/imap/syncnews.c
@@ -1,274 +1,276 @@
/* syncnews.c -- program to synchronize active file with mailbox list
*
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: syncnews.c,v 1.27 2006/11/30 17:11:20 murch Exp $
+ * $Id: syncnews.c,v 1.28 2007/03/30 18:40:21 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include "assert.h"
#include "global.h"
#include "xmalloc.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "mboxlist.h"
#include "convert_code.h"
extern int optind;
extern char *optarg;
int code = 0;
void do_syncnews(void);
char **group = 0;
int *group_seen;
int group_num = 0;
int group_alloc = 0;
/* Forward declarations */
void readactive(char *active);
void usage(void)
{
fprintf(stderr, "usage: syncnews [-C <alt_config>] active\n");
exit(EC_USAGE);
}
int main(int argc, char **argv)
{
int opt;
char *alt_config = NULL;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
default:
usage();
}
}
cyrus_init(alt_config, "syncnews", 0);
if (!argv[optind] || argv[optind+1]) usage();
readactive(argv[optind]);
do_syncnews();
cyrus_done();
return code;
}
#define GROUPGROW 300
/*
* comparison function for qsort() of the group list
*/
int compare_group(char **a,char **b)
{
return strcmp(*a, *b);
}
/*
* Read a news active file, building the group list
*/
void readactive(char *active)
{
FILE *active_file;
char buf[1024];
char *p;
const char *newsprefix;
int newsprefixlen = 0;
int lineno = 0;
newsprefix = config_getstring("newsprefix", 0);
if (newsprefix) {
newsprefixlen = strlen(newsprefix);
if (newsprefix[newsprefixlen-1] == '.') {
newsprefixlen--;
}
}
active_file = fopen(active, "r");
if (!active_file) {
perror(active);
syslog(LOG_ERR, "cannot read active file %s: %m", active);
cyrus_done();
exit(EC_NOINPUT);
}
while (fgets(buf, sizeof(buf), active_file)) {
lineno++;
p = strchr(buf, ' '); /* end of group */
if (!p) goto badactive;
*p++ = '\0';
p = strchr(p, ' '); /* start of min */
if (!p) goto badactive;
p = strchr(p+1, ' ');
if (!p) goto badactive;
p++;
if (*p == 'y' || *p == 'm' || *p == 'n') {
/* Add group to list */
if (group_num == group_alloc) {
/* Grow arrary */
group_alloc += GROUPGROW;
group = (char **) xrealloc((char *)group,
group_alloc * sizeof(char *));
group_seen = (int *) xrealloc((char *)group_seen,
group_alloc * sizeof(int));
}
if (newsprefixlen) {
group[group_num] = xmalloc(strlen(buf)+newsprefixlen+2);
strcpy(group[group_num], newsprefix);
group[group_num][newsprefixlen] = '.';
strcpy(group[group_num]+newsprefixlen+1, buf);
}
else {
group[group_num] = xstrdup(buf);
}
group_seen[group_num] = 0;
group_num++;
}
}
if (ferror(active_file)) {
fprintf(stderr, "syncnews: error reading active file\n");
syslog(LOG_ERR, "error reading active file");
cyrus_done();
exit(EC_DATAERR);
}
fclose(active_file);
if (group_num == 0) {
fprintf(stderr, "syncnews: no groups in active file\n");
syslog(LOG_ERR, "no groups in active file");
cyrus_done();
exit(EC_DATAERR);
}
qsort(group, group_num, sizeof(char *), (int (*)(const void *, const void *)) compare_group);
return;
badactive:
fprintf(stderr, "syncnews: bad line %d in active file\n", lineno);
syslog(LOG_ERR, "bad line %d in active file", lineno);
cyrus_done();
exit(EC_DATAERR);
}
/*
* Do the real work.
*/
void do_syncnews(void)
{
int r;
int i;
/*
* call mboxlist_syncnews() to check our group list against
* the mailboxes file. mboxlist_syncnews() will remove any
* mailboxes that aren't in the group list.
*/
r = mboxlist_syncnews(group_num, group, group_seen);
if (r) {
com_err("syncnews: resynchronizing", r,
(r == IMAP_IOERROR) ? error_message(errno) : NULL);
code = convert_code(r);
return;
}
/*
* Go through the group list creating mailboxes for
* those groups which were not found in the mailboxes file.
*/
for (i = 0; i < group_num; i++) {
if (!group_seen[i]) {
r = mboxlist_createmailbox(group[i],
MBTYPE_NETNEWS, "news",
1, "anonymous", NULL, 0, 0, 0);
if (r == IMAP_MAILBOX_BADNAME) {
printf("ignored %s\n", group[i]);
}
else if (r) {
fprintf(stderr, "syncnews: cannot creat %s: %s\n",
group[i], error_message(r));
syslog(LOG_ERR, "cannot create %s: %s",
group[i], error_message(r));
}
else {
printf("created %s\n", group[i]);
}
}
}
return;
}
void fatal(const char* s, int code)
{
fprintf(stderr, "syncnews: %s\n", s);
cyrus_done();
exit(code);
}
diff --git a/imap/tls_prune.c b/imap/tls_prune.c
index 9a9e27a2b..89c555b9b 100644
--- a/imap/tls_prune.c
+++ b/imap/tls_prune.c
@@ -1,91 +1,93 @@
/*
* tls_prune.c -- program to prune TLS session db of expired sessions
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: tls_prune.c,v 1.7 2006/11/30 17:11:20 murch Exp $ */
+/* $Id: tls_prune.c,v 1.8 2007/03/30 18:40:21 murch Exp $ */
#include <config.h>
#include <stdio.h>
#include <unistd.h>
#include "exitcodes.h"
#include "global.h"
#include "tls.h"
#include "xmalloc.h"
/* global state */
const int config_need_data = 0;
void usage(void)
{
fprintf(stderr, "tls_prune [-C <altconfig>]\n");
exit(-1);
}
int main(int argc, char *argv[])
{
extern char *optarg;
int opt,r;
char *alt_config = NULL;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
default:
usage();
break;
}
}
cyrus_init(alt_config, "tls_prune", 0);
r = tls_prune_sessions();
cyrus_done();
return r;
}
diff --git a/imap/unexpunge.c b/imap/unexpunge.c
index 8d6d7e6c9..5d2851841 100644
--- a/imap/unexpunge.c
+++ b/imap/unexpunge.c
@@ -1,632 +1,634 @@
/* unexpunge.c -- Program to unexpunge messages
*
* Copyright (c) 1998-2005 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
- * $Id: unexpunge.c,v 1.4 2007/02/07 14:27:52 murch Exp $
+ * $Id: unexpunge.c,v 1.5 2007/03/30 18:40:21 murch Exp $
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include "annotate.h"
#include "cyrusdb.h"
#include "duplicate.h"
#include "exitcodes.h"
#include "global.h"
#include "hash.h"
#include "imap_err.h"
#include "index.h"
#include "libcyr_cfg.h"
#include "lock.h"
#include "map.h"
#include "mailbox.h"
#include "mboxlist.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
/* global state */
const int config_need_data = 0;
/* current namespace */
static struct namespace unex_namespace;
int verbose = 0;
void usage(void)
{
fprintf(stderr,
"unexpunge [-C <altconfig>] -l <mailbox>\n"
"unexpunge [-C <altconfig>] -a [-d] [-v] <mailbox>\n"
"unexpunge [-C <altconfig>] -u [-d] [-v] <mailbox> <uid>...\n");
exit(-1);
}
enum {
MODE_UNKNOWN = -1,
MODE_LIST,
MODE_ALL,
MODE_UID
};
struct msg {
unsigned recno;
unsigned long uid;
int restore;
};
int compare_uid(const void *a, const void *b)
{
return *((unsigned long *) a) - *((unsigned long *) b);
}
int compare_msg(const void *a, const void *b)
{
return ((struct msg *) a)->uid - ((struct msg *) b)->uid;
}
void list_expunged(struct mailbox *mailbox,
struct msg *msgs, unsigned long exists,
const char *expunge_index_base)
{
const char *rec;
unsigned msgno;
unsigned long uid, size, cache_offset;
time_t internaldate, sentdate, last_updated;
const char *cacheitem;
for (msgno = 0; msgno < exists; msgno++) {
/* Jump to index record for this message */
rec = expunge_index_base + mailbox->start_offset +
msgs[msgno].recno * mailbox->record_size;
uid = ntohl(*((bit32 *)(rec+OFFSET_UID)));
internaldate = ntohl(*((bit32 *)(rec+OFFSET_INTERNALDATE)));
sentdate = ntohl(*((bit32 *)(rec+OFFSET_SENTDATE)));
size = ntohl(*((bit32 *)(rec+OFFSET_SIZE)));
cache_offset = ntohl(*((bit32 *)(rec+OFFSET_CACHE_OFFSET)));
last_updated = ntohl(*((bit32 *)(rec+OFFSET_LAST_UPDATED)));
printf("UID: %lu\n", uid);
printf("\tSize: %lu\n", size);
printf("\tSent: %s", ctime(&sentdate));
printf("\tRecv: %s", ctime(&internaldate));
printf("\tExpg: %s", ctime(&last_updated));
cacheitem = mailbox->cache_base + cache_offset;
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip envelope */
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip body structure */
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip body */
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip binary body */
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip cached headers */
printf("\tFrom: %s\n", cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip from */
printf("\tTo : %s\n", cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip to */
printf("\tCc : %s\n", cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip cc */
printf("\tBcc : %s\n", cacheitem + CACHE_ITEM_SIZE_SKIP);
cacheitem = CACHE_ITEM_NEXT(cacheitem); /* skip bcc */
printf("\tSubj: %s\n\n", cacheitem + CACHE_ITEM_SIZE_SKIP);
}
}
int restore_expunged(struct mailbox *mailbox,
struct msg *msgs, unsigned long eexists,
const char *expunge_index_base,
unsigned *numrestored, int unsetdeleted)
{
int r = 0;
const char *irec;
char buf[INDEX_HEADER_SIZE > INDEX_RECORD_SIZE ?
INDEX_HEADER_SIZE : INDEX_RECORD_SIZE];
char *path, fnamebuf[MAX_MAILBOX_PATH+1], fnamebufnew[MAX_MAILBOX_PATH+1];
FILE *newindex = NULL, *newexpungeindex = NULL;
unsigned emsgno, imsgno;
unsigned long iexists, euid, iuid;
uquota_t quotarestored = 0, newquotaused;
unsigned numansweredflag = 0, numdeletedflag = 0, numflaggedflag = 0;
unsigned newexists, newexpunged, newdeleted, newanswered, newflagged;
time_t now = time(NULL);
struct txn *tid = NULL;
/* Open new index/expunge files */
path = (mailbox->mpath &&
(config_metapartition_files & IMAP_ENUM_METAPARTITION_FILES_INDEX)) ?
mailbox->mpath : mailbox->path;
strlcpy(fnamebufnew, path, sizeof(fnamebufnew));
strlcat(fnamebufnew, FNAME_INDEX, sizeof(fnamebufnew));
strlcat(fnamebufnew, ".NEW", sizeof(fnamebufnew));
newindex = fopen(fnamebufnew, "w+");
if (!newindex) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fnamebufnew);
return IMAP_IOERROR;
}
path = (mailbox->mpath &&
(config_metapartition_files & IMAP_ENUM_METAPARTITION_FILES_EXPUNGE)) ?
mailbox->mpath : mailbox->path;
strlcpy(fnamebufnew, path, sizeof(fnamebufnew));
strlcat(fnamebufnew, FNAME_EXPUNGE_INDEX, sizeof(fnamebufnew));
strlcat(fnamebufnew, ".NEW", sizeof(fnamebufnew));
newexpungeindex = fopen(fnamebufnew, "w+");
if (!newindex) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fnamebufnew);
fclose(newindex);
return IMAP_IOERROR;
}
/* Copy over index/expunge headers
*
* XXX do we want/need to bump the generation number?
*/
fwrite(mailbox->index_base, 1, mailbox->start_offset, newindex);
fwrite(expunge_index_base, 1, mailbox->start_offset, newexpungeindex);
iexists = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_EXISTS)));
for (imsgno = 0, emsgno = 0; emsgno < eexists; emsgno++) {
/* Copy expunge index record for this message */
memcpy(buf,
expunge_index_base + mailbox->start_offset +
msgs[emsgno].recno * mailbox->record_size,
mailbox->record_size);
euid = ntohl(*((bit32 *)(buf+OFFSET_UID)));
/* Write all cyrus.index records w/ iuid < euid to cyrus.index */
for (; imsgno < iexists; imsgno++) {
/* Jump to index record for this message */
irec = mailbox->index_base + mailbox->start_offset +
imsgno * mailbox->record_size;
iuid = ntohl(*((bit32 *)(irec+OFFSET_UID)));
if (iuid > euid) break;
fwrite(irec, 1, mailbox->record_size, newindex);
}
if (msgs[emsgno].restore) {
bit32 sysflags = ntohl(*((bit32 *)(buf+OFFSET_SYSTEM_FLAGS)));
if (verbose) {
printf("\trestoring UID %ld\n", msgs[emsgno].uid);
syslog(LOG_INFO, "restoring UID %ld in mailbox '%s'",
msgs[emsgno].uid, mailbox->name);
}
/* Update counts */
(*numrestored)++;
quotarestored += ntohl(*((bit32 *)(buf+OFFSET_SIZE)));
if (sysflags & FLAG_ANSWERED) numansweredflag++;
if (sysflags & FLAG_FLAGGED) numflaggedflag++;
if (unsetdeleted) {
sysflags &= ~FLAG_DELETED;
*((bit32 *)(buf+OFFSET_SYSTEM_FLAGS)) = htonl(sysflags);
}
else if (sysflags & FLAG_DELETED) numdeletedflag++;
/* Write record to cyrus.index */
*((bit32 *)(buf+OFFSET_LAST_UPDATED)) = htonl(now);
fwrite(buf, 1, mailbox->record_size, newindex);
}
else {
/* Write record to cyrus.expunge */
fwrite(buf, 1, mailbox->record_size, newexpungeindex);
}
}
/* Write all remaining cyrus.index records to cyrus.index */
if (imsgno < iexists) {
/* Jump to index record for next message */
irec = mailbox->index_base + mailbox->start_offset +
imsgno * mailbox->record_size;
fwrite(irec, 1, (iexists - imsgno) * mailbox->record_size, newindex);
}
/* Fix up information in index header */
memcpy(buf, mailbox->index_base, mailbox->start_offset);
/* Update uidvalidity */
*((bit32 *)(buf+OFFSET_UIDVALIDITY)) = now;
/* Fix up exists */
newexists = ntohl(*((bit32 *)(buf+OFFSET_EXISTS))) + *numrestored;
*((bit32 *)(buf+OFFSET_EXISTS)) = htonl(newexists);
/* Fix up expunged count */
newexpunged = ntohl(*((bit32 *)(buf+OFFSET_LEAKED_CACHE))) - *numrestored;
*((bit32 *)(buf+OFFSET_LEAKED_CACHE)) = htonl(newexpunged);
/* Fix up other counts */
newanswered = ntohl(*((bit32 *)(buf+OFFSET_ANSWERED))) + numansweredflag;
*((bit32 *)(buf+OFFSET_ANSWERED)) = htonl(newanswered);
newdeleted = ntohl(*((bit32 *)(buf+OFFSET_DELETED))) + numdeletedflag;
*((bit32 *)(buf+OFFSET_DELETED)) = htonl(newdeleted);
newflagged = ntohl(*((bit32 *)(buf+OFFSET_FLAGGED))) + numflaggedflag;
*((bit32 *)(buf+OFFSET_FLAGGED)) = htonl(newflagged);
/* Fix up quota_mailbox_used */
#ifdef HAVE_LONG_LONG_INT
newquotaused =
ntohll(*((bit64 *)(buf+OFFSET_QUOTA_MAILBOX_USED64))) + quotarestored;
*((bit64 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonll(newquotaused);
#else
/* Zero the unused 32bits */
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonl(0);
newquotaused =
ntohl(*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED))) + quotarestored;
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED)) = htonl(newquotaused);
#endif
/* Write out new index header */
rewind(newindex);
fwrite(buf, 1, mailbox->start_offset, newindex);
/* Ensure everything made it to disk */
fflush(newindex);
fclose(newindex);
/* Fix up information in expunge index header */
memcpy(buf, expunge_index_base, mailbox->start_offset);
/* Update uidvalidity */
*((bit32 *)(buf+OFFSET_UIDVALIDITY)) = now;
/* Fix up exists */
newexists = ntohl(*((bit32 *)(buf+OFFSET_EXISTS))) - *numrestored;
*((bit32 *)(buf+OFFSET_EXISTS)) = htonl(newexists);
/* Fix up other counts */
newanswered = ntohl(*((bit32 *)(buf+OFFSET_ANSWERED))) - numansweredflag;
*((bit32 *)(buf+OFFSET_ANSWERED)) = htonl(newanswered);
/* XXX we use the numrestored count here because we may have unset
* the \Deleted flag when we copied the record to cyrus.index,
* but we know that any message that has to be restored had the
* \Deleted set in cyrus.expunge in the first place
*/
newdeleted = ntohl(*((bit32 *)(buf+OFFSET_DELETED))) - *numrestored;
*((bit32 *)(buf+OFFSET_DELETED)) = htonl(newdeleted);
newflagged = ntohl(*((bit32 *)(buf+OFFSET_FLAGGED))) - numflaggedflag;
*((bit32 *)(buf+OFFSET_FLAGGED)) = htonl(newflagged);
/* Fix up quota_mailbox_used */
#ifdef HAVE_LONG_LONG_INT
newquotaused =
ntohll(*((bit64 *)(buf+OFFSET_QUOTA_MAILBOX_USED64))) - quotarestored;
*((bit64 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonll(newquotaused);
#else
/* Zero the unused 32bits */
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED64)) = htonl(0);
newquotaused =
ntohl(*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED))) - quotarestored;
*((bit32 *)(buf+OFFSET_QUOTA_MAILBOX_USED)) = htonl(newquotaused);
#endif
/* Write out new expunge index header */
rewind(newexpungeindex);
fwrite(buf, 1, mailbox->start_offset, newexpungeindex);
/* Ensure everything made it to disk */
fflush(newexpungeindex);
fclose(newexpungeindex);
/* Rename our files */
path = (mailbox->mpath &&
(config_metapartition_files & IMAP_ENUM_METAPARTITION_FILES_INDEX)) ?
mailbox->mpath : mailbox->path;
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_INDEX, sizeof(fnamebuf));
strlcpy(fnamebufnew, fnamebuf, sizeof(fnamebufnew));
strlcat(fnamebufnew, ".NEW", sizeof(fnamebufnew));
if (rename(fnamebufnew, fnamebuf)) {
syslog(LOG_ERR, "IOERROR: renaming index file for %s: %m",
mailbox->name);
return IMAP_IOERROR;
}
path = (mailbox->mpath &&
(config_metapartition_files & IMAP_ENUM_METAPARTITION_FILES_EXPUNGE)) ?
mailbox->mpath : mailbox->path;
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_EXPUNGE_INDEX, sizeof(fnamebuf));
strlcpy(fnamebufnew, fnamebuf, sizeof(fnamebufnew));
strlcat(fnamebufnew, ".NEW", sizeof(fnamebufnew));
if (rename(fnamebufnew, fnamebuf)) {
syslog(LOG_ERR, "IOERROR: renaming expunge index file for %s: %m",
mailbox->name);
return IMAP_IOERROR;
}
/* Record quota restore */
r = quota_read(&mailbox->quota, &tid, 1);
if (!r) {
mailbox->quota.used += quotarestored;
r = quota_write(&mailbox->quota, &tid);
if (!r) quota_commit(&tid);
else {
syslog(LOG_ERR,
"LOSTQUOTA: unable to record restore of " UQUOTA_T_FMT " bytes in quota %s",
quotarestored, mailbox->quota.root);
}
}
else if (r == IMAP_QUOTAROOT_NONEXISTENT) r = 0;
return r;
}
int main(int argc, char *argv[])
{
extern char *optarg;
int opt, r = 0;
char *alt_config = NULL;
char buf[MAX_MAILBOX_PATH+1];
struct mailbox mailbox;
int doclose = 0, mode = MODE_UNKNOWN, unsetdeleted = 0;
char expunge_fname[MAX_MAILBOX_PATH+1];
int expunge_fd = -1;
struct stat sbuf;
const char *lockfailaction;
struct msg *msgs;
unsigned numrestored = 0;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
while ((opt = getopt(argc, argv, "C:laudv")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'l':
if (mode != MODE_UNKNOWN) usage();
mode = MODE_LIST;
break;
case 'a':
if (mode != MODE_UNKNOWN) usage();
mode = MODE_ALL;
break;
case 'u':
if (mode != MODE_UNKNOWN) usage();
mode = MODE_UID;
break;
case 'd':
unsetdeleted = 1;
break;
case 'v':
verbose = 1;
break;
default:
usage();
break;
}
}
/* sanity check */
if (mode == MODE_UNKNOWN ||
(optind + (mode == MODE_UID ? 1 : 0)) >= argc) usage();
cyrus_init(alt_config, "unexpunge", 0);
mboxlist_init(0);
mboxlist_open(NULL);
quotadb_init(0);
quotadb_open(NULL);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&unex_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
/* Translate mailboxname */
(*unex_namespace.mboxname_tointernal)(&unex_namespace, argv[optind],
NULL, buf);
/* Open/lock header */
r = mailbox_open_header(buf, 0, &mailbox);
if (!r && mailbox.header_fd != -1) {
doclose = 1;
(void) mailbox_lock_header(&mailbox);
mailbox.header_lock_count = 1;
}
/* Attempt to open/lock index */
if (!r) r = mailbox_open_index(&mailbox);
if (!r) {
(void) mailbox_lock_index(&mailbox);
mailbox.index_lock_count = 1;
}
if (!r) r = mailbox_lock_pop(&mailbox);
/* Open expunge index */
if (!r) {
char *path =
(mailbox.mpath &&
(config_metapartition_files & IMAP_ENUM_METAPARTITION_FILES_EXPUNGE)) ?
mailbox.mpath : mailbox.path;
strlcpy(expunge_fname, path, sizeof(expunge_fname));
strlcat(expunge_fname, FNAME_EXPUNGE_INDEX, sizeof(expunge_fname));
expunge_fd = open(expunge_fname, O_RDWR, 0666);
}
if (r || expunge_fd == -1) {
/* mailbox corrupt/nonexistent -- skip it */
syslog(LOG_WARNING, "unable to open/lock mailbox %s", argv[optind]);
if (doclose) mailbox_close(&mailbox);
return 0;
}
if ((r = lock_reopen(expunge_fd, expunge_fname, &sbuf, &lockfailaction))) {
syslog(LOG_ERR, "IOERROR: %s expunge index for %s: %m",
lockfailaction, mailbox.name);
}
if (!r) {
const char *expunge_index_base = NULL;
unsigned long expunge_index_len = 0; /* mapped size */
unsigned long exists, uid;
const char *rec;
unsigned msgno;
unsigned long *uids = NULL;
unsigned nuids;
map_refresh(expunge_fd, 1, &expunge_index_base,
&expunge_index_len, sbuf.st_size, "expunge",
mailbox.name);
exists = ntohl(*((bit32 *)(expunge_index_base+OFFSET_EXISTS)));
msgs = (struct msg *) xmalloc(exists * sizeof(struct msg));
/* Get UIDs of messages to restore */
if (mode == MODE_UID) {
int i;
nuids = argc - ++optind;
uids = (unsigned long *) xmalloc(nuids * sizeof(unsigned long));
for (i = 0; i < nuids; i++)
uids[i] = strtoul(argv[optind+i], NULL, 10);
/* Sort the UIDs so we can binary search */
qsort(uids, nuids, sizeof(unsigned long), compare_uid);
}
/* Get UIDs of expunged messages */
for (msgno = 0; msgno < exists; msgno++) {
/* Jump to index record for this message */
rec = expunge_index_base + mailbox.start_offset +
msgno * mailbox.record_size;
uid = ntohl(*((bit32 *)(rec+OFFSET_UID)));
msgs[msgno].recno = msgno;
msgs[msgno].uid = uid;
switch (mode) {
case MODE_LIST: msgs[msgno].restore = 0; break;
case MODE_ALL: msgs[msgno].restore = 1; break;
case MODE_UID:
/* see if this UID is in our list */
msgs[msgno].restore = bsearch(&uid, uids, nuids,
sizeof(unsigned long),
compare_uid) != NULL;
break;
}
}
if (uids) free(uids);
/* Sort msgs by UID */
qsort(msgs, exists, sizeof(struct msg), compare_msg);
if (mode == MODE_LIST)
list_expunged(&mailbox, msgs, exists, expunge_index_base);
else {
printf("restoring %sexpunged messages in mailbox '%s'\n",
mode == MODE_ALL ? "all " : "", mailbox.name);
r = restore_expunged(&mailbox, msgs, exists, expunge_index_base,
&numrestored, unsetdeleted);
if (!r) {
printf("restored %u out of %lu expunged messages\n",
numrestored, exists);
syslog(LOG_NOTICE,
"restored %u out of %lu expunged messages in mailbox '%s'",
numrestored, exists, mailbox.name);
}
}
map_free(&expunge_index_base, &expunge_index_len);
free(msgs);
if (lock_unlock(expunge_fd))
syslog(LOG_ERR,
"IOERROR: unlocking expunge index of %s: %m",
mailbox.name);
}
close(expunge_fd);
mailbox_unlock_pop(&mailbox);
mailbox_unlock_index(&mailbox);
mailbox_unlock_header(&mailbox);
mailbox_close(&mailbox);
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
cyrus_done();
exit(r);
}
diff --git a/lib/Makefile.in b/lib/Makefile.in
index 3ba8085d2..6fc71fc88 100644
--- a/lib/Makefile.in
+++ b/lib/Makefile.in
@@ -1,168 +1,168 @@
# Makefile for cyrus library
#
# @configure_input@
#
# Copyright (c) 1998-2000 Carnegie Mellon University. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. The name "Carnegie Mellon University" must not be used to
# endorse or promote products derived from this software without
# prior written permission. For permission or any other legal
# details, please contact
# Office of Technology Transfer
# Carnegie Mellon University
# 5000 Forbes Avenue
# Pittsburgh, PA 15213-3890
# (412) 268-4387, fax: (412) 268-7395
# tech-transfer@andrew.cmu.edu
#
# 4. Redistributions of any form whatsoever must retain the following
# acknowledgment:
# "This product includes software developed by Computing Services
# at Carnegie Mellon University (http://www.cmu.edu/computing/)."
#
# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# $Id: Makefile.in,v 1.68 2007/03/27 19:29:56 murch Exp $
+# $Id: Makefile.in,v 1.69 2007/03/30 18:40:21 murch Exp $
# Authorization namespace.
AUTH=auth.o auth_krb.o auth_unix.o auth_krb5.o auth_pts.o
# ACL interpretation. Only one choice for now:
ACL=acl_afs.o
srcdir = @srcdir@
top_srcdir = @top_srcdir@
VPATH = @srcdir@
CC = @CC@
INSTALL = @INSTALL@
RANLIB = @RANLIB@
DEFS = @DEFS@ @LOCALDEFS@
CPPFLAGS = -I.. @CPPFLAGS@ @COM_ERR_CPPFLAGS@ @SASLFLAGS@
LIBS = @LIBS@
MAKEDEPEND_CFLAGS = @CFLAGS@
CFLAGS = @CFLAGS@ @PERL_CCCDLFLAGS@
LDFLAGS = @LDFLAGS@
SHELL = /bin/sh
MAKEDEPEND = @MAKEDEPEND@
prefix = @prefix@
exec_prefix = @exec_prefix@
cyrus_prefix = @cyrus_prefix@
libdir = @libdir@
BUILTSOURCES = imapopts.h imapopts.c
LIBCYR_HDRS = $(srcdir)/acl.h $(srcdir)/assert.h $(srcdir)/auth.h \
$(srcdir)/bsearch.h $(srcdir)/charset.h $(srcdir)/glob.h \
$(srcdir)/gmtoff.h $(srcdir)/imclient.h $(srcdir)/imparse.h \
$(srcdir)/lock.h $(srcdir)/map.h $(srcdir)/mkgmtime.h \
$(srcdir)/nonblock.h $(srcdir)/parseaddr.h $(srcdir)/prot.h \
$(srcdir)/retry.h $(srcdir)/sysexits.h $(srcdir)/strhash.h \
$(srcdir)/lsort.h $(srcdir)/stristr.h \
$(srcdir)/util.h $(srcdir)/xstrlcpy.h $(srcdir)/xstrlcat.h $(srcdir)/xmalloc.h $(srcdir)/imapurl.h \
$(srcdir)/cyrusdb.h $(srcdir)/iptostring.h $(srcdir)/rfc822date.h \
$(srcdir)/libcyr_cfg.h $(srcdir)/byteorder64.h \
$(srcdir)/message_uuid.h $(srcdir)/md5.h $(srcdir)/hmac-md5.h
LIBCYR_OBJS = acl.o bsearch.o charset.o glob.o retry.o util.o \
libcyr_cfg.o mkgmtime.o prot.o parseaddr.o imclient.o imparse.o \
lsort.o stristr.o rfc822date.o cyrusdb.o strhash.o \
chartable.o imapurl.o nonblock_@WITH_NONBLOCK@.o lock_@WITH_LOCK@.o \
gmtoff_@WITH_GMTOFF@.o map_@WITH_MAP@.o $(ACL) $(AUTH) \
@LIBOBJS@ @CYRUSDB_OBJS@ @MD5OBJ@ \
iptostring.o xmalloc.o wildmat.o byteorder64.o \
message_uuid.o xstrlcat.o xstrlcpy.o
LIBCYRM_HDRS = $(srcdir)/hash.h $(srcdir)/mpool.h $(srcdir)/xmalloc.h \
- $(srcdir)/xstrlcat.h $(srcdir)/xstrlcpy.h \
+ $(srcdir)/xstrlcat.h $(srcdir)/xstrlcpy.h $(srcdir)/util.h \
$(srcdir)/strhash.h $(srcdir)/libconfig.h $(srcdir)/assert.h \
imapopts.h
LIBCYRM_OBJS = libconfig.o imapopts.o hash.o mpool.o xmalloc.o strhash.o \
- xstrlcat.o xstrlcpy.o assert.o @IPV6_OBJS@
+ xstrlcat.o xstrlcpy.o assert.o util.o @IPV6_OBJS@
all: $(BUILTSOURCES) libcyrus_min.a libcyrus.a
install:
$(srcdir)/../install-sh -d $(DESTDIR)$(libdir)
$(INSTALL) -m 644 libcyrus.a $(DESTDIR)$(libdir)
$(INSTALL) -m 644 libcyrus_min.a $(DESTDIR)$(libdir)
$(RANLIB) $(DESTDIR)$(libdir)/libcyrus.a
for file in $(LIBCYR_HDRS); \
do \
$(INSTALL) -m 644 $$file $(DESTDIR)$(prefix)/include/cyrus || exit 1; \
done
for file in $(LIBCYRM_HDRS); \
do \
$(INSTALL) -m 644 $$file $(DESTDIR)$(prefix)/include/cyrus || exit 1; \
done
.c.o:
$(CC) -c $(CPPFLAGS) $(DEFS) $(CFLAGS) \
$<
libcyrus.a: $(LIBCYR_OBJS)
rm -f libcyrus.a
ar cr libcyrus.a $(LIBCYR_OBJS)
$(RANLIB) libcyrus.a
libcyrus_min.a: $(LIBCYRM_OBJS)
rm -f libcyrus_min.a
ar cr libcyrus_min.a $(LIBCYRM_OBJS)
$(RANLIB) libcyrus_min.a
imapopts.c: imapoptions $(srcdir)/../tools/config2header
$(srcdir)/../tools/config2header CC=$(CC) $(srcdir)/imapopts.c $(srcdir)/imapopts.h < $(srcdir)/imapoptions
imapopts.h: imapopts.c
chartable.c: mkchartable
@echo "### Building chartables..."
rm -f chartable.c
./mkchartable \
-m $(srcdir)/charset/unifix.txt \
-m $(srcdir)/charset/unidata2.txt \
$(srcdir)/charset/*.t \
> chartable.c \
|| (rm -f chartable.c && exit 1)
@echo "### Done building chartables."
# ./mkchartable -m $(srcdir)/charset/unicode.map $(srcdir)/charset/*.t >x-chartables.h
# mv x-chartables.h chartables.h
mkchartable: mkchartable.o xstrlcpy.o xstrlcat.o xmalloc.o assert.o
$(CC) $(LDFLAGS) -o mkchartable mkchartable.o xstrlcpy.o xstrlcat.o xmalloc.o assert.o
clean:
rm -f *.o *.a chartable.c Makefile.bak mkchartable makedepend.log \
$(BUILTSOURCES)
distclean: clean
rm -f Makefile
depend:
${MAKEDEPEND} $(CPPFLAGS) $(DEFS) -I$(srcdir) $(MAKEDEPEND_CFLAGS) *.c $(srcdir)/*.c 1>makedepend.log 2>&1
# DO NOT DELETE THIS LINE -- make depend depends on it.
diff --git a/lib/util.c b/lib/util.c
index 5c31f214f..26a22203f 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -1,362 +1,403 @@
/* util.c -- general utility functions
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: util.c,v 1.34 2007/03/27 19:53:09 murch Exp $
+ * $Id: util.c,v 1.35 2007/03/30 18:40:21 murch Exp $
*/
#include <config.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/socket.h>
#include <errno.h>
#include "exitcodes.h"
#include "util.h"
#include "xmalloc.h"
#define BEAUTYBUFSIZE 4096
const unsigned char convert_to_lowercase[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
const unsigned char convert_to_uppercase[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
/* convert string to all lower case
*/
char *lcase(char* str)
{
char *scan = str;
while (*scan) {
*scan = TOLOWER(*scan);
scan++;
}
return (str);
}
/* convert string to all upper case
*/
char *ucase(char* str)
{
char *scan = str;
while (*scan) {
*scan = convert_to_uppercase[(unsigned char)(*scan)];
scan++;
}
return (str);
}
/* clean up control characters in a string while copying it
* returns pointer to end of dst string.
* dst must have twice the length of source
*/
char *beautify_copy(char* dst, const char* src)
{
unsigned char c;
while (*src) {
c = *src++ & 0x7F;
if (!isprint(c)) {
*dst++ = '^';
if (c > ' ') {
c = '?';
} else {
c += '@';
}
}
*dst++ = c;
}
*dst = '\0';
return (dst);
}
/* clean up control characters in a string while copying it
* returns pointer to a static buffer containing the cleaned-up version
* returns NULL on malloc() error
*/
char *beautify_string(const char* src)
{
static char *beautybuf = NULL;
static int beautysize = 0;
int len;
len = strlen(src) * 2 + 1;
if (beautysize < len) {
if (!beautysize) {
beautysize = len > BEAUTYBUFSIZE ? len : BEAUTYBUFSIZE;
beautybuf = xmalloc(beautysize);
} else {
beautysize *= 2;
if (len > beautysize) beautysize = len;
beautybuf = xrealloc(beautybuf, beautysize);
}
if (!beautybuf) {
beautysize = 0;
return "";
}
}
(void) beautify_copy(beautybuf, src);
return (beautybuf);
}
/* do a binary search in a keyvalue array
* nelem is the number of keyvalue elements in the kv array
* cmpf is the comparison function (strcmp, strcasecmp, etc).
* returns NULL if not found, or key/value pair if found.
*/
keyvalue *kv_bsearch(const char* key, keyvalue* kv, int nelem,
int (*cmpf) (const char *s1, const char *s2))
{
int top, mid = 0, bot, cmp = 0;
cmp = 1;
bot = 0;
top = nelem - 1;
while (top >= bot && (cmp = (*cmpf)(key, kv[mid = (bot + top) >> 1].key)))
if (cmp < 0) {
top = mid - 1;
} else {
bot = mid + 1;
}
return (cmp ? NULL : kv + mid);
}
/* Examine the name of a file, and return a single character
* (as an int) that can be used as the name of a hash
* directory. Stop before the first dot. Caller is responsible
* for skipping any prefix of the name.
*/
int dir_hash_c(const char *name, int full)
{
int c;
if (full) {
unsigned char *pt;
unsigned int n;
enum {
DIR_X = 3,
DIR_Y = 5,
DIR_P = 23,
DIR_A = 'A'
};
n = 0;
pt = (unsigned char *)name;
while (*pt && *pt != '.') {
n = ((n << DIR_X) ^ (n >> DIR_Y)) ^ *pt;
++pt;
}
c = DIR_A + (n % DIR_P);
}
else {
c = tolower(*name);
if (!isascii(c) || !islower(c)) c = 'q';
}
return c;
}
int cyrus_close_sock(int fd)
{
shutdown(fd, SHUT_RD);
return close(fd);
}
void cyrus_reset_stdio()
{
int devnull = open("/dev/null", O_RDWR, 0);
if (devnull == -1) {
fatal("open() on /dev/null failed", EC_TEMPFAIL);
}
/* stdin */
shutdown(0, SHUT_RD);
dup2(devnull, 0);
/* stdout */
shutdown(1, SHUT_RD);
dup2(devnull, 1);
/* stderr */
shutdown(2, SHUT_RD);
dup2(devnull, 2);
if (devnull > 2) close(devnull);
}
/* Given a mkstemp(3) pattern for a filename,
* create the file and return the file descriptor.
*
* This routine also unlinks the file so it won't appear in the
* directory listing (but you won't have to worry about cleaning up
* after it)
*/
int create_tempfile(const char *path)
{
int fd;
char pattern[2048];
if(snprintf(pattern, sizeof(pattern), "%s/cyrus_tmpfile_XXXXXX",
path) >= sizeof(pattern)){
fatal("temporary file pathname is too long in prot_flush",
EC_TEMPFAIL);
}
fd = mkstemp(pattern);
if(fd == -1) {
return -1;
} else if(unlink(pattern) == -1) {
close(fd);
return -1;
}
return fd;
}
/* Create all parent directories for the given path,
* up to but not including the basename.
*/
int cyrus_mkdir(const char *path, mode_t mode __attribute__((unused)))
{
char *p = (char *) path;
int save_errno;
struct stat sbuf;
while ((p = strchr(p+1, '/'))) {
*p = '\0';
if (mkdir(path, 0755) == -1 && errno != EEXIST) {
save_errno = errno;
if (stat(path, &sbuf) == -1) {
errno = save_errno;
syslog(LOG_ERR, "IOERROR: creating directory %s: %m", path);
return -1;
}
}
*p = '/';
}
return 0;
}
+
+int become_cyrus(void)
+{
+ struct passwd *p;
+ int newuid, newgid;
+ int result;
+ static int uid = 0;
+
+ if (uid) return setuid(uid);
+
+ p = getpwnam(CYRUS_USER);
+ if (p == NULL) {
+ syslog(LOG_ERR, "no entry in /etc/passwd for user %s", CYRUS_USER);
+ return -1;
+ }
+
+ /* Save these in case initgroups does a getpw*() */
+ newuid = p->pw_uid;
+ newgid = p->pw_gid;
+
+ if (initgroups(CYRUS_USER, newgid)) {
+ syslog(LOG_ERR, "unable to initialize groups for user %s: %s",
+ CYRUS_USER, strerror(errno));
+ return -1;
+ }
+
+ if (setgid(newgid)) {
+ syslog(LOG_ERR, "unable to set group id to %d for user %s: %s",
+ newgid, CYRUS_USER, strerror(errno));
+ return -1;
+ }
+
+ result = setuid(newuid);
+
+ /* Only set static uid if successful, else future calls won't reset gid */
+ if (result == 0)
+ uid = newuid;
+ return result;
+}
diff --git a/lib/util.h b/lib/util.h
index 701204684..9a38ada4a 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -1,116 +1,118 @@
/* util.h -- general utility functions
- * $Id: util.h,v 1.23 2007/03/27 19:53:09 murch Exp $
+ * $Id: util.h,v 1.24 2007/03/30 18:40:21 murch Exp $
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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.
*
* Author: Chris Newman
* Start Date: 4/6/93
*/
#ifndef INCLUDED_UTIL_H
#define INCLUDED_UTIL_H
extern const unsigned char convert_to_lowercase[256];
extern const unsigned char convert_to_uppercase[256];
#define TOUPPER(c) (convert_to_uppercase[(unsigned char)(c)])
#define TOLOWER(c) (convert_to_lowercase[(unsigned char)(c)])
typedef struct keyvalue {
char *key, *value;
} keyvalue;
/* convert string to all lower case
*/
extern char *lcase (char *str);
/* convert string to all upper case
*/
extern char *ucase (char *str);
/* clean up control characters in a string while copying it
* returns pointer to end of dst string.
* dst must have twice the length of source
*/
extern char *beautify_copy (char *dst, const char *src);
/* clean up control characters in a string while copying it
* returns pointer to a static buffer containing the cleaned-up version
* returns NULL on malloc() error
*/
extern char *beautify_string (const char *src);
/* do a binary search in a keyvalue array
* nelem is the number of keyvalue elements in the kv array
* cmpf is the comparison function (strcmp, stricmp, etc).
* returns NULL if not found, or key/value pair if found.
*/
extern keyvalue *kv_bsearch (const char *key, keyvalue *kv, int nelem,
int (*cmpf)(const char *s1, const char *s2));
/* Examine the name of a file, and return a single character
* (as an int) that can be used as the name of a hash
* directory. Caller is responsible for skipping any prefix
* of the name.
*/
extern int dir_hash_c(const char *name, int full);
/*
* create an [unlinked] temporary file and return the file descriptor.
*/
extern int create_tempfile(const char *path);
/* Close a network filedescriptor the "safe" way */
extern int cyrus_close_sock(int fd);
/* Reset stdin/stdout/stderr */
extern void cyrus_reset_stdio();
/* Create all parent directories for the given path,
* up to but not including the basename.
*/
extern int cyrus_mkdir(const char *path, mode_t mode);
+extern int become_cyrus(void);
+
/* Some systems have very inefficient implementations of isdigit,
* and we use it in a lot of inner loops
*/
#define cyrus_isdigit(x) ((x) >= '0' && (x) <= '9')
#endif /* INCLUDED_UTIL_H */
diff --git a/master/master.c b/master/master.c
index 9944ba5e3..406f0db5c 100644
--- a/master/master.c
+++ b/master/master.c
@@ -1,2134 +1,2093 @@
/* master.c -- IMAP master process to handle recovery, checkpointing, spawning
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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: master.c,v 1.104 2006/11/30 17:11:23 murch Exp $ */
+/* $Id: master.c,v 1.105 2007/03/30 18:40:21 murch Exp $ */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
-#include <grp.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <fcntl.h>
#include <signal.h>
-#include <pwd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <syslog.h>
#include <netdb.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <sysexits.h>
#include <errno.h>
#include <limits.h>
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
#ifndef INADDR_ANY
#define INADDR_ANY 0x00000000
#endif
#if !defined(IPV6_V6ONLY) && defined(IPV6_BINDV6ONLY)
#define IPV6_V6ONLY IPV6_BINDV6ONLY
#endif
#if defined(HAVE_NETSNMP)
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/agent_module_config.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "cyrusMasterMIB.h"
#elif defined(HAVE_UCDSNMP)
#include <ucd-snmp/ucd-snmp-config.h>
#include <ucd-snmp/ucd-snmp-includes.h>
#include <ucd-snmp/ucd-snmp-agent-includes.h>
#include "cyrusMasterMIB.h"
int allow_severity = LOG_DEBUG;
int deny_severity = LOG_ERR;
#endif
#include "masterconf.h"
#include "master.h"
#include "service.h"
#include "lock.h"
-
+#include "util.h"
#include "xmalloc.h"
#include "message_uuid_master.h"
enum {
become_cyrus_early = 1,
child_table_size = 10000,
child_table_inc = 100
};
static int verbose = 0;
static int listen_queue_backlog = 32;
static int pidfd = -1;
static int have_uuid = 0;
const char *MASTER_CONFIG_FILENAME = DEFAULT_MASTER_CONFIG_FILENAME;
#define SERVICE_NONE -1
#define SERVICE_MAX INT_MAX-10
#define SERVICENAME(x) ((x) ? x : "unknown")
struct service *Services = NULL;
int allocservices = 0;
int nservices = 0;
/* make libcyrus_min happy */
int config_need_data = 0;
struct event {
char *name;
time_t mark;
time_t period;
int periodic;
char *const *exec;
struct event *next;
};
static struct event *schedule = NULL;
enum sstate {
SERVICE_STATE_UNKNOWN = 0, /* duh */
SERVICE_STATE_INIT = 1, /* Service forked - UNUSED */
SERVICE_STATE_READY = 2, /* Service told us it is ready */
/* or it just forked and has not
* talked to us yet */
SERVICE_STATE_BUSY = 3, /* Service told us it is not ready */
SERVICE_STATE_DEAD = 4 /* We received a sigchld from this service */
};
struct centry {
pid_t pid;
enum sstate service_state; /* SERVICE_STATE_* */
time_t janitor_deadline; /* cleanup deadline */
int si; /* Services[] index */
struct centry *next;
};
static struct centry *ctable[child_table_size];
static struct centry *cfreelist;
static int janitor_frequency = 1; /* Janitor sweeps per second */
static int janitor_position; /* Entry to begin at in next sweep */
static struct timeval janitor_mark; /* Last time janitor did a sweep */
void limit_fds(rlim_t);
void schedule_event(struct event *a);
void fatal(const char *msg, int code)
{
syslog(LOG_CRIT, "%s", msg);
syslog(LOG_NOTICE, "exiting");
exit(code);
}
void event_free(struct event *a)
{
if(a->exec) free((char**)a->exec);
if(a->name) free((char*)a->name);
free(a);
}
-int become_cyrus(void)
-{
- struct passwd *p;
- int newuid, newgid;
- int result;
- static int uid = 0;
-
- if (uid) return setuid(uid);
-
- p = getpwnam(CYRUS_USER);
- if (p == NULL) {
- syslog(LOG_ERR, "no entry in /etc/passwd for user %s", CYRUS_USER);
- return -1;
- }
-
- /* Save these in case initgroups does a getpw*() */
- newuid = p->pw_uid;
- newgid = p->pw_gid;
-
- if (initgroups(CYRUS_USER, newgid)) {
- syslog(LOG_ERR, "unable to initialize groups for user %s: %s",
- CYRUS_USER, strerror(errno));
- return -1;
- }
-
- if (setgid(newgid)) {
- syslog(LOG_ERR, "unable to set group id to %d for user %s: %s",
- newgid, CYRUS_USER, strerror(errno));
- return -1;
- }
-
- result = setuid(newuid);
-
- /* Only set static uid if successful, else future calls won't reset gid */
- if (result == 0)
- uid = newuid;
- return result;
-}
-
void get_prog(char *path, unsigned size, char *const *cmd)
{
if (cmd[0][0] == '/') {
/* master lacks strlcpy, due to no libcyrus */
snprintf(path, size, "%s", cmd[0]);
}
else snprintf(path, size, "%s/%s", SERVICE_PATH, cmd[0]);
}
void get_statsock(int filedes[2])
{
int r, fdflags;
r = pipe(filedes);
if (r != 0) {
fatal("couldn't create status socket: %m", 1);
}
/* we don't want the master blocking on reads */
fdflags = fcntl(filedes[0], F_GETFL, 0);
if (fdflags != -1) fdflags = fcntl(filedes[0], F_SETFL,
fdflags | O_NONBLOCK);
if (fdflags == -1) {
fatal("unable to set non-blocking: %m", 1);
}
/* we don't want the services to be able to read from it */
fdflags = fcntl(filedes[0], F_GETFD, 0);
if (fdflags != -1) fdflags = fcntl(filedes[0], F_SETFD,
fdflags | FD_CLOEXEC);
if (fdflags == -1) {
fatal("unable to set close-on-exec: %m", 1);
}
}
/* return a new 'centry', either from the freelist or by malloc'ing it */
static struct centry *get_centry(void)
{
struct centry *t;
if (!cfreelist) {
/* create child_table_inc more and add them to the freelist */
struct centry *n;
int i;
n = xmalloc(child_table_inc * sizeof(struct centry));
cfreelist = n;
for (i = 0; i < child_table_inc - 1; i++) {
n[i].next = n + (i + 1);
}
/* i == child_table_inc - 1, last item in block */
n[i].next = NULL;
}
t = cfreelist;
cfreelist = cfreelist->next;
t->janitor_deadline = 0;
return t;
}
/* see if 'listen' parameter has both hostname and port, or just port */
char *parse_listen(char *listen)
{
char *cp;
char *port = NULL;
if ((cp = strrchr(listen,']')) != NULL) {
/* ":port" after closing bracket for IP address? */
if (*cp++ != '\0' && *cp == ':') {
*cp++ = '\0';
if (*cp != '\0') {
port = cp;
}
}
} else if ((cp = strrchr(listen,':')) != NULL) {
/* ":port" after hostname? */
*cp++ = '\0';
if (*cp != '\0') {
port = cp;
}
}
return port;
}
char *parse_host(char *listen)
{
char *cp;
/* do we have a hostname, or IP number? */
/* XXX are brackets necessary */
if (*listen == '[') {
listen++; /* skip first bracket */
if ((cp = strrchr(listen,']')) != NULL) {
*cp = '\0';
}
}
return listen;
}
int verify_service_file(char *const *filename)
{
char path[PATH_MAX];
struct stat statbuf;
get_prog(path, sizeof(path), filename);
if (stat(path, &statbuf)) return 0;
if (! S_ISREG(statbuf.st_mode)) return 0;
return statbuf.st_mode & S_IXUSR;
}
void service_create(struct service *s)
{
struct service service0, service;
struct addrinfo hints, *res0, *res;
int error, nsocket = 0;
struct sockaddr_un sunsock;
mode_t oldumask;
int on = 1;
int res0_is_local = 0;
int r;
if (s->associate > 0)
return; /* service is already activated */
if (!s->name)
fatal("Serious software bug found: service_create() called on unnamed service!",
EX_SOFTWARE);
if (s->listen[0] == '/') { /* unix socket */
res0_is_local = 1;
res0 = (struct addrinfo *)malloc(sizeof(struct addrinfo));
if (!res0)
fatal("out of memory", EX_UNAVAILABLE);
memset(res0, 0, sizeof(struct addrinfo));
res0->ai_flags = AI_PASSIVE;
res0->ai_family = PF_UNIX;
if(!strcmp(s->proto, "tcp")) {
res0->ai_socktype = SOCK_STREAM;
} else {
/* udp */
res0->ai_socktype = SOCK_DGRAM;
}
res0->ai_addr = (struct sockaddr *)&sunsock;
res0->ai_addrlen = sizeof(sunsock.sun_family) + strlen(s->listen) + 1;
#ifdef SIN6_LEN
res0->ai_addrlen += sizeof(sunsock.sun_len);
sunsock.sun_len = res0->ai_addrlen;
#endif
sunsock.sun_family = AF_UNIX;
strcpy(sunsock.sun_path, s->listen);
unlink(s->listen);
} else { /* inet socket */
char *listen, *port;
char *listen_addr;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
if (!strcmp(s->proto, "tcp")) {
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
} else if (!strcmp(s->proto, "tcp4")) {
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
#ifdef PF_INET6
} else if (!strcmp(s->proto, "tcp6")) {
hints.ai_family = PF_INET6;
hints.ai_socktype = SOCK_STREAM;
#endif
} else if (!strcmp(s->proto, "udp")) {
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
} else if (!strcmp(s->proto, "udp4")) {
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_DGRAM;
#ifdef PF_INET6
} else if (!strcmp(s->proto, "udp6")) {
hints.ai_family = PF_INET6;
hints.ai_socktype = SOCK_DGRAM;
#endif
} else {
syslog(LOG_INFO, "invalid proto '%s', disabling %s",
s->proto, s->name);
s->exec = NULL;
return;
}
/* parse_listen() and resolve_host() are destructive,
* so make a work copy of s->listen
*/
listen = xstrdup(s->listen);
if ((port = parse_listen(listen)) == NULL) {
/* listen IS the port */
port = listen;
listen_addr = NULL;
} else {
/* s->listen is now just the address */
listen_addr = parse_host(listen);
if (*listen_addr == '\0')
listen_addr = NULL;
}
error = getaddrinfo(listen_addr, port, &hints, &res0);
free(listen);
if (error) {
syslog(LOG_INFO, "%s, disabling %s", gai_strerror(error), s->name);
s->exec = NULL;
return;
}
}
memcpy(&service0, s, sizeof(struct service));
for (res = res0; res; res = res->ai_next) {
if (s->socket > 0) {
memcpy(&service, &service0, sizeof(struct service));
s = &service;
}
s->socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (s->socket < 0) {
s->socket = 0;
if (verbose > 2)
syslog(LOG_ERR, "unable to open %s socket: %m", s->name);
continue;
}
/* allow reuse of address */
r = setsockopt(s->socket, SOL_SOCKET, SO_REUSEADDR,
(void *) &on, sizeof(on));
if (r < 0) {
syslog(LOG_ERR, "unable to setsocketopt(SO_REUSEADDR): %m");
}
#if defined(IPV6_V6ONLY) && !(defined(__FreeBSD__) && __FreeBSD__ < 3)
if (res->ai_family == AF_INET6) {
r = setsockopt(s->socket, IPPROTO_IPV6, IPV6_V6ONLY,
(void *) &on, sizeof(on));
if (r < 0) {
syslog(LOG_ERR, "unable to setsocketopt(IPV6_V6ONLY): %m");
}
}
#endif
oldumask = umask((mode_t) 0); /* for linux */
r = bind(s->socket, res->ai_addr, res->ai_addrlen);
umask(oldumask);
if (r < 0) {
close(s->socket);
s->socket = 0;
if (verbose > 2)
syslog(LOG_ERR, "unable to bind to %s socket: %m", s->name);
continue;
}
if (s->listen[0] == '/') { /* unix socket */
/* for DUX, where this isn't the default.
(harmlessly fails on some systems) */
chmod(s->listen, (mode_t) 0777);
}
if ((!strcmp(s->proto, "tcp") || !strcmp(s->proto, "tcp4")
|| !strcmp(s->proto, "tcp6"))
&& listen(s->socket, listen_queue_backlog) < 0) {
syslog(LOG_ERR, "unable to listen to %s socket: %m", s->name);
close(s->socket);
s->socket = 0;
continue;
}
s->ready_workers = 0;
s->associate = nsocket;
s->family = res->ai_family;
get_statsock(s->stat);
if (s == &service) {
if (nservices == allocservices) {
if (allocservices > SERVICE_MAX - 5)
fatal("out of service structures, please restart", EX_UNAVAILABLE);
Services = xrealloc(Services,
(allocservices+=5) * sizeof(struct service));
if (!Services) fatal("out of memory", EX_UNAVAILABLE);
}
memcpy(&Services[nservices++], s, sizeof(struct service));
}
nsocket++;
}
if (res0) {
if(res0_is_local)
free(res0);
else
freeaddrinfo(res0);
}
if (nsocket <= 0) {
syslog(LOG_ERR, "unable to create %s listener socket: %m", s->name);
s->exec = NULL;
return;
}
}
void run_startup(char **cmd)
{
pid_t pid;
int status;
char path[PATH_MAX];
switch (pid = fork()) {
case -1:
syslog(LOG_CRIT, "can't fork process to run startup: %m");
fatal("can't run startup", 1);
break;
case 0:
/* Child - Release our pidfile lock. */
if(pidfd != -1) close(pidfd);
if (become_cyrus() != 0) {
syslog(LOG_ERR, "can't change to the cyrus user: %m");
exit(1);
}
limit_fds(256);
get_prog(path, sizeof(path), cmd);
syslog(LOG_DEBUG, "about to exec %s", path);
execv(path, cmd);
syslog(LOG_ERR, "can't exec %s for startup: %m", path);
exit(EX_OSERR);
default: /* parent */
if (waitpid(pid, &status, 0) < 0) {
syslog(LOG_ERR, "waitpid(): %m");
} else if (status != 0) {
if (WIFEXITED(status)) {
syslog(LOG_ERR, "process %d exited, status %d\n", pid,
WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) {
syslog(LOG_ERR,
"process %d exited, signaled to death by %d\n",
pid, WTERMSIG(status));
}
}
break;
}
}
void fcntl_unset(int fd, int flag)
{
int fdflags = fcntl(fd, F_GETFD, 0);
if (fdflags != -1) fdflags = fcntl(STATUS_FD, F_SETFD,
fdflags & ~flag);
if (fdflags == -1) {
syslog(LOG_ERR, "fcntl(): unable to unset %d: %m", flag);
}
}
void spawn_service(const int si)
{
/* Note that there is logic that depends on this being 2 */
const int FORKRATE_INTERVAL = 2;
pid_t p;
int i;
char path[PATH_MAX];
static char name_env[100], name_env2[100];
struct centry *c;
struct service * const s = &Services[si];
time_t now = time(NULL);
struct message_uuid uuid_prefix;
char *uuid_prefix_text;
static char uuid_env[100];
if (!s->name) {
fatal("Serious software bug found: spawn_service() called on unnamed service!",
EX_SOFTWARE);
}
/* update our fork rate */
if(now - s->last_interval_start >= FORKRATE_INTERVAL) {
int interval;
s->forkrate = (s->interval_forks/2) + (s->forkrate/2);
s->interval_forks = 0;
s->last_interval_start += FORKRATE_INTERVAL;
/* if there is an even wider window, however, we need
* to account for a good deal of zeros, we can do this at once */
interval = now - s->last_interval_start;
if(interval > 2) {
int skipped_intervals = interval / FORKRATE_INTERVAL;
/* avoid a > 30 bit right shift) */
if(skipped_intervals > 30) s->forkrate = 0;
else {
/* divide by 2^(skipped_intervals).
* this is the logic mentioned in the comment above */
s->forkrate >>= skipped_intervals;
s->last_interval_start = now;
}
}
}
/* If we've been busy lately, we will refuse to fork! */
/* (We schedule a wakeup call for sometime soon though to be
* sure that we don't wait to do the fork that is required forever! */
if(s->maxforkrate && s->forkrate >= s->maxforkrate) {
struct event *evt = (struct event *) xmalloc(sizeof(struct event));
memset(evt, 0, sizeof(struct event));
evt->name = xstrdup("forkrate wakeup call");
evt->mark = time(NULL) + FORKRATE_INTERVAL;
schedule_event(evt);
return;
}
if (s->provide_uuid) {
if (!message_uuid_master_next_child(&uuid_prefix)) {
syslog(LOG_ERR, "Failed to generate UUID for %s", s->name);
message_uuid_set_null(&uuid_prefix);
}
if (!message_uuid_master_checksum(&uuid_prefix)) {
syslog(LOG_ERR, "Failed to checksum UUID for %s", s->name);
message_uuid_set_null(&uuid_prefix);
}
uuid_prefix_text = message_uuid_text(&uuid_prefix);
} else
uuid_prefix_text = NULL;
switch (p = fork()) {
case -1:
syslog(LOG_ERR, "can't fork process to run service %s: %m", s->name);
break;
case 0:
/* Child - Release our pidfile lock. */
if(pidfd != -1) close(pidfd);
if (become_cyrus() != 0) {
syslog(LOG_ERR, "can't change to the cyrus user");
exit(1);
}
get_prog(path, sizeof(path), s->exec);
if (dup2(s->stat[1], STATUS_FD) < 0) {
syslog(LOG_ERR, "can't duplicate status fd: %m");
exit(1);
}
if (dup2(s->socket, LISTEN_FD) < 0) {
syslog(LOG_ERR, "can't duplicate listener fd: %m");
exit(1);
}
fcntl_unset(STATUS_FD, FD_CLOEXEC);
fcntl_unset(LISTEN_FD, FD_CLOEXEC);
/* close all listeners */
for (i = 0; i < nservices; i++) {
if (Services[i].socket > 0) close(Services[i].socket);
if (Services[i].stat[0] > 0) close(Services[i].stat[0]);
if (Services[i].stat[1] > 0) close(Services[i].stat[1]);
}
limit_fds(s->maxfds);
syslog(LOG_DEBUG, "about to exec %s", path);
/* add service name to environment */
snprintf(name_env, sizeof(name_env), "CYRUS_SERVICE=%s", s->name);
putenv(name_env);
snprintf(name_env2, sizeof(name_env2), "CYRUS_ID=%d", s->associate);
putenv(name_env2);
/* add UUID prefix to environment */
if (s->provide_uuid) {
snprintf(uuid_env, sizeof(uuid_env), "CYRUS_UUID_PREFIX=%s",
uuid_prefix_text);
putenv(uuid_env);
}
execv(path, s->exec);
syslog(LOG_ERR, "couldn't exec %s: %m", path);
exit(EX_OSERR);
default: /* parent */
s->ready_workers++;
s->interval_forks++;
s->nforks++;
s->nactive++;
/* add to child table */
c = get_centry();
c->pid = p;
c->service_state = SERVICE_STATE_READY;
c->si = si;
c->next = ctable[p % child_table_size];
ctable[p % child_table_size] = c;
break;
}
}
void schedule_event(struct event *a)
{
struct event *ptr;
if (! a->name)
fatal("Serious software bug found: schedule_event() called on unnamed event!",
EX_SOFTWARE);
if (!schedule || a->mark < schedule->mark) {
a->next = schedule;
schedule = a;
return;
}
for (ptr = schedule; ptr->next && ptr->next->mark <= a->mark;
ptr = ptr->next) ;
/* insert a */
a->next = ptr->next;
ptr->next = a;
}
void spawn_schedule(time_t now)
{
struct event *a, *b;
int i;
char path[PATH_MAX];
pid_t p;
struct centry *c;
a = NULL;
/* update schedule accordingly */
while (schedule && schedule->mark <= now) {
/* delete from schedule, insert into a */
struct event *ptr = schedule;
/* delete */
schedule = schedule->next;
/* insert */
ptr->next = a;
a = ptr;
}
/* run all events */
while (a && a != schedule) {
/* if a->exec is NULL, we just used the event to wake up,
* so we actually don't need to exec anything at the moment */
if(a->exec) {
switch (p = fork()) {
case -1:
syslog(LOG_CRIT,
"can't fork process to run event %s", a->name);
break;
case 0:
/* Child - Release our pidfile lock. */
if(pidfd != -1) close(pidfd);
if (become_cyrus() != 0) {
syslog(LOG_ERR, "can't change to the cyrus user");
exit(1);
}
/* close all listeners */
for (i = 0; i < nservices; i++) {
if (Services[i].socket > 0) close(Services[i].socket);
if (Services[i].stat[0] > 0) close(Services[i].stat[0]);
if (Services[i].stat[1] > 0) close(Services[i].stat[1]);
}
limit_fds(256);
get_prog(path, sizeof(path), a->exec);
syslog(LOG_DEBUG, "about to exec %s", path);
execv(path, a->exec);
syslog(LOG_ERR, "can't exec %s on schedule: %m", path);
exit(EX_OSERR);
break;
default:
/* we don't wait for it to complete */
/* add to child table */
c = get_centry();
c->pid = p;
c->service_state = SERVICE_STATE_READY;
c->si = SERVICE_NONE;
c->next = ctable[p % child_table_size];
ctable[p % child_table_size] = c;
break;
}
} /* a->exec */
/* reschedule as needed */
b = a->next;
if (a->period) {
if(a->periodic) {
a->mark = now + a->period;
} else {
/* Daily Event */
while(a->mark <= now) {
a->mark += a->period;
}
}
/* reschedule a */
schedule_event(a);
} else {
event_free(a);
}
/* examine next event */
a = b;
}
}
void reap_child(void)
{
int status;
pid_t pid;
struct centry *c;
struct service *s;
while ((pid = waitpid((pid_t) -1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
syslog(LOG_DEBUG, "process %d exited, status %d", pid,
WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) {
syslog(LOG_ERR, "process %d exited, signaled to death by %d",
pid, WTERMSIG(status));
}
/* account for the child */
c = ctable[pid % child_table_size];
while(c && c->pid != pid) c = c->next;
if (c && c->pid == pid) {
s = ((c->si) != SERVICE_NONE) ? &Services[c->si] : NULL;
/* paranoia */
switch (c->service_state) {
case SERVICE_STATE_READY:
case SERVICE_STATE_BUSY:
case SERVICE_STATE_UNKNOWN:
case SERVICE_STATE_DEAD:
break;
default:
syslog(LOG_CRIT,
"service %s pid %d in ILLEGAL STATE: exited. Serious software bug or memory corruption detected!",
SERVICENAME(s->name), pid);
syslog(LOG_DEBUG,
"service %s pid %d in ILLEGAL state: forced to valid UNKNOWN state",
SERVICENAME(s->name), pid);
c->service_state = SERVICE_STATE_UNKNOWN;
}
if (s) {
/* update counters for known services */
switch (c->service_state) {
case SERVICE_STATE_READY:
s->nactive--;
s->ready_workers--;
if (WIFSIGNALED(status) ||
(WIFEXITED(status) && WEXITSTATUS(status))) {
syslog(LOG_WARNING,
"service %s pid %d in READY state: terminated abnormally",
SERVICENAME(s->name), pid);
}
break;
case SERVICE_STATE_DEAD:
/* uh? either we got duplicate signals, or we are now MT */
syslog(LOG_WARNING,
"service %s pid %d in DEAD state: receiving duplicate signals",
SERVICENAME(s->name), pid);
break;
case SERVICE_STATE_BUSY:
s->nactive--;
if (WIFSIGNALED(status) ||
(WIFEXITED(status) && WEXITSTATUS(status))) {
syslog(LOG_DEBUG,
"service %s pid %d in BUSY state: terminated abnormally",
SERVICENAME(s->name), pid);
}
break;
case SERVICE_STATE_UNKNOWN:
s->nactive--;
syslog(LOG_WARNING,
"service %s pid %d in UNKNOWN state: exited",
SERVICENAME(s->name), pid);
break;
}
} else {
/* children from spawn_schedule (events) or
* children that messaged us before being registered or
* children of services removed by reread_conf() */
if (c->service_state != SERVICE_STATE_READY) {
syslog(LOG_WARNING,
"unknown service pid %d in state %d: exited (maybe using a service as an event,"
" or a service was removed by SIGHUP?)",
pid, c->service_state);
}
}
c->service_state = SERVICE_STATE_DEAD;
c->janitor_deadline = time(NULL) + 2;
} else {
/* weird. Are we multithreaded now? we don't know this child */
syslog(LOG_WARNING,
"receiving signals from unregistered child %d. Handling it anyway",
pid);
c = get_centry();
c->pid = pid;
c->service_state = SERVICE_STATE_DEAD;
c->janitor_deadline = time(NULL) + 2;
c->si = SERVICE_NONE;
c->next = ctable[pid % child_table_size];
ctable[pid % child_table_size] = c;
}
if (verbose && c && (c->si != SERVICE_NONE))
syslog(LOG_DEBUG, "service %s now has %d ready workers\n",
SERVICENAME(Services[c->si].name),
Services[c->si].ready_workers);
}
}
void init_janitor(void)
{
struct event *evt = (struct event *) malloc(sizeof(struct event));
if (!evt) fatal("out of memory", EX_UNAVAILABLE);
memset(evt, 0, sizeof(struct event));
gettimeofday(&janitor_mark, NULL);
janitor_position = 0;
evt->name = xstrdup("janitor periodic wakeup call");
evt->period = 10;
evt->mark = time(NULL) + 2;
schedule_event(evt);
}
void child_janitor(time_t now)
{
int i;
struct centry **p;
struct centry *c;
struct timeval rightnow;
/* Estimate the number of entries to clean up in this sweep */
gettimeofday(&rightnow, NULL);
if (rightnow.tv_sec > janitor_mark.tv_sec + 1) {
/* overflow protection */
i = child_table_size;
} else {
double n;
n = child_table_size * janitor_frequency *
(double) ((rightnow.tv_sec - janitor_mark.tv_sec) * 1000000 +
rightnow.tv_usec - janitor_mark.tv_usec ) / 1000000;
if (n < child_table_size) {
i = n;
} else {
i = child_table_size;
}
}
while (i-- > 0) {
p = &ctable[janitor_position++];
janitor_position = janitor_position % child_table_size;
while (*p) {
c = *p;
if (c->service_state == SERVICE_STATE_DEAD) {
if (c->janitor_deadline < now) {
*p = c->next;
c->next = cfreelist;
cfreelist = c;
} else {
p = &((*p)->next);
}
} else {
p = &((*p)->next);
}
}
}
}
static volatile int gotsigchld = 0;
void sigchld_handler(int sig __attribute__((unused)))
{
gotsigchld = 1;
}
static volatile int gotsighup = 0;
void sighup_handler(int sig __attribute__((unused)))
{
gotsighup = 1;
}
void sigterm_handler(int sig __attribute__((unused)))
{
struct sigaction action;
/* send all the other processes SIGTERM, then exit */
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = SIG_IGN;
if (sigaction(SIGTERM, &action, (struct sigaction *) 0) < 0) {
syslog(LOG_ERR, "sigaction: %m");
exit(1);
}
/* kill my process group */
if (kill(0, SIGTERM) < 0) {
syslog(LOG_ERR, "sigterm_handler: kill(0, SIGTERM): %m");
}
#if defined(HAVE_UCDSNMP) || defined(HAVE_NETSNMP)
/* tell master agent we're exiting */
snmp_shutdown("cyrusMaster");
#endif
syslog(LOG_INFO, "exiting on SIGTERM/SIGINT");
exit(0);
}
void sigalrm_handler(int sig __attribute__((unused)))
{
return;
}
void sighandler_setup(void)
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = sighup_handler;
#ifdef SA_RESTART
action.sa_flags |= SA_RESTART;
#endif
if (sigaction(SIGHUP, &action, NULL) < 0) {
fatal("unable to install signal handler for SIGHUP: %m", 1);
}
action.sa_handler = sigalrm_handler;
if (sigaction(SIGALRM, &action, NULL) < 0) {
fatal("unable to install signal handler for SIGALRM: %m", 1);
}
/* Handle SIGTERM and SIGINT the same way -- kill
* off our children! */
action.sa_handler = sigterm_handler;
if (sigaction(SIGTERM, &action, NULL) < 0) {
fatal("unable to install signal handler for SIGTERM: %m", 1);
}
if (sigaction(SIGINT, &action, NULL) < 0) {
fatal("unable to install signal handler for SIGINT: %m", 1);
}
action.sa_flags |= SA_NOCLDSTOP;
action.sa_handler = sigchld_handler;
if (sigaction(SIGCHLD, &action, NULL) < 0) {
fatal("unable to install signal handler for SIGCHLD: %m", 1);
}
}
void process_msg(const int si, struct notify_message *msg)
{
struct centry *c;
/* si must NOT point to an invalid service */
struct service * const s = &Services[si];;
/* Search hash table with linked list for pid */
c = ctable[msg->service_pid % child_table_size];
while (c && c->pid != msg->service_pid) c = c->next;
/* Did we find it? */
if (!c || c->pid != msg->service_pid) {
syslog(LOG_WARNING, "service %s pid %d: while trying to process message 0x%x: not registered yet",
SERVICENAME(s->name), msg->service_pid, msg->message);
/* resilience paranoia. Causes small performance loss when used */
c = get_centry();
c->si = si;
c->pid = msg->service_pid;
c->service_state = SERVICE_STATE_UNKNOWN;
c->next = ctable[c->pid % child_table_size];
ctable[c->pid % child_table_size] = c;
}
/* paranoia */
if (si != c->si) {
syslog(LOG_ERR,
"service %s pid %d: changing from service %s due to received message",
SERVICENAME(s->name), c->pid,
((c->si != SERVICE_NONE && Services[c->si].name) ? Services[c->si].name : "unknown"));
c->si = si;
}
switch (c->service_state) {
case SERVICE_STATE_UNKNOWN:
syslog(LOG_WARNING,
"service %s pid %d in UNKNOWN state: processing message 0x%x",
SERVICENAME(s->name), c->pid, msg->message);
break;
case SERVICE_STATE_READY:
case SERVICE_STATE_BUSY:
case SERVICE_STATE_DEAD:
break;
default:
syslog(LOG_CRIT,
"service %s pid %d in ILLEGAL state: detected. Serious software bug or memory corruption uncloaked while processing message 0x%x from child!",
SERVICENAME(s->name), c->pid, msg->message);
syslog(LOG_DEBUG,
"service %s pid %d in ILLEGAL state: forced to valid UNKNOWN state",
SERVICENAME(s->name), c->pid);
c->service_state = SERVICE_STATE_UNKNOWN;
break;
}
/* process message, according to state machine */
switch (msg->message) {
case MASTER_SERVICE_AVAILABLE:
switch (c->service_state) {
case SERVICE_STATE_READY:
/* duplicate message? */
syslog(LOG_WARNING,
"service %s pid %d in READY state: sent available message but it is already ready",
SERVICENAME(s->name), c->pid);
break;
case SERVICE_STATE_UNKNOWN:
/* since state is unknwon, error in non-DoS way, i.e.
* we don't increment ready_workers */
syslog(LOG_DEBUG,
"service %s pid %d in UNKNOWN state: now available and in READY state",
SERVICENAME(s->name), c->pid);
c->service_state = SERVICE_STATE_READY;
break;
case SERVICE_STATE_BUSY:
if (verbose)
syslog(LOG_DEBUG,
"service %s pid %d in BUSY state: now available and in READY state",
SERVICENAME(s->name), c->pid);
c->service_state = SERVICE_STATE_READY;
s->ready_workers++;
break;
}
break;
case MASTER_SERVICE_UNAVAILABLE:
switch (c->service_state) {
case SERVICE_STATE_BUSY:
/* duplicate message? */
syslog(LOG_WARNING,
"service %s pid %d in BUSY state: sent unavailable message but it is already busy",
SERVICENAME(s->name), c->pid);
break;
case SERVICE_STATE_UNKNOWN:
syslog(LOG_DEBUG,
"service %s pid %d in UNKNOWN state: now unavailable and in BUSY state",
SERVICENAME(s->name), c->pid);
c->service_state = SERVICE_STATE_BUSY;
break;
case SERVICE_STATE_READY:
if (verbose)
syslog(LOG_DEBUG,
"service %s pid %d in READY state: now unavailable and in BUSY state",
SERVICENAME(s->name), c->pid);
c->service_state = SERVICE_STATE_BUSY;
s->ready_workers--;
break;
}
break;
case MASTER_SERVICE_CONNECTION:
switch (c->service_state) {
case SERVICE_STATE_BUSY:
s->nconnections++;
if (verbose)
syslog(LOG_DEBUG,
"service %s pid %d in BUSY state: now serving connection",
SERVICENAME(s->name), c->pid);
break;
case SERVICE_STATE_UNKNOWN:
s->nconnections++;
c->service_state = SERVICE_STATE_BUSY;
syslog(LOG_DEBUG,
"service %s pid %d in UNKNOWN state: now in BUSY state and serving connection",
SERVICENAME(s->name), c->pid);
break;
case SERVICE_STATE_READY:
syslog(LOG_ERR,
"service %s pid %d in READY state: reported new connection, forced to BUSY state",
SERVICENAME(s->name), c->pid);
/* be resilient on face of a bogon source, so lets err to the side
* of non-denial-of-service */
c->service_state = SERVICE_STATE_BUSY;
s->nconnections++;
s->ready_workers--;
}
break;
case MASTER_SERVICE_CONNECTION_MULTI:
switch (c->service_state) {
case SERVICE_STATE_READY:
s->nconnections++;
if (verbose)
syslog(LOG_DEBUG,
"service %s pid %d in READY state: serving one more multi-threaded connection",
SERVICENAME(s->name), c->pid);
break;
case SERVICE_STATE_BUSY:
syslog(LOG_ERR,
"service %s pid %d in BUSY state: serving one more multi-threaded connection, forced to READY state",
SERVICENAME(s->name), c->pid);
/* be resilient on face of a bogon source, so lets err to the side
* of non-denial-of-service */
c->service_state = SERVICE_STATE_READY;
s->nconnections++;
s->ready_workers++;
break;
case SERVICE_STATE_UNKNOWN:
s->nconnections++;
c->service_state = SERVICE_STATE_READY;
syslog(LOG_ERR,
"service %s pid %d in UNKNOWN state: serving one more multi-threaded connection, forced to READY state",
SERVICENAME(s->name), c->pid);
break;
}
break;
default:
syslog(LOG_CRIT, "service %s pid %d: Software bug: unrecognized message 0x%x",
SERVICENAME(s->name), c->pid, msg->message);
break;
}
if (verbose)
syslog(LOG_DEBUG, "service %s now has %d ready workers\n",
SERVICENAME(s->name), s->ready_workers);
}
static char **tokenize(char *p)
{
char **tokens = NULL; /* allocated in increments of 10 */
int i = 0;
if (!p || !*p) return NULL; /* sanity check */
while (*p) {
while (*p && isspace((int) *p)) p++; /* skip whitespace */
if (!(i % 10)) tokens = xrealloc(tokens, (i+10) * sizeof(char *));
/* got a token */
tokens[i++] = p;
while (*p && !isspace((int) *p)) p++;
/* p is whitespace or end of cmd */
if (*p) *p++ = '\0';
}
/* add a NULL on the end */
if (!(i % 10)) tokens = xrealloc(tokens, (i+1) * sizeof(char *));
if (!tokens) return NULL;
tokens[i] = NULL;
return tokens;
}
void add_start(const char *name, struct entry *e,
void *rock __attribute__((unused)))
{
char *cmd = xstrdup(masterconf_getstring(e, "cmd", ""));
char buf[256];
char **tok;
if (!strcmp(cmd,"")) {
snprintf(buf, sizeof(buf), "unable to find command for %s", name);
fatal(buf, EX_CONFIG);
}
tok = tokenize(cmd);
if (!tok) fatal("out of memory", EX_UNAVAILABLE);
run_startup(tok);
free(tok);
free(cmd);
}
void add_service(const char *name, struct entry *e, void *rock)
{
int ignore_err = (int) rock;
char *cmd = xstrdup(masterconf_getstring(e, "cmd", ""));
int prefork = masterconf_getint(e, "prefork", 0);
int babysit = masterconf_getswitch(e, "babysit", 0);
int maxforkrate = masterconf_getint(e, "maxforkrate", 0);
char *listen = xstrdup(masterconf_getstring(e, "listen", ""));
char *proto = xstrdup(masterconf_getstring(e, "proto", "tcp"));
char *max = xstrdup(masterconf_getstring(e, "maxchild", "-1"));
rlim_t maxfds = (rlim_t) masterconf_getint(e, "maxfds", 256);
int reconfig = 0;
int i, j;
int provide_uuid = have_uuid && masterconf_getswitch(e, "provide_uuid", 0);
if(babysit && prefork == 0) prefork = 1;
if(babysit && maxforkrate == 0) maxforkrate = 10; /* reasonable safety */
if (!strcmp(cmd,"") || !strcmp(listen,"")) {
char buf[256];
snprintf(buf, sizeof(buf),
"unable to find command or port for service '%s'", name);
if (ignore_err) {
syslog(LOG_WARNING, "WARNING: %s -- ignored", buf);
return;
}
fatal(buf, EX_CONFIG);
}
/* see if we have an existing entry that can be reused */
for (i = 0; i < nservices; i++) {
/* skip non-primary instances */
if (Services[i].associate > 0)
continue;
/* must have empty/same service name, listen and proto */
if ((!Services[i].name || !strcmp(Services[i].name, name)) &&
(!Services[i].listen || !strcmp(Services[i].listen, listen)) &&
(!Services[i].proto || !strcmp(Services[i].proto, proto)))
break;
}
/* we have duplicate service names in the config file */
if ((i < nservices) && Services[i].exec) {
char buf[256];
snprintf(buf, sizeof(buf), "multiple entries for service '%s'", name);
if (ignore_err) {
syslog(LOG_WARNING, "WARNING: %s -- ignored", buf);
return;
}
fatal(buf, EX_CONFIG);
}
if (i == nservices) {
/* either we don't have an existing entry or we are changing
* the port parameters, so create a new service
*/
if (nservices == allocservices) {
if (allocservices > SERVICE_MAX - 5)
fatal("out of service structures, please restart", EX_UNAVAILABLE);
Services = xrealloc(Services,
(allocservices+=5) * sizeof(struct service));
}
memset(&Services[nservices++], 0, sizeof(struct service));
Services[i].last_interval_start = time(NULL);
}
else if (Services[i].listen) reconfig = 1;
if (!Services[i].name) Services[i].name = xstrdup(name);
if (Services[i].listen) free(Services[i].listen);
Services[i].listen = listen;
if (Services[i].proto) free(Services[i].proto);
Services[i].proto = proto;
Services[i].exec = tokenize(cmd);
if (!Services[i].exec) fatal("out of memory", EX_UNAVAILABLE);
/* is this service actually there? */
if (!verify_service_file(Services[i].exec)) {
char buf[1024];
snprintf(buf, sizeof(buf),
"cannot find executable for service '%s'", name);
/* if it is not, we're misconfigured, die. */
fatal(buf, EX_CONFIG);
}
Services[i].maxforkrate = maxforkrate;
Services[i].maxfds = maxfds;
Services[i].provide_uuid = provide_uuid;
if (!strcmp(Services[i].proto, "tcp") ||
!strcmp(Services[i].proto, "tcp4") ||
!strcmp(Services[i].proto, "tcp6")) {
Services[i].desired_workers = prefork;
Services[i].babysit = babysit;
Services[i].max_workers = atoi(max);
if (Services[i].max_workers == -1) {
Services[i].max_workers = INT_MAX;
}
} else {
/* udp */
if (prefork > 1) prefork = 1;
Services[i].desired_workers = prefork;
Services[i].max_workers = 1;
}
free(max);
if (reconfig) {
/* reconfiguring an existing service, update any other instances */
for (j = 0; j < nservices; j++) {
if (Services[j].associate > 0 && Services[j].listen &&
Services[j].name && !strcmp(Services[j].name, name)) {
Services[j].maxforkrate = Services[i].maxforkrate;
Services[j].exec = Services[i].exec;
Services[j].desired_workers = Services[i].desired_workers;
Services[j].babysit = Services[i].babysit;
Services[j].max_workers = Services[i].max_workers;
Services[j].provide_uuid = Services[i].provide_uuid;
}
}
}
if (verbose > 2)
syslog(LOG_DEBUG, "%s: service '%s' (%s, %s:%s, %d, %d, %d)",
reconfig ? "reconfig" : "add",
Services[i].name, cmd,
Services[i].proto, Services[i].listen,
Services[i].desired_workers,
Services[i].max_workers,
(int) Services[i].maxfds);
}
void add_event(const char *name, struct entry *e, void *rock)
{
int ignore_err = (int) rock;
char *cmd = xstrdup(masterconf_getstring(e, "cmd", ""));
int period = 60 * masterconf_getint(e, "period", 0);
int at = masterconf_getint(e, "at", -1), hour, min;
time_t now = time(NULL);
struct event *evt;
if (!strcmp(cmd,"")) {
char buf[256];
snprintf(buf, sizeof(buf),
"unable to find command or port for event '%s'", name);
if (ignore_err) {
syslog(LOG_WARNING, "WARNING: %s -- ignored", buf);
return;
}
fatal(buf, EX_CONFIG);
}
evt = (struct event *) xmalloc(sizeof(struct event));
evt->name = xstrdup(name);
if (at >= 0 && ((hour = at / 100) <= 23) && ((min = at % 100) <= 59)) {
struct tm *tm = localtime(&now);
period = 86400; /* 24 hours */
evt->periodic = 0;
tm->tm_hour = hour;
tm->tm_min = min;
tm->tm_sec = 0;
if ((evt->mark = mktime(tm)) < now) {
/* already missed it, so schedule for next day */
evt->mark += period;
}
}
else {
evt->periodic = 1;
evt->mark = now;
}
evt->period = period;
evt->exec = tokenize(cmd);
if (!evt->exec) fatal("out of memory", EX_UNAVAILABLE);
schedule_event(evt);
}
#ifdef HAVE_SETRLIMIT
#ifdef RLIMIT_NOFILE
# define RLIMIT_NUMFDS RLIMIT_NOFILE
#else
# ifdef RLIMIT_OFILE
# define RLIMIT_NUMFDS RLIMIT_OFILE
# endif
#endif
void limit_fds(rlim_t x)
{
struct rlimit rl;
int r;
rl.rlim_cur = x;
rl.rlim_max = x;
if (setrlimit(RLIMIT_NUMFDS, &rl) < 0) {
syslog(LOG_ERR, "setrlimit: Unable to set file descriptors limit to %ld: %m", x);
#ifdef HAVE_GETRLIMIT
if (!getrlimit(RLIMIT_NUMFDS, &rl)) {
syslog(LOG_ERR, "retrying with %ld (current max)", rl.rlim_max);
rl.rlim_cur = rl.rlim_max;
if (setrlimit(RLIMIT_NUMFDS, &rl) < 0) {
syslog(LOG_ERR, "setrlimit: Unable to set file descriptors limit to %ld: %m", x);
}
}
}
if (verbose > 1) {
r = getrlimit(RLIMIT_NUMFDS, &rl);
syslog(LOG_DEBUG, "set maximum file descriptors to %ld/%ld", rl.rlim_cur,
rl.rlim_max);
}
#else
}
#endif /* HAVE_GETRLIMIT */
}
#else
void limit_fds(rlim_t x)
{
}
#endif /* HAVE_SETRLIMIT */
void reread_conf(void)
{
int i,j;
struct event *ptr;
struct centry *c;
/* disable all services -
they will be re-enabled if they appear in config file */
for (i = 0; i < nservices; i++) Services[i].exec = NULL;
/* read services */
masterconf_getsection("SERVICES", &add_service, (void*) 1);
for (i = 0; i < nservices; i++) {
if (!Services[i].exec && Services[i].socket) {
/* cleanup newly disabled services */
if (verbose > 2)
syslog(LOG_DEBUG, "disable: service %s socket %d pipe %d %d",
Services[i].name, Services[i].socket,
Services[i].stat[0], Services[i].stat[1]);
/* Only free the service info on the primary */
if(Services[i].associate == 0) {
free(Services[i].listen);
free(Services[i].proto);
}
Services[i].listen = NULL;
Services[i].proto = NULL;
Services[i].desired_workers = 0;
/* send SIGHUP to all children */
for (j = 0 ; j < child_table_size ; j++ ) {
c = ctable[j];
while (c != NULL) {
if ((c->si == i) &&
(c->service_state != SERVICE_STATE_DEAD)) {
kill(c->pid, SIGHUP);
}
c = c->next;
}
}
/* close all listeners */
if (Services[i].socket > 0) {
shutdown(Services[i].socket, SHUT_RDWR);
close(Services[i].socket);
}
Services[i].socket = 0;
}
else if (Services[i].exec && !Services[i].socket) {
/* initialize new services */
service_create(&Services[i]);
if (verbose > 2)
syslog(LOG_DEBUG, "init: service %s socket %d pipe %d %d",
Services[i].name, Services[i].socket,
Services[i].stat[0], Services[i].stat[1]);
}
}
/* remove existing events */
while (schedule) {
ptr = schedule;
schedule = schedule->next;
event_free(ptr);
}
schedule = NULL;
/* read events */
masterconf_getsection("EVENTS", &add_event, (void*) 1);
/* reinit child janitor */
init_janitor();
/* send some feedback to admin */
syslog(LOG_NOTICE,
"Services reconfigured. %d out of %d (max %d) services structures are now in use",
nservices, allocservices, SERVICE_MAX);
}
int main(int argc, char **argv)
{
const char *default_pidfile = MASTER_PIDFILE;
const char *lock_suffix = ".lock";
const char *pidfile = default_pidfile;
char *pidfile_lock = NULL;
int startup_pipe[2] = { -1, -1 };
int pidlock_fd = -1;
int i, opt, close_std = 1, daemon_mode = 0;
extern int optind;
extern char *optarg;
char *alt_config = NULL;
int fd;
fd_set rfds;
char *p = NULL;
#ifdef HAVE_NETSNMP
char *agentxsocket = NULL;
int agentxpinginterval = -1;
#endif
time_t now;
p = getenv("CYRUS_VERBOSE");
if (p) verbose = atoi(p) + 1;
#ifdef HAVE_NETSNMP
while ((opt = getopt(argc, argv, "C:M:p:l:Ddj:P:x:")) != EOF) {
#else
while ((opt = getopt(argc, argv, "C:M:p:l:Ddj:")) != EOF) {
#endif
switch (opt) {
case 'C': /* alt imapd.conf file */
alt_config = optarg;
break;
case 'M': /* alt cyrus.conf file */
MASTER_CONFIG_FILENAME = optarg;
break;
case 'l':
/* user defined listen queue backlog */
listen_queue_backlog = atoi(optarg);
break;
case 'p':
/* Set the pidfile name */
pidfile = optarg;
break;
case 'd':
/* Daemon Mode */
if(!close_std)
fatal("Unable to both be debug and daemon mode", EX_CONFIG);
daemon_mode = 1;
break;
case 'D':
/* Debug Mode */
if(daemon_mode)
fatal("Unable to be both debug and daemon mode", EX_CONFIG);
close_std = 0;
break;
case 'j':
/* Janitor frequency */
janitor_frequency = atoi(optarg);
if(janitor_frequency < 1)
fatal("The janitor period must be at least 1 second", EX_CONFIG);
break;
#ifdef HAVE_NETSNMP
case 'P': /* snmp AgentXPingInterval */
agentxpinginterval = atoi(optarg);
break;
case 'x': /* snmp AgentXSocket */
agentxsocket = optarg;
break;
#endif
default:
break;
}
}
masterconf_init("master", alt_config);
/* zero out the children table */
memset(&ctable, 0, sizeof(struct centry *) * child_table_size);
if (close_std) {
/* close stdin/out/err */
for (fd = 0; fd < 3; fd++) {
close(fd);
if (open("/dev/null", O_RDWR, 0) != fd)
fatal("couldn't open /dev/null: %m", 2);
}
}
/* we reserve fds 3 and 4 for children to communicate with us, so they
better be available. */
for (fd = 3; fd < 5; fd++) {
close(fd);
if (dup(0) != fd) fatal("couldn't dup fd 0: %m", 2);
}
/* Pidfile Algorithm in Daemon Mode. This is a little subtle because
* we want to ensure that we can report an error to our parent if the
* child fails to lock the pidfile.
*
* [A] Create/lock pidfile.lock. If locked, exit(failure).
* [A] Create a pipe
* [A] Fork [B]
* [A] Block on reading exit code from pipe
* [B] Create/lock pidfile. If locked, write failure code to pipe and
* exit(failure)
* [B] write pid to pidfile
* [B] write success code to pipe & finish starting up
* [A] unlink pidfile.lock and exit(code read from pipe)
*
*/
if(daemon_mode) {
/* Daemonize */
pid_t pid = -1;
pidfile_lock = xmalloc(strlen(pidfile) + strlen(lock_suffix) + 1);
strcpy(pidfile_lock, pidfile);
strcat(pidfile_lock, lock_suffix);
pidlock_fd = open(pidfile_lock, O_CREAT|O_TRUNC|O_RDWR, 0644);
if(pidlock_fd == -1) {
syslog(LOG_ERR, "can't open pidfile lock: %s (%m)", pidfile_lock);
exit(EX_OSERR);
} else {
if(lock_nonblocking(pidlock_fd)) {
syslog(LOG_ERR, "can't get exclusive lock on %s",
pidfile_lock);
exit(EX_TEMPFAIL);
}
}
if(pipe(startup_pipe) == -1) {
syslog(LOG_ERR, "can't create startup pipe (%m)");
exit(EX_OSERR);
}
do {
pid = fork();
if ((pid == -1) && (errno == EAGAIN)) {
syslog(LOG_WARNING, "master fork failed (sleeping): %m");
sleep(5);
}
} while ((pid == -1) && (errno == EAGAIN));
if (pid == -1) {
fatal("fork error", EX_OSERR);
} else if (pid != 0) {
int exit_code;
/* Parent, wait for child */
if(read(startup_pipe[0], &exit_code, sizeof(exit_code)) == -1) {
syslog(LOG_ERR, "could not read from startup_pipe (%m)");
unlink(pidfile_lock);
exit(EX_OSERR);
} else {
unlink(pidfile_lock);
exit(exit_code);
}
}
/* Child! */
close(startup_pipe[0]);
free(pidfile_lock);
/*
* We're now running in the child. Lose our controlling terminal
* and obtain a new process group.
*/
if (setsid() == -1) {
int exit_result = EX_OSERR;
/* Tell our parent that we failed. */
write(startup_pipe[1], &exit_result, sizeof(exit_result));
fatal("setsid failure", EX_OSERR);
}
}
limit_fds(RLIM_INFINITY);
/* Write out the pidfile */
pidfd = open(pidfile, O_CREAT|O_RDWR, 0644);
if(pidfd == -1) {
int exit_result = EX_OSERR;
/* Tell our parent that we failed. */
write(startup_pipe[1], &exit_result, sizeof(exit_result));
syslog(LOG_ERR, "can't open pidfile: %m");
exit(EX_OSERR);
} else {
char buf[100];
if(lock_nonblocking(pidfd)) {
int exit_result = EX_OSERR;
/* Tell our parent that we failed. */
write(startup_pipe[1], &exit_result, sizeof(exit_result));
fatal("cannot get exclusive lock on pidfile (is another master still running?)", EX_OSERR);
} else {
int pidfd_flags = fcntl(pidfd, F_GETFD, 0);
if (pidfd_flags != -1)
pidfd_flags = fcntl(pidfd, F_SETFD,
pidfd_flags | FD_CLOEXEC);
if (pidfd_flags == -1) {
int exit_result = EX_OSERR;
/* Tell our parent that we failed. */
write(startup_pipe[1], &exit_result, sizeof(exit_result));
fatal("unable to set close-on-exec for pidfile: %m", EX_OSERR);
}
/* Write PID */
snprintf(buf, sizeof(buf), "%lu\n", (unsigned long int)getpid());
if(lseek(pidfd, 0, SEEK_SET) == -1 ||
ftruncate(pidfd, 0) == -1 ||
write(pidfd, buf, strlen(buf)) == -1) {
int exit_result = EX_OSERR;
/* Tell our parent that we failed. */
write(startup_pipe[1], &exit_result, sizeof(exit_result));
fatal("unable to write to pidfile: %m", EX_OSERR);
}
fsync(pidfd);
}
}
if(daemon_mode) {
int exit_result = 0;
/* success! */
if(write(startup_pipe[1], &exit_result, sizeof(exit_result)) == -1) {
syslog(LOG_ERR,
"could not write success result to startup pipe (%m)");
exit(EX_OSERR);
}
close(startup_pipe[1]);
if(pidlock_fd != -1) close(pidlock_fd);
}
syslog(LOG_NOTICE, "process started");
#if defined(HAVE_UCDSNMP) || defined(HAVE_NETSNMP)
/* initialize SNMP agent */
/* make us a agentx client. */
#ifdef HAVE_NETSNMP
netsnmp_enable_subagent();
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_ALARM_DONT_USE_SIG, 1);
if (agentxpinginterval >= 0)
netsnmp_ds_set_int(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_AGENTX_PING_INTERVAL, agentxpinginterval);
if (agentxsocket != NULL)
netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID,
NETSNMP_DS_AGENT_X_SOCKET, agentxsocket);
#else
ds_set_boolean(DS_APPLICATION_ID, DS_AGENT_ROLE, 1);
#endif
/* initialize the agent library */
init_agent("cyrusMaster");
init_cyrusMasterMIB();
init_snmp("cyrusMaster");
#endif
masterconf_getsection("START", &add_start, NULL);
masterconf_getsection("SERVICES", &add_service, NULL);
masterconf_getsection("EVENTS", &add_event, NULL);
/* set signal handlers */
sighandler_setup();
/* initialize services */
for (i = 0; i < nservices; i++) {
service_create(&Services[i]);
if (verbose > 2)
syslog(LOG_DEBUG, "init: service %s socket %d pipe %d %d",
Services[i].name, Services[i].socket,
Services[i].stat[0], Services[i].stat[1]);
}
if (become_cyrus_early) {
if (become_cyrus() != 0) {
syslog(LOG_ERR, "can't change to the cyrus user: %m");
exit(1);
}
}
have_uuid = (config_getint(IMAPOPT_SYNC_MACHINEID) >= 0);
if (have_uuid && !message_uuid_master_init()) {
syslog(LOG_ERR, "Couldn't initialise UUID subsystem");
exit(EX_OSERR);
}
/* init ctable janitor */
init_janitor();
/* ok, we're going to start spawning like mad now */
syslog(LOG_NOTICE, "ready for work");
now = time(NULL);
for (;;) {
int r, i, maxfd;
struct timeval tv, *tvptr;
struct notify_message msg;
#if defined(HAVE_UCDSNMP) || defined(HAVE_NETSNMP)
int blockp = 0;
#endif
/* run any scheduled processes */
spawn_schedule(now);
/* reap first, that way if we need to babysit we will */
if (gotsigchld) {
/* order matters here */
gotsigchld = 0;
reap_child();
}
/* do we have any services undermanned? */
for (i = 0; i < nservices; i++) {
if (Services[i].exec /* enabled */ &&
(Services[i].nactive < Services[i].max_workers) &&
(Services[i].ready_workers < Services[i].desired_workers)) {
spawn_service(i);
} else if (Services[i].exec
&& Services[i].babysit
&& Services[i].nactive == 0) {
syslog(LOG_ERR,
"lost all children for service: %s. " \
"Applying babysitter.",
Services[i].name);
spawn_service(i);
} else if (!Services[i].exec /* disabled */ &&
Services[i].name /* not yet removed */ &&
Services[i].nactive == 0) {
if (verbose > 2)
syslog(LOG_DEBUG, "remove: service %s pipe %d %d",
Services[i].name,
Services[i].stat[0], Services[i].stat[1]);
/* Only free the service info on the primary */
if (Services[i].associate == 0) {
free(Services[i].name);
}
Services[i].name = NULL;
Services[i].nforks = 0;
Services[i].nactive = 0;
Services[i].nconnections = 0;
Services[i].associate = 0;
Services[i].provide_uuid = 0;
if (Services[i].stat[0] > 0) close(Services[i].stat[0]);
if (Services[i].stat[1] > 0) close(Services[i].stat[1]);
memset(Services[i].stat, 0, sizeof(Services[i].stat));
}
}
if (gotsighup) {
syslog(LOG_NOTICE, "got SIGHUP");
gotsighup = 0;
reread_conf();
}
FD_ZERO(&rfds);
maxfd = 0;
for (i = 0; i < nservices; i++) {
int x = Services[i].stat[0];
int y = Services[i].socket;
/* messages */
if (x > 0) {
if (verbose > 2)
syslog(LOG_DEBUG, "listening for messages from %s",
Services[i].name);
FD_SET(x, &rfds);
}
if (x > maxfd) maxfd = x;
/* connections */
if (y > 0 && Services[i].ready_workers == 0 &&
Services[i].nactive < Services[i].max_workers) {
if (verbose > 2)
syslog(LOG_DEBUG, "listening for connections for %s",
Services[i].name);
FD_SET(y, &rfds);
if (y > maxfd) maxfd = y;
}
/* paranoia */
if (Services[i].ready_workers < 0) {
syslog(LOG_ERR, "%s has %d workers?!?", Services[i].name,
Services[i].ready_workers);
}
}
maxfd++; /* need 1 greater than maxfd */
/* how long to wait? - do now so that any scheduled wakeup
* calls get accounted for*/
tvptr = NULL;
if (schedule) {
if (now < schedule->mark) tv.tv_sec = schedule->mark - now;
else tv.tv_sec = 0;
tv.tv_usec = 0;
tvptr = &tv;
}
#if defined(HAVE_UCDSNMP) || defined(HAVE_NETSNMP)
if (tvptr == NULL) blockp = 1;
snmp_select_info(&maxfd, &rfds, tvptr, &blockp);
#endif
errno = 0;
r = select(maxfd, &rfds, NULL, NULL, tvptr);
if (r == -1 && errno == EAGAIN) continue;
if (r == -1 && errno == EINTR) continue;
if (r == -1) {
/* uh oh */
fatal("select failed: %m", 1);
}
#if defined(HAVE_UCDSNMP) || defined(HAVE_NETSNMP)
/* check for SNMP queries */
snmp_read(&rfds);
snmp_timeout();
#endif
for (i = 0; i < nservices; i++) {
int x = Services[i].stat[0];
int y = Services[i].socket;
int j;
if (FD_ISSET(x, &rfds)) {
r = read(x, &msg, sizeof(msg));
if (r != sizeof(msg)) {
syslog(LOG_ERR, "got incorrectly sized response from child: %x", i);
continue;
}
process_msg(i, &msg);
}
if (Services[i].exec &&
Services[i].nactive < Services[i].max_workers) {
/* bring us up to desired_workers */
for (j = Services[i].ready_workers;
j < Services[i].desired_workers;
j++)
{
spawn_service(i);
}
if (Services[i].ready_workers == 0 &&
FD_ISSET(y, &rfds)) {
/* huh, someone wants to talk to us */
spawn_service(i);
}
}
}
now = time(NULL);
child_janitor(now);
#ifdef HAVE_NETSNMP
run_alarms();
#endif
}
}
diff --git a/ptclient/ptexpire.c b/ptclient/ptexpire.c
index 438db7c22..7680c130d 100644
--- a/ptclient/ptexpire.c
+++ b/ptclient/ptexpire.c
@@ -1,165 +1,167 @@
/*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* 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.
*
*/
/* This program purges old entries from the database. It holds an exclusive
* lock throughout the process.
*
* NOTE: by adding the alt_file flag, we let exit() handle the cleanup of
* the lock file's fd. That's bad in principal but not in practice. We do
* to make the code easier to read.
*/
#include <config.h>
#include <sys/param.h>
#ifndef MAXPATHLEN
#define MAXPATHLEN MAXPATHNAMELEN
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "auth_pts.h"
#include "cyrusdb.h"
#include "exitcodes.h"
#include "global.h"
#include "libconfig.h"
#include "lock.h"
#include "xmalloc.h"
-static char rcsid[] = "$Id: ptexpire.c,v 1.19 2006/11/30 17:11:24 murch Exp $";
+static char rcsid[] = "$Id: ptexpire.c,v 1.20 2007/03/30 18:40:21 murch Exp $";
/* global */
time_t timenow;
time_t expire_time = (3*60*60); /* 3 Hours */
int config_need_data = 0;
static int expire_p(void *rockp __attribute__((unused)),
const char *key __attribute__((unused)),
int keylen __attribute__((unused)),
const char *data,
int datalen __attribute__((unused)))
{
struct auth_state *authstate = (struct auth_state *)data;
if (authstate->mark + expire_time < timenow) {
return 1;
}
return 0; /* skip this one */
}
static int expire_cb(void *rockp,
const char *key, int keylen,
const char *data __attribute__((unused)),
int datalen __attribute__((unused)))
{
/* We only get called when we want to delete it */
syslog(LOG_DEBUG, "deleteing entry for %s", key);
/* xxx maybe we should use transactions for this */
config_ptscache_db->delete((struct db *)rockp, key, keylen, NULL, 0);
return 0;
}
int main(int argc, char *argv[])
{
struct db *ptdb;
char fnamebuf[1024];
extern char *optarg;
int opt;
int r;
char *alt_config = NULL;
- if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
+ if ((geteuid()) == 0 && (become_cyrus() != 0)) {
+ fatal("must run as the Cyrus user", EC_USAGE);
+ }
openlog("ptexpire", LOG_PID, SYSLOG_FACILITY);
while ((opt = getopt(argc, argv, "C:E:")) != EOF) {
switch (opt) {
case 'C': /* alt config file */
alt_config = optarg;
break;
case 'E':
expire_time = atoi(optarg);
break;
default:
fprintf(stderr,"usage: [-C filename] [-E time]"
"\n\t-C <filename>\tAlternate Config File"
"\n\t-E <seconds>\tExpiration time"
"\n");
syslog(LOG_ERR, "Invalid command line option");
exit(-1);
break;
/* just pass through */
}
}
cyrus_init(alt_config, "ptexpire", 0);
timenow = time(0);
syslog(LOG_INFO, "Expiring entries older than %d seconds (currently %d)",
expire_time, timenow);
syslog(LOG_DEBUG, "%s", rcsid);
/* open database */
strcpy(fnamebuf, config_dir);
strcat(fnamebuf, PTS_DBFIL);
r = config_ptscache_db->open(fnamebuf, CYRUSDB_CREATE, &ptdb);
if(r != CYRUSDB_OK) {
syslog(LOG_ERR, "error opening %s (%s)", fnamebuf,
cyrusdb_strerror(r));
exit(1);
}
/* iterate through db, wiping expired entries */
config_ptscache_db->foreach(ptdb, "", 0, expire_p, expire_cb, ptdb, NULL);
config_ptscache_db->close(ptdb);
cyrus_done();
syslog(LOG_INFO, "finished");
return 0;
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 4, 2:52 AM (3 d, 18 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822303
Default Alt Text
(443 KB)

Event Timeline