/*
 * $Id: auth_basic.c,v 1.25 2006/07/30 23:27:04 hno Exp $
 *
 * DEBUG: section 29    Authenticator
 * AUTHOR: Duane Wessels
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

/* The functions in this file handle authentication.
 * They DO NOT perform access control or auditing.
 * See acl.c for access control and client_side.c for auditing */


#include "squid.h"
#include "auth_basic.h"

static void
authenticateStateFree(authenticateStateData * r)
{
    if (r->auth_user_request) {
	authenticateAuthUserRequestUnlock(r->auth_user_request);
	r->auth_user_request = NULL;
    }
    cbdataFree(r);
}

/* Basic Scheme */

static HLPCB authenticateBasicHandleReply;
static AUTHSACTIVE authenticateBasicActive;
static AUTHSAUTHED authenticateBasicAuthenticated;
static AUTHSAUTHUSER authenticateBasicAuthenticateUser;
static AUTHSCONFIGURED authBasicConfigured;
static AUTHSDIRECTION authenticateBasicDirection;
static AUTHSDECODE authenticateBasicDecodeAuth;
static AUTHSDUMP authBasicCfgDump;
static AUTHSFIXERR authenticateBasicFixErrorHeader;
static AUTHSFREE authenticateBasicFreeUser;
static AUTHSFREECONFIG authBasicFreeConfig;
static AUTHSPARSE authBasicParse;
static AUTHSCHECKCONFIG authBasicCheckConfig;
static AUTHSINIT authBasicInit;
static AUTHSSTART authenticateBasicStart;
static AUTHSSTATS authenticateBasicStats;
static AUTHSUSERNAME authenticateBasicUsername;
static AUTHSSHUTDOWN authBasicDone;

static helper *basicauthenticators = NULL;

static auth_basic_config *basicConfig = NULL;

static int authbasic_initialised = 0;
MemPool *basic_data_pool = NULL;


/*
 *
 * Public Functions
 *
 */

AUTHSSETUP authSchemeSetup_basic;

void
authSchemeSetup_basic(authscheme_entry_t * authscheme)
{
    assert(!authbasic_initialised);
    authscheme->Active = authenticateBasicActive;
    authscheme->parse = authBasicParse;
    authscheme->checkconfig = authBasicCheckConfig;
    authscheme->dump = authBasicCfgDump;
    authscheme->init = authBasicInit;
    authscheme->authAuthenticate = authenticateBasicAuthenticateUser;
    authscheme->authenticated = authenticateBasicAuthenticated;
    authscheme->configured = authBasicConfigured;
    authscheme->authFixHeader = authenticateBasicFixErrorHeader;
    authscheme->FreeUser = authenticateBasicFreeUser;
    authscheme->freeconfig = authBasicFreeConfig;
    authscheme->authStart = authenticateBasicStart;
    authscheme->authStats = authenticateBasicStats;
    authscheme->authUserUsername = authenticateBasicUsername;
    authscheme->getdirection = authenticateBasicDirection;
    authscheme->oncloseconnection = NULL;
    authscheme->decodeauth = authenticateBasicDecodeAuth;
    authscheme->donefunc = authBasicDone;
    authscheme->authConnLastHeader = NULL;
}

/* internal functions */

static void
authBasicDone(void)
{
    if (basicauthenticators)
	helperShutdown(basicauthenticators);
    authbasic_initialised = 0;
    if (!shutting_down)
	return;
    if (basicauthenticators)
	helperFree(basicauthenticators);
    basicauthenticators = NULL;
    if (basic_data_pool) {
	memPoolDestroy(basic_data_pool);
	basic_data_pool = NULL;
    }
    debug(29, 2) ("authBasicDone: Basic authentication Shutdown.\n");
}

static int
authenticateBasicActive()
{
    return (authbasic_initialised == 1) ? 1 : 0;
}

