/*
**    spasm - The spasm anti-spam milter
**    Copyright (C) 2002-2004 Sean Hanson <aradia@theasylum.org>
**
**    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <ctype.h>
#include <curses.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <syslog.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "spasm.h"
#include "dbfunc.h"

#define SPASM_USER_INTERFACE
#include "filters.h"

/* dangerous macros */
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))

#define ITEM_OPT    0     /* filter option    */
#define ITEM_PARAM  1     /* filter parameter */
#define ITEM_FILTER 2     /* filter           */
#define ITEM_EOM    3

WINDOW *mw;
int xmax, ymax;
char *USERNAME = NULL;

/* globally visible qMenu data structure */
struct qStruct {
	char *filename;
	struct qStruct *next;
    };

const static char anykey[]        = "Press any key to continue";
const static char cancel_hlp[]    = "C[ancel] or X[it] -> cancels this prompt";
const static char bh_prefix_hlp[] =
    "This list is meant to contain all known networks assigned to ";
const static char bh_suffix_hlp[] =
    "\n\nNote that a \"blackhole.us\" filter does not imply that the "
    "affected ISP/country is a source of spam or prone to abuse. "
    "This filter simply encompasses the network address space of the "
    "target entity.";

/* describe all the things that can exist on a filter menu */
typedef struct filter_menu_item_struct
{
    int  itemtype;   /* ITEM_EOM, ITEM_OPT, ITEM_PARAM, or ITEM_FILTER */
    union
    {
        const SPASM_FILTER           *fltr;
        const SPASM_FILTER_OPTION    *opt;
        const SPASM_FILTER_PARAMETER *param;
    } u;
} FILTER_MENU_ITEM;

static FILTER_MENU_ITEM *fltr_items;  /* filter "things" placed on screen   */

static int nfltrs,                    /* #spasm-supported filters           */
           nfltr_opts,                /* #spasm-supported filter options    */
           nfltr_params;              /* #spasm-supported filter parameters */

const static char *set_filter_prompt_help[] = 
    {"D[isable] -> disables the filter",
     "",
     "T[ag] -> enables the filter; when triggered, the affected",
     "         email is Tagged with \"X-spasm\" headers that",
     "         denote which filter(s) caught the apparent spam.",
     "         The modified message is then delivered to the user.",
     "",
     "Q[uarantine] -> enables the filter; when triggered the",
     "                affected email is tagged with \"X-spasm\"",
     "                headers as above and stored for the recipient", 
     "                for later retrieval.",
     "",
     "R[eject] -> enables the filter; when triggered, the affected",
     "            email is rejected (not delivered, not saved).",
     NULL};

const static char *set_filter_use_logging_prompt_help[] = 
    {"This option controls which email messages are logged.  Valid",
     "values:",
     "",
     "D[isable] -> disables logging",
     "",
     "T[ag] -> enables logging for all messages flagged by filters at",
     "         level \"Tag\" and higher. That is to say, _all_ messages",
     "         flagged by spasm's filters are logged.",
     "",
     "Q[uarantine] -> enables logging for all messages flagged at level",
     "                \"Quarantine\" and higher.",
     "",
     "R[eject] -> enables logging for _only_ those messages flagged at",
     "            the \"Reject\" level.",
     NULL};

const static char *set_filter_option_prompt_help[] = 
    {"D[isable] -> disables the option",
     "",
     "E[nable] -> enables the option",
     NULL};

const static char *set_filter_param_uint_prompt_help[] = 
    {"Enter an unsigned integer value from 0 to N, where N",
     "makes sense for the parameter in question.",
     NULL};

const static char *general_filter_help[] = 
    {"This menu displays all supported antispam filters, their",
     "associated options and parameters, and current settings of same",
     "(shown in brackets).  Visual settings representations are:",
     "",
     "-       : filter/option Disabled",
     "+       : option Enabled.",
     "T, Q, R : filter enabled.  When a filter triggers, the affected",
     "          email is either:",
     "",
     "          T[agged] with X-spasm headers and delivered to",
     "              recipient, or", 
     "          Q[uarantined] for later retrieval (message is",
     "              also tagged as above), or",
     "          R[ejected] -- not delivered, not saved.",
     "",
     "To modify a menu item's current setting, enter its display column",
     "column number (integer to left of '.') at this prompt.",
     "",
     "To see a short description of a menu item's purpose, type 'h' and",
     "then its display column number.  For example, to learn more about",
     "menu item 2, type:    h2 <enter>",
     NULL};

/* Returns T if all is well, F otherwise */
static int
build_filter_items_array(void)
{
    int i, j, nf, np, no, nitems = 0;

    /* "- 1" accounts for end of table marker */
    nfltrs       = nf = sizeof(spasm_filters) / sizeof(spasm_filters[0]) - 1;
    nfltr_params = np = sizeof(spasm_filter_params) / 
                        sizeof(spasm_filter_params[0]) - 1;
    nfltr_opts   = no = sizeof(spasm_filter_opts) / 
                        sizeof(spasm_filter_opts[0]) - 1;

    /* "+ 1" slots an end of table marker */
    nitems = nf + np + no + 1;
    if ((fltr_items = malloc(nitems * sizeof(FILTER_MENU_ITEM))) == NULL) {
        spasm_error(LOG_ERR,
                    "Error allocating memory for filter item array: %m");
        return (0);
    }
    j = 0;
    for (i = 0; i < no; i++, j++) {
        fltr_items[j].itemtype = ITEM_OPT;
        fltr_items[j].u.opt    = spasm_filter_opts + i;
    }
    for (i = 0; i < np; i++, j++) {
        fltr_items[j].itemtype = ITEM_PARAM;
        fltr_items[j].u.param  = spasm_filter_params + i;
    }
    for (i = 0; i < nf; i++, j++) {
        fltr_items[j].itemtype = ITEM_FILTER;
        fltr_items[j].u.fltr   = spasm_filters + i;
    }
    fltr_items[j].itemtype = ITEM_EOM;
    fltr_items[j].u.fltr   = NULL;
    return (1);
}

static void
lcase(char *str)
{
    if (str) {
        while (*str) {
            if (isupper(*str))
                *str = tolower(*str);
            str++;
        }
    }
}

static char *
skipwhite(char *buf)
{
    if (buf)
    {
        while (isspace(*buf))
            buf++;
    }
    return (buf);
}

