
/* MODULE							HTAABrow.c
**		BROWSER SIDE ACCESS AUTHORIZATION MODULE
**
**	Containts the code for keeping track on server hostnames,
**	port numbers, scheme names, usernames, passwords
**	(and servers' public keys).
**
** IMPORTANT:
**	Routines in this module use dynamic allocation, but free
**	automatically all the memory reserved by them.
**
**	Therefore the caller never has to (and never should)
**	free() any object returned by these functions.
**
**	Therefore also all the strings returned by this package
**	are only valid until the next call to the same function
**	is made. This approach is selected, because of the nature
**	of access authorization: no string returned by the package
**	needs to be valid longer than until the next call.
**
**	This also makes it easy to plug the AA package in:
**	you don't have to ponder whether to free() something
**	here or is it done somewhere else (because it is always
**	done somewhere else).
**
**	The strings that the package needs to store are copied
**	so the original strings given as parameters to AA
**	functions may be freed or modified with no side effects.
**
**	The AA package does not free() anything else than what
**	it has itself allocated.
**
** AUTHORS:
**	AL	Ari Luotonen	luotonen@dxcern.cern.ch
**
** HISTORY:
**	Oct 17	AL	Made corrections suggested by marca:
**			Added  if (!realm->username) return NULL;
**			Changed some ""s to NULLs.
**			Now doing 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.
**
** 28 Apr 1997	AJL	Do Proxy Authorisation.
**
** BUGS:
**
**
*/

#include <HTUtils.h>
#include <HTString.h>
#include <HTParse.h>		/* URL parsing function		*/
#include <HTList.h>		/* HTList object		*/
#include <HTAlert.h>		/* HTConfirm(), HTPrompt()	*/
#include <HTAAUtil.h>		/* AA common to both sides	*/
#include <HTAssoc.h>		/* Assoc list			*/
#include <HTAABrow.h>		/* Implemented here		*/
#include <HTUU.h>		/* Uuencoding and uudecoding	*/

#include <LYLeaks.h>

extern BOOL using_proxy;	/* Are we using an HTTP gateway? */

/*
**  Local datatype definitions
**
**  HTAAServer contains all the information about one server.
*/
typedef struct {

    char *	hostname;	/* Host's name			*/
    int		portnumber;	/* Port number			*/
    BOOL	IsProxy;	/* Is it a proxy?		*/
    HTList *	setups;		/* List of protection setups	*/
                                /* on this server; i.e. valid	*/
                                /* authentication schemes and	*/
                                /* templates when to use them.	*/
                                /* This is actually a list of	*/
                                /* HTAASetup objects.		*/
    HTList *	realms;		/* Information about passwords	*/
} HTAAServer;

/*
**  HTAASetup contains information about one server's one
**  protected tree of documents.
*/
typedef struct {
    HTAAServer *server;		/* Which server serves this tree	     */
    char *	template;	/* Template for this tree		     */
    HTList *	valid_schemes;	/* Valid authentic.schemes   		     */
    HTAssocList**scheme_specifics;/* Scheme specific params		     */
    BOOL	retry;		/* Failed last time -- reprompt (or whatever)*/
} HTAASetup;

/*
**  Information about usernames and passwords in
**  Basic and Pubkey authentication schemes;
*/
typedef struct {
    char *	realmname;	/* Password domain name		*/
    char *	username;	/* Username in that domain	*/
    char *	password;	/* Corresponding password	*/
} HTAARealm;

/*
**  To free off all globals. - FM
*/
PRIVATE void free_HTAAGlobals NOPARAMS;
PRIVATE BOOL free_HTAAGlobalsSet = FALSE;
PRIVATE char *HTAA_composeAuthResult = NULL;
PRIVATE char *compose_auth_stringResult = NULL;	/* Uuencoded presentation */

/*
**  Module-wide global variables
*/
PRIVATE HTList *server_table	= NULL;	/* Browser's info about servers	     */
PRIVATE char *secret_key	= NULL;	/* Browser's latest secret key       */
PRIVATE HTAASetup *current_setup= NULL;	/* The server setup we are currently */
                                        /* talking to			     */
PRIVATE char *current_hostname	= NULL;	/* The server's name and portnumber  */
PRIVATE int current_portnumber	= 80;	/* where we are currently trying to  */
                                        /* connect.			     */
PRIVATE char *current_docname	= NULL;	/* The document's name we are        */
                                        /* trying to access.		     */
PRIVATE char *HTAAForwardAuth	= NULL;	/* Authorization: line to forward    */
                                        /* (used by gateway httpds)	     */
PRIVATE HTAASetup *proxy_setup	= NULL;	/* Same as above, but for Proxy -AJL */
PRIVATE char *proxy_hostname	= NULL;
PRIVATE char *proxy_docname	= NULL;
PRIVATE int proxy_portnumber	= 80;


/*** HTAAForwardAuth for enabling gateway-httpds to forward Authorization ***/

