/* $Cambridge: hermes/src/prayer/servers/prayer_server.c,v 1.12 2010/07/02 14:42:55 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_login.h"
#include "prayer_server.h"

static void
prayer_compose_dump(struct prayer *prayer, struct request *request, char *user)
{
    struct template_vals *tvals = prayer->template_vals;
    struct buffer *b = request->write_buffer;
    struct assoc *h  = NIL;
    char *s;

    request_decode_form(request);
    h = request->form;

    template_vals_string(tvals, "user", user);

    if ((s=assoc_lookup(h, "hdr_To")))
        template_vals_string(tvals, "$hdr_to", s);

    if ((s=assoc_lookup(h, "hdr_Cc")))
        template_vals_string(tvals, "$hdr_cc", s);

    if ((s=assoc_lookup(h, "hdr_Bcc")) && s[0])
        template_vals_string(tvals, "$hdr_bcc", s);
 
    if ((s=assoc_lookup(h, "hdr_Fcc")) && s[0])
        template_vals_string(tvals, "$hdr_fcc", s);
 
    if ((s=assoc_lookup(h, "hdr_Reply_To")) && s[0])
        template_vals_string(tvals, "$hdr_reply_to", s);

    if ((s=assoc_lookup(h, "hdr_Subject")))
        template_vals_string(tvals, "$hdr_subject", s);

    if ((s=assoc_lookup(h, "body")))
        template_vals_string(tvals, "body", s);
 
    template_expand("frontend_compose_timeout", tvals, b);
}

/* check_argv_valid_() ***************************************************
 *
 * Check that the username argument supplied in URL looks sane before
 * we try to build the path to a socket using it. Should be a short
 * but not empty string without any "/" or ".." sequences.
 *
 ************************************************************************/

static BOOL check_argv_valid(char *s)
{
    if (!(s && s[0] && (strlen(s) < 32)))
        return(NIL);

    /* Check for ".." or "/" embedded into username */
    while (*s) {
        if (s[0] == '/')
            return(NIL);

        if ((s[0] == '.') && (s[1] == '.'))
            return(NIL);
        s++;
    }

    return(T);
}

static void generate_connect_error_page(struct prayer *prayer)
{
    struct request *request = prayer->request;
    struct buffer *b = request->write_buffer;
    struct template_vals *tvals = prayer->template_vals;
    char *username = "";
    char *s;

    prayer_template_setup(prayer);
    tvals = prayer->template_vals;

    if (request->argc >= 1) {
        username = pool_strdup(request->pool, request->argv[1]);
        if ((s=strchr(username, ':')))
            *s = '\0';

        template_vals_string(tvals, "user", username);
    }

    if ((request->argc >= 5) && (!strcmp(request->argv[4], "compose")) &&
        (request->method == POST)) {
        prayer_compose_dump(prayer, request, username);
    } else {
        template_expand("frontend_timeout", tvals, b);
    }
    response_html(request, 404);
}

/* prayer_process_session_request() **************************************
 *
 * Process a session URL: connect to session server and proxy request
 * verbatim there
 ************************************************************************/