static int
authBasicConfigured()
{
    if ((basicConfig != NULL) && (basicConfig->authenticate != NULL) &&
	(basicConfig->authenticateChildren != 0) &&
	(basicConfig->basicAuthRealm != NULL)) {
	debug(29, 9) ("authBasicConfigured: returning configured\n");
	return 1;
    }
    debug(29, 9) ("authBasicConfigured: returning unconfigured\n");
    return 0;
}

static int
authenticateBasicAuthenticated(auth_user_request_t * auth_user_request)
{
    basic_data *basic_auth = auth_user_request->auth_user->scheme_data;
    if ((basic_auth->flags.credentials_ok == 1) && (basic_auth->credentials_checkedtime + basicConfig->credentialsTTL > squid_curtime))
	return 1;
    debug(29, 4) ("User not authenticated or credentials need rechecking.\n");
    return 0;
}

#if UNUSED_CODE
static int
authenticateBasiccmpUsername(basic_data * u1, basic_data * u2)
{
    return strcmp(u1->username, u2->username);
}
#endif

/* log a basic user in
 */
static void
authenticateBasicAuthenticateUser(auth_user_request_t * auth_user_request, request_t * request, ConnStateData * conn, http_hdr_type type)
{
    auth_user_t *auth_user;
    basic_data *basic_auth;

    assert(auth_user_request->auth_user != NULL);
    auth_user = auth_user_request->auth_user;

    assert(auth_user->scheme_data != NULL);
    basic_auth = auth_user->scheme_data;

    /* if the password is not ok, do an identity */
    if (basic_auth->flags.credentials_ok != 1)
	return;

    /* are we about to recheck the credentials externally? */
    if ((basic_auth->credentials_checkedtime + basicConfig->credentialsTTL) <= squid_curtime) {
	debug(29, 4) ("authBasicAuthenticate: credentials expired - rechecking\n");
	return;
    }
    /* we have been through the external helper, and the credentials haven't expired */
    debug(29, 9) ("authenticateBasicAuthenticateuser: user '%s' authenticated\n",
	basic_auth->username);

    /* Decode now takes care of finding the auth_user struct in the cache */
    /* after external auth occurs anyway */
    auth_user->expiretime = current_time.tv_sec;
    return;
}

int
authenticateBasicDirection(auth_user_request_t * auth_user_request)
{
/* null auth_user is checked for by authenticateDirection */
    auth_user_t *auth_user = auth_user_request->auth_user;
    basic_data *basic_auth = auth_user->scheme_data;
    switch (basic_auth->flags.credentials_ok) {
    case 0:			/* not checked */
	return -1;
    case 1:			/* checked & ok */
	if (basic_auth->credentials_checkedtime + basicConfig->credentialsTTL <= squid_curtime)
	    return -1;
	return 0;
    case 2:			/* paused while waiting for a username:password check on another request */
	return -1;
    case 3:			/* authentication process failed. */
	return 0;
    }
    return -2;
}

void
authenticateBasicFixErrorHeader(auth_user_request_t * auth_user_request, HttpReply * rep, http_hdr_type type, request_t * request)
{
    if (basicConfig->authenticate) {
	debug(29, 9) ("authenticateFixErrorHeader: Sending type:%d header: 'Basic realm=\"%s\"'\n", type, basicConfig->basicAuthRealm);
	httpHeaderPutStrf(&rep->header, type, "Basic realm=\"%s\"", basicConfig->basicAuthRealm);
    }
}

/* free any allocated configuration details */
void
authBasicFreeConfig(authScheme * scheme)
{
    if (basicConfig == NULL)
	return;
    assert(basicConfig == scheme->scheme_data);
    if (basicConfig->authenticate)
	wordlistDestroy(&basicConfig->authenticate);
    if (basicConfig->basicAuthRealm)
	safe_free(basicConfig->basicAuthRealm);
    xfree(basicConfig);
    basicConfig = NULL;
}

