/* $Cambridge: hermes/src/prayer/servers/session_unix.c,v 1.4 2010/07/02 14:32:06 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

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

#include "server.h"

/* Set of routines to manage incoming connections via proxy HTTP server
 * frontend */

/* session_unix_process_connection() ************************************
 *
 * Process incoming connection from frontend server (HTTP single shot).
 *  session:
 *   sockfd: Socket descriptor for incoming connection.
 ************************************************************************/

static void
session_unix_process_connection(struct session *session, int sockfd)
{
    struct config *config = session->config;
    struct iostream *stream = iostream_create(NIL, sockfd, 0);
    struct request *request = request_create(config, stream, NIL);
    struct ipaddr remote;
    int byte0, byte1;

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

    session->request = request;
    request->user_agent =
        user_agent_clone(request->pool, session->user_agent);

    if (session->config->http_dump)
        request_dump(request);

    if (session->telemetry)
        request_telemetry(request, session->telemetry_fd,
                          session->telemetry_all);

    /* Proxy: request follows IP address */
    if (!ipaddr_fetch_iostream(&remote, stream)) {
        struct buffer *b = request->write_buffer;

        html_common_start(config, b, "Protocol error");
        bputs(b, "Protocol error: couldn't determine IP address of peer.");
        html_common_end(b);
        response_html(request, 404);

        ioputc('0', request->stream);
        response_send(request);
        log_misc
            ("Couldn't determine IP address of peer for running session %s",
             session->url_prefix_asession);
        iostream_close(stream);
        request_free(request);
        return;
    }

    if (config->fix_client_ipaddr
        && !ipaddr_compare(session->ipaddr, &remote)) {
        struct buffer *b = request->write_buffer;

        html_common_start(config, b, "Security Alert");
        bputs(b, "Security alert: this session did not login ");
        bputs(b, "from this IP address. Please login properly" CRLF);
        html_common_end(b);
        response_html(request, 404);

        ioputc('0', request->stream);
        response_send(request);
        log_misc
            ("HTTP Request from invalid IP address %s to running session %s",
             ipaddr_text(&remote), session->url_prefix_asession);
        iostream_close(stream);
        request_free(request);
        return;
    }

    if (((byte0 = iogetc(stream)) == EOF)
        || ((byte1 = iogetc(stream)) == EOF)) {
        struct buffer *b = request->write_buffer;

        html_common_start(config, b, "Protocol error");
        bputs(b, "Protocol error: couldnt determine process ID of peer");
        html_common_end(b);
        response_html(request, 404);

        ioputc('0', request->stream);
        response_send(request);
        log_misc("Couldn't determine process ID of peer",
                 session->url_prefix_asession);
        iostream_close(stream);
        request_free(request);
        return;
    }
    session->frontend_pid = (byte0 * 256) + byte1;

    if (request_parse(request))
        session_exchange(session);      /* Send single response to frontend */
    else if (!request->iseof)
        response_error(request, request->status);

    if (request->persist) {
        ioputc('1', request->stream);
    } else {
        ioputc('0', request->stream);
    }
    response_send(request);

    session_accesslog(session, request);
    iostream_close(stream);
    request_free(request);
    session->request = NIL;
}

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

/* session_unix_loop() **************************************************
 *
 * Master loop for single login session using proxy server
 *   session:
 *    sockfd: Socket descriptor for Unix domain socket.
 *            (the thing that we are going to accept() on).
 ************************************************************************/