static BOOL prayer_process_session_request(struct prayer *prayer)
{
    struct config *config = prayer->config;
    struct request *request = prayer->request;
    struct buffer *b = request->write_buffer;
    struct iostream *stream;
    int fd, c;
    BOOL persist;
    char *socketname;

    /* Blind proxy to backend server */
    prayer->is_session = T;

    /* Check that the user hasn't foolishly removed the s from https */
    if (!prayer->use_ssl && config->ssl_required) {
        response_error(request, 403);   /* Forbidden */
        response_send(request);
        prayer_accesslog(prayer, request);
        return (NIL);
    }

    /* argv: /session/username/session/... */
    if (request->argc < 3) {
        response_error(request, 404);
        response_send(request);
        prayer_accesslog(prayer, request);
        return (NIL);
    }

    /* Decode username+pid argument */
    string_url_decode(request->argv[1]);

    /* Check decoded string for sneakiness */
    if (!check_argv_valid(request->argv[1])) {
        response_error(request, 404);
        response_send(request);
        prayer_accesslog(prayer, request);
        return (NIL);
    }

    /* Replace empty argv[2] with "CRSid=session" value if cookie exists */
    if (request->argv[2][0] == '\0') {
        char *s = assoc_lookup(request->hdrs, "cookie");
        char *t;
        char *u;

        /* Need cookie parsing library? */
        while (s && *s && (t = strchr(s, '='))) {
            *t++ = '\0';
            if ((u = strchr(t, ';')))
                *u++ = '\0';

            if (!strcmp(string_trim_whitespace(s), request->argv[1])) {
                request->argv[2] = string_trim_whitespace(t);
                break;
            }
            s = u;
        }
    }

    /* No session id, even after cookie lookup */
    if (request->argv[2][0] == '\0') {
        generate_connect_error_page(prayer);

        /* Do we need to kill session cookie? Next login should kill anyway */
        request->persist = NIL;
        response_send(request);
        prayer_accesslog(prayer, request);
        return (NIL);
    }

    /* Check decoded string for sneakiness */
    if (!check_argv_valid(request->argv[2])) {
        response_error(request, 404);
        response_send(request);
        prayer_accesslog(prayer, request);
        return (NIL);
    }

    if (config->socket_split_dir) {
        socketname = pool_printf(request->pool, "%s/%c/%s:%s",
                                 config->socket_dir, request->argv[2][0],
                                 request->argv[1], request->argv[2]);

        fd = os_connect_unix_socket(socketname);

        /* Backwards compatibility */
        if (fd < 0) {
            socketname =
                pool_printf(request->pool, "%s/%s:%s", config->socket_dir,
                            request->argv[1], request->argv[2]);
            fd = os_connect_unix_socket(socketname);
        }
    } else {
        socketname =
            pool_printf(request->pool, "%s/%s:%s", config->socket_dir,
                        request->argv[1], request->argv[2]);
        fd = os_connect_unix_socket(socketname);

        /* Forwards compatibility */
        if (fd < 0) {
            socketname = pool_printf(request->pool, "%s/%c/%s:%s",
                                     config->socket_dir,
                                     request->argv[2][0], request->argv[1],
                                     request->argv[2]);
            fd = os_connect_unix_socket(socketname);
        }
    }

    if (fd < 0) {
        generate_connect_error_page(prayer);

        /* Do we need to kill session cookie? Next login should kill anyway */
        request->persist = NIL;
        response_send(request);
        prayer_accesslog(prayer, request);
        return (NIL);
    }

    stream = iostream_create(request->pool, fd, 0);

    /* Set a (Long) timeout as backstop in case session goes into loop */
    iostream_set_timeout(stream, config->session_timeout);

    /* Copy IP address through to the session server */
    ipaddr_send_iostream(prayer->ipaddr, stream);

    /* Send Process ID to backend for logging purposes */
    ioputc((getpid() / 256), stream);
    ioputc((getpid() % 256), stream);

    /* Copy buffer verbatim */
    buffer_rewind(request->read_buffer);
    while ((c = bgetc(request->read_buffer)) != EOF)
        ioputc(c, stream);

    /* Flush data to backend */
    ioflush(stream);

    /* Bail out if far end died */
    if (stream->oerror) {
        iostream_close(stream);
        return (NIL);
    }

    /* Get persist status byte from backend */
    if ((c = iogetc(stream)) == EOF) {
        iostream_close(stream);
        return (NIL);
    }

    /* First byte sent to proxy is '1' or '0' indicating Keep-Alive state */
    persist = (c == '1');

    /* Now fetch entire response from backend before we start to pass it
     * on. This way backend isn't stalled if prayer is passing data on
     * across slow link and user shuts us down */
    while ((c = iogetc(stream)) != EOF)
        bputc(b, c);

    /* Finished with this connection to backup */
    iostream_close(stream);

    /* Pass cached copy onto client */
    buffer_rewind(b);
    while ((c = bgetc(b)) != EOF)
        ioputc(c, request->stream);

    return (persist);
}

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

/* prayer_process_request_internal() **************************************
 *
 * Process a single request to prayer frontend server
 *    prayer: Global state
 *   request: (Complete) HTTP request to process
 *    offset: Offset into request argv array: allows us to skip over
 *            metadata
 ************************************************************************/

static void
prayer_process_request_internal(struct prayer *prayer,
                                struct request *request)
{
    struct config *config = prayer->config;
    int argc    = request->argc;
    char **argv = &request->argv[0];

