
/* MODULE							HTAAServ.c
**		SERVER SIDE ACCESS AUTHORIZATION MODULE
**
**	Contains the means for checking the user access
**	authorization for a file.
**
** 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:
**
**
** BUGS:
**
**
*/

#include <HTUtils.h>

#include <HTString.h>
#include <HTAccess.h>		/* HTSecure			*/
#include <HTFile.h>		/* HTLocalName			*/
#include <HTRules.h>		/*				*/
#include <HTParse.h>		/* URL parsing function 	*/
#include <HTList.h>		/* HTList object		*/

#include <HTAAUtil.h>		/* AA common parts		*/
#include <HTAuth.h>		/* Authentication		*/
#include <HTACL.h>		/* Access Control List		*/
#include <HTGroup.h>		/* Group handling		*/
#include <HTAAProt.h>		/* Protection file parsing	*/
#include <HTAAServ.h>		/* Implemented here		*/

#include <LYLeaks.h>

/*
** Global variables
*/
PUBLIC time_t theTime;


/*
** Module-wide global variables
*/
PRIVATE FILE *	htaa_logfile	    = NULL;		/* Log file	      */
PRIVATE HTAAUser *htaa_user = NULL;			/* Authenticated user */
PRIVATE HTAAFailReasonType HTAAFailReason = HTAA_OK;	/* AA fail reason     */


/* SERVER PUBLIC					HTAA_statusMessage()
**		RETURN A STRING EXPLAINING ACCESS
**		AUTHORIZATION FAILURE
**		(Can be used in server reply status line
**		 with 401/403 replies.)
** ON EXIT:
**	returns a string containing the error message
**		corresponding to internal HTAAFailReason.
*/
PUBLIC char *HTAA_statusMessage NOARGS
{
    switch (HTAAFailReason) {

    /* 401 cases */
      case HTAA_NO_AUTH:
	return "Unauthorized -- authentication failed";
      case HTAA_NOT_MEMBER:
	return "Unauthorized to access the document";

    /* 403 cases */
      case HTAA_BY_RULE:
	return "Forbidden -- by rule";
      case HTAA_IP_MASK:
	return "Forbidden -- server refuses to serve to your IP address";
      case HTAA_NO_ACL:
      case HTAA_NO_ENTRY:
	return "Forbidden -- access to file is never allowed";
      case HTAA_SETUP_ERROR:
	return "Forbidden -- server protection setup error";
      case HTAA_DOTDOT:
	return "Forbidden -- URL containing /../ disallowed";
      case HTAA_HTBIN:
	return "Forbidden -- /htbin feature not enabled on this server";

    /* 404 cases */
      case HTAA_NOT_FOUND:
	return "Not found -- file doesn't exist or is read protected";

    /* Success */
      case HTAA_OK:
	return "AA: Access should be ok but something went wrong";

      case HTAA_OK_GATEWAY:
	return "AA check bypassed (gatewaying) but something went wrong";

    /* Others */
      default:
	return "Access denied -- unable to specify reason (bug)";

    } /* switch */
}


PRIVATE char *status_name ARGS1(HTAAFailReasonType, reason)
{
    switch (reason) {

    /* 401 cases */
      case HTAA_NO_AUTH:
	return "NO-AUTHENTICATION";
      case HTAA_NOT_MEMBER:
	return "NOT-AUTHORIZED";

    /* 403 cases */
      case HTAA_BY_RULE:
	return "FORB-RULE";
      case HTAA_IP_MASK:
	return "FORB-IP";
      case HTAA_NO_ACL:
	return "NO-ACL-FILE";
      case HTAA_NO_ENTRY:
	return "NO-ACL-ENTRY";
      case HTAA_SETUP_ERROR:
	return "SETUP-ERROR";
      case HTAA_DOTDOT:
	return "SLASH-DOT-DOT";
      case HTAA_HTBIN:
	return "HTBIN-OFF";

    /* 404 cases */
      case HTAA_NOT_FOUND:
	return "NOT-FOUND";

    /* Success */
      case HTAA_OK:
	return "OK";
      case HTAA_OK_GATEWAY:
	return "OK-GATEWAY";

    /* Others */
      default:
	return "SERVER-BUG";
    } /* switch */
}