PUBLIC void HTAAForwardAuth_set ARGS2(
	CONST char *,	scheme_name,
	CONST char *,	scheme_specifics)
{
    int len = 20 + (scheme_name      ? strlen(scheme_name)      : 0) 
	         + (scheme_specifics ? strlen(scheme_specifics) : 0);

    FREE(HTAAForwardAuth);
    if (!(HTAAForwardAuth = (char*)calloc(1, sizeof(char) * len)))
	outofmem(__FILE__, "HTAAForwardAuth_set");

    strcpy(HTAAForwardAuth, "Authorization: ");
    if (scheme_name) {
	strcat(HTAAForwardAuth, scheme_name);
	strcat(HTAAForwardAuth, " ");
	if (scheme_specifics) {
	    strcat(HTAAForwardAuth, scheme_specifics);
	}
    }
}

PUBLIC void HTAAForwardAuth_reset NOARGS
{
    FREE(HTAAForwardAuth);
}


/**************************** HTAAServer ***********************************/

PRIVATE void HTAASetup_delete PARAMS((HTAASetup * killme));	/* Forward */

/* PRIVATE						HTAAServer_new()
**		ALLOCATE A NEW NODE TO HOLD SERVER INFO
**		AND ADD IT TO THE LIST OF SERVERS
** ON ENTRY:
**	hostname	is the name of the host that the server
**			is running in.
**	portnumber	is the portnumber which the server listens.
**	IsProxy		should be TRUE if this is a proxy.
**
** ON EXIT:
**	returns		the newly-allocated node with all the strings
**			duplicated.
**			Strings will be automatically freed by
**			the function HTAAServer_delete(), which also
**			frees the node itself.
*/
PRIVATE HTAAServer *HTAAServer_new ARGS3(
	CONST char*,	hostname,
	int,		portnumber,
	BOOL,		IsProxy)
{
    HTAAServer *server;

    if (!(server = (HTAAServer *)calloc(1, sizeof(HTAAServer))))
	outofmem(__FILE__, "HTAAServer_new");

    server->hostname	= NULL;
    server->portnumber	= (portnumber > 0 ? portnumber : 80);
    server->IsProxy	= IsProxy;
    server->setups	= HTList_new();
    server->realms	= HTList_new();

    if (hostname)
        StrAllocCopy(server->hostname, hostname);

    if (!server_table)
        server_table = HTList_new();
    
    HTList_addObject(server_table, (void*)server);

    return server;
}


/* PRIVATE						HTAAServer_delete()
**
**	DELETE THE ENTRY FOR THE SERVER FROM THE HOST TABLE,
**	AND FREE THE MEMORY USED BY IT.
**
** ON ENTRY:
**	killme		points to the HTAAServer to be freed.
**
** ON EXIT:
**	returns		nothing.
*/
PRIVATE void HTAAServer_delete ARGS1(
	HTAAServer *,	killme)
{
    int n, i;
    HTAASetup *setup;
    HTAARealm *realm;
    HTList *cur;

    if (killme) {
	if (killme->setups != NULL) {
	    n = HTList_count(killme->setups);
	    for (i = (n - 1); i >= 0; i--) {
	        if ((setup = (HTAASetup*)HTList_objectAt(killme->setups, 
	     						 i)) != NULL) {
		    HTAASetup_delete(setup);
		    setup = NULL;
		}
	    }
	    HTList_delete(killme->setups);
	    killme->setups = NULL;
	}

	cur = killme->realms;
	while (NULL != (realm = (HTAARealm*)HTList_nextObject(cur))) {
	    FREE(realm->realmname);
	    FREE(realm->username);
	    FREE(realm->password);
	    FREE(realm);
	}
	HTList_delete(killme->realms);
	killme->realms = NULL;

	FREE(killme->hostname);

	HTList_removeObject(server_table, (void*)killme);
	FREE(killme);
    }
}

/* PRIVATE						HTAAServer_lookup()
**		LOOK UP SERVER BY HOSTNAME AND PORTNUMBER
** ON ENTRY:
**	hostname	obvious.
**	portnumber	if non-positive defaults to 80.
**	IsProxy		should be TRUE if this is a proxy.
**
**	Looks up the server in the module-global server_table.
**
** ON EXIT:
**	returns		pointer to a HTAAServer structure
**			representing the looked-up server.
**			NULL, if not found.
*/
PRIVATE HTAAServer *HTAAServer_lookup ARGS3(
	CONST char *,	hostname,
	int,		portnumber,
	BOOL,		IsProxy)
{
    if (hostname) {
	HTList *cur = server_table;
	HTAAServer *server;

	if (portnumber <= 0)
	    portnumber = 80;

	while (NULL != (server = (HTAAServer*)HTList_nextObject(cur))) {
	    if (server->portnumber == portnumber  &&
		0==strcmp(server->hostname, hostname) &&
		server->IsProxy == IsProxy)
		return server;
	}
    }
    return NULL;	/* NULL parameter, or not found */
}


/*************************** HTAASetup *******************************/    

