OpenVMS Source-Code Demos

mod_auth_openvms

/* ====================================================================
 * Copyright (c) 1995 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
**  FACILITY:
**	Compaq Secure Web Server for OpenVMS Alpha
**
**  ABSTRACT:
**	This module provides a basic routine for authenticating a user based
**	on a username and password contained in the SYSUAF records.
**
**      NOTE: This module is based on MOD_AUTH_NIS.C.  Credit goes to
**      Dirk.vanGulik@jrc.it, the author of that module.
**
**  RESTRICTION:
**	There is no support for secondary passwords or external authentication
**	at this time.  (The MOD_AUTH_xxx services only provide one password.)
**
**  CREATION DATE:  November 29, 2000
**
**  AUTHORS:
**	Kevin D. O'Kelley
**
**  MODIFICATION HISTORY:
**
**	000	KDO/29-Nov-2000
**		Created.
**
** 	001	RJB/02-Feb-2001
**		vms_auth_princ_sysuaf syntax has changed to require
**		the caller to supply the client's host-name string.
**
**	002	KDO/02-Nov-2001
**		Add authoritative/non-authoritative support.  Add support
**		for case-insensitive username comparison.  Add support for
**		the "require group" directive.
**
**	003	MPD/15-Jan-2003
**		Added Apache 2.0 API changes
**
*/


/*
**  Include files
*/
#include <ctype.h>
#include <types.h>
#include <limits.h>
#include <string.h>

#include <ssdef.h>
#include <kgbdef.h>
#include <lgidef.h>
#include <stsdef.h>
#include <descrip.h>
#include <starlet.h>
#include <builtins.h>

#ifdef SHADOW
#undef SHADOW
#endif
#ifdef MULTITHREADING
#undef MULTITHREADING
#endif

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "apr_strings.h"
#include "protshr.h"

/*
**  Conditional asssembly macros
*/
#ifndef __VMS_AUTH_SYSUAF_DEBUG
#define __VMS_AUTH_SYSUAF_DEBUG 0		/* 1= debugging */
#endif
#if     __VMS_AUTH_SYSUAF_DEBUG
#define DEBUG_PRINTF(ARGS) printf ARGS
#define DEBUG_OFF (0)
#define DEBUG_ON (1)
#include <stdio.h>
#else
#define DEBUG_PRINTF(ARGS)
#define DEBUG_OFF (1)
#define DEBUG_ON (0)
#endif


/*
**  Definitions
*/
#ifndef INTERNAL
#define INTERNAL static
#endif
#ifndef NULL
#define NULL (void *) 0
#endif
#ifndef alloca
#define alloca __ALLOCA
#endif

#define BAD_STATUS(I) (((I) & STS$M_SUCCESS) == 0)
#define GOOD_STATUS(I) (((I) & STS$M_SUCCESS) == 1)


/*
**  Data structures
*/
typedef unsigned int VMS_STATUS;
typedef struct dsc$descriptor_s DSC_S;

typedef struct                          /* context block */
{
    int     fUserEnable;		/* 0= false (this module abstains) */
    int     fGroupEnable;		/* 0= false (no group processing) */
    int     fAuthoritative;		/* 0= false (DECLINE, not REJECT) */
}
CTXBLK;

typedef struct				/* temp structure for identifiers */
{
    int     iUsed;			/* number of identifiers */
    int     iAllocated;			/* number of slots allocated */
    int     aiIds[10];			/* initial allocation of space */
}
IDENT_LIST;


/*
**  Function prototypes
*/
INTERNAL int save_identifier(const char *pcszName, IDENT_LIST **ppident);

extern module AP_MODULE_DECLARE_DATA auth_openvms_module;


void *create_auth_openvms_context (apr_pool_t *p, char *d)
{
    CTXBLK *sec = (CTXBLK *) apr_pcalloc (p, sizeof(CTXBLK));
    sec->fUserEnable = sec->fGroupEnable = -1;
    return (void *) sec;
}


