/* ====================================================================
 * Copyright (c) 1995 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */


/* Netscape Cookies Access control
 * 
 * This module allows access, and pretends to the rest of the
 * system that the user used BasicAuth to get in. In the very
 * near future we will have to use some encryption to make
 * second-guess spoofing harder.
 *
 * (c) Dirk-Willem van Gulik, June 1996, for 
 *      http://ewse.ceo.org/. and http://enrm.ceo.org/.
 *      a http://www.ceo.org/ CEO Programme Project.
 *
 * Example:
 * Cookie_Access                        on
 * Cookie_AuthFile                      <filename>
 * Cookie_Authorative                   off
 * Cookie_MustGive                      off
 * Cookie_EncryptedCookies              on
 *
 * <limit GET,POST>
 * require user name...
 * or
 * require valid-user
 * </limit>
 *
 * Person to contact/blame
 *      Dirk.vanGulik@jrc.it
 *
 * Revision History
 *      0.0     First version
 */


#include "httpd.h"
#include "http_log.h"		/* log reason */
#include "http_config.h"	/* config read stuff */
#include "http_protocol.h"	/* basic auth */

#ifdef HAS_CRYPT_H
#include <crypt.h>
#endif

/* Arbitrary limit for the length of the
 * value of the cookie name and the cookie
 * value
 */
#define MAX_USER_NAME_LEN (32)
#define MAX_COOKIE_VALUE_LEN (32)

/* Do you really want to keep the connection open ?? */
#undef KEEP_MSQL_CONNECTION_OPEN

module cookie_file_access_module;

typedef struct {
    int cookie_file_active;	/* on/off flag */
    char *cookie_file_auth_file;	/* .htpasswd like file */
    int cookie_file_auth_authorative;	/* do we have a final say ? */
    int cookie_file_auth_encrypted;	/* security ? */
    int cookie_file_auth_must;	/* Enforce cookie eating ? */
} cookie_file_auth_config_rec;

static void *create_cookie_file_access_config(pool * p, char *d)
{
    cookie_file_auth_config_rec *sec = (cookie_file_auth_config_rec *)
    ap_pcalloc(p, sizeof(cookie_file_auth_config_rec));

    /* Just to illustrate the defaults forcefully */
    sec->cookie_file_active = 0;	/* not active */
    sec->cookie_file_auth_file = NULL;	/* no file.. */
    sec->cookie_file_auth_authorative = 0;	/* do we have a final say ? */
    sec->cookie_file_auth_encrypted = 1;	/* security ? */
    sec->cookie_file_auth_must = 0;	/* Forcefull cooky eating */

    return sec;
}

static const char *cookie_file_set_string_slot(cmd_parms * cmd, char *struct_ptr, char *arg)
{
    *(char **) (struct_ptr + ((int) cmd->info)) = ap_pstrdup(cmd->pool, arg);
    return NULL;
}

static const char *cookie_file_set_flag_slot(cmd_parms * cmd, char *struct_ptr, int arg)
{
    (int) *(char **) (struct_ptr + ((int) cmd->info)) = arg;
    return NULL;
}

static command_rec cookie_file_access_cmds[] =
{
    {"Cookie_Access", cookie_file_set_flag_slot,
     (void *) XtOffsetOf(cookie_file_auth_config_rec, cookie_file_active),
     OR_AUTHCFG, FLAG,
     "Switch cookie access on/off"},

    {"Cookie_AccFile", cookie_file_set_string_slot,
     (void *) XtOffsetOf(cookie_file_auth_config_rec, cookie_file_auth_file),
     OR_AUTHCFG, TAKE1,
     "'.htpasswd' like file with cookie name/value pairs and the userid"},

    {"Cookie_Authorative", cookie_file_set_flag_slot,
     (void *) XtOffsetOf(cookie_file_auth_config_rec, cookie_file_auth_authorative),
     OR_AUTHCFG, FLAG,
     "When 'on' the Cookie is taken to be authorative and access control is not passed."},

    {"Cookie_MustGive", cookie_file_set_flag_slot,
     (void *) XtOffsetOf(cookie_file_auth_config_rec, cookie_file_auth_must),
     OR_AUTHCFG, FLAG,
     "When 'on' the client must present a cookie."},

    {"Cookie_EncryptedCookies", cookie_file_set_flag_slot,
(void *) XtOffsetOf(cookie_file_auth_config_rec, cookie_file_auth_encrypted),
     OR_AUTHCFG, FLAG,
     "When 'on' the cookie values in the table are taken to be crypt()ed using your machines crypt() function."},

    {NULL}
};