/* PRIVATE						check_uthorization()
**		CHECK IF USER IS AUTHORIZED TO ACCESS A FILE
** ON ENTRY:
**	pathname	is the physical file pathname
**			to access.
**	method		method, e.g. METHOD_GET, METHOD_PUT, ...
**	scheme		authentication scheme.
**	scheme_specifics authentication string (or other
**			scheme specific parameters, like
**			Kerberos-ticket).
**
** ON EXIT:
**	returns 	HTAA_OK on success.
**			Otherwise the reason for failing.
** NOTE:
**	This function does not check whether the file
**	exists or not -- so the status	404 Not found
**	must be returned from somewhere else (this is
**	to avoid unnecessary overhead of opening the
**	file twice).
*/
PRIVATE HTAAFailReasonType check_authorization ARGS4(CONST char *,  pathname,
						     HTAAMethod,    method,
						     HTAAScheme,    scheme,
						     char *, scheme_specifics)
{
    HTAAFailReasonType reason;
    GroupDef *allowed_groups;
    FILE *acl_file = NULL;
    HTAAProt *prot = NULL;	/* Protection mode */

    htaa_user = NULL;

    if (!pathname) {
	CTRACE(tfp, "HTAA_checkAuthorization: Forbidden by rule\n");
	return HTAA_BY_RULE;
    }
    CTRACE(tfp, "%s `%s' %s %s\n",
		"HTAA_checkAuthorization: translated path:",
		pathname, "method:", HTAAMethod_name(method));

    /*
    ** Get protection setting (set up by callbacks from rule system)
    ** NULL, if not protected by a "protect" rule.
    */
    prot = HTAA_getCurrentProtection();

    /*
    ** Check ACL existence
    */
    if (!(acl_file = HTAA_openAcl(pathname))) {
	if (prot) { /* protect rule, but no ACL */
	    if (prot->mask_group) {
		/*
		** Only mask enabled, check that
		*/
		GroupDefList *group_def_list =
		    HTAA_readGroupFile(HTAssocList_lookup(prot->values,
							  "group"));
		/*
		** Authenticate if authentication info given
		*/
		if (scheme != HTAA_UNKNOWN  &&	scheme != HTAA_NONE) {
		    htaa_user = HTAA_authenticate(scheme,
						  scheme_specifics,
						  prot);
		    CTRACE(tfp, "Authentication returned: %s\n",
				(htaa_user ? htaa_user->username
					   : "NOT-AUTHENTICATED"));
		}
		HTAA_resolveGroupReferences(prot->mask_group, group_def_list);
		reason = HTAA_userAndInetInGroup(prot->mask_group,
						 htaa_user
						  ? htaa_user->username : "",
						 HTClientHost,
						 NULL);
		if (reason != HTAA_OK) {
		    CTRACE(tfp, "%s %s %s %s\n",
				"HTAA_checkAuthorization: access denied",
				"by mask (no ACL, only Protect rule)",
				"host", HTClientHost);
		} else {
		    CTRACE(tfp, "%s %s %s %s\n",
				"HTAA_checkAuthorization: request from",
				HTClientHost,
				"accepted by only mask match (no ACL, only",
				"Protect rule, and only mask enabled)");
		}
		return reason;
	    }
	    else {	/* 403 Forbidden */
		CTRACE(tfp, "%s %s\n",
			    "HTAA_checkAuthorization: Protected, but",
			    "no mask group nor ACL -- forbidden");
		return HTAA_NO_ACL;
	    }
	}
	else { /* No protect rule and no ACL => OK 200 */
	    CTRACE(tfp, "HTAA_checkAuthorization: %s\n",
			"no protect rule nor ACL -- ok\n");
	    return HTAA_OK;
	}
    }

    /*
    ** Now we know that ACL exists
    */
    if (!prot) {		/* Not protected by "protect" rule */
	CTRACE(tfp, "HTAA_checkAuthorization: default protection\n");
	prot = HTAA_getDefaultProtection(); /* Also sets current protection */

	if (!prot) {		/* @@ Default protection not set ?? */
	    CTRACE(tfp, "%s %s\n",
			"HTAA_checkAuthorization: default protection",
			"not set (internal server error)!!");
	    return HTAA_SETUP_ERROR;
	}
    }

    /*
    ** Now we know that document is protected and ACL exists.
    ** Check against ACL entry.
    */
    {
	GroupDefList *group_def_list =
	    HTAA_readGroupFile(HTAssocList_lookup(prot->values, "group"));

	/*
	** Authenticate now that we know protection mode
	*/
	if (scheme != HTAA_UNKNOWN  &&	scheme != HTAA_NONE) {
	    htaa_user = HTAA_authenticate(scheme,
					  scheme_specifics,
					  prot);
	    CTRACE(tfp, "Authentication returned: %s\n",
			(htaa_user
			 ? htaa_user->username : "NOT-AUTHENTICATED"));
	}
	/*
	** Check mask group
	*/
	if (prot->mask_group) {
	    HTAA_resolveGroupReferences(prot->mask_group, group_def_list);
	    reason=HTAA_userAndInetInGroup(prot->mask_group,
					   htaa_user ? htaa_user->username : "",
					   HTClientHost,
					   NULL);
	    if (reason != HTAA_OK) {
		CTRACE(tfp, "%s %s %s\n",
			    "HTAA_checkAuthorization: access denied",
			    "by mask, host:", HTClientHost);
		return reason;
	    }
	    else {
		CTRACE(tfp, "%s %s %s %s %s\n",
			    "HTAA_checkAuthorization: request from",
			    HTClientHost,
			    "accepted by just mask group match",
			    "(no ACL, only Protect rule, and only",
			    "mask enabled)");
		/* And continue authorization checking */
	    }
	}
	/*
	** Get ACL entries; get first one first, the loop others
	** Remember, allowed_groups is automatically freed by
	** HTAA_getAclEntry().
	*/
	allowed_groups = HTAA_getAclEntry(acl_file, pathname, method);
	if (!allowed_groups) {
	    CTRACE(tfp, "%s `%s' %s\n",
			"No entry for file", pathname, "in ACL");
	    HTAA_closeAcl(acl_file);
	    return HTAA_NO_ENTRY;  /* Forbidden -- no entry in the ACL */
	}
	else {
	    do {
		HTAA_resolveGroupReferences(allowed_groups, group_def_list);
		reason = HTAA_userAndInetInGroup(allowed_groups,
						 htaa_user
						 ? htaa_user->username : "",
						 HTClientHost,
						 NULL);
		if (reason == HTAA_OK) {
		    HTAA_closeAcl(acl_file);
		    return HTAA_OK;	/* OK */
		}
		allowed_groups = HTAA_getAclEntry(acl_file, pathname, method);
	    } while (allowed_groups);
	    HTAA_closeAcl(acl_file);
	    return HTAA_NOT_MEMBER;	/* Unauthorized */
	}
    }
}