void
authenticateBasicFreeUser(auth_user_t * auth_user)
{
    basic_data *basic_auth = auth_user->scheme_data;
    debug(29, 5) ("authenticateBasicFreeUser: Clearing Basic scheme data\n");
    if (basic_auth->username)
	xfree(basic_auth->username);
    if (basic_auth->passwd)
	xfree(basic_auth->passwd);
    memPoolFree(basic_data_pool, auth_user->scheme_data);
    auth_user->scheme_data = NULL;
}

static void
authenticateBasicHandleReply(void *data, char *reply)
{
    authenticateStateData *r = data;
    auth_user_t *auth_user;
    basic_data *basic_auth;
    auth_basic_queue_node *tmpnode;
    int valid;
    char *t = NULL;
    debug(29, 9) ("authenticateBasicHandleReply: {%s}\n", reply ? reply : "<NULL>");
    if (reply) {
	if ((t = strchr(reply, ' ')))
	    *t++ = '\0';
	if (*reply == '\0')
	    reply = NULL;
    }
    assert(r->auth_user_request != NULL);
    assert(r->auth_user_request->auth_user->auth_type == AUTH_BASIC);
    auth_user = r->auth_user_request->auth_user;
    basic_auth = auth_user->scheme_data;
    if (reply && (strncasecmp(reply, "OK", 2) == 0))
	basic_auth->flags.credentials_ok = 1;
    else {
	basic_auth->flags.credentials_ok = 3;
	safe_free(r->auth_user_request->message);
	if (t && *t)
	    r->auth_user_request->message = xstrdup(t);
    }
    basic_auth->credentials_checkedtime = squid_curtime;
    valid = cbdataValid(r->data);
    if (valid)
	r->handler(r->data, NULL);
    cbdataUnlock(r->data);
    while (basic_auth->auth_queue) {
	tmpnode = basic_auth->auth_queue->next;
	valid = cbdataValid(basic_auth->auth_queue->data);
	if (valid)
	    basic_auth->auth_queue->handler(basic_auth->auth_queue->data, NULL);
	cbdataUnlock(basic_auth->auth_queue->data);
	xfree(basic_auth->auth_queue);
	basic_auth->auth_queue = tmpnode;
    }
    authenticateStateFree(r);
}

static void
authBasicCfgDump(StoreEntry * entry, const char *name, authScheme * scheme)
{
    auth_basic_config *config = scheme->scheme_data;
    wordlist *list = config->authenticate;
    storeAppendPrintf(entry, "%s %s", name, "basic");
    while (list != NULL) {
	storeAppendPrintf(entry, " %s", list->key);
	list = list->next;
    }
    storeAppendPrintf(entry, "\n%s %s realm %s\n", name, "basic", config->basicAuthRealm);
    storeAppendPrintf(entry, "%s %s children %d\n", name, "basic", config->authenticateChildren);
    storeAppendPrintf(entry, "%s %s concurrency %d\n", name, "basic", config->authenticateConcurrency);
    storeAppendPrintf(entry, "%s %s credentialsttl %d seconds\n", name, "basic", (int) config->credentialsTTL);
    storeAppendPrintf(entry, "%s %s casesensitive %s\n", name, "basic", config->casesensitive ? "on" : "off");
    storeAppendPrintf(entry, "%s %s blankpassword %s\n", name, "basic", config->blankpassword ? "on" : "off");
}

