/* $Cambridge: hermes/src/prayer/session/session.c,v 1.11 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 "prayer_session.h"

/* Class containing global data for login sessions (includes large number of
 *  links to subsiduary structures */

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

/* session_log_open() ***************************************************
 *
 * Open access and session log files.
 *   session: Global state
 ***********************************************************************/

BOOL session_log_open(struct session *session)
{
    if (!log_open(session->accesslog, "access_log"))
        return (NIL);

    if (!log_open(session->sessionlog, "session_log"))
        return (NIL);

    return (T);
}

/* session_log_ping() ***************************************************
 *
 * Reopen access and session log file.
 *   session: Global state
 ***********************************************************************/

BOOL session_log_ping(struct session * session)
{
    if (!log_ping(session->accesslog))
        return (NIL);

    if (!log_ping(session->sessionlog))
        return (NIL);

    return (T);
}

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

/* session_create() *****************************************************
 *
 * Create a fresh session structure including its own pool.
 *  config: Prayer configuration
 *
 * Returns: Ptr to new session structure
 ***********************************************************************/

struct session *session_create(struct config *config)
{
    struct pool *p = pool_create(4096);
    struct session *session = pool_alloc(p, sizeof(struct session));
    time_t now = time(NIL);

    memset(session, 0, sizeof(struct session));

    session->pool = p;
    session->config = config;

    /* Set up log files */
    session->accesslog = log_create(config, p);
    session->sessionlog = log_create(config, p);
    session_log_open(session);

    /* General session stuff */
    session->debug = NIL;
    session->want_disconnect = NIL;
    string_strdup(&session->dir_filter, "");       /* Directory filter */
    session->username = "";     /* No username yet */
    session->password = "";     /* No password yet */
    session->newpassword = NIL; /* No new password yet */

    session->last_cmd = memblock_create(p, 128);        /* Last cmd string */

    session->last_help = memblock_create(p, 128);       /* Help cmd string */

    /* Seed connections to support servers */
    session->account = account_create(p, session);
    session->sieve = sieve_create(p, session);
    session->use_sieve = NIL;

    /* Location of servers */
    session->imapd_server = NIL;
    session->accountd_server = NIL;
    session->accountd_nis_server = NIL;

    /* Persistent session state */
    session->rename_foldername = NIL;   /* Name of folder to be renamed */
    session->aggregate = NIL;   /* Aggregate operation */
    session->reply_all = NIL;   /* Reply to all        */
    session->half_filter = NIL; /* Half build filter item */
    session->upload_name = NIL; /* Name for folder upload */
    session->upload_file = NIL; /* Temp file for folder upload */
    session->abook_compose_name = NIL;  /* Name and address for        */
    session->abook_compose_email = NIL; /* Abook compose option        */
    session->help_enabled = NIL;        /* Help text enabled */
    session->help_toolbar = NIL;        /* Toolbar Help text enabled */
    session->recips_count_session =  0; /* Running total to spot phishers */
    session->sending_allow = NIL;       /* Whitelist to override: */
    session->sending_block = NIL;       /* Shut down compromised accounts */

    session->telemetry = NIL;   /* Telemetry information */
    session->telemetry_all = NIL;
    session->telemetry_fd = -1;

    /* HTTP Connection stuff, including request/response */
    session->request = NIL;     /* No request yet */
    session->ipaddr = ipaddr_create(p); /* Record IP address of client */
    session->frontend_pid = 0;  /* PID of frontend process     */
    session->sequence = 0;      /* Sequence number */
    session->sequence_last_change = 0;  /* of last folder change */
    session->sequence_okay = T;
    session->use_ssl = NIL;
    session->frontend_port = 0L;        /* Set up by active session */
    session->session_port = 0L;
    session->is_direct = NIL;
    session->url_prefix = "";
    session->url_prefix_icons = "";
    session->url_prefix_asession = "";
    session->url_prefix_bsession = "";
    session->url_prefix_short = "";
    session->url_prefix_session = "";
    session->sessionid = "";

    /* User Interface stuff */
    session->use_cookie = NIL;  /* Until we know better */
    session->use_short = NIL;   /* Until we know better */
    session->use_gzip = T;      /* Until we know better */
    session->abook_parent_cmd = "list"; /* Parent cmd for abook stuff */
    session->compose_parent_cmd = "list";    /* Parent cmd for compose */
    session->take_parent_cmd    = "display"; /* Parent cmd for abook_take */
    session->copy_parent_cmd = "list";  /* Parent cmd for copy */
    session->compose_large = NIL;       /* Large compose window */
    session->full_hdrs = NIL;   /* Show full hdrs */

    /* Define main and help themes */
    session->theme_main = config->theme_main;
    session->theme_help = config->theme_help;

    /* Set up for each new action */
    session->template_vals = NIL;

    /* Folder stuff */
    session->personal_hierarchy = config->personal_hierarchy;
    session->hiersep = config->hiersep;

    session->foldername = pool_strdup(NIL, "INBOX");    /* Name of current mail folder */
    session->other_foldername = NIL;    /* Name of "other" mail folder */
    session->draft_foldername = NIL;    /* Name of "draft" mail folder */
    session->stream = NIL;      /* Mail stream  */
    session->inbox_stream = NIL;        /* Inbox stream */
    session->other_stream = NIL;        /* Other stream */
    session->draft_stream = NIL;        /* Draft stream */
    session->prefs_stream = NIL;        /* Preferences stream */
    session->xfer_stream = NIL; /* Transfer stream */
    session->inbox_last_ping_time = now;        /* Record current time */
    session->other_last_ping_time = now;        /* Record current time */
    session->draft_last_ping_time = now;        /* Record current time */
    session->prefs_last_ping_time = now;        /* Record current time */
    session->xfer_last_ping_time = now; /* Record current time */
    session->current = 0L;      /* Current message */
    session->last_displayed = 0L;       /* Last message displayed */

