/* $Cambridge: hermes/src/prayer/accountd/accountd.c,v 1.8 2010/07/07 16:08:14 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2002 */
/* See the file NOTICE for conditions of use and distribution. */

/* Prayer account management daemon */

#include "accountd.h"

static void ucase(char *s)
{
    while (*s) {
        *s = Utoupper(*s);
        s++;
    }
}

/* ====================================================================== */

/* Process line of input once authenticated */

static BOOL
do_authenticated_line(struct config *config,
                      char *line, struct iostream *stream, char *username)
{
    char *cmd;

    if (!(cmd = string_get_token(&line))) {
        ioputs(stream, "BAD Invalid command" CRLF);
        ioflush(stream);
        return (T);
    }
    ucase(cmd);
    log_misc("%s: %s", username, cmd);

    switch (cmd[0]) {
    case 'C':
        if (!strcmp(cmd, "CHECKFULLNAME")) {
            fullname_check(config, stream);
            return (T);
        }
        break;
    case 'F':
        if (!strcmp(cmd, "FULLNAME"))
            return (fullname_change(config, stream, line));
        break;
    case 'G':
        if (!strcmp(cmd, "GET"))
            return (file_get(config, stream, line));
        break;
    case 'M':
        /* Merge into single mail_upload command */
        if (!strcmp(cmd, "MAILSTATUS"))
            return (mail_status(config, stream, line));
        else if (!strcmp(cmd, "MAILCHANGE"))
            return (mail_change(config, stream, line));
        break;
    case 'L':
        if (!strcmp(cmd, "LOGOUT")) {
            ioputs(stream, "OK Logout" CRLF);
            ioflush(stream);
            return (NIL);
        } else if (!strcmp(cmd, "LOGIN")) {
            ioputs(stream, "NO Already authenticated" CRLF);
            ioflush(stream);
            return (T);
        }
        break;
    case 'P':
        if (!strcmp(cmd, "PASSWORD"))
            return (password_change(config, stream, line));
        else if (!strcmp(cmd, "PUT"))
            return (file_put(config, stream, line));
        break;
    case 'Q':
        if (!strcmp(cmd, "QUOTA")) {
            quota_check(config, stream);
            return (T);
        }
        break;
    case 'V':
        if (!strcmp(cmd, "VACATION_CLEAR"))
            return (mail_vacclear(config, stream, line));
        break;
    }

    ioputs(stream, "BAD Unknown command" CRLF);
    ioflush(stream);
    return (T);
}

/* ====================================================================== */

/* Run a single accountd session on the nominated socket descriptor */

static BOOL session(struct config *config, int sockfd)
{
    char buffer[MAXLENGTH];
    char *username = NIL;
    struct iostream *stream;

    if (!
        (stream =
         iostream_create(NIL, sockfd, IOSTREAM_PREFERRED_BLOCK_SIZE)))
        return (NIL);

    iostream_set_timeout(stream, ACCOUNTD_TIMEOUT);

    if (config->use_ssl && !iostream_ssl_start_server(stream))
        return (NIL);

    if (getuid() == 0) {
        if (!authenticate(config, stream, &username))
            return (NIL);
    } else {
        if (!authenticate_preauth(config, stream, &username))
            return (NIL);
    }

    if (getuid() == 0) {
        log_fatal("Authenticated session running with root privilege");
        /* NOTREACHED */
        exit(1);
    }

    log_misc("User login: %s", username);

    while (1) {
        if (!iostream_getline(stream, buffer, MAXLENGTH))
            break;

        if (buffer[0] == '\0')  /* Ignore empty lines */
            continue;

        if (!do_authenticated_line(config, buffer, stream, username))
            break;
    }

    log_misc("User logout: %s", username);
    return (T);
}

/* ====================================================================== */

/* Code for running accountd as standalone daemon rather than from inetd */

