/*
 * $LynxId: HTAABrow.c,v 1.42 2016/11/24 23:57:57 tom Exp $
 *
 * MODULE							HTAABrow.c
 *		BROWSER SIDE ACCESS AUTHORIZATION MODULE
 *
 *	Contains 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 <HTAccess.h>		/* Are we using an HTTP gateway? */
#include <HTAABrow.h>		/* Implemented here             */
#include <HTUU.h>		/* Uuencoding and uudecoding    */

#include <LYLeaks.h>

/*
 *  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 *ctemplate;		/* 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
 */
static void free_HTAAGlobals(void);
static BOOL free_HTAAGlobalsSet = FALSE;
static char *HTAA_composeAuthResult = NULL;
static char *compose_auth_stringResult = NULL;	/* Uuencoded presentation */

/*
 *  Module-wide global variables
 */
static HTList *server_table = NULL;	/* Browser's info about servers      */
static char *secret_key = NULL;	/* Browser's latest secret key       */
static HTAASetup *current_setup = NULL;		/* The server setup we are currently */

					/* talking to                        */
static char *current_hostname = NULL;	/* The server's name and portnumber  */
static int current_portnumber = 80;	/* where we are currently trying to  */

					/* connect.                          */
static char *current_docname = NULL;	/* The document's name we are        */

					/* trying to access.                 */
static char *HTAAForwardAuth = NULL;	/* Authorization: line to forward    */

					/* (used by gateway httpds)          */
static HTAASetup *proxy_setup = NULL;	/* Same as above, but for Proxy -AJL */
static char *proxy_hostname = NULL;
static char *proxy_docname = NULL;
static int proxy_portnumber = 80;

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

void HTAAForwardAuth_set(const char *scheme_name,
			 const char *scheme_specifics)
{
    size_t len = (20
		  + (scheme_name ? strlen(scheme_name) : 0)
		  + (scheme_specifics ? strlen(scheme_specifics) : 0));

    FREE(HTAAForwardAuth);
    if ((HTAAForwardAuth = typecallocn(char, len)) == 0)
	  outofmem(__FILE__, "HTAAForwardAuth_set");

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

void HTAAForwardAuth_reset(void)
{
    FREE(HTAAForwardAuth);
}

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

static void HTAASetup_delete(HTAASetup * killme);	/* Forward */

/* static						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.
 */
static HTAAServer *HTAAServer_new(const char *hostname,
				  int portnumber,
				  int IsProxy)
{
    HTAAServer *server;

    if ((server = typecalloc(HTAAServer)) == 0)
	  outofmem(__FILE__, "HTAAServer_new");

    server->hostname = NULL;
    server->portnumber = (portnumber > 0 ? portnumber : 80);
    server->IsProxy = (BOOLEAN) 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;
}

/* static						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.
 */
static void HTAAServer_delete(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);
    }
}

/* static						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.
 */
static HTAAServer *HTAAServer_lookup(const char *hostname,
				     int portnumber,
				     int 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 *******************************/

/* static						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.
 *
 */
static HTAASetup *HTAASetup_lookup(const char *hostname,
				   int portnumber,
				   const char *docname,
				   int 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->ctemplate, docname)) {
		CTRACE((tfp, "%s `%s' %s `%s'\n",
			"HTAASetup_lookup:", docname,
			"matched template", setup->ctemplate));
		return setup;
	    } else {
		CTRACE((tfp, "%s `%s' %s `%s'\n",
			"HTAASetup_lookup:", docname,
			"did NOT match template", setup->ctemplate));
	    }
	}			/* while setups remain */
    }
    /* if valid parameters and server found */
    CTRACE((tfp, "%s `%s' %s\n",
	    "HTAASetup_lookup: No template matched",
	    NONNULL(docname),
	    "(so probably not protected)"));

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

/* static						HTAASetup_new()
 *			CREATE A NEW SETUP NODE
 * ON ENTRY:
 *	server		is a pointer to a HTAAServer structure
 *			to which this setup belongs.
 *	ctemplate	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.
 */
static HTAASetup *HTAASetup_new(HTAAServer *server, char *ctemplate,
				HTList *valid_schemes,
				HTAssocList **scheme_specifics)
{
    HTAASetup *setup;

    if (!server || isEmpty(ctemplate))
	return NULL;

    if ((setup = typecalloc(HTAASetup)) == 0)
	outofmem(__FILE__, "HTAASetup_new");

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

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

    return setup;
}

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

    if (killme) {
	FREE(killme->ctemplate);
	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);
    }
}

/* static					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.
 */