/* note: calls cbreak() and noecho() */
static void
show_simple_filter_menu_help(const char *hlpstrings[], int add_cancel_help)
{
    int        add_blank_rows, height, x, y, nstr, width, starty, startx,
               maxlen, maxtext_width;
    WINDOW     *helpwdw;
    const char **hptr;
    size_t     tlen;

    nstr   = 0;
    hptr   = hlpstrings;
    maxlen = (add_cancel_help) ? sizeof(cancel_hlp) - 1 : sizeof(anykey) - 1;
    while (*hptr) {
        nstr++;
        tlen = strlen(*hptr);
        if (tlen > maxlen)
            maxlen = tlen;
        hptr++;
    }
    nstr += 4;             /* account for "\n<anykey str>", box borders. */
    if (add_cancel_help)
        nstr += 2;
    if (nstr + 2 <= LINES) {
        nstr += 2;         /* add blank rows after box borders */
        add_blank_rows = 1;
    } else {
        add_blank_rows = 0;
    }
    height = min(nstr, LINES);
    maxtext_width = maxlen + 4;  /* 4 => 2 border cols + 2 indents */
    width  = (COLS > maxtext_width) ? maxtext_width : COLS;
    starty = (LINES - height) / 2 ; 
    startx = (COLS - width) / 2 ; 

    helpwdw = newwin(height, width, starty, startx);
    box(helpwdw, 0, 0);
    x = 2;
    y = (add_blank_rows) ? 2 : 1;
    hptr = hlpstrings;
    while (*hptr) {
        mvwaddstr(helpwdw, y++, x, *hptr);
        hptr++;
    }
    if (add_cancel_help) {
        y++;                                  /* insert blank line */
        mvwaddstr(helpwdw, y++, x, cancel_hlp);
    }
    y++;                                      /* insert blank line */
    wattron(helpwdw, A_REVERSE);
    x = (width - (sizeof(anykey) - 1)) / 2;   /* center string */
    mvwaddstr(helpwdw, y, x, anykey);
    wmove(helpwdw, height - 2, 1);            /* park the cursor */
    wrefresh(helpwdw);
    cbreak();
    noecho();
    getch();
    werase(helpwdw);
    touchwin(stdscr);
    wrefresh(helpwdw);
    refresh();
    delwin(helpwdw);
}

/*
 * Formats a filter/option/parameter help string in a generally pleasing way.
 * Help text may include newline characters to force line breaks, but tabs
 * are _not_ (I say, NOT) supported.
 *
 * It's just a small matter of code....
 *
 * note: calls cbreak() and noecho()
 *
 * Preconditions: "mnuitem" is a valid index to fltr_items[].
 */
static void
show_complex_filter_menu_help(int mnuitem)
{
    int              height, i, isbhfilter, est1, est2, maxx, width, nlines,
                     startx, starty, x, y;
    char             *hlpstr, *nxtnl, *nxtspc, *saved_hlpstr = NULL, *tmp;
    WINDOW           *helpwdw;
    size_t           nchar, nwords, wordwidth;
    FILTER_MENU_ITEM *pitem;

    pitem      = fltr_items + mnuitem;
    isbhfilter = 0;                    /* an assumption */
    if (pitem->itemtype == ITEM_FILTER) {
          isbhfilter = pitem->u.fltr->is_blackholes_filter;
          hlpstr     = pitem->u.fltr->help;
    } else if (pitem->itemtype == ITEM_PARAM) {
          hlpstr = pitem->u.param->help;
    } else {
          hlpstr = pitem->u.opt->help;
    }
    tmp = hlpstr;

    if (isbhfilter) {
        /*
         * prepend invariant help text and terminating invariant suffix to
         * remainder of filter's help msg.
         */

        size_t n;

        n = strlen(hlpstr) + strlen(bh_prefix_hlp) + strlen(bh_suffix_hlp) + 2;
        tmp = malloc(n);
        if (tmp == NULL) {
            spasm_error(LOG_ERR, "Error allocating memory for filter help: %m");
            sleep(5);  /* let user see message... */
            return;
        }
        snprintf(tmp, n, "%s%s.%s", bh_prefix_hlp, hlpstr, bh_suffix_hlp);
        saved_hlpstr = hlpstr = tmp;
    }

    /* precondition: tmp == hlpstr */

    nchar = nlines = nwords = 0;
    while (*tmp) {
        if (*tmp == '\n') {
            tmp++;
            nlines++;
        } else {
            nchar++;
            if (*tmp == ' ')
                nwords++;
            tmp++;
        }
    }
    if (COLS - 18 > sizeof(anykey))
        width = COLS - 18;
    else if (COLS - 10 > sizeof(anykey))
        width = COLS - 10;
    else
        width = COLS;
    if (width < sizeof(anykey)) {
        if (isbhfilter)
            free(saved_hlpstr);
        return;     /* hopeless, pointless, etc. */
    }

    nlines += 6; /* box borders + empty line delimiting each border +
                  * "\n<anykey>" message.
                  */
    /* 
     * in code below...
     *   "- 4" accounts for box borders and 2 char indents
     *   "* 5" is an estimate of 5 chars per word
     *   "+ 1" picks up a remainder from integer math
     */
    est1    = ((nwords * 5) / (width - 4)) + 1;
    est2    = (nchar / (width - 4)) + 1;
    nlines += max(est1, est2);
    height  = min(nlines, LINES);
    starty  = (LINES - height) / 2;
    startx  = (COLS - width) / 2;

    helpwdw = newwin(height, width, starty, startx);
    box(helpwdw, 0, 0);
    x = y = 2;
    maxx = width - 2;     /* right box border, 1 char indent */
    while (*hlpstr) {
        if (x >= maxx) {
            x = 2;
            y++;
        }
        if (*hlpstr == '\n') {
            /* trivial case */

            x = 2;
            y++;
            hlpstr++;
            continue;
        }
        if (*hlpstr == ' ') {
            /* trivial case */

            if (x != 2) /* if not at BOL */
                mvwaddch(helpwdw, y, x++, *hlpstr);
            hlpstr++;
            continue;
        }
        nxtnl  = strchr(hlpstr, '\n');
        nxtspc = strchr(hlpstr, ' ');
        if (nxtnl || nxtspc) {
            if (nxtnl && nxtspc) {
                if (nxtnl < nxtspc)
                    wordwidth = nxtnl - hlpstr;
                else
                    wordwidth = nxtspc - hlpstr;
            } else if (nxtnl) {
                wordwidth = nxtnl - hlpstr;
            } else {
                wordwidth = nxtspc - hlpstr;
            }
            if (wordwidth + x >= maxx) {
                /* 
                 * Can't fit current word on this line.  Ck for corner case
                 * where the entire word won't fit in the window.
                 */

                if (x == 2) {
                    break;   /* bail out...administrator must use shorter
                              * words or end user use bigger windows
                              */
                }
                x = 2;
                y++;
                continue;
            } else {
                for (i = 0; i < wordwidth; i++) {
                    mvwaddch(helpwdw, y, x++, *hlpstr);
                    hlpstr++;
                }
            }
        } else {
            /*
             * no more delimiters left to break a line.  Will the last
             * "word" in the string fit, or require a new line?
             */

            wordwidth = strlen(hlpstr);
            if (wordwidth + x >= maxx) {
                /* cut n paste some code directly from above */

                if (x == 2)
                    break;  /* At BOL, word won't fit */
                x = 2;
                y++;
            }
            for (i = 0; i < wordwidth; i++) {
                mvwaddch(helpwdw, y, x++, *hlpstr);
                hlpstr++;
            }
        }
    }
    if (isbhfilter) {
        free(saved_hlpstr);
        saved_hlpstr = NULL;
    }

    y += 2;                                   /* insert blank line */
    wattron(helpwdw, A_REVERSE);
    x = (width - (sizeof(anykey) - 1)) / 2;   /* center string */
    mvwaddstr(helpwdw, y, x, anykey);
    wmove(helpwdw, height - 2, 1);            /* park the cursor */
    wrefresh(helpwdw);
    cbreak();
    noecho();
    getch();
    werase(helpwdw);
    touchwin(stdscr);
    wrefresh(helpwdw);
    refresh();
    delwin(helpwdw);
}