/* PRIVATE						HTAASetup_lookup()
**	FIGURE OUT WHICH AUTHENTICATION SETUP THE SERVER
**	IS USING FOR A GIVEN FILE ON A GIVEN HOST AND PORT
**
** ON ENTRY:
**	hostname	is the name of the server host machine.
**	portnumber	is the port that the server is running in.
**	docname		is the (URL-)pathname of the document we
**			are trying to access.
**	IsProxy		should be TRUE if this is a proxy.
**
** 	This function goes through the information known about
**	all the setups of the server, and finds out if the given
**	filename resides in one of the protected directories.
**
** ON EXIT:
**	returns		NULL if no match.
**			Otherwise, a HTAASetup structure representing
**			the protected server setup on the corresponding
**			document tree.
**			
*/
PRIVATE HTAASetup *HTAASetup_lookup ARGS4(
	CONST char *,	hostname,
	int,		portnumber,
	CONST char *,	docname,
	BOOL,		IsProxy)
{
    HTAAServer *server;
    HTAASetup *setup;

    if (portnumber <= 0)
        portnumber = 80;

    if (hostname && docname && *hostname && *docname &&
	NULL != (server = HTAAServer_lookup(hostname,
					    portnumber,
					    IsProxy))) {

	HTList *cur = server->setups;

	CTRACE(tfp, "%s %s (%s:%d:%s)\n",
		    "HTAASetup_lookup: resolving setup for",
		    (IsProxy ? "proxy" : "server"),
		    hostname, portnumber, docname);

	while (NULL != (setup = (HTAASetup*)HTList_nextObject(cur))) {
	    if (HTAA_templateMatch(setup->template, docname)) {
		CTRACE(tfp, "%s `%s' %s `%s'\n",
			    "HTAASetup_lookup:", docname,
			    "matched template", setup->template);
		return setup;
	    } else {
	        CTRACE(tfp, "%s `%s' %s `%s'\n",
			    "HTAASetup_lookup:", docname,
			    "did NOT match template", setup->template);
	    }
	} /* while setups remain */
    } /* if valid parameters and server found */

    CTRACE(tfp, "%s `%s' %s\n",
		 "HTAASetup_lookup: No template matched",
		 (docname ? docname : "(null)"),
		 "(so probably not protected)");

    return NULL;	/* NULL in parameters, or not found */
}

/* PRIVATE						HTAASetup_new()
**			CREATE A NEW SETUP NODE
** ON ENTRY:
**	server		is a pointer to a HTAAServer structure
**			to which this setup belongs.
**	template	documents matching this template
**			are protected according to this setup.
**	valid_schemes	a list containing all valid authentication
**			schemes for this setup.
**			If NULL, all schemes are disallowed.
**	scheme_specifics is an array of assoc lists, which
**			contain scheme specific parameters given
**			by server in Authenticate: fields.
**			If NULL, all scheme specifics are
**			set to NULL.
** ON EXIT:
**	returns		a new HTAASetup node, and also adds it as
**			part of the HTAAServer given as parameter.
*/
PRIVATE HTAASetup *HTAASetup_new ARGS4(
	HTAAServer *,	server,
	char *,		template,
	HTList *,	valid_schemes,
	HTAssocList **,	scheme_specifics)
{
    HTAASetup *setup;

    if (!server || !template || !*template)
        return NULL;

    if (!(setup = (HTAASetup*)calloc(1, sizeof(HTAASetup))))
	outofmem(__FILE__, "HTAASetup_new");

    setup->retry = NO;
    setup->server = server;
    setup->template = NULL;
    if (template)
        StrAllocCopy(setup->template, template);
    setup->valid_schemes = valid_schemes;
    setup->scheme_specifics = scheme_specifics;

    HTList_addObject(server->setups, (void*)setup);

    return setup;
}

/* PRIVATE						HTAASetup_delete()
**			FREE A HTAASetup STRUCTURE
** ON ENTRY:
**	killme		is a pointer to the structure to free().
**
** ON EXIT:
**	returns		nothing.
*/
PRIVATE void HTAASetup_delete ARGS1(
	HTAASetup *,	killme)
{
    int scheme;

    if (killme) {
	FREE(killme->template);
	if (killme->valid_schemes)
	    HTList_delete(killme->valid_schemes);
	    killme->valid_schemes = NULL;
	for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++)
	    if (killme->scheme_specifics[scheme])
		HTAssocList_delete(killme->scheme_specifics[scheme]);
	FREE(killme->scheme_specifics);
	FREE(killme);
    }
}

/* PRIVATE					HTAASetup_updateSpecifics()
*		COPY SCHEME SPECIFIC PARAMETERS
**		TO HTAASetup STRUCTURE
** ON ENTRY:
**	setup		destination setup structure.
**	specifics	string array containing scheme
**			specific parameters for each scheme.
**			If NULL, all the scheme specific
**			parameters are set to NULL.
**
** ON EXIT:
**	returns		nothing.
*/
PRIVATE void HTAASetup_updateSpecifics ARGS2(
	HTAASetup *,	setup,
	HTAssocList **,	specifics)
{
    int scheme;

    if (setup) {
	if (setup->scheme_specifics) {
	    for (scheme = 0; scheme < HTAA_MAX_SCHEMES; scheme++) {
		if (setup->scheme_specifics[scheme])
		    HTAssocList_delete(setup->scheme_specifics[scheme]);
	    }
	    FREE(setup->scheme_specifics);
	}
	setup->scheme_specifics = specifics;
    }
}


/*************************** HTAARealm **********************************/

