/* $Cambridge: hermes/src/prayer/servers/prayer_chroot.c,v 1.3 2008/09/16 09:59:57 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

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

#include "prayer_shared.h"
#include "prayer.h"
#include "prayer_server.h"

BOOL prayer_main_use_existing(struct prayer *prayer, char *ports)
{
    char *use_ssl, *fd, *next;

    while (ports) {
        if ((next = strchr(ports, ',')))
            *next++ = '\0';

        if (!(use_ssl = strchr(ports, ':')))
            return (NIL);
        *use_ssl++ = '\0';

        if (!(fd = strchr(use_ssl, ':')))
            return (NIL);
        *fd++ = '\0';

        if (((atoi(ports) == 0) || (atoi(fd) == 0)))
            return (NIL);

        if (!strcmp(use_ssl, "1"))
            prayer_http_port_alloc(prayer, atoi(ports), T, atoi(fd));
        else
            prayer_http_port_alloc(prayer, atoi(ports), NIL, atoi(fd));

        ports = next;
    }
    return (T);
}

BOOL prayer_main_exec(struct prayer * prayer, int argc, char *argv[])
{
    struct config *config = prayer->config;
    struct pool *p = pool_create(8192);
    struct buffer *b = buffer_create(p, 256);
    char *cmd;
    char **nargv = pool_alloc(p, (argc + 2) * sizeof(char *));
    int i;
    struct list_item *li;

    bputs(b, "--ports=");
    for (li = prayer->http_port_list->head; li; li = li->next) {
        struct prayer_http_port *fhp = (struct prayer_http_port *) li;

        if (fhp->use_ssl)
            bprintf(b, "%lu:1:%lu,", fhp->port, fhp->sockfd);
        else
            bprintf(b, "%lu:0:%lu,", fhp->port, fhp->sockfd);
    }
    cmd = pool_printf(p, "%s/prayer", config->bin_dir);

    for (i = 0; i < argc; i++)
        nargv[i] = argv[i];
    nargv[argc++] = buffer_fetch(b, 0, buffer_size(b) - 1, NIL);
    nargv[argc] = NIL;

    execv(cmd, nargv);
    prayer_fatal(prayer, "execv(%s) failed: %s", cmd, strerror(errno));
    /* NOTREACHED */
    return (NIL);
}

BOOL
prayer_main_start_session(struct prayer * prayer, int argc, char *argv[])
{
    struct config *config = prayer->config;
    char *cmd = pool_printf(NIL, "%s/prayer-session", config->bin_dir);
    char **nargv = pool_alloc(NIL, (1 + argc) * sizeof(char *));
    int i, j;

    nargv[0] = "prayer-session";
    i = 1;
    j = 1;

    while (i < argc) {
        if (!strcmp(argv[i], "--")) {
            i++;
            while (i < argc)
                nargv[j++] = argv[i++];
        } else if (!strcmp(argv[i], "--config-option")) {
            nargv[j++] = argv[i++];
            if (i < argc)
                nargv[j++] = argv[i++];
        } else
            if (!strncmp
                (argv[i], "--config-file=", strlen("--config-file=")))
            nargv[j++] = argv[i++];
        else
            i++;
    }
    nargv[j] = NIL;

    execv(cmd, nargv);
    prayer_fatal(prayer, "execv(%s) failed: %s", cmd, strerror(errno));
    /* NOTREACHED */
    return (NIL);
}

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

static BOOL prayer_main_write_pid(struct config *config)
{
    char *name = pool_printf(NIL, "%s/prayer.pid", config->pid_dir);
    FILE *file;

    if ((file = fopen(name, "w")) == NIL)
        return (NIL);

    fprintf(file, "%lu\n", (unsigned long) getpid());
    fclose(file);

    return (T);
}

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