    /* Icons, if configured */
    if (config->icon_dir && (argc == 2) && !strcmp(argv[0], "icons")) {
        char *filename = pool_strcat3(request->pool, config->icon_dir, "/",
                                      argv[1]);
        if ((request->method == GET) || (request->method == HEAD)) {
            /* Send an icon */
            response_file(request, request->method,
                          filename, config->icon_expire_timeout,
                          assoc_lookup(request->hdrs,
                                       "if-modified-since"));
        } else
            response_error(request, 501);   /* Not implemented */
        return;
    }

    /* Static files (CSS/JS), if configured */
    if (config->static_dir && (argc == 2) && !strcmp(argv[0], "static")) {
        char *filename = pool_strcat3(request->pool, config->static_dir, "/",
                                      argv[1]);
        if ((request->method == GET) || (request->method == HEAD)) {
            response_file(request, request->method,
                          filename, config->static_expire_timeout,
                          assoc_lookup(request->hdrs, "if-modified-since"));
        } else
            response_error(request, 501);   /* Not implemented */
        return;
    }

    /* Cope with "/robots.txt" */
    if ((argc == 1) && (!strcasecmp(argv[0], "robots.txt"))) {
        struct buffer *b = request->write_buffer;

        bputs(b, "User-agent: *" CRLF);
        bputs(b, "Disallow: /session" CRLF);
        bputs(b, "Disallow: /login" CRLF);
        bputs(b, "Disallow: /icons" CRLF);
        response_text(request, 200);
        return;
    }

    /* Cope with /login/.../xyz screen */
    if ((argc == 2) && (!strcmp(argv[0], "login"))) {
        string_url_decode(request->argv[1]);
        prayer_login(prayer, request->argv[1]);
        return;
    }

    /* Deal with /raven request */
    if ((argc == 1) && (!strcmp(argv[0], "raven"))) {
        prayer_login_raven(prayer);
        return;
    }

    /* Page not found for anything other than the login page */
    if (argc > 0) {
        response_error(request, 404);
        return;
    }

    prayer_login_preamble(prayer);
}

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

/* prayer_process_request() **********************************************
 *
 * Process a single request to prayer frontend server.
 *    prayer: Global state
 *   request: (Complete) HTTP request to process
 ************************************************************************/

static BOOL
prayer_process_request(struct prayer *prayer, struct request *request)
{
    prayer->is_session = NIL;
    prayer_process_request_internal(prayer, request);
    response_send(request);
    prayer_accesslog(prayer, request);

    return (request->persist);
}

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

/* prayer_process_connection() *******************************************
 *
 * Process HTTP connection (which may consist of one or many separate
 * HTTP requests).
 *    prayer:
 *    sockfd: Socket descriptor for incoming connection
 ************************************************************************/

