/* $Cambridge: hermes/src/prayer/servers/prayer_login.c,v 1.15 2009/08/20 09:48:35 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"

char *read_file(struct pool *pool, char *filename)
{
    struct buffer *b = buffer_create(pool, 4096);
    FILE *file;
    int c;

    if (!(file = fopen(filename, "r")))
        return("");

    while ((c = fgetc(file)) != EOF) {
        if (c == '\n')
            bputs(b, ""CRLF);
        else
            bputc(b, c);
    }
    fclose(file);

    return (buffer_fetch(b, 0, buffer_size(b), NIL));
}

/* prayer_login_generate() ***********************************************
 *
 * Generate login screen
 *   prayer: Global state
 *  request: HTTP request that initiated this action
 *     user: Default username for login scren.
 *     form: Whether to include the login form.
 ************************************************************************/

static void
prayer_login_generate(struct prayer *prayer,
                      struct request *request, char *user, BOOL form)
{
    struct config *config = prayer->config;
    struct buffer *b    = request->write_buffer;
    struct pool   *pool = request->pool;
    struct template_vals *tvals = NIL;
    char *hostname;

    if (!prayer->use_ssl && config->ssl_required) {
        response_error(request, 403);   /* Forbidden */
        return;
    }

    prayer_template_setup(prayer);  /* Adds $url_prefix automatically */
    tvals = prayer->template_vals;

    hostname = config->hostname_service ? config->hostname_service
        : config->hostname;

    template_vals_string(tvals, "$service_name", config->login_service_name);
    template_vals_string(tvals, "$hostname", hostname);
    template_vals_string(tvals, "$user", user);

    if (config->raven_enable) {
        template_vals_ulong(tvals, "$raven_enable", 1);
    }

    if (config->login_banner) {
        template_vals_string(tvals, "$login_banner", config->login_banner);
    }
    if (config->motd_path) {
        template_vals_string(tvals, "$motd",
                             read_file(pool, config->motd_path));
    }
    if (config->login_insert1_path) {
        template_vals_string(tvals, "$login_insert1",
                             read_file(pool, config->login_insert1_path));
    }
    if (config->login_insert2_path) {
        template_vals_string(tvals, "$login_insert2",
                             read_file(pool, config->login_insert2_path));
    }

    if ((prayer->use_ssl == NIL) && (config->ssl_default_port > 0)) {
        template_vals_ulong(tvals, "$ssl_available", 1);
        template_vals_ulong(tvals, "$ssl_port", config->ssl_default_port);
    
    }

    template_expand(config->login_template, tvals, b);
    response_html(request, 200);
}

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

/* prayer_login_attempt() ************************************************
 *
 * Attempt login to prayer-session backend
 *   prayer: Global state
 * username: Username to send to IMAP server
 * password: Password to send to IMAP server
 *  statusp: Returns Server status code
 *   valuep: Returns Descritive text or URL, depending on status code
 *
 * Connection/protocol error if either *statusp and *valuep NIL on return
 ************************************************************************/

static BOOL
prayer_login_attempt(struct prayer *prayer,
                     char *username, char *password,
                     char **statusp, char **valuep)
{
    struct config *config = prayer->config;
    struct request *request = prayer->request;
    struct pool *pool = request->pool;
    struct user_agent *user_agent = request->user_agent;
    struct iostream *iostream;
    char *socketname;
    int sockfd;
    struct buffer *b = buffer_create(pool, 0L);
    int c;
    char *line, *status = "NO", *value = "";
    char *ua_options;

    *statusp = NIL;
    *valuep = NIL;

    if (!(username && username[0])) {
        *statusp = "NO";
        *valuep = "No username supplied";
        return(NIL);
    }

    if (!(password && password[0])) {
        *statusp = "NO";
        *valuep = "No password supplied";
        return(NIL);
    }

    socketname = pool_printf(pool, "%s/%s",
                             config->socket_dir, config->init_socket_name);

    if ((sockfd = os_connect_unix_socket(socketname)) < 0)
        return(NIL);

    if ((iostream = iostream_create(pool, sockfd, 0)) == NIL) {
        log_misc("[process_login_request()] iostream_create() failed");
        return(NIL);
    }

    iostream_set_timeout(iostream, config->session_timeout);

    ua_options = user_agent_options(user_agent);

    ioprintf(iostream, "%s %s %s %d %d %s" CRLF,
             string_canon_encode(pool, username),
             string_canon_encode(pool, password),
             string_canon_encode(pool, ua_options),
             prayer->port, prayer->use_ssl, ipaddr_text(prayer->ipaddr));

    ioflush(iostream);

    while (((c = iogetc(iostream)) != EOF) && (c != '\015')
           && (c != '\012'))
        bputc(b, c);

    if (c == EOF) {
        log_misc("[process_login_request()] iogetc() got end of file");
        return(NIL);
    }

    line = buffer_fetch(b, 0, buffer_size(b), NIL);

    status = string_get_token(&line);
    value = string_next_token(&line);

    if (!(status && value)) {
        log_misc("[process_login_request()] Invalid response from server");
        return(NIL);
    }

    *statusp = status;
    *valuep = value;
    return(T);
}

