/*
 * main.c:
 * Entry point, initialisation code and main loop for pop3 server.
 *
 * Copyright (c) 2001 Chris Lightfoot. All rights reserved.
 *
 */

static const char copyright[] = "$Copyright: (c) 2001 Chris Lightfoot. All rights reserved. $";
static const char rcsid[] = "$Id: main.c,v 1.93 2003/11/24 19:58:28 chris Exp $";

#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */

#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/utsname.h>

#include "authswitch.h"
#include "config.h"
#include "listener.h"
#include "pidfile.h"
#include "signals.h"
#include "stringmap.h"
#include "tokenise.h"
#include "vector.h"
#include "util.h"

/* Data structure representing the config file, and global variable which we
 * set from it. */
stringmap config;

/* Various configuration options. */
extern int append_domain;           /* Do we automatically try user@domain if user alone fails to authenticate? In pop3.c. */
extern int strip_domain;            /* Do we automatically try user if user@domain fails to authenticate? */
extern int apop_only;               /* Quit after receiving USER. */
extern int log_bad_pass;            /* Log failing passwords. */
int log_stderr;                     /* Are log messages also sent to standard error? */
int verbose;                        /* Should we be verbose about data going to/from the client? */


char *pidfile = NULL;               /* The name of a PID file to use; if NULL, don't use one. */

/* Various things in netloop.c */
extern vector listeners;
extern int max_running_children, post_fork, timeout_seconds;
extern sig_atomic_t foad, restart;

#ifdef USE_TCP_WRAPPERS
extern char *tcpwrappersname;
#endif

void net_loop(void);


#define EXIT_REMOVING_PIDFILE(n) do { if (pidfile) remove_pid_file(pidfile); exit((n)); } while (0)

/* usage STREAM
 * Print usage information to STREAM.  */
void usage(FILE *fp) {
    fprintf(fp, _(
"tpop3d, version %s\n"
"\n"
"Synopsis: tpop3d -h | [options]\n"
"\n"
"  -h               Display this message\n"
"  -f file          Read configuration from file\n"
"                   (default: %s/tpop3d.conf)\n"
"  -p file          Write PID to file (default: don't use a PID file)\n"
"  -d               Do not detach from controlling terminal\n"
"  -v               Log traffic to/from server for debugging purposes\n"
            )
#ifdef USE_TLS
            _(
"  -P               Permit reading of certificate/private key pass phrases\n"
"                   for TLS operation from the terminal on startup\n"
            )
#endif
"\n"
                , TPOP3D_VERSION, CONFIG_DIR);

    /* Describe the compiled-in options. */
    authswitch_describe(fp);
    mailbox_describe(fp);

    fprintf(fp, _("Enabled features:\n\n"));

#ifdef MASS_HOSTING
    fprintf(fp, _("  Mass virtual hosting\n"));
#endif
#ifdef USE_DRAC
    fprintf(fp, _("  DRAC (dynamic relay authentication control\n"));
#endif
#ifdef IGNORE_CCLIENT_METADATA
    fprintf(fp, _("  Suppress C-client metadata\n"));
#endif
#ifdef USE_TCP_WRAPPERS
    fprintf(fp, _("  TCP Wrappers\n"));
#endif
#ifdef USE_TLS
    fprintf(fp, _("  TLS\n"));
#endif
#ifdef USE_WHOSON
    fprintf(fp, _("  WHOSON protocol\n"));
#endif

    
    fprintf(fp, _(
"\n"
"tpop3d, copyright (c) 2000-2 Chris Lightfoot <chris@ex-parrot.com>; portions\n"
"copyright (c) 2001-2 Mark Longair, Paul Makepeace, Sebastien Thomas,\n"
"Yann Grossel, Arkadiusz Miskiewicz, Ben Schumacher and others. See the\n"
"CREDITS file for full details.\n"
"\n"
"Home page: http://www.ex-parrot.com/~chris/tpop3d/\n"
"\n"
"This program is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version 2 of the License, or\n"
"(at your option) any later version.\n"
"\n"
                ));
}

/* parse_listeners STMT
 * Parse STMT as a list of specifications for addresses on which to listen,
 * creating listener objects as we go. Returns the number of listeners
 * successfully created.
 *
 * The syntax for a listener spec is
 *
 *  addr[:port][(domain)|/regex/][;tls=(immediate|stls),<certificate-file>[,private-key-file]
 *
 */