/* PUBLIC					      HTAA_checkAuthorization()
**		CHECK IF USER IS AUTHORIZED TO ACCESS A FILE
** ON ENTRY:
**	url		is the document to be accessed.
**	method_name	name of the method, e.g. "GET"
**	scheme_name	authentication scheme name.
**	scheme_specifics authentication string (or other
**			scheme specific parameters, like
**			Kerberos-ticket).
**
** ON EXIT:
**	returns status codes uniform with those of HTTP:
**	  200 OK	   if file access is ok.
**	  401 Unauthorized if user is not authorized to
**			   access the file.
**	  403 Forbidden    if there is no entry for the
**			   requested file in the ACL.
**
** NOTE:
**	This function does not check whether the file
**	exists or not -- so the status	404 Not found
**	must be returned from somewhere else (this is
**	to avoid unnecessary overhead of opening the
**	file twice).
**
*/
PUBLIC int HTAA_checkAuthorization ARGS4(CONST char *,	url,
					 CONST char *,	method_name,
					 CONST char *,	scheme_name,
					 char *,	scheme_specifics)
{
    static char *pathname = NULL;
    char *local_copy = NULL;
    HTAAMethod method = HTAAMethod_enum(method_name);
    HTAAScheme scheme = HTAAScheme_enum(scheme_name);

    HTAAFailReason = HTAA_OK;

    /*
    ** Translate into absolute pathname, and
    ** check for "protect" and "defprot" rules.
    */
    FREE(pathname);		/* From previous call	*/
    StrAllocCopy(local_copy, url);
    {
	char *keywords = strchr(local_copy, '?');
	if (keywords)
	    *keywords = '\0';	/* Chop off keywords */
    }
    HTSimplify(local_copy);	/* Remove ".." etc. */

    /* HTSimplify will leave in a "/../" at the top, which can
    ** be a security hole.
    */
    if (strstr(local_copy, "/../")) {
	CTRACE(tfp, "HTAA_checkAuthorization: %s (`%s')\n",
		    "Illegal attempt to use /../", url);
	HTAAFailReason = HTAA_DOTDOT;
    }
    else {
	pathname = HTTranslate(local_copy); /* Translate rules even if */
					    /* a /htbin call to set up */
					    /* protections.	       */
	if (0 == strncmp(local_copy, "/htbin/", 7)) {
	    if (!HTBinDir)
		HTAAFailReason = HTAA_HTBIN;
	    else {
		char *end = strchr(local_copy+7, '/');
		if (end)
		    *end = '\0';
		FREE(pathname);
		pathname=(char*)malloc(strlen(HTBinDir)+strlen(local_copy)+1);
		strcpy(pathname, HTBinDir);
		strcat(pathname, local_copy+6);
	    }
	}

	if (!pathname) {		/* Forbidden by rule */
	    CTRACE(tfp, "HTAA_checkAuthorization: Forbidden by rule\n");
	    HTAAFailReason = HTAA_BY_RULE;
	}
	else if (HTAAFailReason != HTAA_HTBIN) {
	    /* pathname != NULL */
	    char *acc_method = HTParse(pathname, "", PARSE_ACCESS);
	    if (!*acc_method || 0 == strcmp(acc_method,"file")) { /*Local file, do AA*/
		if (!HTSecure && 0 != strncmp(local_copy, "/htbin/", 7)) {
		    char *localname = HTLocalName(pathname);
		    FREE(pathname);
		    pathname = localname;
		}
		HTAAFailReason = check_authorization(pathname, method,
						     scheme, scheme_specifics);
	    }
	    else {  /* Not local access */
		HTAAFailReason = HTAA_OK_GATEWAY;
		CTRACE(tfp, "HTAA_checkAuthorization: %s (%s access)\n",
			    "Gatewaying -- skipping authorization check",
			    acc_method);
	    }
	} /* pathname */
    }
    FREE(local_copy);

    if (htaa_logfile) {
	time(&theTime);
	fprintf(htaa_logfile, "%24.24s %s %s %s %s %s\n",
		ctime(&theTime),
		HTClientHost ? HTClientHost : "local",
		method_name,
		url,
		status_name(HTAAFailReason),
		htaa_user && htaa_user->username
		? htaa_user->username : "");
	fflush(htaa_logfile);	/* Actually update it on disk */
	CTRACE(tfp, "Log: %24.24s %s %s %s %s %s\n",
		    ctime(&theTime),
		    HTClientHost ? HTClientHost : "local",
		    method_name,
		    url,
		    status_name(HTAAFailReason),
		    htaa_user && htaa_user->username
		    ? htaa_user->username : "");
    }

    switch (HTAAFailReason) {

      case HTAA_NO_AUTH:
      case HTAA_NOT_MEMBER:
	return 401;

      case HTAA_BY_RULE:
      case HTAA_IP_MASK:
      case HTAA_NO_ACL:
      case HTAA_NO_ENTRY:
      case HTAA_SETUP_ERROR:
      case HTAA_DOTDOT:
      case HTAA_HTBIN:
	return 403;

      case HTAA_NOT_FOUND:
	return 404;

      case HTAA_OK:
      case HTAA_OK_GATEWAY:
	return 200;

      default:
	return 500;
    } /* switch */
}