/* PRIVATE 						HTAARealm_lookup()
**		LOOKUP HTAARealm STRUCTURE BY REALM NAME
** ON ENTRY:
**	realm_table	a list of realm objects.
**	realmname	is the name of realm to look for.
**
** ON EXIT:
**	returns		the realm.  NULL, if not found.
*/
PRIVATE HTAARealm *HTAARealm_lookup ARGS2(
	HTList *,	realm_table,
	CONST char *,	realmname)
{
    if (realm_table && realmname) {
	HTList *cur = realm_table;
	HTAARealm *realm;
	
	while (NULL != (realm = (HTAARealm*)HTList_nextObject(cur))) {
	    if (0==strcmp(realm->realmname, realmname))
		return realm;
	}
    }
    return NULL;	/* No table, NULL param, or not found */
}

/* PRIVATE						HTAARealm_new()
**		CREATE A NODE CONTAINING USERNAME AND
**		PASSWORD USED FOR THE GIVEN REALM.
**		IF REALM ALREADY EXISTS, CHANGE
**		USERNAME/PASSWORD.
** ON ENTRY:
**	realm_table	a list of realms to where to add
**			the new one, too.
**	realmname	is the name of the password domain.
**	username	and
**	password	are what you can expect them to be.
**
** ON EXIT:
**	returns		the created realm.
*/
PRIVATE HTAARealm *HTAARealm_new ARGS4(
	HTList *,	realm_table,
	CONST char *,	realmname,
	CONST char *,	username,
	CONST char *,	password)
{
    HTAARealm *realm;

    realm = HTAARealm_lookup(realm_table, realmname);

    if (!realm) {
	if (!(realm = (HTAARealm*)calloc(1, sizeof(HTAARealm))))
	    outofmem(__FILE__, "HTAARealm_new");
	realm->realmname = NULL;
	realm->username = NULL;
	realm->password = NULL;
	StrAllocCopy(realm->realmname, realmname);
	if (realm_table)
	    HTList_addObject(realm_table, (void*)realm);
    }
    if (username)
        StrAllocCopy(realm->username, username);
    if (password)
        StrAllocCopy(realm->password, password);

    return realm;
}


/***************** Basic and Pubkey Authentication ************************/