    session->zm = msgmap_create(p);     /* Zoommap */
    session->message = memblock_create(p, 64);  /* Feedback message */
    session->message_isalert = NIL;
    session->options = options_create(config, p);       /* Set up default options */
    session->lookup  = lookup_create(session, p);
                                                /* List of mailboxes */
    session->folderlist = folderlist_create(config->hiersep);  
    session->speller = speller_create(p);       /* Spell check */
    session->draft = draft_create(session);     /* Draft message    */
    session->draft0 = draft_create(session);    /* Previous Draft message */
    session->ua_initial = user_agent_create(p); /* Initial User agent config */
    session->user_agent = user_agent_create(p); /* User agent config */
    return (session);
}

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

/* session_free() ********************************************************
 *
 * Free session and all subsiduary structures.
 ***********************************************************************/

void session_free(struct session *session)
{
    log_free(session->accesslog);
    log_free(session->sessionlog);
    session_streams_close(session);

    string_free((void **) &session->dir_filter);

    memblock_free(session->last_cmd);
    memblock_free(session->last_help);
    account_free(session->account);
    sieve_free(session->sieve);

    string_free((void **) &session->rename_foldername);

    if (session->half_filter)
        filter_free(session->half_filter);

    if (session->upload_file) {
        unlink(session->upload_file);
        string_free((void **) &session->upload_file);
    }
    string_free((void **) &session->upload_name);

    string_free((void **) &session->abook_compose_name);
    string_free((void **) &session->abook_compose_email);

    if (session->request)
        request_free(session->request);

    string_free((void **) &session->foldername);
    string_free((void **) &session->other_foldername);
    string_free((void **) &session->draft_foldername);

    msgmap_free(session->zm);
    memblock_free(session->message);
    options_free(session->options);
    lookup_free(session->lookup);
    folderlist_free(session->folderlist);
    speller_free(session->speller);
    draft_free(session->draft);
    draft_free(session->draft0);
    user_agent_free(session->user_agent);

    if (session->pool)
        pool_free(session->pool);
}

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

/* XXX Should become redundant with templates */

/* session_update_sequence() ********************************************
 *
 * Update various url_prefixes with correct sequence signature.
 ***********************************************************************/

void session_update_sequence(struct session *session)
{
    struct request *request = session->request;
    struct pool *pool = (request) ? request->pool : NIL;
    unsigned char s[3];
    unsigned char d[5];
    char *v =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-";

    s[0] = (session->sequence >> 16) & 255;     /* Bits 23->16 */
    s[1] = (session->sequence >> 8) & 255;      /* Bits 15->8  */
    s[2] = (session->sequence) & 255;   /* Bits  7->0  */

    /* Quick (URL friendly) base64 encode of three byte sequence */
    d[0] = v[s[0] >> 2];
    d[1] = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f];
    d[2] = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
    d[3] = v[s[2] & 0x3f];
    d[4] = '\0';

    session->url_prefix_bsession
        = pool_printf(pool,
                      "%s/%s",
                      session->url_prefix_asession,
                      ((session->use_cookie) ? "" : session->sessionid));

    session->url_prefix_short = pool_strdup(pool, (char *) d);

    session->url_prefix_session
        =
        pool_printf(pool, "%s/%s", session->url_prefix_bsession,
                    (char *) d);
}

/* session_bump_sequence() **********************************************
 *
 * Increase session sequence number.
 ***********************************************************************/

void session_bump_sequence(struct session *session)
{
    session->sequence++;
}

/* session_extract_urlstate() ********************************************
 *
 * Extract urlstate information used by the template system;
 ************************************************************************/

struct template_vals_urlstate *
session_extract_urlstate(struct session *session)
{
    struct template_vals_urlstate *urlstate
        = template_vals_urlstate_create(session->request->pool);

    urlstate->url_prefix_icons    = session->url_prefix_icons;
    urlstate->url_prefix_bsession = session->url_prefix_bsession;
    urlstate->sequence            = session->sequence;
    urlstate->use_short           = session->use_short;

    return(urlstate);
}

/* session_seed_template() *********************************************
 *
 * Seed gloval variables used by the template system.
 *
 ***********************************************************************/