static void
authBasicParse(authScheme * scheme, int n_configured, char *param_str)
{
    if (scheme->scheme_data == NULL) {
	assert(basicConfig == NULL);
	/* this is the first param to be found */
	scheme->scheme_data = xmalloc(sizeof(auth_basic_config));
	memset(scheme->scheme_data, 0, sizeof(auth_basic_config));
	basicConfig = scheme->scheme_data;
	basicConfig->basicAuthRealm = xstrdup("Squid proxy-caching web server");
	basicConfig->authenticateChildren = 5;
	basicConfig->credentialsTTL = 2 * 60 * 60;	/* two hours */
    }
    basicConfig = scheme->scheme_data;
    if (strcasecmp(param_str, "program") == 0) {
	if (basicConfig->authenticate)
	    wordlistDestroy(&basicConfig->authenticate);
	parse_wordlist(&basicConfig->authenticate);
    } else if (strcasecmp(param_str, "children") == 0) {
	parse_int(&basicConfig->authenticateChildren);
    } else if (strcasecmp(param_str, "concurrency") == 0) {
	parse_int(&basicConfig->authenticateConcurrency);
    } else if (strcasecmp(param_str, "realm") == 0) {
	parse_eol(&basicConfig->basicAuthRealm);
    } else if (strcasecmp(param_str, "credentialsttl") == 0) {
	parse_time_t(&basicConfig->credentialsTTL);
    } else if (strcasecmp(param_str, "casesensitive") == 0) {
	parse_onoff(&basicConfig->casesensitive);
    } else if (strcasecmp(param_str, "blankpassword") == 0) {
	parse_onoff(&basicConfig->blankpassword);
    } else {
	debug(29, 0) ("unrecognised basic auth scheme parameter '%s'\n", param_str);
    }
}

static void
authBasicCheckConfig(authScheme * scheme)
{
    auth_basic_config *config = scheme->scheme_data;
    requirePathnameExists("auth_param basic program", config->authenticate->key);
}

static void
authenticateBasicStats(StoreEntry * sentry)
{
    storeAppendPrintf(sentry, "Basic Authenticator Statistics:\n");
    helperStats(sentry, basicauthenticators);
}

CBDATA_TYPE(authenticateStateData);

/* authenticateBasicUsername: return a pointer to the username in the */
char *
authenticateBasicUsername(auth_user_t * auth_user)
{
    basic_data *basic_auth = auth_user->scheme_data;
    if (basic_auth)
	return basic_auth->username;
    return NULL;
}

static basic_data *
authBasicDataNew(void)
{
    basic_data *temp;
    temp = memPoolAlloc(basic_data_pool);
    assert(temp != NULL);
    temp->username = NULL;
    temp->passwd = NULL;
    temp->auth_queue = NULL;
    return temp;
}

#if UNUSED_CODE
static void
authBasicDataFree(basic_data * basic_auth)
{
}

#endif

static auth_user_t *
authBasicAuthUserFindUsername(const char *username)
{
    auth_user_hash_pointer *usernamehash;
    debug(29, 9) ("authBasicAuthUserFindUsername: Looking for user '%s'\n", username);
    if (username && (usernamehash = hash_lookup(proxy_auth_username_cache, username))) {
	while (usernamehash) {
	    if ((usernamehash->auth_user->auth_type == AUTH_BASIC) &&
		!strcmp(username, usernamehash->key))
		return usernamehash->auth_user;
	    usernamehash = usernamehash->next;
	}
    }
    return NULL;
}



/*
 * Decode a Basic [Proxy-]Auth string, linking the passed auth_user_request structure 
 * to any existing user structure or creating one if needed. Note that just returning
 * will be treated as "cannot decode credentials". Use the message field to return a 
 * descriptive message to the user.
 */