/* PRIVATE						compose_auth_string()
**
**		COMPOSE Basic OR Pubkey AUTHENTICATION STRING;
**		PROMPTS FOR USERNAME AND PASSWORD IF NEEDED
**
** ON ENTRY:
**	scheme		is either HTAA_BASIC or HTAA_PUBKEY.
**	setup		is the current server setup.
**	IsProxy		should be TRUE if this is a proxy.
**
** ON EXIT:
**	returns		a newly composed authorization string,
**			(with, of course, a newly generated secret
**			key and fresh timestamp, if Pubkey-scheme
**			is being used).
**			NULL, if something fails.
** NOTE:
**	Like throughout the entire AA package, no string or structure
**	returned by AA package needs to (or should) be freed.
**
*/
PRIVATE char *compose_auth_string ARGS3(
	HTAAScheme,	scheme,
	HTAASetup *,	setup,
	BOOL,		IsProxy)
{
    char *cleartext = NULL;	/* Cleartext presentation */
    char *ciphertext = NULL;	/* Encrypted presentation */
    int len;
    char *msg = NULL;
    char *username = NULL;
    char *password = NULL;
    char *realmname = NULL;
    char *theHost = NULL;
    char *proxiedHost = NULL;
    char *thePort = NULL;
    HTAARealm *realm;
    char *i_net_addr = "0.0.0.0";	/* Change... @@@@ */
    char *timestamp = "42";		/* ... these @@@@ */
    

    FREE(compose_auth_stringResult);	/* From previous call */

    if ((scheme != HTAA_BASIC && scheme != HTAA_PUBKEY) || !setup ||
	!setup->scheme_specifics || !setup->scheme_specifics[scheme] ||
	!setup->server  ||  !setup->server->realms)
	return NULL;

    realmname = HTAssocList_lookup(setup->scheme_specifics[scheme], "realm");
    if (!realmname)
        return NULL;

    realm = HTAARealm_lookup(setup->server->realms, realmname);
    if (!(realm &&
    	  realm->username && *realm->username &&
	  realm->password) || setup->retry) { 
	if (!realm) {
	    CTRACE(tfp, "%s `%s' %s\n",
			"compose_auth_string: realm:", realmname,
			"not found -- creating");
	    realm = HTAARealm_new(setup->server->realms,
				  realmname, NULL, NULL);
	}
	/*
	 *  The template should be either the '*' global
	 *  for everthing on the server (always true for
	 *  proxy authorization setups), or a path for
	 *  the start of a protected limb, with no host
	 *  field, but we'll check for a host anyway in
	 *  case a WWW-Protection-Template header set an
	 *  absolute URL instead of a path.  If we do get
	 *  a host from this, it will include the port. - FM
	 */
	if ((!IsProxy) && using_proxy && setup->template) {
	    proxiedHost = HTParse(setup->template, "", PARSE_HOST);
	    if (proxiedHost && *proxiedHost != '\0') {
	        theHost = proxiedHost;
	    }
	}
	/*
	 *  If we didn't get a host field from the
	 *  template, set up the host name and port
	 *  from the setup->server elements. - FM
	 */
	if (!theHost)
	    theHost = setup->server->hostname;
	    if (setup->server->portnumber > 0 &&
		setup->server->portnumber != 80) {
		if (!(thePort = (char *)calloc(1, sizeof(char) * 40)))
		    outofmem(__FILE__, "compose_auth_string");
		sprintf(thePort, ":%d", setup->server->portnumber);
	    }
	/*
	 *  Set up the message for the username prompt,
	 *  and then issue the prompt.  The default
	 *  username is included in the call to the
	 *  prompting function, but the password is
	 *  NULL-ed and always replaced. - FM
	 */
	len = strlen(realm->realmname) +
	      strlen(theHost ?
	      	     theHost : "??") + 50;
	if (!(msg = (char *)calloc(1, sizeof(char) * len)))
	    outofmem(__FILE__, "compose_auth_string");
	sprintf(msg, "Username for '%s' at %s '%s%s':",
		     realm->realmname,
		     (IsProxy ? "proxy" : "server"),
		     (theHost ? theHost : "??"),
		     (thePort ? thePort : ""));
	FREE(proxiedHost);
	FREE(thePort);
	username = realm->username;
	password = NULL;
	HTPromptUsernameAndPassword(msg, &username, &password, IsProxy);

	FREE(msg);
	FREE(realm->username);
	FREE(realm->password);
	realm->username = username;
	realm->password = password;

	if (!realm->username || !realm->password) {
	    /*
	     *  Signals to retry. - FM
	     */
	    return NULL;
	} else if (*realm->username == '\0') { 
	    /*
	     *  Signals to abort. - FM
	     */
	    StrAllocCopy(compose_auth_stringResult, "");
	    return compose_auth_stringResult;
	}
    }
    
    len = strlen(realm->username ? realm->username : "") +
	  strlen(realm->password ? realm->password : "") + 3;

    if (scheme == HTAA_PUBKEY) {
#ifdef PUBKEY
	/* Generate new secret key */
	StrAllocCopy(secret_key, HTAA_generateRandomKey());
#endif /* PUBKEY */
	/* Room for secret key, timestamp and inet address */
	len += strlen(secret_key ? secret_key : "") + 30;
    } else {
	FREE(secret_key);
    }

    if (!(cleartext  = (char*)calloc(1, sizeof(char) * len)))
	outofmem(__FILE__, "compose_auth_string");

    if (realm->username)
        strcpy(cleartext, realm->username);
    else
        *cleartext = '\0';

    strcat(cleartext, ":");

    if (realm->password)
        strcat(cleartext, realm->password);

    if (scheme == HTAA_PUBKEY) {
	strcat(cleartext, ":");
	strcat(cleartext, i_net_addr);
	strcat(cleartext, ":");
	strcat(cleartext, timestamp);
	strcat(cleartext, ":");
	if (secret_key)
	    strcat(cleartext, secret_key);

	if (!((ciphertext = (char *)calloc(1, (sizeof(char) * 2) * len)) &&
	      (compose_auth_stringResult =
	      		    (char *)calloc(1, (sizeof(char) * 3) * len))))
	    outofmem(__FILE__, "compose_auth_string");
#ifdef PUBKEY
	HTPK_encrypt(cleartext, ciphertext, server->public_key);
	HTUU_encode((unsigned char *)ciphertext, strlen(ciphertext),
		    compose_auth_stringResult);
#endif /* PUBKEY */
	FREE(cleartext);
	FREE(ciphertext);
    }
    else { /* scheme == HTAA_BASIC */
	if (!(compose_auth_stringResult =
				(char*)calloc(1, (4 * ((len+2)/3)) + 1)))
	    outofmem(__FILE__, "compose_auth_string");
	HTUU_encode((unsigned char *)cleartext, strlen(cleartext),
		    compose_auth_stringResult);
	FREE(cleartext);
    }
    return compose_auth_stringResult;
}

/* BROWSER PRIVATE					HTAA_selectScheme()
**		SELECT THE AUTHENTICATION SCHEME TO USE
** ON ENTRY:
**	setup	is the server setup structure which can
**		be used to make the decision about the
**		used scheme.
**
**	When new authentication methods are added to library
**	this function makes the decision about which one to
**	use at a given time.  This can be done by inspecting
**	environment variables etc.
**
**	Currently only searches for the first valid scheme,
**	and if nothing found suggests Basic scheme;
**
** ON EXIT:
**	returns	the authentication scheme to use.
*/
PRIVATE HTAAScheme HTAA_selectScheme ARGS1(
	HTAASetup *,	setup)
{
    HTAAScheme scheme;

    if (setup && setup->valid_schemes) {
	for (scheme = HTAA_BASIC; scheme < HTAA_MAX_SCHEMES; scheme++)
	    if (-1 < HTList_indexOf(setup->valid_schemes, (void*)scheme))
		return scheme;
    }
    return HTAA_BASIC;
}

/*
**  Purpose:	Free off all module globals.
**  Arguments:	void
**  Return Value:	void
**  Remarks/Portability/Dependencies/Restrictions:
**	To be used at program exit.
**  Revision History:
**	06-19-96	created - FM
*/
PRIVATE void free_HTAAGlobals NOARGS
{
    HTAAServer * server;
    int n, i;

    if (server_table != NULL) {
        n = HTList_count(server_table);
	for (i = (n - 1); i >= 0; i--) {
	    if ((server = (HTAAServer*)HTList_objectAt(server_table, 
	     						i)) != NULL) {
	        HTAAServer_delete(server);
		server = NULL;
	    }
	}
	HTList_delete(server_table);
	server_table = NULL;
    }

    HTAAForwardAuth_reset();
    FREE(HTAA_composeAuthResult);
    FREE(current_hostname);
    FREE(current_docname);
    FREE(proxy_hostname);
    FREE(proxy_docname);
    FREE(compose_auth_stringResult);
    FREE(secret_key);
}

