#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 "mboxlist.h"
#include "prot.h"
#include "quota.h"
#include "seen.h"
#include "xmalloc.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;
}
int dump_mailbox(const char *tag, const char *mbname, const char *mbpath,
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 filefd;
const char *base;
unsigned long len;
int first = 1;
struct mailbox mb;
struct stat sbuf;
char c;
int i;
const char *data_files[] = { "cyrus.header",
"cyrus.cache",
"cyrus.index",
NULL
};
const char *userid = NULL;
enum { SEEN_DB = 0, SUBS_DB = 1 };
char *user_data_files[3];
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;
memset(user_data_files, 0, sizeof(user_data_files));
user_data_files[SEEN_DB] = seen_getpath(userid);
user_data_files[SUBS_DB] = mboxlist_hash_usersubs(userid);
}
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, 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, "NIL ");
if(r == IMAP_QUOTAROOT_NONEXISTENT) r = 0;
goto dump_files;
}
prot_printf(pout, "%d ", quota.limit);
}
dump_files:
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);
filefd = open(filename, O_RDONLY, 0666);
if (filefd == -1) {
syslog(LOG_ERR, "IOERROR: open on %s: %m", filename);
r = IMAP_SYS_ERROR;
goto done;
}
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(name));
if(!tag) {
c = prot_getc(pin);
eatline(pin, c);
if(c != '+') {
syslog(LOG_ERR, "Sync Error: expected '+' got '%c'",c);
r = IMAP_SERVER_UNAVAILABLE;
goto done;
}
}
prot_printf(pout, "%s {%lu%s}\r\n",
name, len,
(!tag ? "+" : ""));
first = 0;
} else {
prot_printf(pout, " {%d%s}\r\n%s {%lu%s}\r\n",
strlen(name),
(!tag ? "+" : ""),
name, len,
(!tag ? "+" : ""));
}
prot_write(pout, base, len);
map_free(&base, &len);
}
for(i=0;data_files[i];i++) {
snprintf(filename,sizeof(filename),"%s/%s",mbpath,data_files[i]);
filefd = open(filename, O_RDONLY, 0666);
if (filefd == -1) {
syslog(LOG_ERR, "IOERROR: open on %s: %m", filename);
r = IMAP_SYS_ERROR;
goto done;
}
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(data_files[i]));
if(!tag) {
c = prot_getc(pin);
if(c != '+') {
r = IMAP_SERVER_UNAVAILABLE;
goto done;
} else {
eatline(pin, c);
}
}
prot_printf(pout, "%s {%lu%s}\r\n",
data_files[i], len,
(!tag ? "+" : ""));
first = 0;
} else {
prot_printf(pout, " {%d%s}\r\n%s {%lu%s}\r\n",
strlen(data_files[i]),
(!tag ? "+" : ""),
data_files[i], len,
(!tag ? "+" : ""));
}
prot_write(pout, base, len);
map_free(&base, &len);
}
{
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);
for(i=0;i<3;i++) {
if(!user_data_files[i]) continue;
filefd = open(user_data_files[i], O_RDONLY, 0666);
if (filefd == -1) {
syslog(LOG_ERR, "IOERROR: open on %s: %m (continuing)",
user_data_files[i]);
continue;
}
if (fstat(filefd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m",
user_data_files[i]);
fatal("can't fstat message file", EC_OSFILE);
}
base = NULL;
len = 0;
map_refresh(filefd, 1, &base, &len, sbuf.st_size,
user_data_files[i], NULL);
close(filefd);
if(i == SEEN_DB) prot_printf(pout, " {4%s}\r\nSEEN",
(!tag ? "+" : ""));
else if(i == SUBS_DB) prot_printf(pout, " {4%s}\r\nSUBS",
(!tag ? "+" : ""));
else fatal("unknown user_data_file", EC_OSFILE);
prot_printf(pout, " {%lu%s}\r\n",
len, (!tag ? "+" : ""));
prot_write(pout, base, len);
map_free(&base, &len);
}
if(!sieve_usehomedir) {
char ext_fname[2048];
if(mbdir) closedir(mbdir);
mbdir = NULL;
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) {
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")))
{
snprintf(filename, sizeof(filename), "%s/%s",
sieve_path, next->d_name);
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);
continue;
}
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(sieve_isactive(sieve_path, next->d_name)) {
snprintf(ext_fname, sizeof(ext_fname),
"SIEVED-%s", next->d_name);
} else {
snprintf(ext_fname, sizeof(ext_fname),
"SIEVE-%s", next->d_name);
}
prot_printf(pout, " {%d%s}\r\n%s {%lu%s}\r\n",
strlen(ext_fname),
(!tag ? "+" : ""),
ext_fname,
len,
(!tag ? "+" : ""));
prot_write(pout, base, len);
map_free(&base, &len);
}
}
}
}
}
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 *mbacl,
struct protstream *pin, struct protstream *pout,
struct auth_state *auth_state)
{
struct buf file, data;
char c;
int 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, mbacl, auth_state, &mb, 0);
if(r) goto done;
while(1) {
char fnamebuf[MAX_MAILBOX_PATH + 1024];
char *seen_file = NULL;
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 = getbastring(pin, pout, &data);
if(c != ' ' && c != ')') {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
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 && !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 {
if(snprintf(fnamebuf, sizeof(fnamebuf),
"%s/%s", mbpath, file.s) == -1) {
r = IMAP_PROTOCOL_ERROR;
goto done;
}
if(strncmp(file.s, "cyrus.", 6)) {
quotaused += data.len;
}
}
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;
}
if(write(curfile,data.s,data.len) == -1) {
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);
}
if(c == ')') break;
}
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;
}