/* Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl 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. */ /* $Id: maintenance.c 1712 2005-03-26 20:23:18Z aaron $ * * This is the dbmail housekeeping program. * It checks the integrity of the database and does a cleanup of all * deleted messages. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "maintenance.h" #include "db.h" #include "debug.h" #include "dbmail.h" #include "list.h" #include "debug.h" #include "auth.h" #include "dm_getopt.h" #include #include #include #include #include /* Loudness and assumptions. */ int yes_to_all = 0; int no_to_all = 0; int verbose = 0; /* Don't be helpful. */ int quiet = 0; /* Don't print errors. */ int reallyquiet = 0; #define qprintf(fmt, args...) \ (quiet ? 0 : printf(fmt, ##args) ) #define qverbosef(fmt, args...) \ (!verbose ? 0 : printf(fmt, ##args) ) #define qerrorf(fmt, args...) \ (reallyquiet ? 0 : fprintf(stderr, fmt, ##args) ) char *configFile = DEFAULT_CONFIG_FILE; int has_errors = 0; /* set up database login data */ extern db_param_t _db_params; static void find_time(char *timestr, const char *timespec); static int do_check_integrity(void); static int do_null_messages(void); static int do_is_header(void); static int do_purge_deleted(void); static int do_set_deleted(void); static int do_check_iplog(char *timestr, const char *timespec); static int do_vacuum_db(void); int do_showhelp(void) { printf("*** dbmail-util ***\n"); printf("Use this program to maintain your DBMail database.\n"); printf("See the man page for more info. Summary:\n\n"); printf(" -a perform all checks (in this release: -ctubpd)\n"); printf(" -c clean up database (optimize/vacuum)\n"); printf(" -t test for message integrity\n"); printf(" -u null message check\n"); printf(" -b body/header check\n"); printf(" -p purge messages have the DELETE status set\n"); printf(" -d set DELETE status for deleted messages\n"); printf(" -l time clear the IP log used for IMAP/POP-before-SMTP\n" " the time is specified as hm\n" " (don't include the angle brackets, though)\n"); printf("\nCommon options for all DBMail utilities:\n"); printf(" -f file specify an alternative config file\n"); printf(" -q quietly skip interactive prompts\n" " use twice to suppress error messages\n"); printf(" -n show the intended action but do not perform it, no to all\n"); printf(" -y perform all proposed actions, as though yes to all\n"); printf(" -v verbose details\n"); printf(" -V show the version\n"); printf(" -h show this help message\n"); return 0; } int main(int argc, char *argv[]) { int check_integrity = 0, check_iplog = 0; int null_messages = 0, is_header = 0; int vacuum_db = 0, purge_deleted = 0, set_deleted = 0; int show_help = 0; int do_nothing = 1; int opt; char timespec[LEN]; char timestr[LEN]; openlog(PNAME, LOG_PID, LOG_MAIL); setvbuf(stdout, 0, _IONBF, 0); /* get options */ dm_opterr = 0; /* suppress error message from getopt() */ while ((opt = dm_getopt(argc, argv, "-acrtl:pudb" /* Main options */ "i" /* Maybe later options */ "f:qnyvVh")) != -1) { /* Common options */ /* The initial "-" of optstring allows unaccompanied * options and reports them as the optarg to opt 1 (not '1') */ switch (opt) { case 'a': /* This list should be kept up to date. */ vacuum_db = 1; purge_deleted = 1; set_deleted = 1; check_integrity = 1; null_messages = 1; is_header = 1; do_nothing = 0; break; case 'c': vacuum_db = 1; do_nothing = 0; break; case 'b': is_header = 1; do_nothing = 0; break; case 'p': purge_deleted = 1; do_nothing = 0; break; case 'd': set_deleted = 1; do_nothing = 0; break; case 't': check_integrity = 1; do_nothing = 0; break; case 'u': null_messages = 1; do_nothing = 0; break; case 'l': check_iplog = 1; do_nothing = 0; if (dm_optarg) strncpy(timespec, dm_optarg, LEN); else timespec[0] = 0; timespec[LEN] = 0; break; case 'i': qerrorf("Interactive console is not supported in this release.\n"); return 1; /* Common options */ case 'h': show_help = 1; do_nothing = 0; break; case 'n': no_to_all = 1; break; case 'r': case 'y': yes_to_all = 1; break; case 'q': /* If we get q twice, be really quiet! */ if (quiet) reallyquiet = 1; if (!verbose) quiet = 1; break; case 'f': if (dm_optarg && strlen(dm_optarg) > 0) configFile = dm_optarg; else { qerrorf("dbmail-util: -f requires a filename\n\n" ); return 1; } break; case 'v': verbose = 1; break; case 'V': printf("DBMail: dbmail-util\n" "Version: %s\n" "$Revision: 1712 $\n" "Copyright: %s\n", VERSION, COPYRIGHT); return 1; default: /*printf("unrecognized option [%c], continuing...\n",optopt); */ break; } } if (do_nothing || show_help || (no_to_all && yes_to_all)) { do_showhelp(); return 1; } /* Don't make any changes unless specifically authorized. */ if (!yes_to_all) no_to_all = 1; ReadConfig("DBMAIL", configFile); SetTraceLevel("DBMAIL"); GetDBParams(&_db_params); qprintf("Opening connection to database... \n"); if (db_connect() != 0) { qerrorf("Failed. An error occured. Please check log.\n"); return -1; } qprintf("Opening connection to authentication... \n"); if (auth_connect() != 0) { qerrorf("Failed. An error occured. Please check log.\n"); return -1; } if (db_check_version() != 0) { qerrorf("Database version incompatible. Please upgrade your database.\n"); return -1; } qprintf("Ok. Connected.\n"); if (check_integrity) do_check_integrity(); if (null_messages) do_null_messages(); if (is_header) do_is_header(); if (purge_deleted) do_purge_deleted(); if (set_deleted) do_set_deleted(); if (check_iplog) do_check_iplog(timestr, timespec); if (vacuum_db) do_vacuum_db(); if (!has_errors) { qprintf("\nMaintenance done. No errors found.\n"); } else { qerrorf("\nMaintenance done. Errors were found"); if (no_to_all) { qerrorf(" but not fixed.\n"); qerrorf("Try running dbmail-util with the '-y' " "option to repair the errors.\n"); } if (yes_to_all) { qerrorf(" and fixed.\n"); qerrorf("Try running dbmail-util again to confirm " "that the errors were repaired.\n"); } } db_disconnect(); auth_disconnect(); config_free(); return 0; } int do_purge_deleted(void) { u64_t deleted_messages; if (no_to_all) { qprintf("\nCounting messages with PURGE status...\n"); if (db_purge_count(&deleted_messages) < 0) { qerrorf ("Failed. An error occured. Please check log.\n"); return -1; } if (deleted_messages > 0) { qerrorf("Ok. Found [%llu] messages with PURGE status.\n", deleted_messages); } else { qprintf("Ok. Found [%llu] messages with PURGE status.\n", deleted_messages); } } if (yes_to_all) { qprintf("\nDeleting messages with PURGE status...\n"); if (db_deleted_purge(&deleted_messages) < 0) { qerrorf ("Failed. An error occured. Please check log.\n"); return -1; } qprintf("Ok. [%llu] messages deleted.\n", deleted_messages); } return 0; } int do_set_deleted(void) { u64_t messages_set_to_delete; if (no_to_all) { qprintf("\nCounting messages with DELETE status...\n"); if (db_deleted_count(&messages_set_to_delete) < 0) { qerrorf ("Failed. An error occured. Please check log.\n"); return -1; } if (messages_set_to_delete > 0) { qerrorf("Ok. Found [%llu] messages with DELETE status.\n", messages_set_to_delete); } else { qprintf("Ok. Found [%llu] messages with DELETE status.\n", messages_set_to_delete); } } if (yes_to_all) { qprintf("\nSetting DELETE status for deleted messages...\n"); if (db_set_deleted(&messages_set_to_delete) == -1) { qerrorf ("Failed. An error occured. Please check log.\n"); return -1; } qprintf("Ok. [%llu] messages set for deletion.\n", messages_set_to_delete); qprintf("Re-calculating used quota for all users...\n"); if (db_calculate_quotum_all() < 0) { qerrorf ("Failed. An error occured. Please check log.\n"); return -1; } qprintf("Ok. Used quota updated for all users.\n"); } return 0; } int do_is_header(void) { time_t start, stop; struct list lostlist; struct element *el; u64_t id; if (no_to_all) { qprintf("\nChecking DBMAIL for incorrect is_header flags...\n"); } if (yes_to_all) { qprintf("\nRepairing DBMAIL for incorrect is_header flags...\n"); } time(&start); if (db_icheck_isheader(&lostlist) < 0) { qerrorf("Failed. An error occured. Please check log.\n"); return -1; } if (lostlist.total_nodes > 0) { qerrorf("Ok. Found [%ld] incorrect is_header flags.\n", lostlist.total_nodes); has_errors = 1; } else { qprintf("Ok. Found [%ld] incorrect is_header flags.\n", lostlist.total_nodes); } if (yes_to_all) { if (lostlist.total_nodes > 0) { el = lostlist.start; while (el) { id = *((u64_t *) el->data); if (db_set_isheader(id, HEAD_BLOCK) < 0) qerrorf("Warning: could not set is_header on message block [%llu]. Check log.\n", id); else qverbosef("[%llu] set to HEAD_BLOCK\n", id); el = el->nextnode; } list_freelist(&lostlist.start); } } time(&stop); qverbosef("--- checking is_header flags took %g seconds\n", difftime(stop, start)); return 0; } int do_null_messages(void) { time_t start, stop; struct list lostlist; struct element *el; u64_t id; if (no_to_all) { qprintf("\nChecking DBMAIL for NULL messages...\n"); } if (yes_to_all) { qprintf("\nRepairing DBMAIL for NULL messages...\n"); } time(&start); if (db_icheck_null_messages(&lostlist) < 0) { qerrorf("Failed. An error occured. Please check log.\n"); return -1; } if (lostlist.total_nodes > 0) { qerrorf("Ok. Found [%ld] NULL messages.\n", lostlist.total_nodes); has_errors = 1; } else { qprintf("Ok. Found [%ld] NULL messages.\n", lostlist.total_nodes); } if (yes_to_all) { if (lostlist.total_nodes > 0) { el = lostlist.start; while (el) { id = *((u64_t *) el->data); if (db_set_message_status(id, MESSAGE_STATUS_PURGE) < 0) qerrorf("Warning: could not set status on message [%llu]. Check log.\n", id); else qverbosef("[%llu] set to MESSAGE_STATUS_PURGE)\n", id); el = el->nextnode; } list_freelist(&lostlist.start); } } time(&stop); qverbosef("--- checking NULL messages took %g seconds\n", difftime(stop, start)); qprintf("\nChecking DBMAIL for NULL physmessages...\n"); time(&start); if (db_icheck_null_physmessages(&lostlist) < 0) { qerrorf("Failed, an error occured. Please check log.\n"); return -1; } if (lostlist.total_nodes > 0) { qerrorf("Ok. Found [%ld] physmessages without messageblocks.\n", lostlist.total_nodes); el = lostlist.start; while (el) { id = *((u64_t *) el->data); if (db_delete_physmessage(id) < 0) qerrorf("Warning: could not delete physmessage [%llu]. Check log.\n", id); else qverbosef("[%llu] deleted.\n", id); el = el->nextnode; } list_freelist(&lostlist.start); } else { qprintf("Ok. Found [%ld] physmessages without messageblocks.\n", lostlist.total_nodes); } time(&stop); qverbosef("--- checking NULL physmessages took %g seconds\n", difftime(stop, start)); return 0; } int do_check_integrity(void) { time_t start, stop; struct list lostlist; struct element *el; u64_t id; if (no_to_all) { qprintf("\nChecking DBMAIL messageblocks integrity...\n"); } if (yes_to_all) { qprintf("\nRepairing DBMAIL messageblocks integrity...\n"); } time(&start); /* this is what we do: * First we're checking for loose messageblocks * Secondly we're chekcing for loose messages * Third we're checking for loose mailboxes */ /* first part */ if (db_icheck_messageblks(&lostlist) < 0) { qerrorf("Failed. An error occured. Please check log.\n"); return -1; } if (lostlist.total_nodes > 0) { qerrorf("Ok. Found [%ld] unconnected messageblks:\n", lostlist.total_nodes); el = lostlist.start; while (el) { id = *((u64_t *) el->data); if (no_to_all) { qerrorf("%llu ", id); } else if (yes_to_all) { if (db_delete_messageblk(id) < 0) qerrorf ("Warning: could not delete messageblock #%llu. Check log.\n", id); else qerrorf ("%llu (removed from dbase)\n", id); } el = el->nextnode; } list_freelist(&lostlist.start); qerrorf("\n"); has_errors = 1; } else { qprintf("Ok. Found [%ld] unconnected messageblks.\n", lostlist.total_nodes); } time(&stop); qverbosef("--- checking block integrity took %g seconds\n", difftime(stop, start)); qverbosef("--- checking block integrity took %g seconds\n", difftime(stop, start)); /* second part */ start = stop; qprintf("\nChecking DBMAIL message integrity...\n"); if (db_icheck_messages(&lostlist) < 0) { qerrorf ("Failed. An error occured. Please check log.\n"); return -1; } if (lostlist.total_nodes > 0) { has_errors = 1; qerrorf("Ok. Found [%ld] unconnected messages:\n", lostlist.total_nodes); } else { qprintf("Ok. Found [%ld] unconnected messages.\n", lostlist.total_nodes); } if (yes_to_all) { if (lostlist.total_nodes > 0) { el = lostlist.start; while (el) { id = *((u64_t *) el->data); if (no_to_all) { qerrorf("%llu ", id); } else if (yes_to_all) { if (db_delete_message(id) < 0) qerrorf ("Warning: could not delete message #%llu. Check log.\n", id); else qerrorf ("%llu (removed from dbase)\n", id); } el = el->nextnode; } qerrorf("\n"); list_freelist(&lostlist.start); } } time(&stop); qverbosef("--- checking message integrity took %g seconds\n", difftime(stop, start)); qverbosef("--- checking message integrity took %g seconds\n", difftime(stop, start)); /* third part */ qprintf("\nChecking DBMAIL mailbox integrity...\n"); start = stop; if (db_icheck_mailboxes(&lostlist) < 0) { qerrorf ("Failed. An error occured. Please check log.\n"); return -1; } if (lostlist.total_nodes > 0) { has_errors = 1; qerrorf("Ok. Found [%ld] unconnected mailboxes.\n", lostlist.total_nodes); } else { qprintf("Ok. Found [%ld] unconnected mailboxes.\n", lostlist.total_nodes); } if (yes_to_all) { if (lostlist.total_nodes > 0) { el = lostlist.start; while (el) { id = *((u64_t *) el->data); if (no_to_all) { qerrorf("%llu ", id); } else if (yes_to_all) { if (db_delete_mailbox(id, 0, 0) < 0) qerrorf ("Warning: could not delete mailbox #%llu. Check log.\n", id); else qerrorf ("%llu (removed from dbase)\n", id); } el = el->nextnode; } qerrorf("\n"); list_freelist(&lostlist.start); } } time(&stop); qverbosef("--- checking mailbox integrity took %g seconds\n", difftime(stop, start)); qverbosef("--- checking mailbox integrity took %g seconds\n", difftime(stop, start)); return 0; } int do_check_iplog(char *timestr, const char *timespec) { u64_t log_count; find_time(timestr, timespec); if (timestr[0] == 0) { qerrorf("\nFailed to find a timestring: [%s] is not hm.\n", timespec); return -1; } if (no_to_all) { qprintf("\nCounting IP entries older than [%s]...\n", timestr); if (db_count_iplog(timestr, &log_count) < 0) { qerrorf("Failed. An error occured. Check the log.\n"); return -1; } qprintf("Ok. [%llu] IP entries are older than [%s].\n", log_count, timestr); } if (yes_to_all) { qprintf("\nRemoving IP entries older than [%s]...\n", timestr); if (db_cleanup_iplog(timestr, &log_count) < 0) { qerrorf("Failed. Please check the log.\n"); return -1; } qprintf("Ok. [%llu] IP entries were older than [%s].\n", log_count, timestr); } return 0; } int do_vacuum_db(void) { if (no_to_all) { qprintf("\nVacuum and optimize not performed.\n"); } if (yes_to_all) { qprintf("\nVacuuming and optimizing database...\n"); fflush(stdout); if (db_cleanup() < 0) { qerrorf("Failed. Please check the log.\n"); return -1; } qprintf("Ok. Database cleaned up.\n"); } return 0; } /* * makes a date/time string: YYYY-MM-DD HH:mm:ss * based on current time minus timespec * timespec contains: hm for a timespan of n hours, m minutes * hours or minutes may be absent, not both * * upon error, timestr[0] = 0 */ void find_time(char *timestr, const char *timespec) { time_t td; struct tm tm; int min = -1, hour = -1; long tmp; char *end; time(&td); /* get time */ timestr[0] = 0; if (!timespec) return; /* find first num */ tmp = strtol(timespec, &end, 10); if (!end) return; if (tmp < 0) return; switch (*end) { case 'h': case 'H': hour = tmp; break; case 'm': case 'M': hour = 0; min = tmp; if (end[1]) /* should end here */ return; break; default: return; } /* find second num */ if (timespec[end - timespec + 1]) { tmp = strtol(×pec[end - timespec + 1], &end, 10); if (end) { if ((*end != 'm' && *end != 'M') || end[1]) return; if (tmp < 0) return; if (min >= 0) /* already specified minutes */ return; min = tmp; } } if (min < 0) min = 0; /* adjust time */ td -= (hour * 3600L + min * 60L); tm = *localtime(&td); /* get components */ strftime(timestr, LEN, "%Y-%m-%d %H:%M:%S", &tm); return; }