/* Couple of support functions shared between prayer_login_process()
 * and prayer_login_raven()
 */

static void
prayer_login_connect_error(struct prayer *prayer, char *username)
{
    struct request *request = prayer->request;
    struct buffer  *b       = request->write_buffer;
    struct template_vals *tvals = NIL;

    prayer_template_setup(prayer);  /* Adds $url_prefix automatically */

    tvals = prayer->template_vals;
    template_vals_string(tvals, "$user", username);
    template_expand("frontend_session", tvals, b);
    response_html(request, 200);
}

static void
prayer_login_error(struct prayer *prayer, char *username, char *value)
{
    struct request *request = prayer->request;
    struct buffer *b = request->write_buffer;
    struct template_vals *tvals = NIL;

    prayer_template_setup(prayer);  /* Adds $url_prefix automatically */

    tvals = prayer->template_vals;
    template_vals_string(tvals, "$user", username);
    template_vals_string(tvals, "$value", value);

    template_expand("frontend_login_error", tvals, b);
    response_html(request, 200);
}

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

/* prayer_login_raven() **************************************************
 *
 * Process Raven login request
 *    prayer: Global state
 *  username: Default login name.
 ************************************************************************/

#ifdef RAVEN_ENABLE
void
prayer_login_raven(struct prayer *prayer)
{
    struct request *request = prayer->request;
    struct config  *config  = request->config;
    struct pool    *pool    = request->pool;
    char *username = NIL;
    char *password = NIL;
    char *status = "NO";
    char *value = "";
    char *url_prefix = prayer_url_prefix(prayer, request->pool);
    char *raven_prefix = pool_strcat(request->pool, url_prefix, "/raven");
    char *wls_token;
    struct raven_wls_response *wls;

    if (request->argc != 1) {
        response_error(request, 404);
        return;
    }

    if (!(config->raven_enable &&
          config->raven_key_path && config->raven_key_path[0])) {
        response_error(request, 404);
        return;
    }

    request_decode_form(request);
    wls_token = assoc_lookup(request->form, "WLS-Response");

    if (!(wls_token && wls_token[0])) {
        char *callback_url = pool_strcat(pool, url_prefix, "/raven");
        char *service_name = "Prayer Webmail Service";
        char *login_url;

        if (config->login_service_name)
            service_name = pool_printf(pool, "%s Webmail Service",
                                       config->login_service_name);
        login_url = pool_printf
            (pool,
             "%s?ver=1&url=%s&date=%s&desc=%s",
             "https://raven.cam.ac.uk/auth/authenticate.html",
             string_url_encode(pool, callback_url),
             string_url_encode(pool, raven_wls_response_now()),
             string_url_encode(pool, service_name));

        response_redirect(request, login_url);
        return;
    }

    wls = raven_wls_response_parse(wls_token, config->raven_key_path, &value);
    
    if (!wls) {
        /* error provided by parse routine */
    } else if (strcmp(wls->status, "200") != 0) {
        if (wls->msg && wls->msg[0]) {
            value = pool_printf(pool, "%s: %s", wls->status, wls->msg);
                                
        } else {
            value = pool_printf(pool, "%s: %s", wls->status,
                                raven_wls_response_status_message(wls));
        }
    } else if (strcmp(wls->url, raven_prefix) != 0) {
        value  = "Authentication response for wrong system";
    } else if (!raven_wls_response_check_timestamp
               (wls, RAVEN_WLS_TICKET_MAXSKEW, RAVEN_WLS_TICKET_TIMEOUT)) {
        value = "Authentication response timed out";
    } else {
        username = pool_strdup(pool, wls->principal);
        password = wls_token;
    }

    if (wls) {
        request->log_entry = pool_printf
            (pool, "GET /raven?WLS-Response=%s!...", wls->lhs); 
        raven_wls_response_free(wls);
    }

    if (username && password &&
        !prayer_login_attempt(prayer, username, password,
                              &status, &value)) {
        if (status && value) {
            prayer_login_error(prayer, username, value);
        } else {
            prayer_login_connect_error(prayer, username);
        }
        return;
    }

    if (status && !strcmp(status, "OK"))
        response_redirect(request, value);
    else /* Should never happen... */
        prayer_login_error(prayer, username, value);
}
#else
void
prayer_login_raven(struct prayer *prayer)
{
    struct request *request = prayer->request;

    response_error(request, 404);
    return;
}
#endif

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