void
session_seed_template(struct session *session, struct template_vals *tvals)
{
    struct config *config = session->config;
    struct prefs  *prefs  = session->options->prefs;
    struct favourite_list *fl = session->options->favourite_list;
    struct draft  *draft  = session->draft;
    struct config_theme *theme = session->theme_main;
    struct memblock *m = session->message;
    struct user_agent *user_agent = session->user_agent;
    char *msg = NIL;

    if ((memblock_size(m) > 0))
        msg = memblock_data(m);

    if (session->help_enabled) {
        template_vals_ulong(tvals, "g_help", 1);
        theme = session->theme_help;
    }
    if (session->help_toolbar)
        template_vals_ulong(tvals, "g_help_toolbar", 1);

    template_vals_string(tvals, "g_user", session->username);
    if (config->login_service_name)
        template_vals_string(tvals, "g_service_name",
                             config->login_service_name);

    if (config->dualuse)
        template_vals_ulong(tvals, "g_dualuse", 1);

    if (msg && msg[0]) {
        template_vals_string(tvals, "g_status", msg);
        if (session->message_isalert)
            template_vals_ulong(tvals, "g_status_alert", 1);
    }

    session_message_clear(session);

    if (user_agent->use_icons)
        template_vals_ulong(tvals, "g_use_icons", 1);

    if (draft->have_draft)
        template_vals_ulong(tvals, "g_have_draft", 1);

    template_vals_string(tvals, "g_alt_pad", "&nbsp;&nbsp;&nbsp;&nbsp;");

    template_vals_hash_string(tvals, "g_theme", "name",
                              theme->name);
    template_vals_hash_string(tvals, "g_theme", "description",
                              theme->description);
    template_vals_hash_string(tvals, "g_theme", "fgcolor",
                              theme->fgcolor);
    template_vals_hash_string(tvals, "g_theme", "fgcolor_link",
                              theme->fgcolor_link);
    template_vals_hash_string(tvals, "g_theme", "bgcolor",
                              theme->bgcolor);
    template_vals_hash_string(tvals, "g_theme", "bgcolor_banner",
                              theme->bgcolor_banner);
    template_vals_hash_string(tvals, "g_theme", "bgcolor_row1",
                              theme->bgcolor_row1);
    template_vals_hash_string(tvals, "g_theme", "bgcolor_row2",
                              theme->bgcolor_row2);
    template_vals_hash_string(tvals, "g_theme", "bgcolor_status",
                         theme->bgcolor_status);
    template_vals_hash_string(tvals, "g_theme", "bgcolor_status_none",
                         theme->bgcolor_status_none);
    template_vals_hash_string(tvals, "g_theme", "fgcolor_quote1",
                         theme->fgcolor_quote1);
    template_vals_hash_string(tvals, "g_theme", "fgcolor_quote2",
                         theme->fgcolor_quote2);
    template_vals_hash_string(tvals, "g_theme", "fgcolor_quote3",
                         theme->fgcolor_quote3);
    template_vals_hash_string(tvals, "g_theme", "fgcolor_quote4",
                              theme->fgcolor_quote4);

    folderlist_template_vals_list(folderlist_fetch(session),
                                  prefs->suppress_dotfiles, tvals,
                                  NIL, "@g_mailbox_list");

    favourite_template_vals(fl, tvals, "@g_favourites");
    template_vals_string(tvals, "$g_preferred", fl->preferred);
}

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

/* session_make_id() ****************************************************
 *
 * Make (crypographically secure) unique session identifier for this
 * login session. Recorded as session->sessionid.
 ***********************************************************************/

static void session_make_id(struct session *session)
{
    struct pool *pool = session->pool;
    struct config *config = session->config;
    unsigned long srcl = 18;
    unsigned char *raw;
    char *encode;
    unsigned char *s;
    unsigned char *d;
    struct ssl_config ssl_config;
    char *v =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-";

    srcl = (config->session_key_len) ? config->session_key_len : 18;
    raw = pool_alloc(pool, srcl);
    encode = pool_alloc(pool, 10 + (srcl * 4) / 3);

    config_extract_ssl(config, &ssl_config);
    if (!os_random(&ssl_config, raw, srcl)) {
        log_fatal("Couldn't read %lu bytes from random number generator",
                  srcl);
        /* NOTREACHED */
        exit(1);
    }

    /* Encode binary data to URL friendly form */

    s = raw;
    d = (unsigned char *) encode;

    while (srcl) {
        *d++ = v[s[0] >> 2];    /* byte 1: high 6 bits (1) */
        /* byte 2: low 2 bits (1), high 4 bits (2) */
        *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
        /* byte 3: low 4 bits (2), high 2 bits (3) */
        *d++ =
            srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] :
            '=';
        /* byte 4: low 6 bits (3) */
        *d++ = srcl ? v[s[2] & 0x3f] : '=';
        if (srcl)
            srcl--;             /* count third character if processed */
        s += 3;                 /* process tuplets */
    }
    *d = '\0';

    session->sessionid = (char *) encode;
}

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

static void
session_setup_namespace(struct session *session)
{
    MAILSTREAM *stream = session->stream;
    NAMESPACE  ***namespacep = mail_parameters(stream, GET_NAMESPACE, NULL);

    if (namespacep && *namespacep && **namespacep) {
        NAMESPACE *ns = **namespacep;

        if (ns->name && ns->name[0])
            session->personal_hierarchy = ns->name;

        if (ns->delimiter)
            session->hiersep = pool_printf(NIL, "%c", ns->delimiter);
    }
}

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

/* NB: session_setup_urls() called at least three times for normal direct
 * login: Initial, session_inet and user preferences setup. malloc/free
 * might be better than pool_alloc(session->pool, ...). However its is
 * only a few bytes that we are talking about here... */

/* session_setup_urls() *************************************************
 *
 * Setup session URLs to correspond to current internal state. Includes
 * some ghastly magic to include user agent preference overrides in
 * icon links.
 ***********************************************************************/