/* BROWSER PUBLIC					HTAA_composeAuth()
**
**	SELECT THE AUTHENTICATION SCHEME AND
**	COMPOSE THE ENTIRE AUTHORIZATION HEADER LINE
**	IF WE ALREADY KNOW THAT THE HOST REQUIRES AUTHENTICATION
**
** ON ENTRY:
**	hostname	is the hostname of the server.
**	portnumber	is the portnumber in which the server runs.
**	docname		is the pathname of the document (as in URL)
**	IsProxy		should be TRUE if this is a proxy.
**
** ON EXIT:
**	returns	NULL, if no authorization seems to be needed, or
**		if it is the entire Authorization: line, e.g.
**
**		   "Authorization: Basic username:password"
**
**		As usual, this string is automatically freed.
*/
PUBLIC char *HTAA_composeAuth ARGS4(
	CONST char *,	hostname,
	CONST int,	portnumber,
	CONST char *,	docname,
	BOOL,		IsProxy)
{
    char *auth_string;
    BOOL retry;
    HTAAScheme scheme;
    int len;

    /*
    **  Setup atexit() freeing if not done already. - FM
    */
    if (!free_HTAAGlobalsSet) {
        atexit(free_HTAAGlobals);
	free_HTAAGlobalsSet = TRUE;
    }

    /*
    ** Make gateway httpds pass authorization field as it was received.
    ** (This still doesn't really work because Authenticate: headers
    **  from remote server are not forwarded to client yet so it cannot
    **  really know that it should send authorization;  I will not
    **  implement it yet because I feel we will soon change radically
    **  the way requests are represented to allow multithreading
    **  on server-side.  Life is hard.)
    */
    if (HTAAForwardAuth) {
	CTRACE(tfp, "HTAA_composeAuth: %s\n",
		    "Forwarding received authorization");
	StrAllocCopy(HTAA_composeAuthResult, HTAAForwardAuth);
	HTAAForwardAuth_reset();	/* Just a precaution */
	return HTAA_composeAuthResult;
    }

    FREE(HTAA_composeAuthResult);	/* From previous call */

    if (IsProxy) {
        /*
	**  Proxy Authorization required. - AJL
	*/

	CTRACE(tfp, "Composing Proxy Authorization for %s:%d/%s\n",
		    hostname, portnumber, docname);

	if (proxy_portnumber != portnumber ||
	    !proxy_hostname || !proxy_docname ||
	    !hostname         || !docname         ||
	    0 != strcmp(proxy_hostname, hostname) ||
	    0 != strcmp(proxy_docname, docname)) {

	    retry = NO;

	    proxy_portnumber = portnumber;

	    if (hostname)
		StrAllocCopy(proxy_hostname, hostname);
	    else
		FREE(proxy_hostname);

	    if (docname)
		StrAllocCopy(proxy_docname, docname);
	    else
		FREE(proxy_docname);
	} else {
	    retry = YES;
	}

	if (!proxy_setup || !retry)
	    proxy_setup = HTAASetup_lookup(hostname, portnumber,
					   docname, IsProxy);

	if (!proxy_setup)
	    return NULL;

    	switch (scheme = HTAA_selectScheme(proxy_setup)) {
	  case HTAA_BASIC:
	  case HTAA_PUBKEY:
	    auth_string = compose_auth_string(scheme, proxy_setup, IsProxy);
	    break;
	  case HTAA_KERBEROS_V4:
	    /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */
	  default:
	    {
		char msg[100];
		sprintf(msg, "%s %s `%s'",
			     "This client doesn't know how to compose proxy",
			     "authorization information for scheme",
			     HTAAScheme_name(scheme));
		HTAlert(msg);
		auth_string = NULL;
	    }
	} /* switch scheme */

	proxy_setup->retry = NO;

	if (!auth_string)
	    /*
	    **  Signal a failure. - FM
	    */
	    return NULL;  /* Added by marca. */
	if (*auth_string == '\0') {
	    /*
	    **  Signal an abort. - FM
	    */
	    StrAllocCopy(HTAA_composeAuthResult, "");
	    return(HTAA_composeAuthResult);
	}
	len = strlen(auth_string) + strlen((char *)HTAAScheme_name(scheme)) + 26;
	if (!(HTAA_composeAuthResult = (char*)calloc(1, sizeof(char) * len)))
	    outofmem(__FILE__, "HTAA_composeAuth");
	strcpy(HTAA_composeAuthResult, "Proxy-Authorization: ");

    } else {
	/*
	**  Normal WWW authorization.
	*/
	CTRACE(tfp, "Composing Authorization for %s:%d/%s\n",
		    hostname, portnumber, docname);

	if (current_portnumber != portnumber ||
	    !current_hostname || !current_docname ||
	    !hostname         || !docname         ||
	    0 != strcmp(current_hostname, hostname) ||
	    0 != strcmp(current_docname, docname)) {

	    retry = NO;

	    current_portnumber = portnumber;

	    if (hostname)
		StrAllocCopy(current_hostname, hostname);
	    else
		FREE(current_hostname);

	    if (docname)
		StrAllocCopy(current_docname, docname);
	    else
		FREE(current_docname);
	} else {
	    retry = YES;
	}

	if (!current_setup || !retry)
	    current_setup = HTAASetup_lookup(hostname, portnumber,
					     docname, IsProxy);

	if (!current_setup)
	    return NULL;

	switch (scheme = HTAA_selectScheme(current_setup)) {
	  case HTAA_BASIC:
	  case HTAA_PUBKEY:
	    auth_string = compose_auth_string(scheme, current_setup, IsProxy);
	    break;
	  case HTAA_KERBEROS_V4:
	    /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */
	  default:
	    {
		char msg[100];
		sprintf(msg, "%s %s `%s'",
			"This client doesn't know how to compose",
			"authoritzation information for scheme",
			HTAAScheme_name(scheme));
		HTAlert(msg);
		auth_string = NULL;
	    }
	} /* switch scheme */

	current_setup->retry = NO;

	if (!auth_string)
	    /*
	    **  Signal a failure. - FM
	    */
	    return NULL;  /* Added by marca. */
	if (*auth_string == '\0') {
	    /*
	    **  Signal an abort. - FM
	    */
	    StrAllocCopy(HTAA_composeAuthResult, "");
	    return(HTAA_composeAuthResult);
	}

	len = strlen(auth_string) + strlen((char *)HTAAScheme_name(scheme)) + 20;
	if (!(HTAA_composeAuthResult = (char*)calloc(1, sizeof(char) * len)))
	    outofmem(__FILE__, "HTAA_composeAuth");
	strcpy(HTAA_composeAuthResult, "Authorization: ");
    }

    strcat(HTAA_composeAuthResult, HTAAScheme_name(scheme));
    strcat(HTAA_composeAuthResult, " ");
    strcat(HTAA_composeAuthResult, auth_string);
    return HTAA_composeAuthResult;
}