/* PRIVATE					compose_scheme_specifics()
**		COMPOSE SCHEME-SPECIFIC PARAMETERS
**		TO BE SENT ALONG WITH SERVER REPLY
**		IN THE WWW-Authenticate: FIELD.
** ON ENTRY:
**	scheme		is the authentication scheme for which
**			parameters are asked for.
**	prot		protection setup structure.
**
** ON EXIT:
**	returns 	scheme specific parameters in an
**			auto-freed string.
*/
PRIVATE char *compose_scheme_specifics ARGS2(HTAAScheme,	scheme,
					     HTAAProt *,	prot)
{
    static char *result = NULL;

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

    switch (scheme) {
      case HTAA_BASIC:
	{
	    char *realm = HTAssocList_lookup(prot->values, "server");
	    result = (char*)malloc(60);
	    sprintf(result, "realm=\"%s\"",
		    (realm ? realm : "UNKNOWN"));
	    return result;
	}

      case HTAA_PUBKEY:
	{
	    char *realm = HTAssocList_lookup(prot->values, "server");
	    result = (char*)malloc(200);
	    sprintf(result, "realm=\"%s\", key=\"%s\"",
		    (realm ? realm : "UNKNOWN"),
		    "PUBKEY-NOT-IMPLEMENTED");
	    return result;
	}
      default:
	return NULL;
    }
}