static void
authenticateBasicDecodeAuth(auth_user_request_t * auth_user_request, const char *proxy_auth)
{
    char *sent_auth;
    char *cleartext;
    basic_data *basic_auth, local_basic;
    auth_user_t *auth_user;
    dlink_node *node;

    /* decode the username */
    /* trim BASIC from string */
    while (xisgraph(*proxy_auth))
	proxy_auth++;

    local_basic.passwd = NULL;

    /* Trim leading whitespace before decoding */
    while (xisspace(*proxy_auth))
	proxy_auth++;
    /* username and password */
    sent_auth = xstrdup(proxy_auth);
    /* Trim trailing \n before decoding */
    strtok(sent_auth, "\n");
    cleartext = uudecode(sent_auth);
    xfree(sent_auth);
    /*
     * Don't allow NL or CR in the credentials.
     * Oezguer Kesim <oec@codeblau.de>
     */
    debug(29, 9) ("authenticateBasicDecodeAuth: cleartext = '%s'\n", cleartext);
    if (strcspn(cleartext, "\r\n") != strlen(cleartext)) {
	debug(29, 1) ("authenticateBasicDecodeAuth: bad characters in authorization header '%s'\n",
	    proxy_auth);
	xfree(cleartext);
	return;
    }
    local_basic.username = cleartext;
    if ((cleartext = strchr(local_basic.username, ':')) != NULL)
	*(cleartext)++ = '\0';
    local_basic.passwd = cleartext;
    if (cleartext == NULL) {
	debug(29, 4) ("authenticateBasicDecodeAuth: no password in proxy authorization header '%s'\n",
	    proxy_auth);
	local_basic.passwd = NULL;
	auth_user_request->message = xstrdup("no password was present in the HTTP [proxy-]authorization header. This is most likely a browser bug");
    } else if (*cleartext == '\0' && !basicConfig->blankpassword) {
	debug(29, 4) ("authenticateBasicDecodeAuth: Disallowing empty password,"
	    "user is '%s'\n", local_basic.username);
	local_basic.passwd = NULL;
	auth_user_request->message = xstrdup("Request denied because you provided an empty password. Users MUST have a password.");
    }
    /* special case: we have to free the strings for user and password
     * if we are not returning a filled out structure 
     */
    if (local_basic.passwd == NULL) {
	if (local_basic.username) {
	    /* log the username */
	    debug(29, 9) ("authBasicDecodeAuth: Creating new user for logging '%s'\n", local_basic.username);
	    /* new auth_user */
	    auth_user = authenticateAuthUserNew("basic");
	    /* new scheme data */
	    basic_auth = authBasicDataNew();
	    /* save the credentials */
	    basic_auth->username = local_basic.username;
	    /* link the scheme data in */
	    auth_user->scheme_data = basic_auth;
	    /* set the auth_user type */
	    auth_user->auth_type = AUTH_BROKEN;
	    /* link the request to the user */
	    auth_user_request->auth_user = auth_user;
	    /* lock for the auth_user_request link */
	    authenticateAuthUserLock(auth_user);
	    node = dlinkNodeNew();
	    dlinkAdd(auth_user_request, node, &auth_user->requests);
	}
	return;
    } else {
	local_basic.passwd = xstrndup(cleartext, USER_IDENT_SZ);
    }

    if (!basicConfig->casesensitive)
	Tolower(local_basic.username);
    /* now lookup and see if we have a matching auth_user structure in memory. */

    if ((auth_user = authBasicAuthUserFindUsername(local_basic.username)) == NULL) {
	/* the user doesn't exist in the username cache yet */
	debug(29, 9) ("authBasicDecodeAuth: Creating new user '%s'\n", local_basic.username);
	/* new auth_user */
	auth_user = authenticateAuthUserNew("basic");
	/* new scheme data */
	basic_auth = authBasicDataNew();
	/* save the credentials */
	basic_auth->username = local_basic.username;
	basic_auth->passwd = local_basic.passwd;
	/* link the scheme data in */
	auth_user->scheme_data = basic_auth;
	/* set the auth_user type */
	auth_user->auth_type = AUTH_BASIC;
	/* current time for timeouts */
	auth_user->expiretime = current_time.tv_sec;

	/* this auth_user struct is the 'lucky one' to get added to the username cache */
	/* the requests after this link to the auth_user */
	/* store user in hash */
	authenticateUserNameCacheAdd(auth_user);
    } else {
	debug(29, 9) ("authBasicDecodeAuth: Found user '%s' in the user cache as '%p'\n", local_basic.username, auth_user);
	xfree(local_basic.username);
	basic_auth = auth_user->scheme_data;
	if (strcmp(local_basic.passwd, basic_auth->passwd)) {
	    debug(29, 4) ("authBasicDecodeAuth: new password found. Updating in user master record and resetting auth state to unchecked\n");
	    basic_auth->flags.credentials_ok = 0;
	    xfree(basic_auth->passwd);
	    basic_auth->passwd = local_basic.passwd;
	} else
	    xfree(local_basic.passwd);
	if (basic_auth->flags.credentials_ok == 3) {
	    debug(29, 4) ("authBasicDecodeAuth: last attempt to authenticate this user failed, resetting auth state to unchecked\n");
	    basic_auth->flags.credentials_ok = 0;
	}
    }
    /* link the request to the user */
    auth_user_request->auth_user = auth_user;
    /* lock for the auth_user_request link */
    authenticateAuthUserLock(auth_user);
    node = dlinkNodeNew();
    dlinkAdd(auth_user_request, node, &auth_user->requests);
    return;
}