static void HTAASetup_updateSpecifics(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 **********************************/

/* static						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.
 */
static HTAARealm *HTAARealm_lookup(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 */
}

/* static						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.
 */
static HTAARealm *HTAARealm_new(HTList *realm_table,
				const char *realmname,
				const char *username,
				const char *password)
{
    HTAARealm *realm;

    realm = HTAARealm_lookup(realm_table, realmname);

    if (!realm) {
	if ((realm = typecalloc(HTAARealm)) == 0)
	      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;
}

BOOL HTAA_HaveUserinfo(const char *hostname)
{
    int gen_delims = 0;
    char *my_info = NULL;
    char *at_sign = HTSkipToAt(StrAllocCopy(my_info, hostname), &gen_delims);

    free(my_info);
    return (at_sign != NULL && gen_delims == 0) ? TRUE : FALSE;
}

/*
 * If there is userinfo in the hostname string, update the realm to use that
 * information.  The command-line "-auth" option will override this.
 */
static void fill_in_userinfo(HTAARealm *realm, const char *hostname)
{
    int gen_delims = 0;
    char *my_info = NULL;
    char *at_sign = HTSkipToAt(StrAllocCopy(my_info, hostname), &gen_delims);

    if (at_sign != NULL && gen_delims == 0) {
	char *colon;

	*at_sign = '\0';
	if ((colon = StrChr(my_info, ':')) != 0) {
	    *colon++ = '\0';
	}
	if (non_empty(my_info)) {
	    char *msg;
	    BOOL prior = non_empty(realm->username);

	    if (prior && strcmp(realm->username, my_info)) {
		msg = 0;
		HTSprintf0(&msg,
			   gettext("username for realm %s changed from %s to %s"),
			   realm->realmname,
			   realm->username,
			   my_info);
		HTAlert(msg);
		free(msg);
		FREE(realm->username);
		StrAllocCopy(realm->username, my_info);
	    } else if (!prior) {
		StrAllocCopy(realm->username, my_info);
	    }
	    if (non_empty(colon)) {
		prior = non_empty(realm->password);
		if (prior && strcmp(realm->password, colon)) {
		    msg = 0;
		    HTSprintf0(&msg,
			       gettext("password for realm %s user %s changed"),
			       realm->realmname,
			       realm->username);
		    HTAlert(msg);
		    free(msg);
		    FREE(realm->password);
		    StrAllocCopy(realm->password, colon);
		} else if (!prior) {
		    StrAllocCopy(realm->password, colon);
		}
	    }
	}
    }
    free(my_info);
}

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

/* static						compose_auth_string()
 *
 *		COMPOSE Basic OR Pubkey AUTHENTICATION STRING;
 *		PROMPTS FOR USERNAME AND PASSWORD IF NEEDED
 *
 * ON ENTRY:
 *	hostname	may include user- and password information
 *	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.
 *
 */
static char *compose_auth_string(const char *hostname,
				 HTAAScheme scheme,
				 HTAASetup * setup,
				 int IsProxy)
{
    char *cleartext = NULL;	/* Cleartext presentation */
    char *ciphertext = NULL;	/* Encrypted presentation */
    size_t len;
    char *msg = NULL;
    char *username = NULL;
    char *password = NULL;
    char *realmname = NULL;
    char *theHost = NULL;
    char *proxiedHost = NULL;
    char *thePort = NULL;
    HTAARealm *realm;
    const char *i_net_addr = "0.0.0.0";		/* Change... @@@@ */
    const 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);
    setup->retry |= HTAA_HaveUserinfo(hostname);

    if (!(realm &&
	  non_empty(realm->username) &&
	  non_empty(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);
	}
	fill_in_userinfo(realm, hostname);
	/*
	 * The template should be either the '*' global for everything 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->ctemplate) {
	    proxiedHost = HTParse(setup->ctemplate, "", 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) {
	    HTSprintf0(&thePort, ":%d", setup->server->portnumber);
	}

	HTSprintf0(&msg, gettext("Username for '%s' at %s '%s%s':"),
		   realm->realmname,
		   (IsProxy ? "proxy" : "server"),
		   (theHost ? theHost : "??"),
		   NonNull(thePort));
	FREE(proxiedHost);
	FREE(thePort);
	if (non_empty(realm->username)) {
	    StrAllocCopy(username, realm->username);
	}
	if (non_empty(realm->password)) {
	    StrAllocCopy(password, realm->password);
	}
	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(NonNull(realm->username)) +
	   strlen(NonNull(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(NonNull(secret_key)) + 30;
    } else {
	FREE(secret_key);
    }

    if ((cleartext = typecallocn(char, len)) == 0)
	  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 = typecallocn(char, 2 * len)) &&
	        (compose_auth_stringResult = typecallocn(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 =
	      typecallocn(char, (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 static					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.
 */
static HTAAScheme HTAA_selectScheme(HTAASetup * setup)
{
    int scheme;

    if (setup && setup->valid_schemes) {
	for (scheme = HTAA_BASIC; scheme < HTAA_MAX_SCHEMES; scheme++) {
	    void *object = (void *) (intptr_t) scheme;

	    if (-1 < HTList_indexOf(setup->valid_schemes, object))
		return (HTAAScheme) 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
 */
static void free_HTAAGlobals(void)
{
    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.
 */
char *HTAA_composeAuth(const char *hostname,
		       const int portnumber,
		       const char *docname,
		       int IsProxy)
{
    char *auth_string;
    BOOL retry;
    HTAAScheme scheme;
    size_t len;

    /*
     * Setup atexit() freeing if not done already.  - FM
     */
    if (!free_HTAAGlobalsSet) {
#ifdef LY_FIND_LEAKS
	atexit(free_HTAAGlobals);
#endif
	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(hostname, scheme, proxy_setup, IsProxy);
	    break;
	case HTAA_KERBEROS_V4:
	    /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */
	default:
	    {
		char *msg = NULL;

		HTSprintf0(&msg, "%s `%s'",
			   gettext("This client doesn't know how to compose proxy authorization information for scheme"),
			   HTAAScheme_name(scheme));
		HTAlert(msg);
		FREE(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(HTAAScheme_name(scheme)) + 26;
	if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0)
	      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(hostname, scheme, current_setup, IsProxy);
	    break;
	case HTAA_KERBEROS_V4:
	    /* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */
	default:
	    {
		char *msg = 0;

		HTSprintf0(&msg, "%s `%s'",
			   gettext("This client doesn't know how to compose authorization information for scheme"),
			   HTAAScheme_name(scheme));
		HTAlert(msg);
		FREE(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(HTAAScheme_name(scheme)) + 20;
	if ((HTAA_composeAuthResult = typecallocn(char, len)) == 0)
	      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.
 */
BOOL HTAA_shouldRetryWithAuth(char *start_of_headers,
			      size_t length,
			      int soc,
			      int IsProxy)
{
    HTAAScheme scheme;
    char *line = NULL;
    int num_schemes = 0;
    HTList *valid_schemes = HTList_new();
    HTAssocList **scheme_specifics = NULL;
    char *ctemplate = NULL;
    char *temp = NULL;
    BOOL result = NO;

    /*
     * Setup atexit() freeing if not done already.  - FM
     */
    if (!free_HTAAGlobalsSet) {
#ifdef LY_FIND_LEAKS
	atexit(free_HTAAGlobals);
#endif
	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 (isEmpty(arg1) || isEmpty(args)) {
		    HTSprintf0(&temp, gettext("Invalid header '%s%s%s%s%s'"), line,
			       (non_empty(arg1) ? " " : ""),
			       NonNull(arg1),
			       (non_empty(args) ? " " : ""),
			       NonNull(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 =
			    typecallocn(HTAssocList *, HTAA_MAX_SCHEMES);

			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",
			    NONNULL(arg1),
			    (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(ctemplate, arg1);
	    }

	} else {
	    CTRACE((tfp, "Invalid header line `%s' ignored\n", 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;
	    result = NO;
	}
	/*
	 * Doing it for proxy.  -AJL
	 */
	else 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(AUTH_FAILED_PROMPT)) {
		proxy_setup = NULL;
		result = NO;
	    } else {
		/*
		 * Re-ask username+password (if misspelled).
		 */
		HTList_delete(valid_schemes);
		proxy_setup->retry = YES;
		result = 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 (!ctemplate)	/* Proxy matches everything  -AJL */
		StrAllocCopy(ctemplate, "*");
	    proxy_setup = HTAASetup_new(server,
					ctemplate,
					valid_schemes,
					scheme_specifics);
	    FREE(ctemplate);

	    HTAlert(gettext("Proxy authorization required -- retrying"));
	    result = YES;
	}
    }
    /*
     * Normal WWW authorization.
     */
    else if (num_schemes == 0) {
	/*
	 * No authorization valid.
	 */
	current_setup = NULL;
	result = NO;
    } else 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(AUTH_FAILED_PROMPT)) {
	    current_setup = NULL;
	    result = NO;
	} else {
	    /*
	     * Re-ask username+password (if misspelled).
	     */
	    current_setup->retry = YES;
	    result = 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 (!ctemplate)
	    ctemplate = HTAA_makeProtectionTemplate(current_docname);
	current_setup = HTAASetup_new(server,
				      ctemplate,
				      valid_schemes,
				      scheme_specifics);
	FREE(ctemplate);

	HTAlert(gettext("Access without authorization denied -- retrying"));
	result = YES;
    }

    if (result == NO) {
	HTList_delete(valid_schemes);
    }
    return result;
}

/*
 *  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
 */
void HTClearHTTPAuthInfo(void)
{
    /*
     * Need code to check cached documents against the protection 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;
}