/* SERVER PUBLIC				    HTAA_composeAuthHeaders()
**		COMPOSE WWW-Authenticate: HEADER LINES
**		INDICATING VALID AUTHENTICATION SCHEMES
**		FOR THE REQUESTED DOCUMENT
** ON ENTRY:
**	No parameters, but HTAA_checkAuthorization() must
**	just before have failed because a wrong (or none)
**	authentication scheme was used.
**
** ON EXIT:
**	returns a buffer containing all the WWW-Authenticate:
**		fields including CRLFs (this buffer is auto-freed).
**		NULL, if authentication won't help in accessing
**		the requested document.
**
*/
PUBLIC char *HTAA_composeAuthHeaders NOARGS
{
    static char *result = NULL;
    int  n;
    char *scheme_name;
    char *scheme_params;
    HTAAProt *prot = HTAA_getCurrentProtection();

    if (!prot) {
	CTRACE(tfp, "%s %s\n",
		    "HTAA_composeAuthHeaders: Document not protected",
		    "-- why was this function called??");
	return NULL;
    } else {
	CTRACE(tfp, "HTAA_composeAuthHeaders: for file `%s'\n",
		    prot->filename);
    }

    FREE(result);	/* From previous call */
    if (!(result = (char*)malloc(4096)))	/* @@ */
	outofmem(__FILE__, "HTAA_composeAuthHeaders");
    *result = '\0';

    for (n = 0; n < (int) HTAA_MAX_SCHEMES; n++) {
	HTAAScheme scheme = (HTAAScheme) n;
	if (-1 < HTList_indexOf(prot->valid_schemes, (void*)scheme)) {
	    if ((scheme_name = HTAAScheme_name(scheme))) {
		scheme_params = compose_scheme_specifics(scheme,prot);
		strcat(result, "WWW-Authenticate: ");
		strcat(result, scheme_name);
		if (scheme_params) {
		    strcat(result, " ");
		    strcat(result, scheme_params);
		}
		strcat(result, "\r\n");
	    } /* scheme name found */
	    else {
		CTRACE(tfp, "HTAA_composeAuthHeaders: %s %d\n",
			    "No name found for scheme number", scheme);
	    }
	} /* scheme valid for requested document */
    } /* for every scheme */

    return result;
}


/* PUBLIC						HTAA_startLogging()
**		START UP ACCESS AUTHORIZATION LOGGING
** ON ENTRY:
**	fp	is the open log file.
**
*/
PUBLIC void HTAA_startLogging ARGS1(FILE *, fp)
{
    htaa_logfile = fp;
}

