earlybrowserreborn - Rev 1

Subversion Repositories:
Rev:

/* 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 <stdio.h>              /* FILE */
#include <string.h>             /* strchr() */

#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             */



/*
** 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";
        break;
      case HTAA_NOT_MEMBER:
        return "Unauthorized to access the document";
        break;

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

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

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

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

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

    } /* switch */
}



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

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

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

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

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

    /* 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) {
        if (TRACE) fprintf(stderr,
                           "HTAA_checkAuthorization: Forbidden by rule\n");
        return HTAA_BY_RULE;
    }
    if (TRACE) fprintf(stderr, "%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);
                    if (TRACE) fprintf(stderr, "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 (TRACE) {
                    if (reason != HTAA_OK)
                        fprintf(stderr, "%s %s %s %s\n",
                                "HTAA_checkAuthorization: access denied",
                                "by mask (no ACL, only Protect rule)",
                                "host", HTClientHost);
                    else fprintf(stderr, "%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 */
                if (TRACE) fprintf(stderr, "%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 */
            if (TRACE) fprintf(stderr, "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 */
        if (TRACE) fprintf(stderr,
                           "HTAA_checkAuthorization: default protection\n");
        prot = HTAA_getDefaultProtection();   /* Also sets current protection */

        if (!prot) {            /* @@ Default protection not set ?? */
            if (TRACE) fprintf(stderr, "%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);
            if (TRACE) fprintf(stderr, "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) {
                if (TRACE) fprintf(stderr, "%s %s %s\n",
                                   "HTAA_checkAuthorization: access denied",
                                   "by mask, host:", HTClientHost);
                return reason;
            }
            else {
                if (TRACE) fprintf(stderr, "%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) {
            if (TRACE) fprintf(stderr, "%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 = (char)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, "/../")) {
        if (TRACE) fprintf(stderr, "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 = (char)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 */
            if (TRACE) fprintf(stderr,
                               "HTAA_checkAuthorization: Forbidden by rule\n");
            HTAAFailReason = HTAA_BY_RULE;
        }
        else if (HTAAFailReason != HTAA_HTBIN) {
            /* pathname != NULL */
            char *access = HTParse(pathname, "", PARSE_ACCESS);
            if (!*access || 0 == strcmp(access,"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;
                fprintf(stderr, "HTAA_checkAuthorization: %s (%s access)\n",
                        "Gatewaying -- skipping authorization check",
                        access);
            }
        } /* 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 */
        if (TRACE) fprintf(stderr, "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;
        break;

      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;
        break;

      case HTAA_NOT_FOUND:
        return 404;
        break;

      case HTAA_OK:
      case HTAA_OK_GATEWAY:
        return 200;
        break;

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





/* 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;
        }
        break;

      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;
        }
        break;
      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;
    HTAAScheme scheme;
    char *scheme_name;
    char *scheme_params;
    HTAAProt *prot = HTAA_getCurrentProtection();

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

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

    for (scheme=0; scheme < HTAA_MAX_SCHEMES; scheme++) {
        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 if (TRACE) fprintf(stderr, "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;
}