/* Copyright (C) 2005-2006 NFG Net Facilities Group BV, support@nfg.nl Copyright (C) 2007 Aaron Stone aaron@serendity.cx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * This is the dbmail-export program to dump mailboxes out to mbox files. */ #include "dbmail.h" char *configFile = DEFAULT_CONFIG_FILE; #define PNAME "dbmail/export" /* UI policy */ int quiet = 0; int reallyquiet = 0; int verbose = 0; extern db_param_t _db_params; void do_showhelp(void) { printf( // Try to stay under the standard 80 column width // 0........10........20........30........40........50........60........70........80 "*** dbmail-export ***\n" "Use this program to export your DBMail mailboxes.\n" "See the man page for more info. Summary:\n" " -u username specify a user, wildcards ? and * accepted, but please be\n" " careful that you may need to escape these from your shell\n" " -m mailbox specify a mailbox (default: export all mailboxes recursively)\n" " -b basedir specify the destination base dir (default: current directory)\n" " note that files are always opened in append mode\n" " -o outfile specify the output file (default: stdout)\n" " note that files are always opened in append mode\n" " -s search use an IMAP SEARCH string to select messages (default: 1:*)\n" " for example, to export all messages received in May:\n" " \"1:* SINCE 1-May-2007 BEFORE 1-Jun-2007\"\n" " -d set \\Deleted flag on exported messages\n" " -D set delete status on exported messages\n" " note that dbmail-util can be used to set deleted status for\n" " \\Deleted messages, and to purge messages with deleted status\n" " -r export mailboxes recursively (default: true unless -m option\n" " is specified)\n" "\n" "Common options for all DBMail utilities:\n" " -f file specify an alternative config file\n" " -q quietly skip interactive prompts\n" " use twice to suppress error messages\n" " -v verbose details\n" " -V show the version\n" " -h show this help message\n" ); } static int mailbox_dump(u64_t mailbox_idnr, const char *dumpfile, const char *search, int delete_after_dump) { FILE *ostream; struct DbmailMailbox *mb = NULL; struct ImapSession *s = NULL; int result = 0; /* * For dbmail the usual filesystem semantics don't really * apply. Mailboxes can contain other mailboxes as well as * messages. For now however, this is solved by appending * the mailboxname with .mbox * * TODO: facilitate maildir type exports */ mb = dbmail_mailbox_new(mailbox_idnr); if (search) { s = dbmail_imap_session_new(); if (! (build_args_array_ext(s, search))) { qerrorf("error parsing search string"); dbmail_imap_session_delete(s); dbmail_mailbox_free(mb); return 1; } if (dbmail_mailbox_build_imap_search(mb, s->args, &(s->args_idx), SEARCH_UNORDERED) < 0) { qerrorf("invalid search string"); dbmail_imap_session_delete(s); dbmail_mailbox_free(mb); return 1; } dbmail_mailbox_search(mb); dbmail_imap_session_delete(s); } if (strcmp(dumpfile, "-") == 0) { ostream = stdout; } else if (! (ostream = fopen(dumpfile, "a"))) { int err = errno; qerrorf("opening [%s] failed [%s]\n", dumpfile, strerror(err)); result = -1; goto cleanup; } if (dbmail_mailbox_dump(mb, ostream) < 0) { qerrorf("Export failed\n"); result = -1; goto cleanup; } if (delete_after_dump) { int deleted_flag[IMAP_NFLAGS]; memset(deleted_flag, 0, IMAP_NFLAGS * sizeof(int)); deleted_flag[IMAP_FLAG_DELETED] = 1; GList *ids = g_tree_keys(mb->ids); while (ids) { // Flag the selected messages \\Deleted // Following this, dbmail-util -d sets deleted status if (delete_after_dump & 1) { if (db_set_msgflag(*(u64_t *)ids->data, mailbox_idnr, deleted_flag, IMAPFA_ADD) < 0) { qerrorf("Error setting flags for message [%llu]\n", *(u64_t *)ids->data); result = -1; } } // Set deleted status on each message // Following this, dbmail-util -p sets purge status if (delete_after_dump & 2) { if (db_set_message_status(*(u64_t *)ids->data, MESSAGE_STATUS_DELETE)) { qerrorf("Error setting status for message [%llu]\n", *(u64_t *)ids->data); result = -1; } } if (!g_list_next(ids)) break; ids = g_list_next(ids); } g_list_free(g_list_first(ids)); } cleanup: if (mb) dbmail_mailbox_free(mb); if (ostream != stdout) fclose(ostream); return result; } static int do_export(char *user, char *base_mailbox, char *basedir, char *outfile, char *search, int delete_after_dump, int recursive) { u64_t user_idnr = 0, owner_idnr = 0, mailbox_idnr = 0; char *dumpfile = NULL, *mailbox = NULL, *search_mailbox = NULL, *dir = NULL; u64_t *children; unsigned nchildren, i; int result = 0; /* Verify the existence of this user */ if (auth_user_exists(user, &user_idnr) == -1) { qerrorf("Error: cannot verify existence of user [%s].\n", user); result = -1; goto cleanup; } if (user_idnr == 0) { qerrorf("Error: user [%s] does not exist.\n", user); result = -1; goto cleanup; } mailbox = g_new0(char, IMAP_MAX_MAILBOX_NAMELEN); if (!base_mailbox) { /* Always recursive without a mailbox base */ search_mailbox = g_strdup("*"); } else if (recursive) { /* Base and everything below */ search_mailbox = g_strdup_printf("%s*", base_mailbox); } else if (!recursive) { /* Should yield same results as plain db_findmailbox */ search_mailbox = g_strdup_printf("%s", base_mailbox); } /* FIXME: What are the possible error conditions here? */ db_findmailbox_by_regex(user_idnr, search_mailbox, &children, &nchildren, 0); /* Decision process for basedir vs. outfile: * If we're dumping one mailbox for one user, it goes to * stdout. If we've been given -o -, dump everything to * stdout (e.g., one giant mbox). If we've been given foo */ if (!outfile && !basedir) { /* Default is to use basedir of . */ basedir = "."; } else if (outfile) { /* Everything goes into this one file */ dumpfile = outfile; } qerrorf("Exporting [%u] mailboxes for [%s]\n", nchildren, user); for (i = 0; i < nchildren; i++) { mailbox_idnr = children[i]; db_getmailboxname(children[i], user_idnr, mailbox); if (! db_get_mailbox_owner(mailbox_idnr, &owner_idnr)) { qerrorf("Error checking mailbox ownership"); goto cleanup; } if (owner_idnr != user_idnr) continue; if (basedir) { /* Prepare the directory */ dumpfile = g_strdup_printf("%s/%s/%s.mbox", basedir, user, mailbox); dir = g_path_get_dirname(dumpfile); if (g_mkdir_with_parents(dir, 0700)) { qerrorf("can't create directory [%s]\n", dir); result = -1; goto cleanup; } } qerrorf(" export mailbox %s -> %s\n", mailbox, dumpfile); if ((result = mailbox_dump(mailbox_idnr, dumpfile, search, delete_after_dump)) != 0) { qerrorf("error exporting mailbox %s -> %s\n", mailbox, dumpfile); goto cleanup; } if (basedir) { g_free(dir); g_free(dumpfile); } } cleanup: g_free(search_mailbox); g_free(mailbox); return result; } int main(int argc, char *argv[]) { int opt = 0, opt_prev = 0; int show_help = 0; int result = 0, delete_after_dump = 0, recursive = 0; char *user=NULL, *mailbox=NULL, *outfile=NULL, *basedir=NULL, *search=NULL; openlog(PNAME, LOG_PID, LOG_MAIL); setvbuf(stdout, 0, _IONBF, 0); g_mime_init(0); /* get options */ opterr = 0; /* suppress error message from getopt() */ while ((opt = getopt(argc, argv, "-u:m:o:b:s:dDr" /* Major modes */ "f:qvVh" /* Common options */ )) != -1) { /* The initial "-" of optstring allows unaccompanied * options and reports them as the optarg to opt 1 (not '1') */ if (opt == 1) opt = opt_prev; opt_prev = opt; switch (opt) { /* export specific options */ case 'u': if (optarg && strlen(optarg)) user = optarg; break; case 'm': if (optarg && strlen(optarg)) mailbox = optarg; break; case 'b': if (optarg && strlen(optarg)) basedir = optarg; break; case 'o': if (optarg && strlen(optarg)) outfile = optarg; break; case 'd': delete_after_dump |= 1; break; case 'D': delete_after_dump |= 2; break; case 'r': recursive = 1; break; case 's': if (optarg && strlen(optarg)) search = optarg; else { qerrorf("dbmail-mailbox: -s requires a value\n\n"); result = 1; } break; /* Common options */ case 'f': if (optarg && strlen(optarg) > 0) configFile = optarg; else { qerrorf("dbmail-mailbox: -f requires a filename\n\n"); result = 1; } break; case 'h': show_help = 1; break; case 'q': /* If we get q twice, be really quiet! */ if (quiet) reallyquiet = 1; if (!verbose) quiet = 1; break; case 'v': if (!quiet) verbose = 1; break; case 'V': /* Show the version and return non-zero. */ PRINTF_THIS_IS_DBMAIL; result = 1; break; default: /* printf("unrecognized option [%c], continuing...\n",optopt); */ break; } /* If there's a non-negative return code, * it's time to free memory and bail out. */ if (result) goto freeall; } /* If nothing is happening, show the help text. */ if (!user || (basedir && outfile) || show_help) { do_showhelp(); result = 1; goto freeall; } /* read the config file */ if (config_read(configFile) == -1) { qerrorf("Failed. Unable to read config file %s\n", configFile); result = -1; goto freeall; } SetTraceLevel("DBMAIL"); GetDBParams(&_db_params); /* open database connection */ if (db_connect() != 0) { qerrorf ("Failed. Could not connect to database (check log)\n"); result = -1; goto freeall; } /* open authentication connection */ if (auth_connect() != 0) { qerrorf("Failed. Could not connect to authentication (check log)\n"); result = -1; goto freeall; } /* Loop over all user accounts if there's a wildcard. */ if (strchr(user, '?') || strchr(user, '*')) { GList *all_users = auth_get_known_users(); GList *matching_users = match_glob_list(user, all_users); GList *users = g_list_first(matching_users); if (!users) { qerrorf("Error: no users matching [%s] were found.\n", user); g_list_destroy(all_users); result = -1; goto freeall; } while (users) { result = do_export(users->data, mailbox, basedir, outfile, search, delete_after_dump, recursive); if (!g_list_next(users)) break; users = g_list_next(users); } g_list_destroy(all_users); g_list_destroy(matching_users); } else { /* No globbing, just run with this one user. */ result = do_export(user, mailbox, basedir, outfile, search, delete_after_dump, recursive); } /* Here's where we free memory and quit. * Be sure that all of these are NULL safe! */ freeall: db_disconnect(); auth_disconnect(); config_free(); g_mime_shutdown(); if (result < 0) qerrorf("Command failed.\n"); return result; }