/*
 * Permit the user to specify a filter menu item's value.  Most
 * filter menu item's are well behaved and can be handled with
 * orthogonal rules (i.e., table-driven).  Unfortunately, the 
 * USE_LOGGING option is not orthogonal--it's an option, but it
 * takes the same set of values as a filter.  It's just a small
 * matter of code....
 *
 * Preconditions: "numitem" is a valid index to fltr_items[].
 */
static void
set_filter_menu_value(int numitem, int xmax, int ymax)
{
    int              blanks, done, is_use_logging = 0;
    char             buf[128], *answer, *db_name, *ui_name;
    FILTER_MENU_ITEM *pitem;
    unsigned         val;

    pitem = fltr_items + numitem;
    if (pitem->itemtype == ITEM_FILTER)
        ui_name = pitem->u.fltr->ui_name;
    else if (pitem->itemtype == ITEM_OPT) {
        ui_name = pitem->u.opt->ui_name;
        is_use_logging = (strcmp(pitem->u.opt->db_name, "USE_LOGGING") == 0);
    } else
        ui_name = pitem->u.param->ui_name;

    done = 0;
    do {
        move(ymax-2, 1);
        clrtoeol();
        mvvline(ymax - 2, xmax - 1, 0, 1);  /* repair border */
        blanks = (strchr(ui_name, ' ') != NULL);
        if (is_use_logging || pitem->itemtype == ITEM_FILTER) {
            mvprintw(ymax-2,
                     1,
                     "Set %s%s%s to [D, T, Q, R - ? for help]: ",
                     (blanks) ? "\"" : "",
                     ui_name,
                     (blanks) ? "\"" : "");
        } else if (pitem->itemtype == ITEM_OPT) {
            mvprintw(ymax-2,
                     1,
                     "Set %s%s%s to [D, E - ? for help]: ",
                     (blanks) ? "\"" : "",
                     ui_name,
                     (blanks) ? "\"" : "");
        } else {
            /* only UINT parameter supported so far.... */

            mvprintw(ymax-2,
                     1,
                     "Set %s%s%s to [0 - N, ? for help]: ",
                     (blanks) ? "\"" : "",
                     ui_name,
                     (blanks) ? "\"" : "");
        }
        crmode();
        echo();
        getnstr(buf, sizeof(buf));
        answer = skipwhite(buf);
        lcase(answer);
        noecho();
        switch (*answer)
        {
            case 'c': case 'x':          /* cancel prompt */
                done = 1;
                break;
            case 'd':                    /* disable filter/option */
                if (pitem->itemtype == ITEM_PARAM)
                    beep();    /* not valid for parameter option */
                else {
                    if (pitem->itemtype == ITEM_FILTER)
                        db_name = pitem->u.fltr->db_name;
                    else
                        db_name = pitem->u.opt->db_name;
                    spasm_set_filter(USERNAME, db_name, SPASM_OFF);
                    done = 1;
                }
                break;
            case '?':
                {
                    const char **hlparray;

                    if (is_use_logging)
                        hlparray = set_filter_use_logging_prompt_help;
                    else if (pitem->itemtype == ITEM_FILTER)
                        hlparray = set_filter_prompt_help;
                    else if (pitem->itemtype == ITEM_OPT)
                        hlparray = set_filter_option_prompt_help;
                    else {
                        /* only UINT parameter supported so far.... */
                    
                        hlparray = set_filter_param_uint_prompt_help;
                    }
                    show_simple_filter_menu_help(hlparray, 1);
                }
                break;
            case 'e':          /* enable option */
                if (is_use_logging || pitem->itemtype != ITEM_OPT)
                    beep();
                else {
                    spasm_set_filter(USERNAME, pitem->u.opt->db_name, 1);
                    done = 1;
                }
                break;
            case 'q':          /* filter operates in quarantine mode */
            case 't':          /* filter operates in "tagged" mode   */
            case 'r':          /* filter operates in "reject" mode   */
                if (! (pitem->itemtype == ITEM_FILTER || is_use_logging))
                    beep();
                else {
                    if (is_use_logging)
                        db_name = pitem->u.opt->db_name;
                    else
                        db_name = pitem->u.fltr->db_name;
                    if (*answer == 'q')
                        val = SPASM_QUARANTINE;
                    else if (*answer == 't')
                        val = SPASM_TAG;
                    else
                        val = SPASM_REJECT;
                    spasm_set_filter(USERNAME, db_name, val);
                    done = 1;
                }
                break;
            case '0':  case '1':  case '2':  case '3':  case '4':
            case '5':  case '6':  case '7':  case '8':  case '9':
                if (pitem->itemtype != ITEM_PARAM)
                    beep();    /* valid only for UINT parameter option */
                else {
                    /* FIXME -- atoi allows overflow... */

                    val = atoi(answer);
                    spasm_set_filter(USERNAME, pitem->u.param->db_name, val);
                    done = 1;
                }
                break;
            default:
                beep();
                break;
        }
    } while (! done);
}