int parse_listeners(const char *stmt) {
    tokens t;
    char **ll;
    int N = 0;
    
    t = tokens_new(stmt, " \t");

    for (ll = t->toks; ll < t->toks + t->num; ++ll) {
        listener L;
        int i;
        char *s, *p;
        struct sockaddr_in sin = {0};
        char *host = NULL, *port = NULL, *domain = NULL;
#ifdef USE_TLS
        enum tls_mode tls = none;
        char *cert = NULL, *pkey = NULL;
#endif
#ifdef MASS_HOSTING
        char *regex = NULL;
#endif

        sin.sin_family = AF_INET;
        
        s = *ll;
        
        /* Address. */
        i = strcspn(s, ":(/;");
        host = xstrndup(s, i);

        p = s + i;
        
        if (*p == ':') {
            /* Port. */
            ++p;
            i = strcspn(p, "(/;");
            port = xstrndup(p, i);
            p += i;
        }
        
        if (*p == '/') {
            /* Regular expression matching domain. */
#ifdef MASS_HOSTING
            ++p;
            i = strcspn(p, "/");
            if (p[i] != '/') {
                log_print(LOG_ERR, _("parse_listeners: `%s': missing trailing `/'"), s);
                goto skip;
            }
            regex = xstrndup(p, i);
            p += i + 1;
#else
            log_print(LOG_ERR, _("parse_listeners: `%s': this tpop3d does not support mass-hosting regular expressions"), s);
            goto skip;
#endif
        } else if (*p == '(') {
            /* Explicit domain. */
            ++p;
            i = strcspn(p, ")");
            if (p[i] != ')') {
                log_print(LOG_ERR, _("parse_listeners: `%s': missing `)'"), s);
                goto skip;
            }
            domain = xstrndup(p, i);
            p += i + 1;
        }


        if (strncmp(p, ";tls=", 5) == 0) {
#ifdef USE_TLS
            /* TLS mode */
            p += 5;
            if (strncmp(p, "immediate", 9) == 0)
                tls = immediate;
            else if (strncmp(p, "stls", 4) == 0)
                tls = stls;
            else {
                log_print(LOG_ERR, _("parse_listeners: `%s': unknown TLS mode `%.*s'"), s, strcspn(p, ","), p);
                goto skip;
            }

            /* Certificate file */
            if (!(p = strchr(p, ',')) || !*(++p)) {
                log_print(LOG_ERR, _("parse_listeners: `%s': no TLS certificate specified"), s);
                goto skip;
            }

            i = strcspn(p, ",");
            cert = xstrndup(p, i);
            p += i;
            
            /* Optional separate private-key file */
            if (*p) {
                ++p;
                if (!*p) {
                    log_print(LOG_ERR, _("parse_listeners: `%s': TLS private key file is blank"), s);
                    goto skip;
                }
                pkey = xstrdup(p);
            }
#else
            log_print(LOG_ERR, _("parse_listeners: `%s': this tpop3d does not support TLS"), s);
            goto skip;
#endif
        } else if (*p) {
            log_print(LOG_ERR, _("parse_listeners: `%s': trailing garbage"), s);
            goto skip;
        }

        /* Yay! Got everything we need.... */

        /* Turn address and port in to numerical values. */
        if (port) {
            sin.sin_port = atoi(port);
            if (!sin.sin_port) {
                struct servent *se;
                se = getservbyname(port, "tcp");
                if (!se) {
                    log_print(LOG_ERR, _("parse_listeners: `%s': invalid port `%s'"), s, port);
                    continue;
                } else sin.sin_port = se->s_port;
            } else sin.sin_port = htons(sin.sin_port);
        } else
#ifdef USE_TLS
            sin.sin_port = htons(tls == immediate ? 995 : 110); /* pop-3 */
#else
            sin.sin_port = htons(110);
#endif

        /* Address. */
        if (!inet_aton(host, &(sin.sin_addr))) {
            struct hostent *he;
            he = gethostbyname(host);
            if (!he) {
                log_print(LOG_ERR, _("parse_listeners: `%s': invalid listen address `%s'"), s, host);
                continue;
            } else memcpy(&sin.sin_addr, he->h_addr, sizeof sin.sin_addr);
        }

        
        if ((L = listener_new(&sin, domain
#ifdef MASS_HOSTING
                                , regex
#endif
#ifdef USE_TLS
                                , tls, cert, pkey
#endif
                            ))) {
            char msg[1024];     /* XXX */
            vector_push_back(listeners, item_ptr(L));
            ++N;
            /* Log a helpful message. */
            sprintf(msg, _("listening on address %s:%d"), inet_ntoa(L->sin.sin_addr), htons(L->sin.sin_port));
#ifdef MASS_HOSTING
            if (L->have_re)
                sprintf(msg + strlen(msg), ", regex /%s/", L->regex);
#endif
#ifdef USE_TLS
            if (L->tls.mode != none)
                sprintf(msg + strlen(msg), "; TLS mode %s", L->tls.mode == immediate ? "immediate" : "STLS");
#endif
            log_print(LOG_INFO, _("parse_listeners: %s"), msg);
        }
        
skip:
        xfree(host);
        xfree(port);
        xfree(domain);
#ifdef USE_TLS
        xfree(cert);
        xfree(pkey);
#endif
#ifdef MASS_HOSTING
        xfree(regex);
#endif
    }

    tokens_delete(t);

    return N;
}