void session_setup_urls(struct session *session)
{
    struct config *config = session->config;
    struct user_agent *user_agent = session->user_agent;
    struct pool *p = session->pool;
    char *icon_opts;
    char *quoted_user;
    unsigned long pid = (unsigned long)getpid();

    if (user_agent->manual) {
        struct buffer *b = buffer_create(p, 128);

        bputs(b, "/opts=");

        if (user_agent->use_http_1_1)
            bputs(b, "http_1.1,");

        if (user_agent->use_persist)
            bputs(b, "persist,");

        if (user_agent->use_pipelining)
            bputs(b, "pipelining,");

        if (user_agent->use_telemetry_frontend)
            bputs(b, "telemetry_frontend,");

        if (buffer_size(b) > strlen("/opts="))
            icon_opts = buffer_fetch(b, 0, buffer_size(b) - 1, NIL);
        else
            icon_opts = "/opts=";
    } else
        icon_opts = "";

    /* Time to define some URLs for this session */

    /* Base URL: route back to the login screen */
    if (session->use_ssl) {
        if (session->frontend_port == 443)
            session->url_prefix =
                pool_printf(p, "https://%s", config->hostname);
        else
            session->url_prefix = pool_printf(p, "https://%s:%d",
                                              config->hostname,
                                              session->frontend_port);
    } else {
        if (session->frontend_port == 80)
            session->url_prefix =
                pool_printf(p, "http://%s", config->hostname);
        else
            session->url_prefix = pool_printf(p, "http://%s:%d",
                                              config->hostname,
                                              session->frontend_port);
    }

    /* Set up appropriate url_icon_prefix */
    session->url_prefix_icons = pool_printf(p, "/icons%s", icon_opts);

    quoted_user = string_url_encode(p, session->username);

    /* url_prefix_asession: root for session URLs */
    if (session->use_ssl) {
        if (session->session_port == 443)
            session->url_prefix_asession
                = pool_printf(p, "https://%s/session/%s:%lu",
                              config->hostname, quoted_user, pid);
        else
            session->url_prefix_asession
                =
                pool_printf(p, "https://%s:%d/session/%s:%lu",
                            config->hostname, session->session_port,
                            quoted_user, pid);
    } else {
        if (session->session_port == 80)
            session->url_prefix_asession
                = pool_printf(p, "http://%s/session/%s:%lu",
                              config->hostname, quoted_user, pid);
        else
            session->url_prefix_asession
                =
                pool_printf(p, "http://%s:%d/session/%s:%lu", config->hostname,
                            session->session_port, quoted_user, pid);
    }

    /* Calculate url_prefix_bsession and url_bsession              */
    /* NB: Stored in request->pool as updated on each interaction  */
    session_update_sequence(session);
}


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

/* A couple of utility routines to extract a string from a comma
   delimited list e.g: imapd_server = "red, orange, yellow" */

static unsigned long session_server_list_count(char *s)
{
    char c;
    unsigned long count = 1;

    while ((c = *s++)) {
        if (c == ',')
            count++;
    }

    return (count);
}

static char *session_server_list_select(char *s, unsigned long offset)
{
    char *next;

    while (offset > 0) {
        if ((s = strchr(s, ',')) == NIL)
            return (NIL);

        s++;
        offset--;
    }

    next = strchr(s, ',');
    if (next)
        *next = '\0';

    return (string_trim_whitespace(s));
}


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

/* session_server_location() ********************************************
 *
 * Derive location of imapd and accountd servers for this user and login
 * session. Includes CDB database lookups, $user expansion and random
 * selection from list of possible servers.
 *
 * Returns: T if all expansion completed sucessfully. NIL on error.
 ***********************************************************************/