void spasm_error(int syslevel, char *errstr, ...)
{
    va_list arglist;

    move(ymax-3, 1);
    clrtoeol();
    va_start(arglist, errstr);
    vw_printw(mw, errstr, arglist);
    va_end(arglist);
    move(ymax-2, 1);
    clrtoeol();
    printw("%s", "Press a key to continue...");
    getch();
    move(ymax-3, 1);
    clrtoeol();
    move(ymax-2, 1);
    clrtoeol();
}

static void
cleanup()
{
    endwin();
    printf("\n");
    if(USERNAME != NULL) free(USERNAME);
    exit(0);
}

static void
changeUsername(void)
{
    char buf[128];
    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    mvprintw(1, ((xmax/2)-8), "Change Username");
    mvprintw(ymax-2, 1, "Username: ");
    echo();
    getnstr(buf, sizeof(buf));
    noecho();
    if(strlen(buf) == 0) return;
    free(USERNAME);
    if((USERNAME = calloc(strlen(buf)+1, 1)) == NULL) {
	spasm_error(LOG_ERR, "Error allocating memory for username: %m");
    } else {
	sprintf(USERNAME, "%s", buf);
    }
}

static void
rlMenu(void)
{
    int option, i;
    char buf[128];
    char fbuf[1024];
    FILE *fp;
    char *ptr, *ptr2;
    char *dest;
    struct logStruct {
	    char *sender;
	    struct logStruct *next;
    };
    struct logStruct *first = NULL;
    struct logStruct *cur = NULL;

    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    mvprintw(1, ((xmax/2)-7), "Rejection Logs");

    if(chdir(LOG_DIRECTORY) != 0) {
	spasm_error(LOG_ERR, "Error changing directories: %m");
	return;
    }

    if((fp = fopen(USERNAME, "r")) == NULL) {
	spasm_error(LOG_ERR, "Error opening log file: %m");
	return;
    }

    while(fgets(fbuf, sizeof(fbuf), fp) != NULL) {
	ptr = fbuf;
	for(i=0;i<5;i++) {
	    if(ptr == NULL) continue;
	    if((ptr = strchr(ptr, ':')) != NULL) ptr++;
	}
	if(ptr == NULL) {
	    spasm_error(LOG_ERR, "Error parsing log entry");
	    continue;
	}
	if((ptr2 = strchr(ptr, ':')) == NULL) {
	    spasm_error(LOG_ERR, "Error parsing log entry");
	    continue;
	}
	if(first == NULL) {
	    if((first = calloc(sizeof(struct logStruct), 1)) == NULL) {
		spasm_error(LOG_ERR, "Error allocating memory for log structure: %m");
	    } else {
		cur = first;
		cur->next = NULL;
		if((cur->sender = calloc(ptr2-ptr+2, 1)) == NULL) {
		    spasm_error(LOG_ERR, "Error allocating memory for log entry: %m");
		} else {
		    strncpy(cur->sender, ptr, ptr2-ptr);
		}
	    }
	} else {
	    if((cur->next = calloc(sizeof(struct logStruct), 1)) == NULL) {
		spasm_error(LOG_ERR, "Error allocating memory for next log entry: %m");
	    } else {
		cur = cur->next;
		cur->next = NULL;
		if((cur->sender = calloc(ptr2-ptr+2, 1)) == NULL) {
		    spasm_error(LOG_ERR, "Error allocating memory for log entry: %m");
		} else {
		    strncpy(cur->sender, ptr, ptr2-ptr);
		}
	    }
	}
    }

    cur = first;
    i = 0;
    while(cur != NULL) {
	if(i % 2 == 0) mvprintw(3+i/2, 2, "%d. %s", i+1, cur->sender);
	else mvprintw(3+i/2, 40, "%d. %s", i+1, cur->sender);
	cur = cur->next;
	i++;
    }

    do {
	option = 0;

	move(ymax-2, 1);
	clrtoeol();
	mvprintw(ymax-2, 1, "(%s) Sender to whitelist, [C]lear logs, or [X]it: ", USERNAME);
	crmode();
	echo();
	getnstr(buf, sizeof(buf));
	noecho();
	if(isdigit(buf[0])) {
	    option = atoi(buf);
	    cur = first;
	    for(i=1; i<option; i++) {
		if(cur != NULL) cur = cur->next;
	    }
	    if(cur != NULL && cur->sender != NULL) {
		spasm_insert_wl(USERNAME, cur->sender, "1");
	    }
	} else if(buf[0] == 'c' || buf[0] == 'C') {
	    if(LOG_ARCHIVE_DIR) {
	        if((dest = calloc(strlen(LOG_ARCHIVE_DIR)+sizeof(time_t)+sizeof(pid_t)+5, 1)) == NULL) {
		    spasm_error(LOG_ERR, "Error allocating memory for archive file: %m");
	        } else {
	            fclose(fp);
		    fp = NULL;
		    sprintf(dest, "%s/%lu-%d", LOG_ARCHIVE_DIR, time(NULL), getpid());
		    rename(USERNAME, dest);
		    free(dest);
	        }
	    } else {
		/* no archive defined, just truncate file */
		fclose(fp);
		fp = fopen(USERNAME, "w");
	    }
	} else if(buf[0] == 'x' || buf[0] == 'X') {
            /* nada -- exit loop and menu */
	} else {
            option = 1;
            beep();
        }
    } while(option);

    if(fp != NULL) fclose(fp);
    cur = first;
    while(cur != NULL) {
	if(cur->sender != NULL) free(cur->sender);
	first = cur->next;
	free(cur);
	cur = first;
    }
}

static void
clean_qMenu_list(struct qStruct *first)
{
    struct qStruct *cur = first;

    while(cur != NULL) {
	if(cur->filename != NULL) free(cur->filename);
	first = cur->next;
	free(cur);
	cur = first;
    }
}

