/*
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * FILE:        lasref.c
 *
 * DESCRIPTION:
 *
 *      This module contains a simple example of a Loadable Attribute
 *      Service (LAS).  It includes an initialization function, an
 *      "attribute getter" function, and a function to evaluate
 *      ACL attribute expressions for the example attribute name.
 *
 */

#include "netsite.h"
#include "nsacl/aclapi.h"

/* Define the name of the attribute as it appears in ACL text */
#define LASREF_ATTRNAME "referer"

/* Define the name of the corresponding property in the subject PList_t */
#define LASREF_PROPNAME "referer"

/*
 * FUNCTION:    las_ref_get
 *
 * DESCRIPTION:
 *
 *      This is the "attribute getter" function for the "referer"
 *      attribute.  It gets the referer from the request HTTP headers,
 *      and adds it to the subject property list.  For a given request,
 *      this function is called the first time the "referer" attribute
 *      is encountered in ACL, typically when an attribute expression
 *      evaluator calls ACL_GetAttribute().
 *
 * ARGUMENTS:
 *
 *      errp            - error stack
 *      subject         - handle for subject property list
 *      resource        - handle for resource property list
 *      auth_info       - handle for "referer" authentication properties
 *      global_auth     - all attributes with authentication properties
 *      arg             - cookie specified to ACL_AttrGetterRegister()
 *
 * RETURNS:
 *
 *      LAS_EVAL_TRUE if the referer was specified in the HTTP request
 *      headers.
 *
 *      LAS_EVAL_FAIL if the referer was not specified in the HTTP
 *      request headers.
 */

int las_ref_get(NSErr_t *errp, PList_t subject, PList_t resource,
                PList_t auth_info, PList_t global_auth, void *arg)
{
    Request *rq;
    char *referer;
    int rv;

    /* Get pointer to NSAPI Request structure from resource property list */
    rv = PListGetValue(resource, ACL_ATTR_REQUEST_INDEX, (void **)&rq, NULL);
    if (rv < 0) {
        ereport(LOG_FAILURE, "las-ref-get missing request");
        return LAS_EVAL_FAIL;
    }

    /*
     * Cannot use the front-end accelerator cache for this, since it
     * references an HTTP header (referer) that is not processed by
     * the accelerator.
     */
    rq->request_is_cacheable &= ~NSAPICacheAccelSafe;

    /* Try to get the HTTP referer header */
    referer = pblock_findval("referer", rq->headers);

    if (referer) {

        /* Add the referer to the subject property list */
        PListInitProp(subject, 0, LASREF_PROPNAME, STRDUP(referer), 0);

#ifdef DEBUG
        /* Log referer string for debug purposes */
        ereport(LOG_INFORM, "las-ref-get: referer=%s", referer);
#endif /* DEBUG */

        /* Successfully retrieved referer */
        return LAS_EVAL_TRUE;
    }

    /* Referer not found */
    return LAS_EVAL_FAIL;
}

/*
 * FUNCTION:    las_ref_eval
 *
 * DESCRIPTION:
 *
 *      This is the function evaluates attribute assertions about the
 *      "referer" attribute.  It uses ACL_GetAttribute() to get the
 *      value of the "referer" attribute, and then evaluates it
 *      against a specified comparator and pattern string.  The
 *      comparators are evaluated as:
 *
 *      referer = "pattern"     referer string matches pattern
 *      referer != "pattern"    referer string does not match pattern
 *      referer < "pattern"     referer string is a prefix of pattern
 *      referer > "pattern"     pattern is a prefix of referer string
 *
 *      Comparisions are case-insensitive.  The comparators "<=" and
 *      ">=" are the same as "<" and ">", respectively.
 *
 *      If the referer attribute is not present, it is taken to be the
 *      value, "none".  So the following are also valid attribute
 *      assertions:
 *
 *      referer = "none"        referer is not present in request
 *      referer != "none"       referer is present in request
 *
 *      An special attribute pattern of "any" is also recognized:
 *
 *      referer = "any"         referer is present in request
 *      referer != "any"        referer is not present in request
 *
 * ARGUMENTS:
 *
 *      errp            - error stack
 *      attr_name       - attribute name, i.e. "referer"
 *      comparator      - comparator (CMP_OP_xxx)
 *      attr_pattern    - pattern string
 *      cachable        - pointer to returned value indicating
 *                        how long the result of this call may be cached
 *      cookie          - pointer to a value/result opaque value that
 *                        is associated with this attribute assertion
 *      subject         - handle for subject property list
 *      resource        - handle for resource property list
 *      auth_info       - handle for "referer" authentication properties
 *      global_auth     - all attributes with authentication properties
 *
 * RETURNS:
 *
 *      LAS_EVAL_TRUE if the attribute assertion is true.
 *
 *      LAS_EVAL_FALSE if the attribute assertion is false.
 *
 *      LAS_EVAL_FAIL if the attr_pattern is NULL.
 */