/* main:
 * Read config file, set up authentication and proceed to main loop. */
char optstring[] = "+hdvf:p:"
#ifdef USE_TLS
                    "P"
#endif
                    ;

#if defined(MBOX_BSD) && defined(MBOX_BSD_SAVE_INDICES)
extern int mailspool_save_indices;  /* in mailspool.c */
#endif

int main(int argc, char **argv, char **envp) {
    int nodaemon = 0;
    char *configfile = CONFIG_DIR"/tpop3d.conf", *s;
    int na, c;
#ifdef USE_TLS
    extern int noreadpassphrase; /* in tls.c */
#endif

#ifdef MTRACE_DEBUGGING
    mtrace(); /* Memory debugging on glibc systems. */
#endif /* MTRACE_DEBUGGING */
    
    /* Read the options. */
    opterr = 0;
    while ((c = getopt(argc, argv, optstring)) != EOF) {
        switch(c) {
            case 'h':
                usage(stdout);
                return 0;

            case 'd':
                nodaemon = 1;
                log_stderr = 1;
                break;

            case 'v':
                verbose = 1;
                break;

            case 'f':
                configfile = optarg;
                break;

            case 'p':
                pidfile = optarg;
                break;

#ifdef USE_TLS
            case 'P':
                noreadpassphrase = 0;
                break;
#endif

            case '?':
            default:
                if (strchr(optstring, optopt))
                    fprintf(stderr, _("tpop3d: option -%c requires an argument\n"), optopt);
                else
                    fprintf(stderr, _("tpop3d: unrecognised option -%c\n"), optopt);
                usage(stderr);
                return 1;
        }
    }

    /* Read the config file. */
    config = read_config_file(configfile);
    if (!config) return 1;

    /* The config file may specify that we aren't to run in daemon mode. */
    if (config_get_bool("no-detach"))
        nodaemon = 1;

    /* ... or that we are to log to standard error. */
    if (config_get_bool("log-stderr")) {
        if (nodaemon)
            log_stderr = 1;
        else
            fprintf(stderr, _("tpop3d: will not log to standard error when running detached"));
    }

    /* Detach from controlling tty etc. */
    if (!nodaemon) daemon(0, 0);

    /* Start logging. */
    log_init();

    /* Maybe start up authentication cache. */
    authcache_init();

    /* Perhaps we have been asked to save metadata caches for BSD mailspools? */
#if defined(MBOX_BSD) && defined(MBOX_BSD_SAVE_INDICES)
    if (config_get_string("mailspool-index")) {
        mailspool_save_indices = 1;
        log_print(LOG_INFO, _("experimental BSD mailbox metadata cache enabled"));
    }
#endif

    /* We may have been compiled with TCP wrappers support. */
#ifdef USE_TCP_WRAPPERS
    if (!(tcpwrappersname = config_get_string("tcp-wrappers-name")))
        tcpwrappersname = "tpop3d";
    log_print(LOG_INFO, _("TCP Wrappers support enabled, using daemon name `%s'"), tcpwrappersname);
#endif
    
    /* Try to write PID file. */
    if (pidfile) {
retry_pid_file:
        switch (write_pid_file(pidfile)) {
            case pid_file_success:
                break;

            case pid_file_existence: {
                pid_t pid;
                switch (read_pid_file(pidfile, &pid)) {
                    case pid_file_success:
                        if (kill(pid, 0)) {
                            log_print(LOG_ERR, _("%s: stale PID file; removing it"), pidfile);
                            if (unlink(pidfile) == -1) {
                                log_print(LOG_ERR, _("%s: stale PID file: unlink: %m"), pidfile);
                                return 1;
                            } else goto retry_pid_file; /* harmful? */
                            
                        } else {
                            log_print(LOG_ERR, _("%s: tpop3d already running, with process ID %d; exiting"), (int)pid);
                            return 1;
                        }
                        break;
                        
                    default:
                        log_print(LOG_ERR, _("%s: PID file seems to be invalid; exiting."), pidfile);
                        return 1;
                }
                break;
            }

            case pid_file_error:
                log_print(LOG_ERR, _("%s: %m: couldn't write PID file; exiting."), pidfile);
                return 1;
        }
    }

    /* Identify addresses on which to listen.
     * The syntax for these is <addr>[:port][(domain)]. */
    s = config_get_string("listen-address");
    listeners = vector_new();
    if (s) 
        parse_listeners(s);

    if (listeners->n_used == 0) {
        log_print(LOG_ERR, _("%s: no listen addresses obtained; exiting"), configfile);
        EXIT_REMOVING_PIDFILE(1);
    }

    /* Find out the maximum number of children we may spawn at once. */
    switch(config_get_int("max-children", &max_running_children)) {
        case -1:
            log_print(LOG_ERR, _("%s: value given for max-children does not make sense; exiting"), configfile);
            EXIT_REMOVING_PIDFILE(1);

        case 1:
            if (max_running_children < 1) {
                log_print(LOG_ERR, _("%s: value for max-children must be 1 or greater; exiting"), configfile);
                EXIT_REMOVING_PIDFILE(1);
            }
            break;

        default:
            max_running_children = 100;
    }

    /* Should we automatically append or strip domain names and retry authentication? */
    if (config_get_bool("append-domain"))
        append_domain = 1;
    if (config_get_bool("strip-domain"))
        strip_domain = 1;

    if (append_domain && strip_domain)
        log_print(LOG_WARNING, _("%s: specifying append-domain and strip-domain does not make much sense"), configfile);

    /* Should we disconnect any client which sends a USER command? */
    if (config_get_bool("apop-only"))
        apop_only = 1;

    /* Should we log failing passwords? */
    if (config_get_bool("log-bad-passwords")) {
        log_print(LOG_WARNING, _("%s: I hope you realise that use of the log-bad-passwords option is an invasion of privacy"), configfile);
        log_bad_pass = 1;
    }

    /* Find out how long we wait before timing out.... */
    switch (config_get_int("timeout-seconds", &timeout_seconds)) {
        case -1:
            log_print(LOG_ERR, _("%s: value given for timeout-seconds does not make sense; exiting"), configfile);
            EXIT_REMOVING_PIDFILE(1);

        case 1:
            if (timeout_seconds < 1) {
                log_print(LOG_ERR, _("%s: value for timeout-seconds must be 1 or greater; exiting"), configfile);
                EXIT_REMOVING_PIDFILE(1);
            }
            break;

        default:
            timeout_seconds = 30;
    }

    set_signals();

    /* Start the authentication drivers. */
    na = authswitch_init();
    if (!na) {
        log_print(LOG_ERR, _("no authentication drivers were loaded; aborting."));
        log_print(LOG_ERR, _("you may wish to check your config file %s"), configfile);
        EXIT_REMOVING_PIDFILE(1);
    } else log_print(LOG_INFO, _("%d authentication drivers successfully loaded"), na);
   
    net_loop();

    if (!post_fork) {
        authswitch_close();
        authcache_close();
    }

    if (listeners) {
        item *I;
        vector_iterate(listeners, I) listener_delete((listener)I->v);
        vector_delete(listeners);
    }

    stringmap_delete_free(config);
    
    /* We may have got here because we're supposed to terminate and restart. */
    if (restart) {
        execve(argv[0], argv, envp);
        log_print(LOG_ERR, "%s: %m", argv[0]);
    }
    
    EXIT_REMOVING_PIDFILE(0);
}



syntax highlighted by Code2HTML, v. 0.9.1