/*
**  Directives handled by this module
**
**  NOTE: "AuthUserOpenVMS" is the old syntax.  We keep it for backwards
**  compatability.  However, by default, this module is enabled, although
**  not authoritative.
*/
command_rec auth_openvms_cmds[] =
{
    { "AuthOpenVMSAuthoritative", ap_set_flag_slot,
            (void *) APR_XtOffsetOf(CTXBLK,fAuthoritative), OR_AUTHCFG, FLAG,
	    "Set to 'no' to allow access control to be passed along "
            "to lower modules if the userID is not known to this module" },
    { "AuthOpenVMSGroup", ap_set_flag_slot,
            (void *) APR_XtOffsetOf(CTXBLK,fGroupEnable), OR_AUTHCFG, FLAG,
	    "OpenVMS authorization on or off" },
    { "AuthOpenVMSUser", ap_set_flag_slot,
            (void *) APR_XtOffsetOf(CTXBLK,fUserEnable), OR_AUTHCFG, FLAG,
	    "OpenVMS authentication/authorization on or off" },
    { "AuthUserOpenVMS", ap_set_flag_slot,
            (void *) APR_XtOffsetOf(CTXBLK,fUserEnable), OR_AUTHCFG, FLAG,
	    "OBSOLETE: Please switch to AuthOpenVMSUser" },
    { NULL }
};


/*
**  These functions return 0 if client is OK, and proper error status
**  if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
**  SERVER_ERROR, if things are so totally confused that we couldn't
**  figure out how to tell if the client is authorized or not.
**
**  If they return DECLINED, and all other modules also decline, that's
**  treated by the server core as a configuration error, logged and
**  reported as such.
*/


/*
**  a u t h e n t i c a t e _ o p e n v m s
**
**  Basic HTTP authentication.
**  Return with OK for success, DECLINED for an unknown user with the
**  AuthOpenVMSAuthoritative directive disabled and HTTP_UNAUTHORIZED for a
**  failure.
*/
INTERNAL int authenticate_openvms (request_rec *r)
{
    unsigned int st;
    CTXBLK *sec = (CTXBLK *)
            ap_get_module_config (r->per_dir_config, &auth_openvms_module);
    conn_rec *c = r->connection;
    const char *pcszPassword;
    const char *pcszHostName;
    char errstr[MAX_STRING_LEN];
    int res;

    if ((res = ap_get_basic_auth_pw (r, &pcszPassword)))
        return res;
    if (!sec->fUserEnable)
        return DECLINED;

    pcszHostName = ap_get_remote_host ( r->connection, r->per_dir_config,
			REMOTE_NOLOOKUP, 0 );

    st = apache$$auth_princ_sysuaf (
            (const char *) r->user, strlen(r->user),
            pcszPassword, strlen(pcszPassword),
            pcszHostName, strlen(pcszHostName) );
    if (st == LGI$_NOSUCHUSER)
    {
        if (!sec->fAuthoritative)
            return DECLINED;
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
               "OpenVMS user %s not found", r->user);
        ap_note_basic_auth_failure(r);
        return HTTP_UNAUTHORIZED;
    }
    if (BAD_STATUS(st))
    {
        apr_snprintf(errstr, sizeof(errstr),
                "OpenVMS authorization failure %s status=0x%08x",
                r->user, st);
	ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
                "access to %s failed for %s, reason: %s",
		r->uri,
                ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME, 0),
                errstr);
	ap_note_basic_auth_failure (r);
        return HTTP_UNAUTHORIZED;
    }
    return OK;
}


