/* 								     HTAABrow.c
**	BROWSER SIDE ACCESS AUTHORIZATION MODULE
**
**	(c) COPYRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**	@(#) $Id: HTAABrow.c,v 2.42 1996/08/13 02:17:06 frystyk Exp $
**
**	Contains code for parsing challenges and creating credentials for 
**	basic authentication schemes. See also the HTAAUtil module
**	for how to handle other authentication schemes. You don't have to use
**	this code at all.
**
** AUTHORS:
**	AL	Ari Luotonen	luotonen@dxcern.cern.ch
**	HFN	Henrik Frystyk
**
** HISTORY:
**	Oct 17	AL	Made corrections suggested by marca:
**			Added  if (!realm->username) return NULL;
**			Changed some ""s to NULLs.
**			Now doing HT_CALLOC() to init uuencode source;
**			otherwise HTUU_encode() reads uninitialized memory
**			every now and then (not a real bug but not pretty).
**			Corrected the formula for uuencode destination size.
**	Feb 96 HFN	Rewritten to make it scheme independent and based on
**			callback functions and an info structure
*/

/* Library include files */
#include "WWWLib.h"
#include "HTAAUtil.h"
#include "HTAABrow.h"					 /* Implemented here */

#define BASIC_AUTH	"basic"

typedef struct _HTBasic {		  /* Basic challenge and credentials */
    char *	uid;
    char *	pw;
    BOOL	retry;			    /* Should we ask the user again? */
    BOOL	proxy;				     /* Proxy authentication */
} HTBasic;

/* ------------------------------------------------------------------------- */

PRIVATE HTBasic * HTBasic_new()
{
    HTBasic * me = NULL;
    if ((me = (HTBasic *) HT_CALLOC(1, sizeof(HTBasic))) == NULL)
	HT_OUTOFMEM("HTBasic_new");
    me->retry = YES;			       /* Ask the first time through */
    return me;
}

/*	HTBasic_delete
**	--------------
**	Deletes a "basic" information object
*/
PUBLIC int HTBasic_delete (void * context)
{
    HTBasic * basic = (HTBasic *) context;
    if (basic) {
	HT_FREE(basic->uid);
	HT_FREE(basic->pw);
	HT_FREE(basic);
	return YES;
    }
    return NO;
}

/*
**	Create a protection template for the files
**	in the same directory as the given file
**	Returns	a template matching docname, and other files in that directory.
**
**		E.g.  /foo/bar/x.html  =>  /foo/bar/ *
**						    ^
**				Space only to prevent it from
**				being a comment marker here,
**				there really isn't any space.
*/
PRIVATE char * make_template (const char * docname)
{
    char * tmplate = NULL;
    if (docname) {
	char * host = HTParse(docname, "", PARSE_ACCESS|PARSE_HOST|PARSE_PUNCTUATION);
	char * path = HTParse(docname, "", PARSE_PATH|PARSE_PUNCTUATION);
	char * slash = strrchr(path, '/');
	if (slash) {
	    if (*(slash+1)) {		
		strcpy(slash, "*");
		StrAllocCat(host, path);
	    } else
		StrAllocCat(host, "*");
	}
	HT_FREE(path);
	tmplate = host;
    } else
	StrAllocCopy(tmplate, "*");
    if (AUTH_TRACE)
	HTTrace("Template.... Made template `%s' for file `%s'\n",
		tmplate, docname ? docname : "<null>");
    return tmplate;
}

/*
**	Make basic authentication scheme credentials and register this
**	information in the request object as credentials. They will then
**	be included in the request header. An example is 
**
**		"Basic AkRDIhEF8sdEgs72F73bfaS=="
**
**	The function can both create normal and proxy credentials
**	Returns	HT_OK or HT_ERROR
*/
PRIVATE BOOL basic_credentials (HTRequest * request, HTBasic * basic)
{
    if (request && basic) {
	char * cleartext = NULL;
	char * cipher = NULL;
	int cl_len = strlen(basic->uid ? basic->uid : "") +
	    strlen(basic->pw ? basic->pw : "") + 5;
	int ci_len = 4 * (((cl_len+2)/3) + 1);
	if ((cleartext = (char *) HT_CALLOC(1, cl_len)) == NULL)
	    HT_OUTOFMEM("basic_credentials");
	*cleartext = '\0';
	if (basic->uid) strcpy(cleartext, basic->uid);
	strcat(cleartext, ":");
	if (basic->pw) strcat(cleartext, basic->pw);
	if ((cipher = (char *) HT_CALLOC(1, ci_len + 3)) == NULL)
	    HT_OUTOFMEM("basic_credentials");
	HTUU_encode((unsigned char *) cleartext, strlen(cleartext), cipher);

	/* Create the credentials and assign them to the request object */
	{
	    int cr_len = strlen("basic") + ci_len + 3;
	    char * cookie = (char *) HT_MALLOC(cr_len+1);
	    if (!cookie) HT_OUTOFMEM("basic_credentials");
	    strcpy(cookie, "Basic ");
	    strcat(cookie, cipher);
	    if (AUTH_TRACE) HTTrace("Basic Cookie `%s\'\n", cookie);

	    /* Check whether it is proxy or normal credentials */
	    if (basic->proxy)
		HTRequest_addCredentials(request, "Proxy-Authorization", cookie);
	    else
		HTRequest_addCredentials(request, "Authorization", cookie);

	    HT_FREE(cookie);
	}
	HT_FREE(cleartext);
	HT_FREE(cipher);
	return HT_OK;
    }
    return HT_ERROR;
}