static BOOL session_server_location(struct session *session)
{
    struct config *config = session->config;
    char *username = session->username;
    void *cdb;
    char *value;

    session->imapd_server = NIL;
    session->accountd_server = NIL;
    session->accountd_nis_server = config->accountd_nis_server;

    /* Define imapd server location */
    if (config->imapd_user_map && config->imapd_user_map[0]) {
        if (!(cdb = cdb_open(config->imapd_user_map))) {
            log_panic("Couldn't open imapd user map file: %s\n",
                      config->imapd_user_map);
            return (NIL);
        }

        if (cdb_find(cdb, username, strlen(username), &value)) {
            session->imapd_server = pool_strdup(session->pool, value);
            free(value);
        }

        cdb_close(cdb);
    }
    if (session->imapd_server == NIL)
        session->imapd_server = config->imapd_server;

    /* Some ghastly magic to select single IMAP server at random from list
     * of servers (for multi-ported hosts) if provided */
    if (session->imapd_server && strchr(session->imapd_server, ',')) {
        unsigned long count =
            session_server_list_count(session->imapd_server);
        char *source = pool_strdup(session->pool, session->imapd_server);
        unsigned long offset;

        /* offset doesn't have to be random, but should be well distributed */
        offset =
            ((unsigned long) getpid() + (unsigned long) time(NIL)) % count;

        session->imapd_server = session_server_list_select(source, offset);

        if (session->imapd_server == NIL) {
            /* Shouldn't be possible! */
            log_panic
                ("[session_server_location(): Invalid offset into list!");
            return (NIL);
        }
    }

    /* Define accountd server location */
    if (config->accountd_user_map && config->accountd_user_map[0]) {
        if (!(cdb = cdb_open(config->accountd_user_map))) {
            log_panic("Couldn't open accountdd user map file: %s\n",
                      config->accountd_user_map);
            return (NIL);
        }

        if (cdb_find(cdb, username, strlen(username), &value)) {
            session->accountd_server = pool_strdup(session->pool, value);
            free(value);
        }

        cdb_close(cdb);
    }

    if (session->accountd_server == NIL)
        session->accountd_server = config->accountd_server;

    /* Some ghastly magic to select single Accountd server at random from list
     * of servers (for multi-ported hosts) if provided */
    if (session->accountd_server && strchr(session->accountd_server, ',')) {
        unsigned long count =
            session_server_list_count(session->accountd_server);
        char *source =
            pool_strdup(session->pool, session->accountd_server);
        unsigned long offset;

        /* offset doesn't have to be random, but should be well distributed */
        offset =
            ((unsigned long) getpid() + (unsigned long) time(NIL)) % count;

        session->accountd_server =
            session_server_list_select(source, offset);

        if (session->accountd_server == NIL) {
            /* Shouldn't be possible! */
            log_panic
                ("[session_server_location(): Invalid offset into list!");
            return (NIL);
        }
    }

    /* Define sieved server location */
    if (config->sieved_user_map && config->sieved_user_map[0]) {
        if (!(cdb = cdb_open(config->imapd_user_map))) {
            log_panic("Couldn't open imapd user map file: %s\n",
                      config->imapd_user_map);
            return (NIL);
        }

        if (cdb_find(cdb, username, strlen(username), &value)) {
            session->sieved_server = pool_strdup(session->pool, value);
            free(value);
        }

        cdb_close(cdb);
    }
    if (session->sieved_server == NIL)
        session->sieved_server = config->sieved_server;

    /* Some ghastly magic to select single Sieved server at random from list
     * of servers (for multi-ported hosts) if provided */
    if (session->sieved_server && strchr(session->sieved_server, ',')) {
        unsigned long count =
            session_server_list_count(session->sieved_server);
        char *source =
            pool_strdup(session->pool, session->sieved_server);
        unsigned long offset;

        /* offset doesn't have to be random, but should be well distributed */
        offset =
            ((unsigned long) getpid() + (unsigned long) time(NIL)) % count;

        session->sieved_server =
            session_server_list_select(source, offset);

        if (session->sieved_server == NIL) {
            /* Shouldn't be possible! */
            log_panic
                ("[session_server_location(): Invalid offset into list!");
            return (NIL);
        }
    }

    if (session->sieved_server)
        session->use_sieve = T;
    else
        session->use_sieve = NIL;

    /* Expand $user prefix in imapd_server and accountd_server */
    if (session->imapd_server) {
        if (!strncmp(session->imapd_server, "$user", strlen("$user")))
            session->imapd_server
                = pool_strcat(session->pool, username,
                              session->imapd_server + strlen("$user"));
        else if (!strncmp(session->imapd_server, "${user}", strlen("${user}")))
            session->imapd_server
                = pool_strcat(session->pool, username,
                              session->imapd_server + strlen("${user}"));
    }

    if (session->accountd_server) {
        if (!strncmp(session->accountd_server, "$user", strlen("$user")))
            session->accountd_server
                = pool_strcat(session->pool, username,
                              session->accountd_server + strlen("$user"));
        else if (!strncmp
                 (session->accountd_server, "${user}", strlen("${user}")))
            session->accountd_server =
                pool_strcat(session->pool, username,
                            session->accountd_server + strlen("${user}"));
    }

    return (T);
}

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

/* session_mailbox_prefs() **********************************************
 *
 * Returns c-client format mailbox specifier for the preferences folder.
 * Can't use session_mailbox() as we don't want this file floating aroun
 * if maildir changes.
 *   session:
 *      pool: Scratch pool (typically request->pool)
 *    folder: Name of folder.
 ***********************************************************************/

char *session_mailbox_prefs(struct session *session, struct pool *pool)
{
    char *name = session->config->prefs_folder_name;

    return (pool_printf(pool, "{%s}%s", session->imapd_server, name));
}

/* session_mailbox() ****************************************************
 *
 * Convert folder name into c-client format mailbox specifier.
 *   session:
 *      pool: Scratch pool (typically request->pool)
 *    folder: Name of folder.
 ***********************************************************************/

char *session_mailbox(struct session *session, struct pool *pool,
                      char *folder)
{
    return (pool_printf(pool, "{%s}%s", session->imapd_server, folder));
}

/* session_dir() ********************************************************
 *
 * Convert directory name into c-client format directory specifier.
 *   session:
 *      pool: Scratch pool (typically request->pool)
 *       dir: Name of directory.
 ***********************************************************************/

char *session_dir(struct session *session, struct pool *pool, char *dir)
{
    char *hiersep = session->hiersep;

    return (pool_printf(pool, "{%s}%s%s",
                        session->imapd_server, dir, hiersep));
}

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

/* session_login() ******************************************************
 *
 * Attempt session login. Sucessful login will set up all aspects of the
 * session structure ready for initial exchange.
 *    session:
 *   username: Username provided by prayer frontend process
 *   password: Password provided by prayer frontend process
 *       port: HTTP port used by login request
 *    use_ssl: SSL enabled in login request?
 * ua_options: User Agent configuration defined by browser + overrides
 *    ipaddr:  IP address of browser. Record for later security.
 *
 * Returns:   T => Login successful, session primed.
 *          NIL => Login failed. Reason may be recorded in session_log
 ***********************************************************************/

BOOL
session_login(struct session * session,
              char *username, char *password,
              unsigned long port, BOOL use_ssl, char *ua_options,
              struct ipaddr * ipaddr)
{
    struct pool *p = session->pool;
    struct config *config = session->config;
    struct user_agent *user_agent = session->user_agent;