/* prayer_login_process() ************************************************
 *
 * Process login request (static internal fn).
 *   prayer: Global state
 *  request: HTTP request that initiated this action
 ************************************************************************/

static void
prayer_login_process(struct prayer *prayer, struct request *request)
{
    struct config *config = request->config;
    struct buffer *b = request->write_buffer;
    char *username;
    char *password;
    char *status = "NO";
    char *value;

    /* Decode information from post request */
    request_decode_form(request);

    username = assoc_lookup(request->form, "username");
    password = assoc_lookup(request->form, "password");

    if (username)
        username = string_trim_whitespace(username);

    if ((config->referer_log_invalid || config->referer_block_invalid) &&
        !request_test_referer(request, config->hostname_service) &&
        !request_test_referer(request, config->hostname)) {

        if (config->referer_log_invalid)
            log_misc("[prayer_login_process()] Invalid Referer: %s",
                     assoc_lookup(request->hdrs, "referer"));

        if (config->referer_block_invalid) {
            prayer_template_setup(prayer);  /* Adds $url_prefix automatically */
            template_expand("frontend_security", prayer->template_vals, b);
            response_html(request, 200);
            return;
        }
    }

    if ((config->raven_enable) && (password) && (strlen(password) > 256)) {
        prayer_login_error(prayer, username, "Password too long");
        return;
    }

    if (!prayer_login_attempt(prayer, username, password, &status, &value)) {
        if (status && value) {
            prayer_login_error(prayer, username, value);
        } else {
            prayer_login_connect_error(prayer, username);
        }
        return;
    }
    if (status && !strcmp(status, "OK"))
        response_redirect(request, value);
    else /* Should never happen... */
        prayer_login_error(prayer, username, value);
}

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

/* prayer_login() ********************************************************
 *
 * Process login URLs: POST => login request, GET => display login screen
 *    prayer: Global state
 *  username: Default login name.
 ************************************************************************/

void prayer_login(struct prayer *prayer, char *username)
{
    struct request *request = prayer->request;

    if (request->method == POST)
        prayer_login_process(prayer, request);
    else
        prayer_login_generate(prayer, request, username, T);
}

/* prayer_login_preamble() ***********************************************
 *
 * Handle pre-login actions for insecure connections, either a warning
 * banner or a redirect to the secure server, depending on configuration.
 *    prayer: Global state
 ************************************************************************/

void prayer_login_preamble(struct prayer *prayer)
{
    struct config *config = prayer->config;
    struct request *request = prayer->request;
    char *hostname, *url;

    /* Normal login if we are secure */
    if (prayer->use_ssl) {
        prayer_login(prayer, NIL);
        return;
    }

    /* Redirect to the secure login url */
    if (config->ssl_redirect) {
        hostname = config->hostname_service ? config->hostname_service
                                            : config->hostname;
        if (config->ssl_default_port != 443)
            url = pool_printf(request->pool, "https://%s:%lu",
                    hostname, config->ssl_default_port);
        else
            url = pool_printf(request->pool, "https://%s", hostname);
        response_redirect(prayer->request, url);
        return;
    }

    if (config->ssl_encouraged) {
        /* Produce a pre-login warning page */
        prayer_login_generate(prayer, request, NIL, NIL);
    } else {
        /* Configuration allows plaintext logins (e.g: testrig on magenta) */
        prayer_login(prayer, NIL);
    }
}