/*
**  c h e c k _ o p e n v m s _ a c c e s s
**
**  Parse the "require user" and "require group" directives to determine
**  whether or not the user is allowed access.
**  Return with OK for success, DECLINED for an unknown user with the
**  AuthOpenVMSAuthoritative directive disabled and HTTP_UNAUTHORIZED for a
**  failure.
*/
INTERNAL int check_openvms_access (request_rec *r)
{
    unsigned int st;
    int fAuthorized = 0;
    int iRequireGroup = 0;
    CTXBLK *sec = (CTXBLK *)
            ap_get_module_config (r->per_dir_config, &auth_openvms_module);
    conn_rec *c = r->connection;
    int m = r->method_number;
    register int x;
    const char *t, *w;
    const apr_array_header_t *reqs_arr = ap_requires(r);
    require_line *reqs;
    IDENT_LIST *pident = NULL;


    /*
    **  If we're not enabled then decline.
    */
    if ((!sec->fGroupEnable) || (!sec->fUserEnable))
        return DECLINED;


    /*
    **  If there are no require directives then any user will do: decline,
    **  same as MOD_AUTH_DB does.
    */
    if (!reqs_arr)
    {
        DEBUG_PRINTF(("(check_openvms_access) No require directives\n"));
        return DECLINED;
    }
    reqs = (require_line *) reqs_arr->elts;


    /*
    **  Parse each require directive.
    **
    **  NOTE: We intentionally parse and handle the "require user" directive
    **  to make the string comparison case-insensitive.
    */
    for (x = 0; x < reqs_arr->nelts; x++)
    {
        if (!(reqs[x].method_mask & (1 << m)))
            continue;
        t = reqs[x].requirement;
        w = ap_getword_white(r->pool, &t);
        if (!strcasecmp(w, "valid-user"))
        {
            DEBUG_PRINTF(("(check_openvms_access) valid-user\n"));
            fAuthorized++;
            break;
        }
        if (!strcasecmp(w, "user"))
        {
            while (t[0])
            {
                w = ap_getword_conf(r->pool, &t);
                DEBUG_PRINTF(("(check_openvms_access) user  \"%s\"\n", w));
                if (!strcasecmp(r->user, w))
                {
                    fAuthorized++;
                    break;
                }
            }
            if (fAuthorized > 0)
                break;
        }
        else if (!strcasecmp(w, "group"))
        {
            while (t[0])
            {
                w = ap_getword_conf(r->pool, &t);
                DEBUG_PRINTF(("(check_openvms_access) group \"%s\"\n", w));
                if (save_identifier(w, &pident))
                {
                    if (pident != NULL)
                        free(pident);
                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                            "internal error: cannot save groups, "
                            "errno=%d (user %s)", errno, r->user);
                    return DECLINED;
                }
                iRequireGroup++;
            }
        }
        else if (sec->fAuthoritative)
        {
            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                    "access to %s failed, reason: unknown require directive:"
                    "\"%s\"", r->uri, reqs[x].requirement);
        }
    }


    /*
    **  All of the require directives have been parsed.  If we didn't find
    **  a "require valid-user" or a matching "require user" directive but we
    **  did find one or more "require group" directives, then see if the
    **  user is a member of the specified group UICs or it the account has
    **  any of the specified identifiers granted to it.  The tests for the
    **  validity of the account will be done at the same time.
    **
    **  Otherwise, we still need to test the validity of the account, whether
    **  the access is allowed or not.
    */
    if ((fAuthorized == 0) && (pident != NULL))
    {
        st = apache$$check_rights_list((const char *) r->user, strlen(r->user),
                (const unsigned int *) &pident->aiIds[0], pident->iUsed);
        if (GOOD_STATUS(st))
        {
            DEBUG_PRINTF(("(check_openvms_access) "
                    "apache$$check_rights_list(%s,%d) OK\n",
                    r->user, pident->iUsed));
            free(pident);
            return OK;
        }
        DEBUG_PRINTF(("(check_openvms_access) "
                "apache$$check_rights_list(%s,%d) status=0x%x\n",
                r->user, pident->iUsed, st));
        free(pident);
    }
    else
    {
        if (pident != NULL)
            free(pident);
        st = apache$$check_rights_list((const char *) r->user, strlen(r->user),
                (const unsigned int *) NULL, 0);
        if (GOOD_STATUS(st))
        {
            DEBUG_PRINTF(("(check_openvms_access) "
                    "apache$$check_rights_list(%s) OK\n",
                    r->user, ((fAuthorized > 0) ? "" : " (not authorized)")));
            if (fAuthorized > 0)
                return OK;
            if (iRequireGroup == 0)
                return DECLINED;	/* no require group directives */
            st = SS$_NOCALLPRIV;	/* some found but none were valid */
        }
        else
            DEBUG_PRINTF(("(check_openvms_access) "
                    "apache$$check_rights_list(%s) status=0x%x\n", r->user, st));
    }


    /*
    **  All is lost: this user is not allowed access.
    */
    if (st == LGI$_NOSUCHUSER)
    {
        DEBUG_PRINTF(("(check_openvms_access) user %s not found\n", r->user));
        if (!sec->fAuthoritative)
            return DECLINED;
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                "access to %s failed, reason: OpenVMS user %s not found",
                r->uri, r->user);
        ap_note_basic_auth_failure(r);
        return HTTP_UNAUTHORIZED;
    }
    if (SS$_NOCALLPRIV)
    {
        DEBUG_PRINTF(("(check_openvms_access) user %s not allowed\n", r->user));
        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                "access to %s failed, reason: user %s is not allowed",
                r->uri, r->user);
        ap_note_basic_auth_failure(r);
        return HTTP_UNAUTHORIZED;
    }
    DEBUG_PRINTF(("(check_openvms_access) unexpected error 0x%x (user %s)\n",
            st, r->user));
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
            "access to %s failed, reason: unexpected error 0x%x (user %s)",
            r->uri, st, r->user);
    ap_note_basic_auth_failure(r);
    return HTTP_UNAUTHORIZED;
}


