#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <sys/types.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
#include <assert.h>
#include "annotate.h"
#include "exitcodes.h"
#include "global.h"
#include "imap_err.h"
#include "imparse.h"
#include "mailbox.h"
#include "map.h"
#include "mbdump.h"
#include "mboxkey.h"
#include "mboxlist.h"
#include "prot.h"
#include "quota.h"
#include "seen.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "util.h"
static int sieve_isactive(char *sievepath, char *name)
{
char filename[1024];
char linkname[1024];
char activelink[1024];
char *file, *link;
int len;
snprintf(filename, sizeof(filename), "%s/%s", sievepath, name);
snprintf(linkname, sizeof(linkname), "%s/defaultbc", sievepath);
len = readlink(linkname, activelink, sizeof(activelink)-1);
if(len < 0) {
if(errno != ENOENT) syslog(LOG_ERR, "readlink(defaultbc): %m");
return 0;
}
activelink[len] = '\0';
file = strrchr(filename, '/');
link = strrchr(activelink, '/');
if(!file) file = filename;
else file++;
if(!link) link = activelink;
else link++;
if (!strcmp(file, link)) {
return 1;
} else {
return 0;
}
}
struct dump_annotation_rock
{
struct protstream *pout;
const char *tag;
};
static int dump_annotations(const char *mailbox __attribute__((unused)),
const char *entry,
const char *userid,
struct annotation_data *attrib, void *rock)
{
struct dump_annotation_rock *ctx = (struct dump_annotation_rock *)rock;
unsigned long ename_size = 2 + strlen(userid) + strlen(entry);
prot_printf(ctx->pout,
" {%ld%s}\r\nA-%s%s (%ld {%d%s}\r\n%s {%d%s}\r\n%s)",
ename_size, (!ctx->tag ? "+" : ""),
userid, entry,
attrib->modifiedsince,
attrib->size, (!ctx->tag ? "+" : ""),
attrib->value,
strlen(attrib->contenttype), (!ctx->tag ? "+" : ""),
attrib->contenttype);
return 0;
}
static int dump_file(int first, int sync,
struct protstream *pin, struct protstream *pout,
const char *filename, const char *ftag)
{
int filefd;
const char *base;
unsigned long len;
struct stat sbuf;
char c;
syslog(LOG_DEBUG, "wanting to dump %s", filename);
filefd = open(filename, O_RDONLY, 0666);
if (filefd == -1) {
syslog(LOG_ERR, "IOERROR: open on %s: %m", filename);
return IMAP_SYS_ERROR;
}
if (fstat(filefd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m", filename);
fatal("can't fstat message file", EC_OSFILE);
}
base = NULL;
len = 0;
map_refresh(filefd, 1, &base, &len, sbuf.st_size, filename, NULL);
close(filefd);
if (first) {
prot_printf(pout, " {%d}\r\n", strlen(ftag));
if (sync) {
c = prot_getc(pin);
eatline(pin, c);
if (c != '+') {
syslog(LOG_ERR, "Sync Error: expected '+' got '%c'",c);
return IMAP_SERVER_UNAVAILABLE;
}
}
prot_printf(pout, "%s {%lu%s}\r\n",
ftag, len, (sync ? "+" : ""));
} else {
prot_printf(pout, " {%d%s}\r\n%s {%lu%s}\r\n",
strlen(ftag), (sync ? "+" : ""),
ftag, len, (sync ? "+" : ""));
}
prot_write(pout, base, len);
map_free(&base, &len);
return 0;
}
struct data_file {
const char *fname;
int optional;
unsigned long flag;
};
static const char fname_header[] = FNAME_HEADER;
static const char fname_index[] = FNAME_INDEX;
static const char fname_cache[] = FNAME_CACHE;
static const char fname_expunge[] = FNAME_EXPUNGE_INDEX;
static struct data_file data_files[] = {
{ fname_header+1, 0, IMAP_ENUM_METAPARTITION_FILES_HEADER },
{ fname_index+1, 0, IMAP_ENUM_METAPARTITION_FILES_INDEX },
{ fname_cache+1, 0, IMAP_ENUM_METAPARTITION_FILES_CACHE },
{ fname_expunge+1, 1, IMAP_ENUM_METAPARTITION_FILES_EXPUNGE },
{ NULL, 0, 0 }
};
enum { SEEN_DB = 0, SUBS_DB = 1, MBOXKEY_DB = 2 };
static int NUM_USER_DATA_FILES = 3;
int dump_mailbox(const char *tag, const char *mbname, const char *mbpath,
const char *metapath, const char *mbacl, int uid_start,
struct protstream *pin, struct protstream *pout,
struct auth_state *auth_state)
{
DIR *mbdir = NULL;
int r = 0;
struct dirent *next = NULL;
char filename[MAX_MAILBOX_PATH + 1024];
int first = 1;
struct mailbox mb;
int i;
struct data_file *df;
const char *userid = NULL;
int domainlen = 0;
char *p = NULL, userbuf[81];
assert(mbpath);
if (config_virtdomains && (p = strchr(mbname, '!')))
domainlen = p - mbname + 1;
if (!strncmp(mbname+domainlen, "user.", 5) &&
!strchr(mbname+domainlen+5, '.')) {
strcpy(userbuf, mbname+domainlen+5);
if (domainlen)
sprintf(userbuf+strlen(userbuf), "@%.*s", domainlen-1, mbname);
userid = userbuf;
}
mbdir = opendir(mbpath);
if (!mbdir && errno == EACCES) {
syslog(LOG_ERR,
"could not dump mailbox in %s (permission denied)", mbpath);
return IMAP_PERMISSION_DENIED;
} else if (!mbdir) {
syslog(LOG_ERR,
"could not dump mailbox in %s (unknown error)", mbpath);
return IMAP_SYS_ERROR;
}
r = mailbox_open_locked(mbname, mbpath, metapath, mbacl, auth_state, &mb, 0);
if (r) {
closedir(mbdir);
return r;
}
if (tag) prot_printf(pout, "%s DUMP ", tag);
prot_putc('(',pout);
{
struct quota quota;
quota.root = (char *) mbname;
r = quota_read("a, NULL, 0);
if (!r) {
prot_printf(pout, "%d", quota.limit);
} else {
prot_printf(pout, "NIL");
if (r == IMAP_QUOTAROOT_NONEXISTENT) r = 0;
}
}
for (df = data_files; df->fname; df++) {
const char *path;
path = (metapath && (config_metapartition_files & df->flag)) ?
metapath : mbpath;
strlcpy(filename, path, sizeof(filename));
strlcat(filename, "/", sizeof(filename));
strlcat(filename, df->fname, sizeof(filename));
r = dump_file(first, !tag, pin, pout, filename, df->fname);
if (r) {
if (df->optional && (r == IMAP_SYS_ERROR) && (errno == ENOENT)) {
r = 0;
continue;
}
goto done;
}
first = 0;
}
while ((next = readdir(mbdir)) != NULL) {
char *name = next->d_name;
char *p = name;
if (name[0] == '.') continue;
while (*p && isdigit((int)(*p))) p++;
if (p[0] != '.' || p[1] != '\0') continue;
if (atoi(name) < uid_start) continue;
snprintf(filename, sizeof(filename), "%s/%s", mbpath, name);
r = dump_file(0, !tag, pin, pout, filename, name);
if (r) goto done;
}
closedir(mbdir);
mbdir = NULL;
{
struct dump_annotation_rock actx;
actx.tag = tag;
actx.pout = pout;
annotatemore_findall(mbname, "*", dump_annotations,
(void *) &actx, NULL);
}
if (userid) {
char sieve_path[MAX_MAILBOX_PATH];
int sieve_usehomedir = config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR);
char *fname, *ftag;
for (i = 0; i< NUM_USER_DATA_FILES; i++) {
switch (i) {
case SEEN_DB:
fname = seen_getpath(userid);
ftag = "SEEN";
break;
case SUBS_DB:
fname = mboxlist_hash_usersubs(userid);
ftag = "SUBS";
break;
case MBOXKEY_DB:
fname = mboxkey_getpath(userid);
ftag = "MBOXKEY";
break;
default:
fatal("unknown user data file", EC_OSFILE);
}
r = dump_file(0, !tag, pin, pout, fname, ftag);
free(fname);
if (r) {
if (errno == ENOENT) {
r = 0;
continue;
}
goto done;
}
}
if (!sieve_usehomedir) {
if (domainlen) {
*p = '\0';
snprintf(sieve_path, sizeof(sieve_path), "%s%s%c/%s/%c/%s",
config_getstring(IMAPOPT_SIEVEDIR),
FNAME_DOMAINDIR, (char) dir_hash_c(mbname), mbname,
(char) dir_hash_c(p+6), p+6);
*p = '!';
}
else {
snprintf(sieve_path, sizeof(sieve_path), "%s/%c/%s",
config_getstring(IMAPOPT_SIEVEDIR),
(char) dir_hash_c(userid), userid);
}
mbdir = opendir(sieve_path);
if (!mbdir) {
syslog(LOG_ERR,
"could not dump sieve scripts in %s: %m)", mbpath);
} else {
char tag_fname[2048];
while((next = readdir(mbdir)) != NULL) {
int length=strlen(next->d_name);
if ((length >= 7 &&
!strcmp(next->d_name + (length - 7), ".script")) ||
(length >= 3 &&
!strcmp(next->d_name + (length - 3), ".bc")))
{
if(sieve_isactive(sieve_path, next->d_name)) {
snprintf(tag_fname, sizeof(tag_fname),
"SIEVED-%s", next->d_name);
} else {
snprintf(tag_fname, sizeof(tag_fname),
"SIEVE-%s", next->d_name);
}
r = dump_file(0, !tag, pin, pout, filename, tag_fname);
if (r) goto done;
}
}
closedir(mbdir);
mbdir = NULL;
}
}
}
prot_printf(pout,")\r\n");
done:
prot_flush(pout);
mailbox_close(&mb);
if (mbdir) closedir(mbdir);
return r;
}
int undump_mailbox(const char *mbname, const char *mbpath,
const char *metapath, const char *mbacl,
struct protstream *pin, struct protstream *pout,
struct auth_state *auth_state)
{
struct buf file, data;
char c;
uquota_t quotaused = 0;
int r = 0;
int curfile = -1;
const char *userid = NULL;
struct mailbox mb;
char sieve_path[2048];
int sieve_usehomedir = config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR);
int domainlen = 0;
char *p = NULL, userbuf[81];
memset(&file, 0, sizeof(file));
memset(&data, 0, sizeof(data));
c = getword(pin, &data);
if (config_virtdomains && (p = strchr(mbname, '!')))
domainlen = p - mbname + 1;
if(!strncmp(mbname+domainlen, "user.", 5) &&
!strchr(mbname+domainlen+5, '.')) {
strcpy(userbuf, mbname+domainlen+5);
if (domainlen)
sprintf(userbuf+strlen(userbuf), "@%.*s", domainlen-1, mbname);
userid = userbuf;
if(!sieve_usehomedir) {
if (domainlen) {
*p = '\0';
snprintf(sieve_path, sizeof(sieve_path), "%s%s%c/%s/%c/%s",
config_getstring(IMAPOPT_SIEVEDIR),
FNAME_DOMAINDIR, (char) dir_hash_c(mbname), mbname,
(char) dir_hash_c(p+6), p+6);
*p = '!';
}
else {
snprintf(sieve_path, sizeof(sieve_path), "%s/%c/%s",
config_getstring(IMAPOPT_SIEVEDIR),
(char) dir_hash_c(userid), userid);
}
}
}
if(c != '(' || data.s[0]) {
freebuf(&data);
eatline(pin, c);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
c = getword(pin, &data);
if(!strcmp(data.s, "NIL")) {
mboxlist_unsetquota(mbname);
} else if(imparse_isnumber(data.s)) {
mboxlist_setquota(mbname, atoi(data.s), 0);
} else {
freebuf(&data);
eatline(pin, c);
return IMAP_PROTOCOL_BAD_PARAMETERS;
}
if(c != ' ' && c != ')') {
freebuf(&data);
eatline(pin, c);
return IMAP_PROTOCOL_BAD_PARAMETERS;
} else if(c == ')') {
goto done;
}
r = mailbox_open_locked(mbname, mbpath, metapath, mbacl, auth_state, &mb, 0);
if(r) goto done;
while(1) {
char fnamebuf[MAX_MAILBOX_PATH + 1024];
char *seen_file = NULL, *mboxkey_file = NULL;
int isnowait, sawdigit;
unsigned long size;
unsigned long cutoff = ULONG_MAX / 10;
unsigned digit, cutlim = ULONG_MAX % 10;
c = getastring(pin, pout, &file);
if(c != ' ') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
if(!strncmp(file.s, "A-", 2)) {
char *userid;
char *annotation;
char *contenttype;
char *content;
size_t contentsize;
time_t modtime = 0;
int i;
for(i=2; file.s[i]; i++) {
if(file.s[i] == '/') break;
}
if(!file.s[i]) {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
userid = xmalloc(i-2+1);
memcpy(userid, &(file.s[2]), i-2);
userid[i-2] = '\0';
annotation = xstrdup(&(file.s[i]));
if(prot_getc(pin) != '(') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
c = getword(pin, &data);
if (c != ' ') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
if (data.s[0] == '\0') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
for (p = data.s; *p; p++) {
if (!isdigit((int) *p)) {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
modtime = modtime * 10 + *p - '0';
}
c = getbastring(pin, pout, &data);
content = xstrdup(data.s);
contentsize = data.len;
if(c != ' ') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
c = getastring(pin, pout, &data);
contenttype = xstrdup(data.s);
if(c != ')') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
annotatemore_write_entry(mbname, annotation, userid, content,
contenttype, contentsize, modtime, NULL);
free(userid);
free(annotation);
free(content);
free(contenttype);
c = prot_getc(pin);
if(c == ')') break;
else if(c != ' ') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
continue;
}
c = prot_getc(pin);
if (c != '{') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
size = isnowait = sawdigit = 0;
while ((c = prot_getc(pin)) != EOF && isdigit(c)) {
sawdigit = 1;
digit = c - '0';
if (size > cutoff || (size == cutoff && digit > cutlim)) {
fatal("literal too big", EC_IOERR);
}
size = size*10 + digit;
}
if (c == '+') {
isnowait++;
c = prot_getc(pin);
}
if (c == '}') {
c = prot_getc(pin);
if (c == '\r') c = prot_getc(pin);
}
if (!sawdigit || c != '\n') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
if (!isnowait) {
prot_printf(pout, "+ go ahead\r\n");
prot_flush(pout);
}
if(userid && !strcmp(file.s, "SUBS")) {
char *s = mboxlist_hash_usersubs(userid);
strlcpy(fnamebuf, s, sizeof(fnamebuf));
free(s);
} else if (userid && !strcmp(file.s, "SEEN")) {
seen_file = seen_getpath(userid);
snprintf(fnamebuf,sizeof(fnamebuf),"%s.%d",seen_file,getpid());
} else if (userid && !strcmp(file.s, "MBOXKEY")) {
mboxkey_file = mboxkey_getpath(userid);
snprintf(fnamebuf,sizeof(fnamebuf),"%s.%d",mboxkey_file,getpid());
} else if (userid && !strncmp(file.s, "SIEVE", 5)) {
int isdefault = !strncmp(file.s, "SIEVED", 6);
char *realname;
int ret;
if(isdefault) realname = file.s + 7;
else realname = file.s + 6;
if(sieve_usehomedir) {
syslog(LOG_ERR,
"dropping sieve file %s since this host is " \
"configured for sieve_usehomedir",
realname);
continue;
} else {
if(snprintf(fnamebuf, sizeof(fnamebuf),
"%s/%s", sieve_path, realname) == -1) {
r = IMAP_PROTOCOL_ERROR;
goto done;
} else if(isdefault) {
char linkbuf[2048];
snprintf(linkbuf, sizeof(linkbuf), "%s/defaultbc",
sieve_path);
ret = symlink(realname, linkbuf);
if(ret && errno == ENOENT) {
if(cyrus_mkdir(linkbuf, 0750) == 0) {
ret = symlink(realname, linkbuf);
}
}
if(ret) {
syslog(LOG_ERR, "symlink(%s, %s): %m", realname,
linkbuf);
}
}
}
} else {
struct data_file *df;
const char *path;
for (df = data_files; df->fname && strcmp(df->fname, file.s); df++);
path = df->fname && metapath &&
(config_metapartition_files & df->flag) ?
metapath : mbpath;
if(snprintf(fnamebuf, sizeof(fnamebuf),
"%s/%s", path, file.s) == -1) {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
if(strncmp(file.s, "cyrus.", 6)) {
quotaused += size;
}
}
curfile = open(fnamebuf, O_WRONLY|O_TRUNC|O_CREAT, 0640);
if(curfile == -1 && errno == ENOENT) {
if(cyrus_mkdir(fnamebuf, 0750) == 0) {
curfile = open(fnamebuf, O_WRONLY|O_TRUNC|O_CREAT, 0640);
}
}
if(curfile == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fnamebuf);
r = IMAP_IOERROR;
goto done;
}
while (size) {
char buf[4096+1];
int n = prot_read(pin, buf, size > 4096 ? 4096 : size);
if (!n) {
syslog(LOG_ERR,
"IOERROR: reading message: unexpected end of file");
r = IMAP_IOERROR;
goto done;
}
size -= n;
if (write(curfile, buf, n) != n) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", fnamebuf);
r = IMAP_IOERROR;
goto done;
}
}
close(curfile);
if(seen_file) {
seen_merge(fnamebuf, seen_file);
free(seen_file);
seen_file = NULL;
unlink(fnamebuf);
}
else if(mboxkey_file) {
mboxkey_merge(fnamebuf, mboxkey_file);
free(mboxkey_file);
mboxkey_file = NULL;
unlink(fnamebuf);
}
c = prot_getc(pin);
if (c == ')') break;
if (c != ' ') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
}
if(!r && quotaused) {
struct quota quota;
char quota_root[MAX_MAILBOX_PATH+1];
struct txn *tid = NULL;
if (quota_findroot(quota_root, sizeof(quota_root), mbname)) {
memset("a, 0, sizeof(quota));
quota.root = quota_root;
r = quota_read("a, &tid, 1);
if(!r) {
quota.used += quotaused;
r = quota_write("a, &tid);
if (!r) quota_commit(&tid);
} else {
syslog(LOG_ERR, "could not lock quota file for %s (%s)",
quota_root, error_message(r));
}
if(r) {
syslog(LOG_ERR, "failed writing quota file for %s (%s)",
quota_root, error_message(r));
}
}
}
done:
eatline(pin, c);
freebuf(&file);
freebuf(&data);
if(curfile >= 0) close(curfile);
mailbox_close(&mb);
return r;
}