static BOOL prayer_process_connection(struct prayer *prayer, int sockfd)
{
    struct config *config = prayer->config;
    struct iostream *stream;
    BOOL persist;
    unsigned long request_count;

    stream = iostream_create(prayer->pool, sockfd, 0);
    iostream_set_timeout(stream, config->http_timeout_icons);

    if (prayer->use_ssl && !iostream_ssl_start_server(stream)) {
        log_misc("Invalid https connection from %s on port %lu",
                 ipaddr_text(prayer->ipaddr), prayer->port);

        iostream_close(stream);
        return (NIL);
    }

    log_misc("Incoming %s connection from %s on port %lu",
             (prayer->use_ssl) ? "https" : "http",
             ipaddr_text(prayer->ipaddr), prayer->port);

    prayer->is_session = T;     /* Until we know better */

    request_count = 0;
    do {
        struct request *request = request_create(config, stream, T);
        struct user_agent *user_agent = user_agent_create(request->pool);

        prayer->request = request;
        request->user_agent = user_agent;

        if (prayer->is_session)
            iostream_set_timeout(stream, config->http_timeout_session);
        else
            iostream_set_timeout(stream, config->http_timeout_icons);

        setproctitle("%s %s connection from %s on port %lu",
                     (prayer->is_session) ? "Session" : "Icon",
                     (prayer->use_ssl) ? "https" : "http",
                     ipaddr_text(prayer->ipaddr), prayer->port);

        if (!request_parse(request)) {
            /* EOF or badly forwarded request */
            if (!request->iseof) {
                response_error(request, request->status);
                response_send(request);
                log_misc("Invalid %s request: %s",
                         (prayer->use_ssl) ? "https" : "http",
                         request->request);
            }
            request_free(request);
            break;
        }

        /* Do something useful with HTTP/0.9 request */
        if (request->major == 0) {
            response_0_9_error(request);
            response_send(request);
            log_misc("Invalid %s request (0.9 format): %s",
                     (prayer->use_ssl) ? "https" : "http",
                     request->request);
            break;
        }

        /* Got a valid request: process it */
        request_count++;

        /* Set up browser specific options and workarounds, e.g:
         *  Lynx and w3m don't need icons
         *  Netscape 4 needs some delibarately broken markup to work properly.
         *  Some old versions of Mac Mozilla have broken SSL implementations.
         */
        user_agent_setup_browser(user_agent,
                                 assoc_lookup(request->hdrs,
                                              "user-agent"));
        request_parse_argv(request);

        if ((request->argc > 0) && !strcmp(request->argv[0], "session")) {
            persist = prayer_process_session_request(prayer);
        } else
            persist = prayer_process_request(prayer, request);

        if (persist && request->use_http_1_1 &&
            user_agent->use_pipelining
            && iostream_have_input(request->stream))
            log_misc("Pipelining on port %lu", prayer->port);
        else if (!ioflush(request->stream))
            persist = NIL;  /* Close connection if we failed to sent to UA */

        request_free(request);

        /* Ping access and miscellaneous logs every few minutes */
        prayer_log_ping(prayer);
        log_misc_ping();

    }
    while (persist);

    iostream_close(stream);

    if (request_count != 1)
        log_misc("Closing %s connection, %lu valid requests processed",
                 (prayer->use_ssl) ? "https" : "http", request_count);
    else
        log_misc("Closing %s connection, 1 valid request processed",
                 (prayer->use_ssl) ? "https" : "http");

    return (T);
}

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

/* prayer_server_single() ************************************************
 *
 * Single server instance used simple fork server
 *      prayer: Global state
 *  foreground: T => Run in foreground: don't fork off child processes
 *      sockfd: Socket descriptor (thing that we accept() on) 
 *        port: Port associated with this socket descriptor
 *     use_ssl: Use SSL for connections to this port
 ************************************************************************/