static void
qMenu(void)
{
    int option, i, valid_ans;
    char buf[128], *cp, fbuf[1024], *tmpname;
    DIR *dir;
    struct stat statbuf;
    struct dirent *dent;
    FILE *fp;
    struct qStruct *first = NULL, *cur = NULL, *tmpcur;

    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    mvprintw(1, ((xmax/2)-5), "Quarantine");

    if(chdir(QUARANTINE_DIR) != 0) {
	spasm_error(LOG_ERR, "Error opening quarantine directory: %m");
	return;
    }
    if (stat(USERNAME, &statbuf) < 0) {
	/* assume no quarantine data for this user */

	spasm_error(LOG_ERR, "No quarantined messages for %s", USERNAME);
	return;
    }
    if(chdir(USERNAME) != 0) {
	spasm_error(LOG_ERR, "Error changing directories: %m");
	return;
    }

    do {
	/* free existing message file list (if any) before rebuilding it */
	clean_qMenu_list(first);
	cur = first = NULL;

	/* now, create the message file list */
        if((dir = opendir(".")) == NULL) {
	    spasm_error(LOG_ERR, "Error reading quarantine list: %m");
	    return;
        }
        while((dent = readdir(dir)) != NULL) {
	    if(dent->d_name[0] == '.') continue;
	    tmpcur  = calloc(sizeof(struct qStruct), 1);
	    tmpname = strdup(dent->d_name);
	    if (! (tmpname && tmpcur)) {
		    spasm_error(LOG_ERR,
			    "Error allocating memory for qMenu() entry: %m");
		    if (tmpcur)
			free(tmpcur);
		    clean_qMenu_list(first);
		    return;
	    }
	    tmpcur->filename = tmpname;
	    if(first == NULL) {
		cur = first = tmpcur;
	    } else {
		cur->next = tmpcur;
		cur = tmpcur;
	    }
        }
        closedir(dir);

	option = 0;

	clear();
	box(mw, ACS_VLINE, ACS_HLINE);
	mvprintw(1, ((xmax/2)-5), "Quarantine");
	cur = first;
	i = 0;
	while(cur != NULL) {
	    if(i % 2 == 0) mvprintw(3+i/2, 2, "%d. %s", i+1, cur->filename);
	    else mvprintw(3+i/2, 40, "%d. %s", i+1, cur->filename);
	    cur = cur->next;
	    i++;
	}
	move(ymax-2, 1);
	clrtoeol();
	mvvline(ymax - 2, xmax - 1, 0, 1);  /* repair border */
	mvprintw(ymax-2, 1, "(%s) Select message, or [X]it: ", USERNAME);
	crmode();
	echo();
	getnstr(buf, sizeof(buf));
	noecho();
	option = atoi(buf);
	if(option > 0) {
	    int opt2 = 0;
	    clear();
	    box(mw, ACS_VLINE, ACS_HLINE);
	    mvprintw(1, ((xmax/2)-7), "Message Excerpt");
	    cur = first;
	    for(i=1; i<option; i++) {
		if(cur != NULL) cur = cur->next;
	    }
	    if(cur == NULL) {
		spasm_error(LOG_ERR, "Invalid message number");
		continue;
	    }
	    if((fp = fopen(cur->filename, "r")) == NULL) {
		spasm_error(LOG_ERR, "Error reading message: %m");
		continue;
	    }
	    while(fgets(fbuf, sizeof(fbuf), fp) != NULL &&
			strcmp(fbuf, "\r\n") != 0) {
		/* skip headers */
	    }
	    for(i=0;i<ymax-6;i++) {
		if(fgets(fbuf, sizeof(fbuf), fp) != NULL) {
		    char *ptr;
		    if((ptr = strchr(fbuf, '\r')) != NULL) *ptr = '\0';
		    if((ptr = strchr(fbuf, '\n')) != NULL) *ptr = '\0';
		    mvprintw(3+i, 2, "%s", fbuf);
		}
	    }
	    do {
		valid_ans = 1;
		move(ymax-2, 1);
		clrtoeol();
		mvvline(ymax - 2, xmax - 1, 0, 1);  /* repair border */
		mvprintw(ymax - 2,
		         1,
			 "(%s) [S]end, [D]elete, [W]hitelist, [X]it: ",
			 USERNAME);
		opt2 = getch();
		if(opt2 == 's' || opt2 == 'S') {
		    int pfd[2];
		    pid_t pid;
		    move(ymax-2, 1);
		    clrtoeol();
		    mvprintw(ymax-2, 1, "Sending message...");
		    if(pipe(pfd) < 0) {
			spasm_error(LOG_ERR, "Error creating mail pipe: %m");
			continue;
		    }
		    if((pid = fork()) < 0) {
			spasm_error(LOG_ERR, "Error forking mail process: %m");
			close(pfd[0]);
			close(pfd[1]);
			continue;
		    }
		    if(pid == 0) {	/* Child process */
			dup2(pfd[0], 0);
			close(pfd[1]);
			execl(SENDMAIL_PATH, "sendmail", USERNAME, 0);
			/* Shouldn't get here */
			spasm_error(LOG_ERR,
			            "Error executing sendmail (%d): %m",
				    errno);
			exit(128);
		    }
		    close(pfd[0]);
		    fseek(fp, 0, SEEK_SET);
		    while(fgets(fbuf, sizeof(fbuf), fp) != NULL) {
			write(pfd[1], fbuf, strlen(fbuf));
		    }
		    close(pfd[1]);
		    waitpid(pid, NULL, 0);
		} else if(opt2 == 'd' || opt2 == 'D') {
		    unlink(cur->filename);
		} else if(opt2 == 'w' || opt2 == 'W') {
		    FILE *qfile;
		    char *buf;
		    char *addr;
		    int foundit = 0;
		    if((buf = calloc(1024, 1)) == NULL) {
			spasm_error(LOG_ERR, "Error allocating memory: %m");
			continue;
		    }
		    if((addr = calloc(1024, 1)) == NULL) {
			spasm_error(LOG_ERR, "Error allocating memory: %m");
			free(buf);
			continue;
		    }
		    if((qfile = fopen(cur->filename, "r")) == NULL) {
			spasm_error(LOG_ERR, "Error reading message: %m");
			free(buf);
			free(addr);
			continue;
		    }
		    while(fgets(buf, 1024, qfile) != NULL) {
		        if(sscanf(buf, "X-spasm-sender: %s\r\n", addr)) {
			    spasm_insert_wl(USERNAME, addr, "1");
			    foundit = 1;
			    break;
			}
		    }
		    if(!foundit) {
		        spasm_error(LOG_ERR, "Error reading X-spasm-sender", buf);
		    }
		    free(buf);
		    free(addr);
		    fclose(qfile);
		} else if(! (opt2 == 'x' || opt2 == 'X')) {
		    beep();
		    valid_ans = 0;
		}
	    } while (! valid_ans);
	    fclose(fp);
	} else {
	    /* option <= 0 or maybe 'X' to xit */

	    cp = buf;
	    while (isspace(*cp))
		cp++;
	    if (! (*cp == 'x' || *cp == 'X')) {
		beep();
		option = 1;
	    }
	}
    } while(option);

    /* cleanup */
    clean_qMenu_list(first);
}