    /* Setup for sucessful login, so client routines can use session */
    session->username = pool_strdup(p, username);       /* Username */
    session->password = pool_strdup(p, password);       /* Password */

    ml_clear_error();

    if (!session_server_location(session))
        return (NIL);

    if (!(session->imapd_server && session->imapd_server[0])) {
        /* Can't map this account onto a backend server */
        /* Probably a typo in the username, so fake a login faked */
        sleep(3);
        ml_set_errmsg("Login incorrect");
        ml_set_error();
        return(NIL);
    }

    if (!strcmp(session->imapd_server, "DEFER")) {
        ml_set_errmsg("Account undergoing maintenance. "
                      "Please try again in a few minutes.");
        ml_set_error();
        return (NIL);
    }

    /* Authenicate against the IMAP server */

    session->stream = ml_open(session, NIL,
                              session_mailbox_prefs(session, NIL), OP_HALFOPEN);

    if (!session->stream)
        return (NIL);

    ipaddr_copy(session->ipaddr, ipaddr);

    /* Use gzip encoding in thie session? */
    if (config->gzip_allow_nets &&
        ipaddr_compare_list(ipaddr, config->gzip_allow_nets))
        session->use_gzip = T;
    else if (config->gzip_deny_nets &&
             ipaddr_compare_list(ipaddr, config->gzip_deny_nets))
        session->use_gzip = NIL;
    else
        session->use_gzip = T;

    session->inbox_stream = session->stream;
    session->use_ssl = use_ssl;
    session->frontend_port = port;
    session->session_port = port;

    user_agent_parse(session->ua_initial, ua_options);
    user_agent_copy(session->user_agent, session->ua_initial);

    if (config->use_namespace)
        session_setup_namespace(session);

    session_make_id(session);
    session_setup_urls(session);

    if ((user_agent->use_telemetry || user_agent->use_telemetry_all) &&
        config && config->tmp_dir && config->tmp_dir[0]) {
        int open_perms = ((config && config->file_perms)
                          ? config->file_perms : 0644);
        char *name = pool_printf(NIL, "%s/telemetry-%s:%lu",
                                 config->tmp_dir, session->username,
                                 (unsigned long) getpid());
        int fd = open(name, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY,
                      open_perms);

        if (fd >= 0) {
            session->telemetry_fd = fd;
            session->telemetry = T;
            session->telemetry_all = user_agent->use_telemetry_all;
        }
        free(name);
    }

    return (T);
}

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

/* Small utility routine */

/* Translate single character from pseudo-BASE64 to int in range 0->63 */
static int session_translate_char(char c)
{
    if ((c >= 'A') && (c <= 'Z'))
        return (c - 'A');

    if ((c >= 'a') && (c <= 'z'))
        return ((c - 'a') + 26);

    if ((c >= '0') && (c <= '9'))
        return ((c - '0') + 26 + 26);

    if (c == '.')
        return (62);

    if (c == '-')
        return (63);

    return (0);
}

/* session_sequence() ****************************************************
 *
 * Convert session sequence string into number for comparison purposes
 *   seqno:  Sequence string to convert
 *
 * Returns: Equivalent number
 ************************************************************************/

unsigned long session_sequence(char *seqno)
{
    unsigned char s[4];
    unsigned long result;

    if ((seqno == NIL) || (strlen(seqno) != 4))
        return (0L);

    /* Convert to four bytes in the range 0:63 (i.e: 6 bits in each byte) */
    s[0] = (unsigned char) session_translate_char(seqno[0]);
    s[1] = (unsigned char) session_translate_char(seqno[1]);
    s[2] = (unsigned char) session_translate_char(seqno[2]);
    s[3] = (unsigned char) session_translate_char(seqno[3]);

    /* Squeeze into single 24 bit result and return */
    result = (((unsigned long) s[0]) << 18);
    result += (((unsigned long) s[1]) << 12);
    result += (((unsigned long) s[2]) << 6);
    result += (((unsigned long) s[3]));

    return (result);
}

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

/* session_use_prefs() ***************************************************
 *
 * Apply user preferences to login session
 *  session:
 *        p: User preferences, passed as (void *) to break circular loop.
 *  initial: Initial setup
 ************************************************************************/

