/*
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;
}
syntax highlighted by Code2HTML, v. 0.9.1