static int get_userid_and_cookie(request_rec * r, cookie_file_auth_config_rec * sec,
			  char *cookie_file_name,	/* in - cookie to look for */
			  char *cookie_file_value,	/* out - (encrypted) cookie value to check */
			  char *userid	/* out - UID to set if all is OK */
)
{
    configfile_t *f;
    char l[MAX_STRING_LEN];
    char *u, *v;
	const char *w;

    if (!(sec->cookie_file_auth_file)) {
	ap_log_reason("No cookie-password file specified", "", r);
	return 0;
    };

    if (!(f =  ap_pcfg_openfile(r->pool, sec->cookie_file_auth_file))) {
	ap_log_reason("Could not open cookie-password file", sec->cookie_file_auth_file, r);
	return 0;
    }

    while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {

	if ((l[0] == '#') || (!l[0]))
	    continue;

	/* Format of the file is userID:name:value<EOL>
	 */
	w = l;
	u = ap_getword(r->pool, &w, ':');	/* u->userID */
	v = ap_getword(r->pool, &w, ':');	/* v->cookie name */
	/* w->cookie value */
	if (!strcmp(cookie_file_name, v)) {
	    ap_cfg_closefile(f);

	    if (strlen(u) >= MAX_USER_NAME_LEN) {
		ap_log_reason("Could not cope with a UserName in the cookie file, too long", "", r);
		return 0;
	    };
	    if (strlen(v) >= MAX_COOKIE_VALUE_LEN) {
		ap_log_reason("Could not cope with a CookieValue in the cookie file, too long", "", r);
		return 0;
	    };

	    strcpy(cookie_file_value, v);
	    strcpy(userid, u);
	    return 1;
	}
    }
    ap_cfg_closefile(f);
    return 0;
}


static int cookie_file_authenticate(request_rec * r)
{
    cookie_file_auth_config_rec *sec =
    (cookie_file_auth_config_rec *) ap_get_module_config(r->per_dir_config,
						&cookie_file_access_module);
    conn_rec *c = r->connection;

    char *cookie;
	const char *sent_pw;
	const char *ptr;
    char *value, real_value[MAX_USER_NAME_LEN], real_uname[MAX_COOKIE_VALUE_LEN];

    /* are we configured ? */
    if (!(sec->cookie_file_active))
	return DECLINED;

    /* Is there a cookie we can act on ? */
    if (!(ptr = ap_table_get(r->headers_in, "Cookie"))) {
	if (sec->cookie_file_auth_must)
	    return AUTH_REQUIRED;
	return DECLINED;
    };

    /* We do NOT have to use cookie access, the client
     * already gave us that stuff.
     */
    if ((OK == ap_get_basic_auth_pw(r, &sent_pw)) && (sent_pw))
	return DECLINED;

    /* make a copy which we can destroy, keep room for the \0 and ; */
    if (!(cookie = ap_palloc(r->pool, 2 + strlen(ptr)))) {
	ap_log_reason("CookieAuth: Could not claim memory for a cookie", r->uri, r);
	return SERVER_ERROR;
    };
    strcpy(cookie, ptr);

    /* Place the elephant in egypt. 
     */
    cookie[0 + strlen(ptr)] = ';';
    cookie[1 + strlen(ptr)] = '\0';

    /* Run Through The Cookies, (White)Space Or ; Separated ?
     */
    for (cookie = strtok(cookie, " ;\n\r\t\f");
	 (cookie);
	 cookie = strtok(NULL, " ;\n\r\t\f")
	) {
	/* The cookie looks something like 'Blah=Bloh' where
	 * Blah & Bloh are in the cookie_file_name and cookie_file_value
	 * fields of the table.
	 */
	while ((!(value = strchr(cookie, (int) '='))) && (cookie)) {
	    /* Misbaked cookie, should we log this, ignore or just give up ? */
	    cookie = strtok(NULL, " ;\n\r\t\f");
	};

	if (!(cookie))
	    break;

	*value = '\0';
	value++;

	/* now look up, for the cookie name, which is pointed
	 * to by 'cookie', the username and the real_value
	 * of the cookie as stored in the db/file.
	 */
	real_value[0] = '\0';
	real_uname[0] = '\0';
	if (get_userid_and_cookie(r, sec, cookie, real_uname, real_value)) {
	    /* Now do we have to crypt the incoming cookie
	     * (for the second time!) to avoid having 
	     * usable cookies in the database.
	     */
	    if (sec->cookie_file_auth_encrypted) {
		/* anyone know where the prototype for crypt is?

		 * PLEASE NOTE:
		 *    The crypt function (at least under FreeBSD 2.0.5) returns
		 *    a ptr to a *static* array (max 120 chars) and does *not*
		 *    modify the string pointed at by sent_pw !
		 */
		value = (char *) crypt(value, real_value);
	    };
	    if (!(strcmp(value, real_value))) {
		/* Jup, this looks real good */
		c->user = real_uname;
		c->ap_auth_type = "COOKIE";
		return OK;
	    };			/* else ... we could log illegal cookies attempts here ?! */
	};			/* else we could log unfound cookies here ?! */
    };				/* end of for loop */

    if (sec->cookie_file_auth_authorative) {
	ap_note_basic_auth_failure(r);
	ap_log_reason("CookieAuth: No valid Cookie(s)", r->filename, r);
	return AUTH_REQUIRED;
    };
    return DECLINED;
}

module cookie_file_access_module =
{
    STANDARD_MODULE_STUFF,
    NULL,			/* initializer */
    create_cookie_file_access_config,	/* dir config creater */
    NULL,			/* dir merger */
    NULL,			/* server config */
    NULL,			/* merge server configs */
    cookie_file_access_cmds,	/* command table */
    NULL,			/* handlers */
    NULL,			/* filename translation */
    cookie_file_authenticate,	/* check_user_id */
    NULL,			/* check auth */
    NULL,			/* check access */
    NULL,			/* type_checker */
    NULL,			/* fixups */
    NULL,			/* logger */
};