int main(int argc, char *argv[])
{
    BOOL want_prefork = T;
    BOOL want_foreground = NIL;
    BOOL want_session = T;
    char *ports = NIL;
    char *config_filename = PRAYER_CONFIG_FILE;
    struct config *config = config_create();
    struct prayer *prayer;
    struct list_item *li;
    int i;
    extern char **environ;
    struct ssl_config ssl_config;

    /* Disable supplementary groups, look for (temporary) unprivileged group */
    if (getuid() == 0) {
        struct group *group;

        setgroups(0, NIL);

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

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

    initsetproctitle("prayer", argc, argv, environ);

    os_signal_init();

    response_record_version(VERSION_PRAYER);

    for (i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--help")) {
            fprintf(stderr, "Options:\n");
            fprintf(stderr,
                    ("  --config-file:     Define prayer config file\n"));
            fprintf(stderr,
                    ("                     "
                     "(Overrides compilation default and PRAYER_CONFIG_FILE)\n"));
            fprintf(stderr,
                    ("  --config-option:   Override single configuration option\n"));
            fprintf(stderr, "\n");
            fprintf(stderr,
                    ("  --foreground:      "
                     "Run single threaded server in  foreground\n"));
            fprintf(stderr,
                    ("  --disable-prefork: Run as simple fork()/exec() daemon\n"));
            fprintf(stderr,
                    ("  --disable-session: Don't start up prayer-session server\n"));

            fprintf(stderr,
                    ("  --help:            Show this list of options\n"));
            fprintf(stderr,
                    ("  --:                "
                     "End of prayer options: remaining options will be passed\n"));
            fprintf(stderr,
                    ("                     to prayer-session server process\n"));
            exit(0);
        } else if (!strcmp(argv[i], "--foreground"))
            want_foreground = T;
        else if (!strcmp(argv[i], "--disable-prefork"))
            want_prefork = NIL;
        else if (!strcmp(argv[i], "--disable-session"))
            want_session = NIL;
        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 (!strncmp(argv[i], "--ports=", strlen("--ports="))) {
            ports = argv[i] + strlen("--ports=");
        } else if (!strcmp(argv[i], "--")) {
            continue;
        } else {
            fprintf(stderr, "Unknown option: %s\n", argv[i]);
            exit(1);
        }
    }

    config = config_create();
    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 (!config_expand(config))
        exit(1);

    /* Generate a clean copy of the configuration without all of the noise */
    {
        struct config *config_clean = config_clone_parsed(config);

        config_free(config);
        config = config_clean;
    }

    /* Set up prayer frontend structure to bind everything together */
    prayer = prayer_create(config);

    if (ports == NIL) {
        if (config->http_port_list == NIL)
            prayer_fatal(prayer, "No HTTP or HTTPS ports defined");

        for (li = config->http_port_list->head; li; li = li->next) {
            struct config_http_port *chp = (struct config_http_port *) li;

            prayer_http_port_open(prayer, chp->port, chp->use_ssl,
                                  chp->interface);
        }
    } else if (!prayer_main_use_existing(prayer, ports))
        prayer_fatal(prayer, "Invalid --ports argument");

    if (list_length(prayer->http_port_list) == 0L)
        prayer_fatal(prayer, "No HTTP or HTTPS ports active");

    if (config->prayer_background && !want_foreground) {
        pid_t pid = fork();

        if (pid < 0)
            prayer_fatal(prayer,
                         "Failed to background server - fork() failed: %s",
                         strerror(errno));

        if (pid > 0)
            exit(0);            /* Parent process */

        /* Child process */

        /* Shut down stdin, stdout, stderr */
        close(0);
        close(1);
        close(2);
        open("/dev/null", O_RDONLY);    /* Reopen stdin */
        open("/dev/null", O_WRONLY);    /* Reopen stdout */
        open("/dev/null", O_WRONLY);    /* Reopen stderr */
    }

    /* Don't need root privs after ports bound, files open */

    if (getuid() == 0) {
        if ((config->prayer_uid == 0) || (config->prayer_gid == 0))
            log_fatal("Configured to run as root!");

        /* XXX Experiment XXX */
        if (config->tmp_dir)
            chdir(config->tmp_dir);
        if (chroot("/chroot/prayer") < 0) {
            prayer_fatal(prayer, "Chroot %s failed: %s",
                         config->var_prefix, strerror(errno));
        }

        if (config->prayer_gid) {
            setgid(config->prayer_gid);

            if (config->prayer_user && config->prayer_user[0])
                initgroups(config->prayer_user, config->prayer_gid);
        }

        if (config->prayer_uid)
            setuid(config->prayer_uid);

        if (getuid() == 0) {
            /* Stop impossible loop */
            log_fatal("Failed to lose root privileges");
        }

        os_prctl_set_dumpable();
    }

    if (getuid() == 0)
        log_fatal("Configured to run as root!");

    config_mkdirs(config);
    log_misc_init(config, argv[0], "prayer");

    prayer_log_open(prayer);

    if (config->limit_vm)
        os_limit_vm(config->limit_vm);

    if (config->tmp_dir)
        chdir(config->tmp_dir);

    /* Required for SSL stuff */
    config_extract_ssl(config, &ssl_config);
    iostream_init(&ssl_config);

    prayer_main_write_pid(config);

    if (want_prefork && !want_foreground)
        prayer_server_prefork(prayer);
    else
        prayer_server(prayer, want_foreground);

    /* NOTREACHED */
    return (0);
}