static void
reMenu(char *dbname)
{
    int option = 0;
    struct spasm_list *entries = NULL;
    struct spasm_list *cur = NULL;
    char buf[128];

    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    mvprintw(1, ((xmax/2)-7), "RegEx Menu");

    do {
	int i = 0;
	clear();
	box(mw, ACS_VLINE, ACS_HLINE);
	getmaxyx(stdscr, ymax, xmax);
	mvprintw(1, ((xmax/2)-7), "RegEx Menu");

	entries = spasm_get_re(dbname);

	cur = entries;
	while(cur != NULL) {
	    if(i % 2 == 0) mvprintw(3+i/2, 2, "%s", cur->entry);
	    else mvprintw(3+i/2, 40, "%s", cur->entry);
	    cur = cur->next;
	    i++;
	}
	cur = entries;
	while(cur != NULL) {
	    if(cur->entry != NULL) free(cur->entry);
	    entries = cur->next;
	    free(cur);
	    cur = entries;
	}

	mvprintw(ymax-6, ((xmax/2)-11), "[A]dd RegEx Entry");
	mvprintw(ymax-5, ((xmax/2)-11), "[D]elete RegEx Entry");
	mvprintw(ymax-4, ((xmax/2)-11), "[X]it to Main Menu");
	mvprintw(ymax-2, 1, "(%s) Ok: ", dbname);
	option = getch();

	if(option == 'a' || option == 'A') {
	    memset(buf, '\0', sizeof(buf));
	    move(ymax-6, 1);
	    clrtoeol();
	    move(ymax-5, 1);
	    clrtoeol();
	    move(ymax-4, 1);
	    clrtoeol();
	    move(ymax-3, 1);
	    clrtoeol();
	    move(ymax-2, 1);
	    clrtoeol();
	    mvprintw(ymax-2, 1, "Entry to add: ");
	    echo();
	    getnstr(buf, sizeof(buf));
	    noecho();
	    if(strlen(buf)) spasm_insert_re(dbname, buf);
	} else if(option == 'd' || option == 'D') {
	    memset(buf, '\0', sizeof(buf));
	    move(ymax-6, 1);
	    clrtoeol();
	    move(ymax-5, 1);
	    clrtoeol();
	    move(ymax-4, 1);
	    clrtoeol();
	    move(ymax-3, 1);
	    clrtoeol();
	    move(ymax-2, 1);
	    clrtoeol();
	    mvprintw(ymax-2, 1, "Entry to delete: ");
	    echo();
	    getnstr(buf, sizeof(buf));
	    noecho();
	    if(strlen(buf)) spasm_delete_re(dbname, buf);
	}
    } while(option != 'x' && option != 'X');
}

void wlMenu()
{
    int option = 0;
    struct spasm_list *entries = NULL;
    struct spasm_list *cur = NULL;
    char buf[128];

    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    mvprintw(1, ((xmax/2)-7), "Whitelist Menu");

    do {
	int i = 0;
	clear();
	box(mw, ACS_VLINE, ACS_HLINE);
	getmaxyx(stdscr, ymax, xmax);
	mvprintw(1, ((xmax/2)-7), "Whitelist Menu");

	entries = spasm_get_wl(USERNAME);

	cur = entries;
	while(cur != NULL) {
	    if(i % 2 == 0) mvprintw(3+i/2, 2, "%s", cur->entry);
	    else mvprintw(3+i/2, 40, "%s", cur->entry);
	    cur = cur->next;
	    i++;
	}
	cur = entries;
	while(cur != NULL) {
	    if(cur->entry != NULL) free(cur->entry);
	    entries = cur->next;
	    free(cur);
	    cur = entries;
	}
	mvprintw(ymax-6, ((xmax/2)-11), "[A]dd Whitelist Entry");
	mvprintw(ymax-5, ((xmax/2)-11), "[D]elete Whitelist Entry");
	mvprintw(ymax-4, ((xmax/2)-11), "[X]it to Main Menu");
	mvprintw(ymax-2, 1, "(%s) Ok: ", USERNAME);
	option = getch();

	if(option == 'a' || option == 'A') {
	    memset(buf, '\0', sizeof(buf));
	    move(ymax-6, 1);
	    clrtoeol();
	    move(ymax-5, 1);
	    clrtoeol();
	    move(ymax-4, 1);
	    clrtoeol();
	    move(ymax-3, 1);
	    clrtoeol();
	    move(ymax-2, 1);
	    clrtoeol();
	    mvprintw(ymax-2, 1, "(%s) Entry to add: ", USERNAME);
	    echo();
	    getnstr(buf, sizeof(buf));
	    noecho();
	    if(strlen(buf)) spasm_insert_wl(USERNAME, buf, "1");
	}
	else if(option == 'd' || option == 'D') {
            memset(buf, '\0', sizeof(buf));
	    move(ymax-6, 1);
	    clrtoeol();
            move(ymax-5, 1);
            clrtoeol();
            move(ymax-4, 1);
            clrtoeol();
            move(ymax-3, 1);
            clrtoeol();
            move(ymax-2, 1);
            clrtoeol();
            mvprintw(ymax-2, 1, "(%s) Entry to delete: ", USERNAME);
            echo();
            getnstr(buf, sizeof(buf));
            noecho();
	    if(strlen(buf)) spasm_delete_wl(USERNAME, buf);
	}
    } while(option != 'x' && option != 'X');
}