void session_use_prefs(struct session *session, void *p, BOOL initial)
{
    struct config *config = session->config;
    struct prefs *prefs = (struct prefs *) p;
    struct request *request = session->request;
    struct user_agent *ua_request = user_agent_create(request->pool);
    struct user_agent *ua_initial = session->ua_initial;
    struct user_agent *user_agent = session->user_agent;

    if (session->draft_foldername)
        free(session->draft_foldername);

    if (prefs->maildir && prefs->maildir[0])
        session->draft_foldername = pool_strcat3(NIL, prefs->maildir,
                                                 session->hiersep,
                                                 prefs->postponed_folder);
    else
        session->draft_foldername = strdup(prefs->postponed_folder);

    /* Reset session->user_agent to known state before we mask off features */
    user_agent_copy(session->user_agent, session->ua_initial);

    if (!user_agent->use_override) {
        /* Disable features, unless preferences and ua_initial agree */
        user_agent->use_icons = (ua_initial->use_icons
                                 && prefs->use_icons);

        user_agent->use_cookie = (ua_initial->use_cookie
                                  && prefs->use_cookie);

        user_agent->use_substitution
            = (ua_initial->use_substitution && prefs->use_substitution);

        user_agent->use_http_1_1
            = (ua_initial->use_http_1_1 && prefs->use_http_1_1);

        user_agent->use_pipelining
            = (ua_initial->use_pipelining && prefs->use_pipelining);

        user_agent->use_persist
            = (ua_initial->use_persist && prefs->use_persist);

        user_agent->use_short = (ua_initial->use_short
                                 && prefs->use_short);

        user_agent->use_gzip = (ua_initial->use_gzip && prefs->use_gzip);

    }

    /* Setup vanilla user_agent for browser to compare against */
    user_agent_setup_browser(ua_request,
                             assoc_lookup(request->hdrs, "user-agent"));

    /* If following options are allowed by browser but not by negotiated
       values then we need to force manual override at frontend */
    if ((user_agent->use_http_1_1 != ua_request->use_http_1_1) ||
        (user_agent->use_pipelining != ua_request->use_pipelining) ||
        (user_agent->use_persist != ua_request->use_persist) ||
        (user_agent->use_pipelining != ua_request->use_pipelining) ||
        (ua_initial->use_telemetry_frontend))
        user_agent->manual = T;
    else
        user_agent->manual = NIL;

    if (initial) {
        session->use_short = (ua_initial->use_short && prefs->use_short);
    }

    /* Frontend URLs may have changed */
    session_setup_urls(session);

    msgmap_sort_mode(session->zm, prefs->sort_mode);

    if (prefs->sort_reverse)
        msgmap_sort_reverse_enable(session->zm);
    else
        msgmap_sort_reverse_disable(session->zm);

    abook_set_sort(session->options->abook,
                   prefs->abook_sort_mode, prefs->abook_sort_reverse);

    /* Initialise themes */
    session->theme_main = (struct config_theme *)
        list_lookup_byname(config->theme_list, prefs->theme_main_name);

    session->theme_help = (struct config_theme *)
        list_lookup_byname(config->theme_list, prefs->theme_help_name);

    /* Catch invalid theme names... */
    if (!session->theme_main)
        session->theme_main = config->theme_main;

    if (!session->theme_help)
        session->theme_help = config->theme_help;
}

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

/* session_make_redirect() ***********************************************
 *
 * Generate HTTP redirect relative to URL prefix. Used if page
 * substitution is disabled and when we want to force a redirect.
 *   session:
 *   request: Current HTTP request
 *  location: Target for redirect.
 ************************************************************************/

void
session_make_redirect(struct session *session,
                      struct request *request, char *location)
{
    char *url = pool_strcat(request->pool, session->url_prefix, location);

    response_redirect(request, url);
}

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

/* session_make_session_redirect() ***************************************
 *
 * Generate HTTP redirect relative to session URL. Used if page
 * substitution is disabled and when we want to force a redirect.
 *   session:
 *   request: Current HTTP request
 *       cmd: Target for redirect.
 ************************************************************************/

void
session_make_session_redirect(struct session *session,
                              struct request *request, char *cmd)
{
    char *url
        =
        pool_strcat3(request->pool, session->url_prefix_session, "/", cmd);

    if (session->use_short) {
        unsigned char
        *s = (unsigned char *) url + strlen(session->url_prefix_session);

        while (*s) {
            if (*s == '/')
                *s = '@';
            s++;
        }
    }

    response_redirect(request, url);
}

/* session_make_session_cookie_redirect() ********************************
 *
 * Generate HTTP redirect relative to session URL. Send a session cookie
 * (username=sessionid) or (username:port=sessionid) at the same time.
 *   session:
 *   request: Current HTTP request
 *       cmd: Target for redirect.
 ************************************************************************/

void
session_make_session_cookie_redirect(struct session *session,
                                     struct request *request, char *cmd)
{
    struct config *config = session->config;
    char *url =
        pool_strcat3(request->pool, session->url_prefix_session, "/", cmd);
    unsigned long pid = (unsigned long)getpid();
    char *path = pool_printf(request->pool,
                             "/session/%s:%lu", session->username, pid);
    char *key;

    if (session->use_short) {
        unsigned char
        *s = (unsigned char *) url + strlen(session->url_prefix_session);

        while (*s) {
            if (*s == '/')
                *s = '@';
            s++;
        }
    }

    key = pool_printf(request->pool, "%s:%lu", session->username, pid);

    response_cookie_redirect(request, url, key, session->sessionid,
			     path, config->hostname, session->use_ssl);
}

/* session_make_session_clear_cookie_redirect() **************************
 *
 * Generate HTTP redirect relative to session URL. Clears session cookie
 * (username=sessionid) or (username:port=sessionid) at the same time.
 *   session:
 *   request: Current HTTP request
 *       cmd: Target for redirect.
 ************************************************************************/

void
session_make_session_clear_cookie_redirect(struct session *session,
                                           struct request *request,
                                           char *cmd)
{
    struct config *config = session->config;
    unsigned long pid = (unsigned long)getpid();
    char *url =
        pool_strcat3(request->pool, session->url_prefix_session, "/", cmd);
    char *path = pool_printf(request->pool,
                             "/session/%s:%lu", session->username, pid);
    char *key;

    key = pool_printf(request->pool, "%s:%lu", session->username, pid);

    response_clear_cookie_redirect(request, url, key, session->sessionid,
                                   path, config->hostname, session->use_ssl);
}

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

