/* 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_authorization()
** 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;
}