void blMenu()
{
    int option = 0;
    struct spasm_list *entries = NULL;
    struct spasm_list *cur = NULL;
    char buf[128];

    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    mvprintw(1, ((xmax/2)-7), "Blacklist Menu");

    do {
        int i = 0;
        clear();
        box(mw, ACS_VLINE, ACS_HLINE);
        getmaxyx(stdscr, ymax, xmax);
        mvprintw(1, ((xmax/2)-7), "Blacklist Menu");

        entries = spasm_get_bl(USERNAME);

        cur = entries;
        while(cur != NULL) {
            if(i % 2 == 0) mvprintw(3+i/2, 2, "%s", cur->entry);
            else mvprintw(3+i/2, 40, "%s", cur->entry);
            cur = cur->next;
            i++;
        }
        cur = entries;
        while(cur != NULL) {
            if(cur->entry != NULL) free(cur->entry);
            entries = cur->next;
            free(cur);
            cur = entries;
        }
        mvprintw(ymax-6, ((xmax/2)-11), "[A]dd Blacklist Entry");
        mvprintw(ymax-5, ((xmax/2)-11), "[D]elete Blacklist Entry");
        mvprintw(ymax-4, ((xmax/2)-11), "[X]it to Main Menu");
        mvprintw(ymax-2, 1, "(%s) Ok: ", USERNAME);
        option = getch();

        if(option == 'a' || option == 'A') {
            memset(buf, '\0', sizeof(buf));
            move(ymax-6, 1);
            clrtoeol();
            move(ymax-5, 1);
            clrtoeol();
            move(ymax-4, 1);
            clrtoeol();
            move(ymax-3, 1);
            clrtoeol();
            move(ymax-2, 1);
            clrtoeol();
            mvprintw(ymax-2, 1, "(%s) Entry to add: ", USERNAME);
            echo();
            getnstr(buf, sizeof(buf));
            noecho();
            if(strlen(buf)) spasm_insert_bl(USERNAME, buf, "1");
        }
        else if(option == 'd' || option == 'D') {
            memset(buf, '\0', sizeof(buf));
            move(ymax-6, 1);
            clrtoeol();
            move(ymax-5, 1);
            clrtoeol();
            move(ymax-4, 1);
            clrtoeol();
            move(ymax-3, 1);
            clrtoeol();
            move(ymax-2, 1);
            clrtoeol();
            mvprintw(ymax-2, 1, "(%s) Entry to delete: ", USERNAME);
            echo();
            getnstr(buf, sizeof(buf));
            noecho();
            if(strlen(buf)) spasm_delete_bl(USERNAME, buf);
        }
    } while(option != 'x' && option != 'X');
}

void gipMenu()
{
    int option = 0;
    struct spasm_list *entries = NULL;
    struct spasm_list *cur = NULL;
    char buf[128];

    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    mvprintw(1, ((xmax/2)-7), "Global IP Whitelist");

    do {
        int i = 0;
        clear();
        box(mw, ACS_VLINE, ACS_HLINE);
        getmaxyx(stdscr, ymax, xmax);
        mvprintw(1, ((xmax/2)-7), "Global IP Whitelist");

        entries = spasm_get_wl(GLOBAL_IP_WL);

        cur = entries;
        while(cur != NULL) {
            if(i % 2 == 0) mvprintw(3+i/2, 2, "%s", cur->entry);
            else mvprintw(3+i/2, 40, "%s", cur->entry);
            cur = cur->next;
            i++;
        }
        cur = entries;
        while(cur != NULL) {
            if(cur->entry != NULL) free(cur->entry);
            entries = cur->next;
            free(cur);
            cur = entries;
        }
        mvprintw(ymax-6, ((xmax/2)-11), "[A]dd IP");
        mvprintw(ymax-5, ((xmax/2)-11), "[D]elete IP");
        mvprintw(ymax-4, ((xmax/2)-11), "[X]it to Main Menu");
        mvprintw(ymax-2, 1, "(%s) Ok: ", USERNAME);
        option = getch();

        if(option == 'a' || option == 'A') {
            memset(buf, '\0', sizeof(buf));
            move(ymax-6, 1);
            clrtoeol();
            move(ymax-5, 1);
            clrtoeol();
            move(ymax-4, 1);
            clrtoeol();
            move(ymax-3, 1);
            clrtoeol();
            move(ymax-2, 1);
            clrtoeol();
            mvprintw(ymax-2, 1, "(%s) Entry to add: ", USERNAME);
            echo();
            getnstr(buf, sizeof(buf));
            noecho();
            if(strlen(buf)) spasm_insert_wl(GLOBAL_IP_WL, buf, "1");
        }
        else if(option == 'd' || option == 'D') {
            memset(buf, '\0', sizeof(buf));
            move(ymax-6, 1);
            clrtoeol();
            move(ymax-5, 1);
            clrtoeol();
            move(ymax-4, 1);
            clrtoeol();
            move(ymax-3, 1);
            clrtoeol();
            move(ymax-2, 1);
            clrtoeol();
            mvprintw(ymax-2, 1, "(%s) Entry to delete: ", USERNAME);
            echo();
            getnstr(buf, sizeof(buf));
            noecho();
            if(strlen(buf)) spasm_delete_wl(GLOBAL_IP_WL, buf);
        }
    } while(option != 'x' && option != 'X');
}

