/* mbdump.c -- Mailbox dump routines * $Id: mbdump.c,v 1.5 2005/03/05 00:36:58 dasenbro 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 #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" /* is this the active script? */ 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'; /* Only compare the part of the file after the last /, * since that is what timsieved does */ 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; /* "A-" userid entry */ /* entry is delimited by its leading / */ unsigned long ename_size = 2 + strlen(userid) + strlen(entry); /* Transfer all attributes for this annotation, don't transfer size * separately since that can be implicitly determined */ 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 }; /* non-null userid means we are moving the user */ 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; /* include separator */ 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; } /* after this point we have to both close the directory and unlock * the mailbox */ /* xxx check to ensure we have the cyrus.* files, but we send those last */ if(tag) prot_printf(pout, "%s DUMP ", tag); prot_putc('(',pout); /* The first member is either a number (if it is a quota root), or NIL * (if it isn't) */ { struct quota quota; quota.root = (char *)mbname; /* xxx */ 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; /* Alias */ char *p = name; /* special case for '.' (well, it gets '..' too) */ if(name[0] == '.') continue; /* skip non-message files */ while(*p && isdigit((int)(*p))) p++; if(p[0] != '.' || p[1] != '\0') continue; /* ensure (number) is >= our target uid */ if(atoi(name) < uid_start) continue; /* map file */ 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); /* send filename, size, and contents */ if(first) { prot_printf(pout, "{%d}\r\n", strlen(name)); if(!tag) { /* synchronize */ c = prot_getc(pin); eatline(pin, c); /* We eat it no matter what */ if(c != '+') { /* Synchronization Failure, Abort! */ 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++) { /* map file */ 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); /* send filename, size, and contents */ if(first) { prot_printf(pout, "{%d}\r\n", strlen(data_files[i])); if(!tag) { /* synchronize */ c = prot_getc(pin); if(c != '+') { /* Synchronization Failure, Abort! */ 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); } /* now dump annotations */ { 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); /* need to transfer seen, subs, and sieve files */ for(i=0;i<3;i++) { if(!user_data_files[i]) continue; /* map file */ 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]); /* but it is allowed to not exist, so... */ 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); /* send user data file type, size, and contents */ /* No need to test synchronization, all mailboxes should have * sent a file by this point! */ 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); } /* xxx can't use home directories currently * (it makes almost no sense in the conext of a murder) */ if(!sieve_usehomedir) { char ext_fname[2048]; if(mbdir) closedir(mbdir); mbdir = NULL; if (domainlen) { *p = '\0'; /* separate domain!mboxname */ 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); /* unqualified userid */ *p = '!'; /* reassemble domain!mboxname */ } 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); /* 7 == strlen(".script"); 3 == strlen(".bc") */ if ((length >= 7 && !strcmp(next->d_name + (length - 7), ".script")) || (length >= 3 && !strcmp(next->d_name + (length - 3), ".bc"))) { /* map file */ 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) { /* non-fatal */ 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); /* send filename w/tag + contents */ 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); } } } } /* transmit sieve script(s) */ /* free strings for user_data_files */ } /* end if user */ 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; /* include separator */ 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'; /* separate domain!mboxname */ 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); /* unqualified userid */ *p = '!'; /* reassemble domain!mboxname */ } else { snprintf(sieve_path, sizeof(sieve_path), "%s/%c/%s", config_getstring(IMAPOPT_SIEVEDIR), (char) dir_hash_c(userid), userid); } } } /* we better be in a list now */ if(c != '(' || data.s[0]) { freebuf(&data); eatline(pin, c); return IMAP_PROTOCOL_BAD_PARAMETERS; } /* We should now have a number or a NIL */ c = getword(pin, &data); if(!strcmp(data.s, "NIL")) { /* Remove any existing quotaroot */ mboxlist_unsetquota(mbname); } else if(imparse_isnumber(data.s)) { /* Set a Quota */ mboxlist_setquota(mbname, atoi(data.s), 0); } else { /* Huh? */ 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)) { /* Annotation */ 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; } /* Parse the modtime */ 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'; /* xxx - we won't catch overflow here, but we really * don't care *THAT* much, do we? */ } c = getbastring(pin, pout, &data); /* xxx binary */ 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; /* that was the last item */ 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")) { /* overwriting this outright is absolutely what we want to do */ 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; /* skip prefixes */ if(isdefault) realname = file.s + 7; else realname = file.s + 6; if(sieve_usehomedir) { /* xxx! */ 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); /* Non-fatal, let's get the file transferred if we can */ } } } } else { if(snprintf(fnamebuf, sizeof(fnamebuf), "%s/%s", mbpath, file.s) == -1) { r = IMAP_PROTOCOL_ERROR; goto done; } if(strncmp(file.s, "cyrus.", 6)) { /* it doesn't match cyrus.*, so its a message file. * charge it against the quota */ quotaused += data.len; } } /* if we haven't opened it, do so */ 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); /* we were operating on the seen state, so merge it and cleanup */ 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)) { /* update the quota file */ 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: /* eat the rest of the line, we have atleast a \r\n coming */ eatline(pin, c); freebuf(&file); freebuf(&data); if(curfile >= 0) close(curfile); mailbox_close(&mb); return r; }