/*
**	Prompt the user for username and password.
**	Returns	YES if user name was typed in, else NO
*/
PRIVATE int prompt_user (HTRequest * request, const char * realm,
			 HTBasic * basic)
{
    HTAlertCallback * cbf = HTAlert_find(HT_A_USER_PW);
    if (request && cbf) {
	HTAlertPar * reply = HTAlert_newReply();
	int msg = basic->proxy ? HT_MSG_PROXY_UID : HT_MSG_UID;
	BOOL res = (*cbf)(request, HT_A_USER_PW, msg,
			  basic->uid, (char *) realm, reply);
	if (res) {
	    HT_FREE(basic->uid);
	    HT_FREE(basic->pw);
	    basic->uid = HTAlert_replyMessage(reply);
	    basic->pw = HTAlert_replySecret(reply);
	}
	HTAlert_deleteReply(reply);
	return res ? HT_OK : HT_ERROR;
    }
    return HT_OK;
}

/*	HTBasic_generate
**	----------------
**	This function generates "basic" credentials for the challenge found in
**	the authentication information base for this request. The result is
**	stored as an association list in the request object.
**	This is a callback function for the AA handler.
*/
PUBLIC int HTBasic_generate (HTRequest * request, void * context, int status)
{ 
    HTBasic * basic = (HTBasic *) context;
    BOOL proxy = status==HT_NO_PROXY_ACCESS ? YES : NO;
    if (request) {
	const char * realm = HTRequest_realm(request);

	/*
	** If we don't have a basic context then add a new one to the tree.
	** We use different trees for normal and proxy authentication
	*/
	if (!basic) {
	    if (proxy) {
		char * url = HTRequest_proxy(request);
		basic = HTBasic_new();
		basic->proxy = YES;
		HTAA_updateNode(proxy, BASIC_AUTH, realm, url, basic);
	    } else {
		char * url = HTAnchor_address((HTAnchor*)HTRequest_anchor(request));
		basic = HTBasic_new();
		HTAA_updateNode(proxy, BASIC_AUTH, realm, url, basic);
		HT_FREE(url);
	    }
	}

	/*
	** If we have a set of credentials (or the user provides a new set)
	** then store it in the request object as the credentials
	*/
	if ((basic->retry && prompt_user(request, realm, basic) == HT_OK) ||
	    (!basic->retry && basic->uid)) {
	    basic->retry = NO;
	    return basic_credentials(request, basic);
	} else
	    return HT_ERROR;
    }
    return HT_OK;
}

/*	HTBasic_parse
**	-------------
**	This function parses the contents of a "basic" challenge 
**	and stores the challenge in our authentication information datebase.
**	We also store the realm in the request object which will help finding
**	the right set of credentials to generate.
**	The function is a callback function for the AA handler.
*/
PUBLIC int HTBasic_parse (HTRequest * request, void * context, int status)
{
    HTAssocList * challenge = HTRequest_challenge(request);
    HTBasic * basic = NULL;
    BOOL proxy = status==HT_NO_PROXY_ACCESS ? YES : NO;
    if (request && challenge) {
	char * p = HTAssocList_findObject(challenge, BASIC_AUTH);
	char * realm = HTNextField(&p);
	char * rm = HTNextField(&p);

	/*
	** If valid challenge then make a template for the resource and
	** store this information in our authentication URL Tree
	*/
	if (realm && !strcasecomp(realm, "realm") && rm) {
	    if (AUTH_TRACE) HTTrace("Basic Parse. Realm `%s\' found\n", rm);
	    HTRequest_setRealm(request, rm);

	    /*
	    **  If we are in proxy mode then add the proxy - not the final URL
	    */
	    if (proxy) {
		char * url = HTRequest_proxy(request);
		if (AUTH_TRACE) HTTrace("Basic Parse. Proxy authentication\n");
		basic = (HTBasic *) HTAA_updateNode(proxy, BASIC_AUTH, rm,
						    url, NULL);
	    } else {
		char * url = HTAnchor_address((HTAnchor *)
					      HTRequest_anchor(request));
		char * tmplate = make_template(url);
		basic = (HTBasic *) HTAA_updateNode(proxy, BASIC_AUTH, rm,
						    tmplate, NULL);
		HT_FREE(url);
		HT_FREE(tmplate);
	    }
	}

	/*
	** For some reason the authentication failed so we have to ask the user
	** if we should try again. It may be because the user typed the wrong
	** user name and password
	*/
	if (basic) {
	    HTAlertCallback * prompt = HTAlert_find(HT_A_CONFIRM);

	    /*
	    ** Do we haev a method registered for prompting the user whether
	    ** we should retry
	    */
	    if (prompt) {
		int code = proxy ?
		    HT_MSG_RETRY_PROXY_AUTH : HT_MSG_RETRY_AUTHENTICATION;
		if ((*prompt)(request, HT_A_CONFIRM, code,
			      NULL, NULL, NULL) != YES)
		    return HT_ERROR;
		basic->retry = YES;
	    }
	}
	return HT_OK;
    }
    if (AUTH_TRACE) HTTrace("Auth........ No challenges found\n");
    return HT_ERROR;
}