static void
filterMenu()
{
    char             buf[128], *answer;
    int              done, halfwidth, i, n_onscreen, nitems, option, x, y, val;
    FILTER_MENU_ITEM *pitem;

    static const char opts_params_title[] = 
                  "===== Options/Parameters =====";
    static const char filters_title[]     =
                  "========= Filters ============";

    /* following array indexed by:
     *
     *   SPASM_OFF, SPASM_TAG, SPASM_QUARANTINE, SPASM_REJECT
     */
    const static char dbvaluemap[] = { '-', 'T', 'Q', 'R' };

    halfwidth = COLS / 2;
    if (! fltr_items) {    /* do only once */
        if (! build_filter_items_array())
            return;
    }

    clear();
    box(mw, ACS_VLINE, ACS_HLINE);
    getmaxyx(stdscr, ymax, xmax);
    nitems = nfltrs + nfltr_params + nfltr_opts;
    done   = 0;

    do {
        y = 1;  /* start below window's top border */
        n_onscreen = 0;
        if (nfltr_opts || nfltr_params) {
            x = (xmax - (sizeof(opts_params_title) - 1)) / 2;
            mvaddstr(y++, x, opts_params_title);
            for (pitem = fltr_items, i = 0; i < nitems; i++, pitem++) {
                if (pitem->itemtype == ITEM_OPT ||
                                            pitem->itemtype == ITEM_PARAM) {
                    x = (n_onscreen % 2 == 0) ? 2 : halfwidth;
                    if (pitem->itemtype == ITEM_OPT) {
                        /* 
                         * The USE_LOGGING option spits out little wrenches
                         * throughout the filterMenu() processing code because
                         * USE_LOGGING is, from the point of view of the end
                         * user, an option.  Internally, however, this option
                         * takes filter values.
                         *
                         * Chop, hack, cut, whack.  Barf.
                         */

                        int valmap;

                        val = spasm_lookup_filter(USERNAME,
                                                  pitem->u.opt->db_name);
                        if (strcmp(pitem->u.opt->db_name, "USE_LOGGING") == 0)
                            valmap = dbvaluemap[val];
                        else
                             valmap = (val) ? '+' : dbvaluemap[SPASM_OFF];
                        
                        /*
                         * since option values can expand and contract 
                         * on screen (e.g., 0 - N), erase current line 
                         * and then repair right border
                         */
                        move(y + n_onscreen / 2, x);
                        clrtoeol();
                        mvvline(y + n_onscreen / 2, xmax - 1, 0, 1);

                        /* finally emit desired data */
                        mvprintw(y + n_onscreen / 2,
                                 x,
                                 "%2d. [%c] %s",
                                 i + 1,
                                 valmap,
                                 pitem->u.opt->ui_name);
                    } else {
                        mvprintw(y + n_onscreen / 2,
                                 x,
                                 "%2d. [%u] %s",
                                 i + 1,
                                 spasm_lookup_filter(USERNAME,
                                                     pitem->u.param->db_name),
                                 pitem->u.param->ui_name);
                    }
                    n_onscreen++;
                }
            }
        }
        if (n_onscreen % 2 == 1)
            n_onscreen++;
        y += n_onscreen / 2;

        /* let's assume some anti-spam filters are available :-) ....*/
        x = (xmax - (sizeof(filters_title) - 1)) / 2;
        mvaddstr(y++, x, filters_title);
        n_onscreen = 0;
        for (pitem = fltr_items, i = 0; i < nitems; i++, pitem++) {
            if (pitem->itemtype == ITEM_FILTER) {
                val = spasm_lookup_filter(USERNAME, pitem->u.fltr->db_name);
                x   = (n_onscreen % 2 == 0) ? 2 : halfwidth;
                mvprintw(y + n_onscreen / 2,
                         x,
                         "%2d. [%c] %s",
                         i + 1,
                         dbvaluemap[val],
                         pitem->u.fltr->ui_name);
                n_onscreen++;
            }
        }
        move(ymax-2, 1);
        clrtoeol();
        mvvline(ymax - 2, xmax - 1, 0, 1);  /* repair border */
        mvprintw(ymax-2,
                 1,
                 "(%s) Set Filter (X to Xit, ? for help): ",
                 USERNAME);
        crmode();
        echo();
        getnstr(buf, sizeof(buf));
        answer = skipwhite(buf);
        lcase(answer);
        noecho();
        switch (*answer)
        {
            case '?':
                show_simple_filter_menu_help(general_filter_help, 0);
                break;
            case 'x':
                done = 1;  /* goodbye */
                break;
            case 'h':        /* help for an individual filter */
                answer++;
                answer = skipwhite(answer);
                if (! isdigit(*answer))
                    beep();
                else {
                    option = atoi(answer);

                    /* displayed filter numbers are 1-based */
                    if (option == 0 || option > nitems)
                        beep();
                    else
                        show_complex_filter_menu_help(option - 1);
                }
                break;
            case '0': case '1':
            case '2': case '3':
            case '4': case '5':
            case '6': case '7':
            case '8': case '9':
                option = atoi(answer);

                /* displayed filter numbers are 1-based */
                if (option == 0 || option > nitems)
                    beep();
                else
                    set_filter_menu_value(option - 1, xmax, ymax);
                break;
            default:
                beep();
                break;
        }
    } while(! done);
}

int main(int argc, char **argv)
{
    int option;
    uid_t uid;
    struct passwd *pw;

    signal(SIGINT, cleanup);
    signal(SIGTERM, cleanup);
    uid = getuid();
    pw = getpwuid(uid);
    if(pw == NULL) {
	printf("Error looking up uid %d: %m\n", uid);
	cleanup();
    }
    if(strcmp(pw->pw_name, SPASM_USER) == 0) uid = 0;
    if((USERNAME = calloc(strlen(pw->pw_name)+strlen(DEFAULT_DOMAIN)+2, 1)) == NULL) {
	printf("Error allocating memory for username: %m\n");
	cleanup();
    }
    sprintf(USERNAME, "%s@%s", pw->pw_name, DEFAULT_DOMAIN);

    mw = initscr();
    keypad(stdscr, TRUE);
    nonl();
    cbreak();
    noecho();

    do {
	clear();
	box(mw, ACS_VLINE, ACS_HLINE);
	getmaxyx(stdscr, ymax, xmax);
	mvprintw(1, ((xmax/2)-4), "Main Menu");
	mvprintw(3, ((xmax/2)-7), "[F]ilter Menu");
	mvprintw(4, ((xmax/2)-7), "[B]lacklist Menu");
	mvprintw(5, ((xmax/2)-7), "[W]hitelist Menu");
	mvprintw(6, ((xmax/2)-7), "[R]ejection Logs");
	mvprintw(7, ((xmax/2)-7), "[V]iew Quarantine");
	if(uid == 0) mvprintw(8, ((xmax/2)-7), "[S]pamtrap Menu");
	if(uid == 0) mvprintw(9, ((xmax/2)-7), "[E]nvelope Sender Menu");
	if(uid == 0) mvprintw(10, ((xmax/2)-7), "[G]lobal IP Whitelist");
	if(uid == 0) mvprintw(11, ((xmax/2)-7), "[C]hange Username");
	mvprintw(13, ((xmax/2)-7), "[X]it spasm");
	mvprintw(ymax-2, 1, "(%s) Ok: ", USERNAME);
	option = getch();

	if(option == 'f' || option == 'F') {
	    filterMenu();
	}
	else if(option == 'w' || option == 'W') {
	    wlMenu();
	}
	else if(option == 'b' || option == 'B') {
	    blMenu();
	}
	else if(option == 'r' || option == 'R') {
	    rlMenu();
	}
	else if(option == 'v' || option == 'V') {
	    qMenu();
	}
	else if(uid == 0 && (option == 'c' || option == 'C')) {
	    changeUsername();
	}
	else if(uid == 0 && (option == 's' || option == 'S')) {
	    reMenu(SPAMTRAPS_DB);
	}
	else if(uid == 0 && (option == 'e' || option == 'E')) {
	    reMenu(ENVFROM_DB);
	}
	else if(uid == 0 && (option == 'g' || option == 'G')) {
	    gipMenu();
	}
    } while(option != 'x' && option != 'X');

    cleanup();
    exit(0); /* Never gets here */
}