/* BROWSER PUBLIC				HTAA_shouldRetryWithAuth()
**
**		DETERMINES IF WE SHOULD RETRY THE SERVER
**		WITH AUTHORIZATION
**		(OR IF ALREADY RETRIED, WITH A DIFFERENT
**		USERNAME AND/OR PASSWORD (IF MISSPELLED))
** ON ENTRY:
**	start_of_headers is the first block already read from socket,
**			but status line skipped; i.e. points to the
**			start of the header section.
**	length		is the remaining length of the first block.
**	soc		is the socket to read the rest of server reply.
**	IsProxy		should be TRUE if this is a proxy.
**
**			This function should only be called when
**			server has replied with a 401 (Unauthorized)
**			status code.
** ON EXIT:
**	returns		YES, if connection should be retried.
**			     The node containing all the necessary
**			     information is
**				* either constructed if it does not exist
**				* or password is reset to NULL to indicate
**				  that username and password should be
**				  reprompted when composing Authorization:
**				  field (in function HTAA_composeAuth()).
**			NO, otherwise.
*/
PUBLIC BOOL HTAA_shouldRetryWithAuth ARGS4(
	char *,		start_of_headers,
	int,		length,
	int,		soc,
	BOOL,		IsProxy)
{
    HTAAScheme scheme;
    char *line = NULL;
    int num_schemes = 0;
    HTList *valid_schemes = HTList_new();
    HTAssocList **scheme_specifics = NULL;
    char *template = NULL;
    char *temp = NULL;

    /*
    **  Setup atexit() freeing if not done already. - FM
    */
    if (!free_HTAAGlobalsSet) {
        atexit(free_HTAAGlobals);
	free_HTAAGlobalsSet = TRUE;
    }

    /*
    **  Read server reply header lines
    */
    CTRACE(tfp, "Server reply header lines:\n");

    HTAA_setupReader(start_of_headers, length, soc);
    while (NULL != (line = HTAA_getUnfoldedLine())  &&  *line != '\0') {
	CTRACE(tfp, "%s\n", line);

	if (strchr(line, ':')) {	/* Valid header line */

	    char *p = line;
	    char *fieldname = HTNextField(&p);
	    char *arg1 = HTNextField(&p);
	    char *args = p;

	    if ((IsProxy &&
		 0==strcasecomp(fieldname, "Proxy-Authenticate:")) ||
	        (!IsProxy &&
		 0==strcasecomp(fieldname, "WWW-Authenticate:"))) {
	        if (!(arg1 && *arg1 && args && *args)) {
		    temp = (char *)calloc(1, strlen(line) +
		    			     (arg1 ? strlen(arg1) : 0) +
					     (args ? strlen(args) : 0) + 24);
		    if (!temp)
		        outofmem(__FILE__, "HTAA_shouldRetryWithAuth");
		    sprintf(temp, "Invalid header '%s%s%s%s%s'", line,
				  ((arg1 && *arg1) ? " "  : ""),
				  ((arg1 && *arg1) ? arg1 : ""),
				  ((args && *args) ? " "  : ""),
				  ((args && *args) ? args : ""));
		    HTAlert(temp);
		    FREE(temp);
		}
		else if (HTAA_UNKNOWN != (scheme = HTAAScheme_enum(arg1))) {
		    HTList_addObject(valid_schemes, (void*)scheme);
		    if (!scheme_specifics) {
			int i;
			scheme_specifics = (HTAssocList**)
			    calloc(1, HTAA_MAX_SCHEMES * sizeof(HTAssocList*));
			if (!scheme_specifics)
			    outofmem(__FILE__, "HTAA_shouldRetryWithAuth");
			for (i=0; i < HTAA_MAX_SCHEMES; i++)
			    scheme_specifics[i] = NULL;
		    }
		    scheme_specifics[scheme] = HTAA_parseArgList(args);
		    num_schemes++;
		} else {
		    CTRACE(tfp, "Unknown scheme `%s' %s\n",
			    (arg1 ? arg1 : "(null)"),
			    (IsProxy ?
			     "in Proxy-Authenticate: field" :
			     "in WWW-Authenticate: field"));
		}
	    }

	    else if (!IsProxy &&
		     0==strcasecomp(fieldname, "WWW-Protection-Template:")) {
		CTRACE(tfp, "Protection template set to `%s'\n", arg1);
		StrAllocCopy(template, arg1);
	    }

	} /* if a valid header line */
	else {
	    CTRACE(tfp, "Invalid header line `%s' ignored\n", line);
	} /* else invalid header line */

	FREE(line);
    } /* while header lines remain */
    FREE(line);


    /*
    **  So should we retry with authorization?
    */
    if (IsProxy) {
	if (num_schemes == 0) {
	    /*
	    **  No proxy authorization valid
	    */
	    proxy_setup = NULL;
	    return NO;
	}
        /*
	**  Doing it for proxy.  -AJL
	*/
	if (proxy_setup && proxy_setup->server) {
	    /* 
	    **  We have already tried with proxy authorization.
	    **  Either we don't have access or username or
	    **  password was misspelled.
	    **
	    **  Update scheme-specific parameters
	    **  (in case they have expired by chance).
	    */
	    HTAASetup_updateSpecifics(proxy_setup, scheme_specifics);

	    if (NO == HTConfirm("Authorization failed.  Retry?")) {
		proxy_setup = NULL;
		return NO;
	    } else {
	        /*
		**  Re-ask username+password (if misspelled).
		*/
		proxy_setup->retry = YES;
		return YES;
	    }
	} else {
	    /*
	    **  proxy_setup == NULL, i.e. we have a
	    **  first connection to a protected server or
	    **  the server serves a wider set of documents
	    **  than we expected so far.
	    */
	    HTAAServer *server = HTAAServer_lookup(proxy_hostname,
						   proxy_portnumber,
						   IsProxy);
	    if (!server) {
		server = HTAAServer_new(proxy_hostname,
					proxy_portnumber,
					IsProxy);
	    }
	    if (!template)	/* Proxy matches everything  -AJL */
		StrAllocCopy(template, "*");
	    proxy_setup = HTAASetup_new(server, 
					  template,
					  valid_schemes,
					  scheme_specifics);
	    FREE(template);

	    HTAlert("Proxy authorization required -- retrying");
	    return YES;
	}
	/* Never reached */
    }
    /*
    **  Normal WWW authorization.
    */
    if (num_schemes == 0) {
	/*
	**  No authorization valid.
	*/
	current_setup = NULL;
	return NO;
    }
    if (current_setup && current_setup->server) {
	/*
	**  So we have already tried with WWW authorization.
	**  Either we don't have access or username or
	**  password was misspelled.
	**
	**  Update scheme-specific parameters
	**  (in case they have expired by chance).
	*/
	HTAASetup_updateSpecifics(current_setup, scheme_specifics);

	if (NO == HTConfirm("Authorization failed.  Retry?")) {
	    current_setup = NULL;
	    return NO;
	} else {
	    /*
	    **  Re-ask username+password (if misspelled).
	    */
	    current_setup->retry = YES;
	    return YES;
	}
    } else {
        /*
	**  current_setup == NULL, i.e. we have a
	**  first connection to a protected server or
	**  the server serves a wider set of documents
	**  than we expected so far.
	*/
	HTAAServer *server = HTAAServer_lookup(current_hostname,
					       current_portnumber,
					       IsProxy);
	if (!server) {
	    server = HTAAServer_new(current_hostname,
				    current_portnumber,
				    IsProxy);
	}
	if (!template)
	    template = HTAA_makeProtectionTemplate(current_docname);
	current_setup = HTAASetup_new(server, 
				      template,
				      valid_schemes,
				      scheme_specifics);
	FREE(template);

        HTAlert("Access without authorization denied -- retrying");
	return YES;
    }
    /* Never reached */
}

/*
**  This function clears all authorization information by
**  invoking the free_HTAAGlobals() function, which normally
**  is invoked at exit.  It allows a browser command to do
**  this at any time, for example, if the user is leaving
**  the terminal for a period of time, but does not want
**  to end the current session.  - FM
*/
PUBLIC void HTClearHTTPAuthInfo NOARGS
{
    /*
    **  Need code to check cached documents against the
    **  protention templates, and do something to ensure
    **  that any protected documents no longer can be
    **  accessed without a new retrieval. - FM
    */

    /*
    **  Now free all of the authorization info, and
    **  reset the free_HTAAGlobalsSet flag. - FM
    */
    free_HTAAGlobals();
    free_HTAAGlobalsSet = FALSE;
}