static BOOL
prayer_server_single(struct prayer *prayer, BOOL foreground,
                     int sockfd, unsigned long port, BOOL use_ssl)
{
    int newsockfd;
    int childpid;
    struct ssl_config ssl_config;

    config_extract_ssl(prayer->config, &ssl_config);

    prayer->port = port;
    prayer->use_ssl = use_ssl;
    prayer->is_session = T;     /* Until we know better */

    if ((newsockfd = os_accept_inet(sockfd, prayer->ipaddr)) < 0)
        return (NIL);           /* May be transcient error: continue */

    if (foreground) {
        /* prayer_process_connection() always closes newsockfd */
        prayer_process_connection(prayer, newsockfd);
        return (T);
    }

    if ((childpid = fork()) < 0) {
        prayer_paniclog(prayer,
                        "[prayer_server_single()] fork() failed: %s",
                        strerror(errno));
        /* May be transcient error: continue */
    }

    if (childpid == 0) {
        /* Child process */
        if (!os_signal_child_clear())
            abort();

        close(sockfd);

        /* Make sure that RSAkey isn't going to expire immediately */
        iostream_freshen_rsakey(&ssl_config);

        /* prayer_process_connection() always closes newsockfd */
        prayer_process_connection(prayer, newsockfd);
        exit(0);
    }

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

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

/* prayer_server() *******************************************************
 *
 * Master server routine for simple fork()/exec() server.
 *     prayer: Global configuration
 * foreground: Run in foreground rather than forking off children
 ************************************************************************/

BOOL prayer_server(struct prayer * prayer, BOOL foreground)
{
    struct list_item *li;
    fd_set fds;
    int maxsockfd;
    struct ssl_config ssl_config;

    config_extract_ssl(prayer->config, &ssl_config);

    if (!os_signal_child_init(os_child_reaper))
        return (NIL);

    log_misc("Frontend master server started okay");
    setproctitle("Master server");

    for (;;) {
        FD_ZERO(&fds);

        maxsockfd = 0;
        for (li = prayer->http_port_list->head; li; li = li->next) {
            struct prayer_http_port *fhp = (struct prayer_http_port *) li;
            FD_SET(fhp->sockfd, &fds);
            if (fhp->sockfd > maxsockfd)
                maxsockfd = fhp->sockfd;
        }

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

        for (li = prayer->http_port_list->head; li; li = li->next) {
            struct prayer_http_port *fhp = (struct prayer_http_port *) li;

            if (FD_ISSET(fhp->sockfd, &fds))
                prayer_server_single(prayer, foreground,
                                     fhp->sockfd, fhp->port, fhp->use_ssl);
        }

        /* Replace (shared) RSA key every few minutes */
        iostream_check_rsakey(&ssl_config);

        /* Ping access and miscellaneous logs every few minutes */
        prayer_log_ping(prayer);
        log_misc_ping();
    }
    return (T);
}

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

/* Prefork version */

/* prayer_slave_server() *************************************************
 *
 * Single server instance used by prefork server.
 *    prayer: Global state
 *   mymutex: Mymutex for select/accept cycle
 *  parentfd: Connection to parent process. Close to indicate that
 *            this child process is now active.
 ************************************************************************/

static BOOL prayer_slave_server(struct prayer *prayer,
                                struct mymutex *mymutex,
                                int parentfd)
{
    struct prayer_http_port *fhp;
    struct config *config = prayer->config;
    unsigned long max_connections = config->http_max_connections;
    struct list_item *li;
    fd_set fds;
    int maxsockfd;
    int newsockfd;
    int rc;
    unsigned long connect_count;
    unsigned long mymutex_timeout;
    struct ssl_config ssl_config;

    config_extract_ssl(config, &ssl_config);

    if (!os_signal_child_init(os_child_reaper))
        return (NIL);

    if (!mymutex_slave_init(mymutex))
        return(NIL);

    log_misc("Frontend slave server started okay");

    for (connect_count = 0;
         ((max_connections == 0) || (connect_count < max_connections));
         connect_count++) {
        FD_ZERO(&fds);

        maxsockfd = 0;
        for (li = prayer->http_port_list->head; li; li = li->next) {
            struct prayer_http_port *fhp = (struct prayer_http_port *) li;
            FD_SET(fhp->sockfd, &fds);
            if (fhp->sockfd > maxsockfd)
                maxsockfd = fhp->sockfd;
        }

        setproctitle("Spare slave server");

        if ((connect_count > 0) && (config->http_timeout_idle > 0)) 
            mymutex_timeout = config->http_timeout_idle;
        else
            mymutex_timeout = 0;

        if (!mymutex_on(mymutex, mymutex_timeout))
            break;

        setproctitle("Active slave server");

        if ((connect_count > 0) && (config->http_timeout_idle)) {
            struct timeval timeout;

            timeout.tv_sec = config->http_timeout_idle;
            timeout.tv_usec = 0;

            do {
                rc = select(maxsockfd + 1, &fds, NIL, NIL, &timeout);
            }
            while ((rc < 0) && (errno == EINTR));
        } else {
            do {
                rc = select(maxsockfd + 1, &fds, NIL, NIL, NIL);
            }
            while ((rc < 0) && (errno == EINTR));
        }

        /* This slave server is now busy, will die after timeout */
        /* Tell parent to start up fresh spare slave server */
        if (parentfd >= 0) {
            close(parentfd);
            parentfd = -1;
        }

        if (rc < 0)
            prayer_fatal(prayer, "prayer_server(): select() failed: %s",
                         strerror(errno));

        /* Shut down connection on timeout */
        if (rc == 0)
            break;

        /* Ping access and miscellaneous logs every few minutes */
        prayer_log_ping(prayer);
        log_misc_ping();

        for (li = prayer->http_port_list->head; li; li = li->next) {
            fhp = (struct prayer_http_port *) li;

            if (FD_ISSET(fhp->sockfd, &fds))
                break;
        }
        if (li == NIL)
            prayer_fatal(prayer,
                         "prayer_server(): select() returned with no fds set");

        fhp = (struct prayer_http_port *) li;
        prayer->port = fhp->port;
        prayer->use_ssl = fhp->use_ssl;
        prayer->is_session = T; /* Until we know better */

        newsockfd = os_accept_inet(fhp->sockfd, prayer->ipaddr);

        if (!mymutex_off(mymutex))
            prayer_fatal(prayer,
                         "prayer_server(): failed to release lock: %s",
                         strerror(errno));

        if (newsockfd < 0)
            continue;           /* May be transcient error: continue */

        /* Make sure that RSAkey isn't going to expire immediately */
        iostream_freshen_rsakey(&ssl_config);

        prayer_process_connection(prayer, newsockfd);
        close(newsockfd);
    }

    mymutex_slave_cleanup(mymutex);
    return (T);
}

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

static unsigned long prayer_prefork_children = 0;

static void prayer_prefork_child_reaper()
{
    int status;
    pid_t child;

    do {
        child = waitpid(0, &status, WNOHANG);
        if ((child > 0) && (prayer_prefork_children > 0))
            prayer_prefork_children--;
    }
    while (child > 0);
}

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

/* prayer_prefork_server() ***********************************************
 *
 * Master routine for prefork server.
 *    prayer: Global state.
 ************************************************************************/

BOOL prayer_server_prefork(struct prayer *prayer)
{
    struct config *config = prayer->config;
    unsigned long i, j;
    unsigned long spare_servers = config->http_min_servers;
    pid_t childpid;
    int *child_fds = pool_alloc(NIL, spare_servers * sizeof(int));
    struct mymutex *mymutex;
    struct ssl_config ssl_config;

    config_extract_ssl(config, &ssl_config);

    if (!(mymutex=mymutex_create(config->pool, config->lock_dir, "accept_lock")))
        log_fatal("Failed to create accept mymutex");

    /* Enable alarm handler */
    if (!os_signal_child_init(prayer_prefork_child_reaper))
        return (NIL);

    log_misc("Frontend master server started okay");
    setproctitle("Master server");

    for (i = 0; i < spare_servers; i++) {
        int sockfd[2];

        if (!os_socketpair(sockfd))
            prayer_fatal(prayer,
                         "[prayer_server_prefork()] socketpair() failed: %s",
                         strerror(errno));

        if ((childpid = fork()) < 0)
            prayer_fatal(prayer,
                         "[prayer_server_prefork()] initial fork() failed: %s",
                         strerror(errno));

        if (childpid == 0) {
            for (j = 0; j < i; j++)
                close(child_fds[j]);

            close(sockfd[0]);
            prayer_slave_server(prayer, mymutex, sockfd[1]);
            exit(0);
        }
        child_fds[i] = sockfd[0];
        close(sockfd[1]);
        prayer_prefork_children++;
    }

    for (;;) {
        fd_set fds;
        int maxsockfd = 0;
        int sockfd[2];
        unsigned long fork_attempts;

        FD_ZERO(&fds);
        for (i = 0; i < spare_servers; i++) {
            FD_SET(child_fds[i], &fds);
            if (child_fds[i] > maxsockfd)
                maxsockfd = child_fds[i];
        }

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

        for (i = 0; i < spare_servers; i++) {
            if (FD_ISSET(child_fds[i], &fds)) {
                close(child_fds[i]);

                if (!os_socketpair(sockfd))
                    prayer_fatal(prayer,
                                 "[prayer_server_prefork()] socketpair() failed: %s",
                                 strerror(errno));

                fork_attempts = 0;
                while ((childpid = fork()) < 0) {
                    prayer_paniclog(prayer,
                                    "[prayer_server_prefork()] fork() failed: %s",
                                    strerror(errno));

                    /* fork() failure may be transient error: Retry a few times */
                    if (++fork_attempts < 10) {
                        sleep(30);
                        continue;
                    }

                    /* And then give up completely */
                    exit(1);
                }
                if (childpid == 0) {
                    for (i = 0; i < spare_servers; i++)
                        close(child_fds[i]);

                    close(sockfd[0]);
                    prayer_slave_server(prayer, mymutex, sockfd[1]);
                    exit(0);
                }
                child_fds[i] = sockfd[0];
                close(sockfd[1]);
                prayer_prefork_children++;
            }
        }

        if (config->http_max_servers > 0) {
            while (prayer_prefork_children > config->http_max_servers)
                sleep(1);       /* Wait for SIGCHLD */
        }

        /* Replace (shared) RSA key every few minutes */
        iostream_check_rsakey(&ssl_config);

        /* Ping access and miscellaneous logs every few minutes */
        prayer_log_ping(prayer);
        log_misc_ping();
    }

    mymutex_free(mymutex);
    return (T);
}