int las_ref_eval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
                 char *attr_pattern, ACLCachable_t *cachable,
                 void **cookie, PList_t subject, PList_t resource,
                 PList_t auth_info, PList_t global_auth)
{
    char *referer;
    int patlen;
    int reflen;
    int rv;

    /* Get the value of the "referer" attribute */
    rv = ACL_GetAttribute(errp, LASREF_ATTRNAME, (void **)&referer,
                          subject, resource, auth_info, global_auth);

    if (rv != LAS_EVAL_TRUE) {

        /* If the referer was not present, use the special value, "none" */
        referer = "none";
    }

    /* If there is a pattern string, evaluate the comparator */
    if (attr_pattern) {

        /* Check for special pattern, "any" */
        if (!strcmp(attr_pattern, "any")) {

            if (strcmp(referer, "none")) {

                /* referer is present, succeed if operator is "=" */
                rv = (comparator == CMP_OP_EQ) ? LAS_EVAL_TRUE
                                               : LAS_EVAL_FALSE;
            }
            else {

                /* referer is not present, succeed if operator is not "=" */
                rv = (comparator == CMP_OP_EQ) ? LAS_EVAL_FALSE
                                               : LAS_EVAL_TRUE;
            }
        }
        else {

            switch (comparator) {

            case CMP_OP_EQ:
                /* Case-insensitive check for equality */
                rv = strcasecmp(referer, attr_pattern) ? LAS_EVAL_FALSE
                                                       : LAS_EVAL_TRUE;
                break;

            case CMP_OP_NE:
                /* Case-insensitive check for inequality */
                rv = strcasecmp(referer, attr_pattern) ? LAS_EVAL_TRUE
                                                       : LAS_EVAL_FALSE;
                break;

            case CMP_OP_LT:
            case CMP_OP_LE:
                /* Case-insensitive check if referer is a prefix of pattern */
                reflen = strlen(referer);
                patlen = strlen(attr_pattern);
                rv = ((reflen > patlen) ||
                      strncasecmp(referer, attr_pattern, reflen))
                           ? LAS_EVAL_FALSE : LAS_EVAL_TRUE;
                break;

            case CMP_OP_GT:
            case CMP_OP_GE:
                /* Case-insensitive check if pattern is a prefix of referer */
                reflen = strlen(referer);
                patlen = strlen(attr_pattern);
                rv = ((patlen > reflen) ||
                      strncasecmp(referer, attr_pattern, patlen))
                           ? LAS_EVAL_FALSE : LAS_EVAL_TRUE;
                break;
            }
        }

        return rv;
    }

    return LAS_EVAL_FAIL;
}

void las_ref_flush(void **cookie)
{
    /* Nothing to do */
}

/*
 *  To load this functions in the web server, compile the file in
 *  "lasref.so" and add the following lines to the
 *  <ServerRoot>/instances/<name>/config/init.conf file.  Be sure to change the
 *  "lasref.so" portion to the full pathname.  E.g.  /nshome/lib/lasref.so.
 *   If you use the sample Makefile included here to compile this then it will
 *   create example.so instead of lasref.so.
 *
 * Init fn="load-modules" funcs="las-ref-init"
 *      shlib=<server-root>/plugins/nsacl/lasref.so"
 * Init fn="acl-register-module" module="lasref" func="las-ref-init"
 */

NSAPI_PUBLIC int
las_ref_init(NSErr_t *errp)
{
    int rv;

    rv = ACL_AttrGetterRegister(errp, LASREF_ATTRNAME, las_ref_get,
                                ACL_METHOD_ANY, ACL_DBTYPE_ANY,
                                ACL_AT_FRONT, NULL);

    rv = ACL_LasRegister(errp, LASREF_ATTRNAME, las_ref_eval, las_ref_flush);

    return 0;
}