/* session_redirect() ***************************************************
 *
 * Redirect session either as transparent page substiution or HTTP redirect
 * exchange with browser. (Depends on use_substitution option).
 *   session:
 *   request: Current HTTP request
 *       url: Target URL
 ***********************************************************************/

void
session_redirect(struct session *session, struct request *request,
                 char *url)
{
    BOOL cmd_dispatch(struct session *session, char *text);

    /* Make sure that msgno and msguid included in any list/display redirect
     * makes browser history work much better... */

    if (!strcmp(url, "list") && (session->stream != NULL)) {
        unsigned long msgno = session->current;
        unsigned long msguid = ml_uid(session, session->stream, msgno);

        url = pool_printf(request->pool, "list/%lu/%lu", msgno, msguid);
    } else if (!strcmp(url, "display") && (session->stream != NULL)) {
        unsigned long msgno = session->current;
        unsigned long msguid = ml_uid(session, session->stream, msgno);

        url = pool_printf(request->pool, "display/%lu/%lu", msgno, msguid);
    }

    if (!session->user_agent->use_substitution) {
        session_make_session_redirect(session, request, url);
        return;
    }

    session_log(session, "[session_redirect] %s", url);

    request_forge_redirect(request, url);

    /* Look for defined session method in redirect */
    if (!cmd_dispatch(session, request->argv[0]))
        response_error(request, 404);

}

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

/* session_message() ****************************************************
 *
 * Record a status message that will be displayed to user at next
 * opportunity.
 *   session:
 *       fmt: Format string, followed by additional arguments a la printf
 *
 ***********************************************************************/

void session_message(struct session *session, char *fmt, ...)
{
    struct memblock *m = session->message;
    va_list ap;
    unsigned long size;
    char *t;

    if (memblock_size(m) > 0) {
        if ((t = memblock_data(m)) && (t[0] != '\0'))
            return;
    }

    va_start(ap, fmt);
    size = memblock_vprintf_size(m, fmt, ap);
    va_end(ap);

    memblock_resize(m, size + 1);

    va_start(ap, fmt);
    memblock_vprintf(m, fmt, ap);
    va_end(ap);

    session->message_isalert = NIL;
}

/* session_message_clear() ***********************************************
 *
 * Clear out existing session message
 ************************************************************************/

void session_message_clear(struct session *session)
{
    struct memblock *m = session->message;

    mputs(m, "");
    session->message_isalert = NIL;
}

/* session_alert() ******************************************************
 *
 * Record a status message that will be displayed to user at next
 * opportunity. Flag this as an "alert" message which will be highlighted.
 *   session:
 *       fmt: Format string, followed by additional arguments a la printf
 *
 ***********************************************************************/

void session_alert(struct session *session, char *fmt, ...)
{
    struct memblock *m = session->message;
    va_list ap;
    unsigned long size;
    char *t;

    if (memblock_size(m) > 0) {
        if ((t = memblock_data(m)) && (t[0] != '\0'))
            return;
    }

    va_start(ap, fmt);
    size = memblock_vprintf_size(m, fmt, ap);
    va_end(ap);

    memblock_resize(m, size + 1);

    va_start(ap, fmt);
    memblock_vprintf(m, fmt, ap);
    va_end(ap);

    session->message_isalert = T;
}

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

/* session_record_last_cmd() ********************************************
 *
 * Record current command so we know where we are on redisplay event.
 *   session:
 *  last_cmd: Command to record.
 *
 ***********************************************************************/

void session_record_last_cmd(struct session *session, char *last_cmd)
{
    memblock_puts(session->last_cmd, last_cmd);
}

/* session_last_cmd() ***************************************************
 *
 * Retrieve last recorded command
 ***********************************************************************/

char *session_last_cmd(struct session *session)
{
    return (memblock_data(session->last_cmd));
}

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

/* Interface to log system */

void session_accesslog(struct session *session, struct request *request)
{
    unsigned long size;

    log_record_peer_pid(session->accesslog, session->frontend_pid);

    if (request->gzip_buffer)
        size = buffer_size(request->gzip_buffer);
    else
        size = buffer_size(request->write_buffer);

    log_here(session->accesslog,
             "[%s] (%lu) %s -> %lu %lu %s",
             ipaddr_text(session->ipaddr), session->session_port,
             request->request, request->status, size,
             ((request->use_http_1_1) ? "1.1" : "1.0"));
}

void session_log(struct session *session, char *fmt, ...)
{
    va_list ap;
    unsigned long len;
    struct pool *pool;

    if (session->request && session->request->pool)
        pool = session->request->pool;
    else
        pool = NIL;

    va_start(ap, fmt);
    len = log_entry_size(fmt, ap);
    va_end(ap);

    va_start(ap, fmt);
    log_ap(session->sessionlog, pool, session->username, len, fmt, ap);
    va_end(ap);
}

/* Compatibility Interface to new fangled panic log system... */

void session_paniclog(struct session *session, char *fmt, ...)
{
    unsigned long len;
    va_list ap;

    va_start(ap, fmt);
    len = log_entry_size(fmt, ap);
    va_end(ap);

    va_start(ap, fmt);
    log_panic_ap(session->config, session->username, len, fmt, ap);
    va_end(ap);
}

void session_fatal(struct session *session, char *fmt, ...)
{
    unsigned long len;
    va_list ap;

    va_start(ap, fmt);
    len = log_entry_size(fmt, ap);
    va_end(ap);


    va_start(ap, fmt);
    log_panic_ap(session->config, session->username, len, fmt, ap);
    va_end(ap);

    abort();
}