/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
 * config file */
static void
authBasicInit(authScheme * scheme)
{
    static int init = 0;
    if (basicConfig->authenticate) {
	if (!basic_data_pool)
	    basic_data_pool = memPoolCreate("Basic Scheme User Data", sizeof(basic_data));
	authbasic_initialised = 1;
	if (basicauthenticators == NULL)
	    basicauthenticators = helperCreate("basicauthenticator");
	basicauthenticators->cmdline = basicConfig->authenticate;
	basicauthenticators->n_to_start = basicConfig->authenticateChildren;
	basicauthenticators->concurrency = basicConfig->authenticateConcurrency;
	basicauthenticators->ipc_type = IPC_STREAM;
	helperOpenServers(basicauthenticators);
	if (!init) {
	    cachemgrRegister("basicauthenticator",
		"Basic User Authenticator Stats",
		authenticateBasicStats, 0, 1);
	    init++;
	}
	CBDATA_INIT_TYPE(authenticateStateData);
    }
}

/* send the initial data to a basic authenticator module */
static void
authenticateBasicStart(auth_user_request_t * auth_user_request, RH * handler, void *data)
{
    authenticateStateData *r = NULL;
    char buf[8192];
    char user[1024], pass[1024];
    basic_data *basic_auth;
    assert(auth_user_request);
    assert(handler);
    assert(auth_user_request->auth_user->auth_type == AUTH_BASIC);
    assert(auth_user_request->auth_user->scheme_data != NULL);
    basic_auth = auth_user_request->auth_user->scheme_data;
    debug(29, 9) ("authenticateStart: '%s:%s'\n", basic_auth->username,
	basic_auth->passwd);
    if (basicConfig->authenticate == NULL) {
	handler(data, NULL);
	return;
    }
    /* check to see if the auth_user already has a request outstanding */
    if (basic_auth->flags.credentials_ok == 2) {
	/* there is a request with the same credentials already being verified */
	auth_basic_queue_node *node;
	node = xmalloc(sizeof(auth_basic_queue_node));
	assert(node);
	/* save the details */
	node->next = basic_auth->auth_queue;
	basic_auth->auth_queue = node;
	node->handler = handler;
	node->data = data;
	cbdataLock(data);
	return;
    } else {
	r = cbdataAlloc(authenticateStateData);
	r->handler = handler;
	cbdataLock(data);
	r->data = data;
	r->auth_user_request = auth_user_request;
	authenticateAuthUserRequestLock(r->auth_user_request);
	/* mark the user as haveing verification in progress */
	basic_auth->flags.credentials_ok = 2;
	xstrncpy(user, rfc1738_escape(basic_auth->username), sizeof(user));
	xstrncpy(pass, rfc1738_escape(basic_auth->passwd), sizeof(pass));
	snprintf(buf, sizeof(buf), "%s %s\n", user, pass);
	helperSubmit(basicauthenticators, buf, authenticateBasicHandleReply, r);
    }
}