void rundaemon(struct config *config, int use_fork, int port)
{
    struct ipaddr ipaddr;
    int *sockfds;
    int newsockfd;
    pid_t childpid;
#ifdef USE_SSL
    struct ssl_config ssl_config;
#endif

    if (!(sockfds = os_bind_inet_socket(port, NIL)))
        log_fatal("socket() failed");

    os_signal_child_init(os_child_reaper);

#ifdef USE_SSL
    if (config->use_ssl) {
        config_extract_ssl(config, &ssl_config);
        iostream_init(&ssl_config);   /* Required for SSL stuff */
    }
#endif

    for (;;) {
        fd_set fds;
        int maxsockfd, i;

        FD_ZERO(&fds);
        maxsockfd = 0;
        for (i=0; sockfds[i] >= 0; i++) {
            FD_SET(sockfds[i], &fds);

            if (sockfds[0] > maxsockfd)
                maxsockfd = sockfds[0];
        }

        while (select(maxsockfd + 1, &fds, NIL, NIL, NIL) < 0) {
            if (errno != EINTR)
                log_fatal("prayer_server(): select() failed: %s",
                          strerror(errno));
        }

        for (i=0; sockfds[i] >= 0; i++) {
            if (FD_ISSET(sockfds[i], &fds)) {
                int sockfd = sockfds[i];

                if ((newsockfd = os_accept_inet(sockfd, &ipaddr)) < 0)
                    log_fatal("accept() failed: errno = %d", errno);

                log_misc("Incoming connection from %s", ipaddr_text(&ipaddr));

                if (use_fork) {
                    if ((childpid = fork()) < 0)
                        log_fatal("fork() failed: errno = %d", errno);
                    
                    if (childpid == 0) {
                        /* Child process */
                        close(sockfd);
                        os_signal_child_clear();

                        session(config, newsockfd);
                        close(newsockfd);
                        exit(0);
                    }
                } else
                    session(config, newsockfd);

                /* Parent */
                close(newsockfd);
            }
        }

#ifdef USE_SSL
        /* Replace (shared) RSA key every few minutes */
        if (config->use_ssl) {
            iostream_check_rsakey(&ssl_config);
        }
#endif
    }
}

/* ====================================================================== */

/* Main routine:
 *  Parse command line options, then run as either permanant daemon or
 *  single shot login using file descriptor 0 for input and output */

int main(int argc, char **argv)
{
    BOOL use_fork = T;
    BOOL hermes = NIL;
    char *config_filename = ACCOUNTD_CONFIG_FILE;
    struct config *config = config_create();
#ifdef USE_SSL
    struct ssl_config ssl_config;
#endif
    int i;

    /* Disable supplementary groups, switch to group "other" (if it exists) */
    if (getuid() == 0) {
        struct group *group;

        setgroups(0, NIL);

        if ((group = getgrnam("hermes")) != NIL)
            setgid(group->gr_gid);
        if ((group = getgrnam("other")) != NIL)
            setgid(group->gr_gid);
        else if ((group = getgrnam("user")) != NIL)
            setgid(group->gr_gid);
    }

    if (getenv("ACCOUNTD_CONFIG_FILE"))
        config_filename = getenv("ACCOUNTD_CONFIG_FILE");

    log_init(config, argv[0]);

    /* Minimal Umask: don't want anyone reading user .forward files. */
    umask(0077);

    for (i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--enable-fork"))
            use_fork = T;
        else if (!strcmp(argv[i], "--disable-fork"))
            use_fork = NIL;
        else if (!strcmp(argv[i], "--hermes")) {
            hermes = T;
        } else if (!strncmp(argv[i],
                            "--config-file=", strlen("--config-file=")))
            config_filename = strdup(argv[i] + strlen("--config-file="));
        else if (!strcmp(argv[i], "--config-option"))
            i++;                /* Processes next argv */
        else if (!strcmp(argv[i], "--help")) {
            fprintf(stderr, "Command Line Options:\n");
            fprintf(stderr,
                    ("   --config-file=x        "
                     "Define location of configuration file\n"));
            fprintf(stderr,
                    ("   --config-option x=y    "
                     "Override option from configuration file\n"));
            fprintf(stderr,
                    ("   --enable-fork          "
                     "Allow fork if running as daemon (default)\n"));
            fprintf(stderr,
                    ("   --disable-fork         "
                     "Disable fork if running as daemon (debugging)\n"));
            exit(0);
        } else
            log_fatal("Unknown command line option");
    }

    if (!config_parse_file(config, config_filename))
        exit(1);

    for (i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--config-option")) {
            if (++i < argc) {
                if (!config_parse_option(config, strdup(argv[i])))
                    exit(1);
            } else
                fprintf(stderr, "--config processes following option");
        }
    }

    if (!config_check(config))
        exit(1);

    if (hermes)
        config->filter_restricted = T;

    /* Required for SSL stuff */
#ifdef USE_SSL
    if (config->use_ssl) {
        config_extract_ssl(config, &ssl_config);
        iostream_init(&ssl_config);
    }
#endif

    /* Run as daemon on nominated port */
    if (config->accountd_port) {
        rundaemon(config, use_fork, config->accountd_port);
        /* NOTREACHED */
        exit(0);
    }

    /* Otherwise run as single shot session on file descriptor 0
     * i.e: inetd service */
    session(config, 0);
    exit(0);
}