/*
**  s a v e _ i d e n t i f i e r
**
**  Add an identifier to the internal list.  If necesssary, expand the list.
**  Return with zero for success or minus one for failure.  However, if the
**  "require group" value is not an identifier, ignore it (not an error).
**
**  NOTE: For simplicity, we use the realloc() function to expand an existing
**  array.  However, this function is not available to the low level pool
**  allocation routines.  Therefore, we must ensure that the structure is
**  deallocated in all cases.
*/
INTERNAL int save_identifier(const char *pcszName, IDENT_LIST **ppident)
{
    int i;
    VMS_STATUS st;
    unsigned int uiId;
    unsigned int uiAttrib;
    IDENT_LIST *pidentNew;
    DSC_S dscsName;


    /*
    **  Convert the name to an identifier.
    */
    dscsName.dsc$b_dtype = DSC$K_DTYPE_T;
    dscsName.dsc$b_class = DSC$K_CLASS_S;
    dscsName.dsc$a_pointer = (char *) pcszName;
    dscsName.dsc$w_length = strlen(dscsName.dsc$a_pointer);
    if (dscsName.dsc$w_length == 0)
        return(0);
    if (dscsName.dsc$w_length > CHAR_MAX)
    {
        errno = EINVAL;
        return(-1);
    }
    st = sys$asctoid(&dscsName, &uiId, &uiAttrib);
    if (BAD_STATUS(st))
    {
        DEBUG_PRINTF(("(save_identifier) sys$asctoid(\"%.*s\") status=0x%x\n",
                dscsName.dsc$w_length, dscsName.dsc$a_pointer, st));
        return(0);
    }


    /*
    **  Only keep plain identifiers (i.e. no attributes), resource identifiers,
    **  and group ids.  User names are also accepted here, but they are not
    **  relevant: user names are immediately handled by a string comparison.
    */
    if (uiAttrib != (uiAttrib & (KGB$M_RESOURCE | KGB$M_DYNAMIC)))
        return(0);
    DEBUG_PRINTF(("(save_identifier) \"%.*s\" = 0x%08x (attrib=0x%08x)\n",
            dscsName.dsc$w_length, dscsName.dsc$a_pointer, uiId, uiAttrib));


    /*
    **  Save the value in the IDENT_LIST structure.
    */
    if ((*ppident) == NULL)
    {
        (*ppident) = malloc(sizeof(**ppident));
        if ((*ppident) == NULL)
            return(-1);
        (*ppident)->iUsed = 0;
        (*ppident)->iAllocated = sizeof((*ppident)->aiIds) /
                sizeof((*ppident)->aiIds[0]);
    }
    else
    {
        if ((*ppident)->iUsed >= (*ppident)->iAllocated)
        {
            pidentNew = (IDENT_LIST *) realloc((*ppident), sizeof(**ppident) +
                    ((*ppident)->iAllocated * sizeof((*ppident)->aiIds[0])));
            if (pidentNew == NULL)
                return(-1);
            (*ppident) = pidentNew;
            (*ppident)->iAllocated += sizeof((*ppident)->aiIds) /
                    sizeof((*ppident)->aiIds[0]);
        }
    }
    (*ppident)->aiIds[(*ppident)->iUsed++] = uiId;
    return(0);
}

static void register_hooks(apr_pool_t *p)
{
    ap_hook_check_user_id(authenticate_openvms,NULL,NULL,APR_HOOK_MIDDLE);
    ap_hook_auth_checker(check_openvms_access,NULL,NULL,APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA auth_openvms_module =
{
    STANDARD20_MODULE_STUFF,
    create_auth_openvms_context,/* dir config creater */
    NULL,			/* dir merger --- default is to override */
    NULL,			/* server config */
    NULL,			/* merge server config */
    auth_openvms_cmds,		/* command apr_table_t */
    register_hooks		/* register hooks */
};