static BOOL session_unix_loop(struct session *session, int sockfd)
{
    struct config *config = session->config;
    int newsockfd;
    fd_set readfds;
    unsigned long timeout = 0;
    int rc;
    BOOL idle = NIL;
    char *last_cmd;
    int use_compose_timeout;

    /* Requests should queue on sockfd until we are ready to serve them */
    for (;;) {
        last_cmd = memblock_string(session->last_cmd);
        use_compose_timeout = NIL;

        if ((config->session_timeout_compose > 0) &&
            (!strcmp(last_cmd, "compose") || !strcmp(last_cmd, "sieve")))
            use_compose_timeout = T;

        if (idle) {
            if (use_compose_timeout)
                timeout = (config->session_timeout_compose -
                           config->session_idle_time);
            else if ((config->session_timeout > 0))
                timeout = config->session_timeout - config->session_idle_time;
        } else {
            if (config->session_idle_time > 0)
                timeout = config->session_idle_time;
            else if (use_compose_timeout)
                timeout = config->session_timeout_compose;
            else
                timeout = config->session_timeout;
        }

        FD_ZERO(&readfds);
        FD_SET(sockfd, &readfds);

        if (timeout > 0) {
            struct timeval timeval;

            timeval.tv_sec = timeout;
            timeval.tv_usec = 0;

            do {
                rc = select(sockfd + 1, &readfds, NIL, NIL, &timeval);
            }
            while ((rc < 0) && (errno == EINTR));
        } else {
            do {
                rc = select(sockfd + 1, &readfds, NIL, NIL, NIL);
            }
            while ((rc < 0) && (errno == EINTR));
        }

        if ((rc < 0) && (errno != EINTR)) {
            session_paniclog(session,
                             "[session_unix_loop()] select() failed, errno = %d",
                             errno);
            return (NIL);
        }

        /* Reopen logs if required */
        session_log_ping(session);
        log_misc_ping();

        if (!FD_ISSET(sockfd, &readfds)) {
            if (!idle && (config->session_idle_time > 0)) {
                session_log(session,
                            "[session_unix_loop] Session going to sleep");
                session_streams_idle(session);
                idle = T;
                continue;
            }

            session_log(session, "[session_unix_loop] Session timed out");
            break;
        }

        if (idle) {
            session_log(session,
                        "[session_unix_loop] Idle session waking up");
            idle = NIL;
        }

        if ((newsockfd = os_accept_unix(sockfd)) < 0)
            return (NIL);

        /* Following will always close newsockfd */
        session_unix_process_connection(session, newsockfd);

        if (session->want_disconnect)   /* Either client or servers wants out */
            break;
    }
    return (T);
}

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

/* session_unix() ********************************************************
 *
 * Run session using unix domain socket for communication via proxy frontend
 *  session:
 *   stream: iostream connection to prayer frontend. Used to send
 *           URL for sucessful login request.
 *
 * Returns: T if session successful. NIL => startup error.
 ************************************************************************/

BOOL session_unix(struct session * session, struct iostream * stream)
{
    struct config *config = session->config;
    int sockfd;
    char *name;
    unsigned long pid = (unsigned long)getpid();

    session->is_direct = NIL;

    /* Unix domain socket */

    /* Need to set up, bind() and listen() on  session stream before
     * we send any response to the frontend. Otherwise race condition
     * exists: frontend might try to connect to non-existant session...
     */

    if (config->socket_split_dir)
        name = pool_printf(NIL, "%s/%c/%s:%lu:%s", config->socket_dir,
                           session->sessionid[0],
                           session->username, pid, session->sessionid);
    else
        name = pool_printf(NIL, "%s/%s:%lu:%s", config->socket_dir,
                           session->username, pid, session->sessionid);

    if ((sockfd = os_bind_unix_socket(name)) < 0) {
        ioputs(stream, "NO Server failed to initialise" CRLF);
        ioflush(stream);
        iostream_close(stream);
        return (NIL);
    }

    /* Login suceeded: okay to tell frontend where to find us now! */
    ioprintf(stream, "OK %s@init" CRLF, session->url_prefix_session);
    ioflush(stream);
    iostream_close(stream);

    setproctitle("Login session for %s", session->username);

    /* Start session loop: accept process connections */
    session_unix_loop(session, sockfd);
    close(sockfd);

    /* Finished with socket file: remove */
    unlink(name);
    free(name);

    return (T);
}
