OpenVMS Source-Code Demos

MOD_AUTH_VMS_EXT

//================================================================================================================================
// title  : mod_auth_vms_ext.c
// author : Neil Rieck (Waterloo, Ontario, Canada)
// created: 2015-03-03
// target : CSWS-2.0 (a.k.a. Apache httpd-2.0.63 on OpenVMS)
// notes  : 1) this program is based upon "mod_auth_openvms.c" from HP
//        : 2) I want to use "application authentication" rather than "SYSUAF authentication" (100% of our current >1k users are
//		web-based so it was easier for us to use store usernames and VMS-created "password hashes" in an application
//		database than create VMS accounts for each one; this will allow us to enable self-serve account generation and
//		maintenance as is done on Amazon.com and eBay.com; this also allows us to have account names longer than 14
//		characters -AND- email addresses)
// ver who when   what
// --- --- ------ ----------------------------------------------------------------------------------------------------------------
// 100 NSR 150303 1. original effort (MOD_AUTH_VMS_EXTERNAL.C); compiles + links but is not complete
//	   150307 2. the saga continues
//	   150311 3. more work after some technical interruptions elsewhere
//	   150313 4. split the module into two pieces (cookie only vs. user only)
//		  5. cleaned up the debug macros
//	   150314 6. renamed to MOD_AUTH_VMS_EXT_100.C (next version will be MOD_AUTH_VMS_RMS.C)
// 101 NSR 150316 1. renamed DCL symbols to something more sensible (now all have a prefix of "VMSEXT_")
//	   150317 2. now also grab the local_host_name (required to create DOMAIN cookies)
// 102 NSR 150318 1. merged two modules back into one (so we only need to register one hook)
//	   150319 2. final finished of the option GroupList testing
//                3. now do my own base64 extraction so we don't need to depend upon mod_auth(_basic)
// 103 NSR 150505 1. the saga continues (back from another distraction)
//     NSR 150506 2. added code to send back a cookie
//     NSR 150507 3. reworked group processing (what was I thinking?)
//		  4. removed my developmental "goto" statements (don't want to be crucified by other c programmers)
// 104 NSR 150519 0. the saga continues (back from another distraction)
//		  1. added code to pickup RealmName (used in password realm)
// 105 NSR 150609 1. started adding code to read cookies from our RMS-based session cache. This would eliminate the need to spawn
//		     multiple times for each page load; expecially important when Autoindex is enabled on a directory with no
//		     predefined index page (see http://httpd.apache.org/docs/2.4/mod/mod_authn_socache.html to see what I mean)
//		     Note: this session cache is written using RMS which is an ISAM technology only available on VMS and OpenVMS
//     NSR 150615 2. bug fix in groupData extraction logic (RMS-cache)							bf_105.2
//     NSR 150616 3. bug fix in cache-hit test										bf_105.3
//     NSR 151210 4. mtce tweak to the VMSIFY macro
//================================================================================================================================
// Docs:
// 1) build this program then copy the execuatable to sys$common:[modules]
//	be sure to check file ownership and protection bits
//	consider using "$SET SECURITY/ACL" to modify/delete access control list params
//    add the next line to file: apache$common:[conf]httpd.conf
//	LoadModule auth_vms_ext_module     modules/mod_auth_vms_ext.exe
//    restart CSWS to load the new module
// 2) sample .htaccess
//	#---------------------------------------------------------
//	# title  : apache$common:[neil_private].htaccess
//	# author : Neil Rieck
//	# created: 2015-03-04
//	#---------------------------------------------------------
//	AuthType Basic
//	AuthVmsExtUser		On					off means plugin is disabled
//	AuthVmsExtAuthoritative	On					off means exit with DECLINED rather than HTTP_UNAUTHORIZED
//	AuthVmsExtCookie	ICSIS_SESSION				optional test (no value means disabled)
//	AuthVmsExtGroupList	ATS,BRT,WST,POW,			optional test (no value means disabled)
//	AuthVmsExtProgram	"r CSMIS$EXE:WCSM_SESSION_CHECK.EXE"	no value means disabled
//	AuthVmsExtLogfile	"CSMIS$LOG:WCSM_SESSION_CHECK.LOG"	no value means disabled
//	AuthVmsExtRealmName	"OpenVMS External Authentication"	optional, displayed during the password dialog
//	AuthVmsExtSessionCache	"csmis$dat:icsis_session_5.dat"		optional, RMS-based ISAM file (no value means disabled)
//	require valid-user
//	#---------------------------------------------------------
//================================================================================================================================
#define RMS_SESSION_CACHE	1					// enable support for RMS-based session cache
//
//  Include files (common c stuff)
//
#include <ctype.h>
#include <types.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>						// need this for getenv
//
//  Include files (VMS and OpenVMS only)
//
#ifdef __VMS							// if VMS or OpenVMS
#define __NEW_STARLET	1					// enable new (strict) starlet (OpenVMS Alpha 7.0 and above)
#include <ssdef.h>						//
#include <kgbdef.h>						//
#include <lgidef.h>						//
#include <stsdef.h>						//
#include <descrip.h>						//
#include <starlet.h>						//
#include <builtins.h>						//
#include <lib$routines.h>					// need this for lib$spawn
#include <rms.h>						// OpenVMS Record Management Services
#else
#define RMS_SESSION_CACHE 0					// no VMS means no RMS
#endif								//
//
#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"
//
//  Definitions
//
#ifndef INTERNAL
#define INTERNAL static
#endif
#ifndef NULL
#define NULL (void *) 0
#endif
#ifndef alloca
#define alloca __ALLOCA
#endif
//
//	VMSIFY
//      a macro for use in the VMS world (VMS strings employ this structure)
//	notes:	this macro can be used to pass VMS strings down to lower modules
//		the $DESCRIPTOR macro does something similar employing sizeof-1
//		this macro combines two operations
//
#define VMSIFY(a,b) {					\
    a.dsc$b_dtype = DSC$K_DTYPE_T;			\
    a.dsc$b_class = DSC$K_CLASS_S;			\
    a.dsc$w_length = strlen(b);				\
    a.dsc$a_pointer = (char *) malloc(strlen(b));	\
    strncpy(a.dsc$a_pointer,b,a.dsc$w_length);		\
}
//
#define DEBUG 1
#if DEBUG
//dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd Neil's debug code
//
//	this block is for development purposes only
//
char spy_stamp[50];								// buffer for time-stamp
void build_stamp() {
#include <time.h>
	//----------------------------------------------------------------------
	struct	timeb	timebuffer;						// for ftime()
	struct	tm	*time_fields;						// for localtime()
	char		millisecs[5];						//
	char		my_date_time[30];					//
	//----------------------------------------------------------------------
	ftime( &timebuffer );							// record current system time
	sprintf(millisecs, "%03hu", timebuffer.millitm);			// extract milliseconds as three chars
	time_fields = localtime( &timebuffer.time );				// breakout other time fields
	strftime(	my_date_time,						// ccyymmdd.hhmmss
			sizeof(my_date_time),					//
			"%Y%m%d.%H%M%S",					//
			time_fields );						//
	sprintf(	spy_stamp,						// ccyymmdd.hhmmss.xxx
			"%s%s%s",						//
			my_date_time,						//
			".",							//
			millisecs);						// xxx
}

char trc_buf[MAX_STRING_LEN];							//
FILE *trc_file = NULL;								//
void TRC1(char *msg) {								// trace (one param)
    build_stamp();								//
    trc_file = fopen("APACHE$COMMON:[000000]aaa_mod_auth_vms_ext.trc", "a");	// open the trace file
    if (trc_file != NULL) {							//
	fprintf(trc_file, "%s %s\n",spy_stamp,msg);				//
	fclose (trc_file);							//
    }										//
}										//
//	trace (two params)
#define TRC2(a,b) {		\
	sprintf(trc_buf,a,b);	\
	TRC1 (trc_buf);		\
}										//
//dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd end of debug
#else
#define TRC2(a,b)
#define TRC1(a)
#endif
//
//  Data structures
//
typedef unsigned int VMS_STATUS;
//
typedef struct dsc$descriptor_s DSC_S;			// all VMS strings must be passed by descriptor
//
typedef struct {                          		// context block
    int     fUserEnable;				// 0 = false (this module abstains)
    int     fAuthoritative;				// 0 = false (DECLINED, not REJECT)
    char    *fCookieName;				// whatever				optional, leave blank to disable
    char    *fGroupList;				// GRP1[,GRP2,GRP3,...]			optional, leave blank to disable
    char    *fLogfile;					// logfile (for program development)	optional, leave blank to disable
    char    *fProgram;					// program				required
    char    *fRealmName;				// authorization name (realm)		optional (to override default)
    char    *fSessionCache;				// session cache			optional, leave blank to disable
}
CTXBLK;

//
//  Function prototypes
//
extern module AP_MODULE_DECLARE_DATA auth_vms_ext_module;
#if RMS_SESSION_CACHE == 1				//
long session_cache_lookup(char*, char*, char*);		//
#endif							//
//
void *create_auth_vms_ext_cntxt (apr_pool_t *p, char *d)
{
    CTXBLK *sec = (CTXBLK *) apr_pcalloc (p, sizeof(CTXBLK));
    sec->fUserEnable	= 0;
    sec->fAuthoritative	= 0;
    sec->fCookieName	= NULL;
    sec->fGroupList	= NULL;
    sec->fLogfile	= NULL;
    sec->fProgram	= NULL;
    sec->fRealmName	= NULL;
    sec->fSessionCache	= NULL;

    return (void *) sec;
}
const char* my_first_cmd_func(cmd_parms* cmd, void* cfg, const char* arg);
//
//  Directives handled by this module
//
command_rec auth_vms_ext_cmds[] =
{
    { "AuthVmsExtAuthoritative",
	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" },
    { "AuthVmsExtUser",
	ap_set_flag_slot,
	(void *) APR_XtOffsetOf(CTXBLK,fUserEnable),
	OR_AUTHCFG,
	FLAG,
	"OpenVMS user authentication/authorization on/off" },
    { "AuthVmsExtGroupList",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fGroupList),
	OR_AUTHCFG,
	TAKE1,
	"comma-delimited list of authorized groups (blank=disabled)" },
    { "AuthVmsExtCookie",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fCookieName),
	OR_AUTHCFG,
	TAKE1,
	"desired cookie (blank=disabled)" },
    { "AuthVmsExtProgram",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fProgram),
	OR_AUTHCFG,
	TAKE1,
	"desired program (eg. \"r path:file.ext\", blank=disabled)" },
    { "AuthVmsExtLogfile",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fLogfile),
	OR_AUTHCFG,
	TAKE1,
	"debug logfile (eg. \"path:file.log\", blank=disabled)" },
    { "AuthVmsExtRealmName",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fRealmName),
	OR_AUTHCFG,
	TAKE1,
	"realm name (eg. \"OpenVMS External Authentication\")" },
    { "AuthVmsExtSessionCache",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fSessionCache),
	OR_AUTHCFG,
	TAKE1,
	"session cache filename (eg. \"path:session.dat\", blank=disabled)" },
    { NULL }
};

//=====================================================================================================================
//  a u t h e n t i c a t e _ v m s _ e x t
//
// General Overview:
//
// 1) On our system, users can come here with a valid session cookie (set by another app) but no Authorization string.
//	In this case we do not want MOD_AUTH(_BASIC) to prompt for authorization credentials before testing the cookie
//	which means the hook for "authenticate_vms_ext" must fire before MOD_AUTH(_BASIC)
// 2) On our system, users might come here with nothing (no cookie or Authorization string) so we will prompt for a
//	username + password then create the session cookie
// 3) In order to keep our transaction overhead as low as possible, I do things here which might have been done by a
//     combination of the core routines and MOD_AUTH(_BASIC)
// 4) Authorative Off means that authentication failures return DECLINED so other other modules can authenticate
// 5) Authorative On  means we never DECLINE to another modules
// 6) Successfull authentication is not enough.
//	If a GroupList has been defined then the external authenticator must return a group string.
//	If the group string is in the list then we return OK else 403 (HTTP_FORBIDDEN)
//
// Caveats:
//
// 1) Cookie authentication only works properly on our system when we employ DOMAIN cookies
// 2) You should never do plain text Basic authentication over port 80; use SSL encryption via port 443 or switch to
//    something else like Digest authentication (mod_auth_digest.c)
//======================================================================================================================
//
//	<<< External Authentication >>>
//
//	pass as much stuff (cookie and user + password) to the external authenticator
//	if the authenticator say the cookie and remote host are valid then that is all we need to move on
//	if the cookie is not valid then we will prompt for a username and password (if the authenticator is satisfied
//		then it will also pass back a session cookie to be used on the next pass through here
//======================================================================================================================
INTERNAL int authenticate_vms_ext (request_rec *r)
{
    unsigned int st;								//
    unsigned int rc;								//
    int cache_hit = 0;								//
    CTXBLK *sec			= (CTXBLK *) ap_get_module_config (r->per_dir_config, &auth_vms_ext_module);
    //
    // cookie-testing is optional but username is not (we prompt for username+password on a cookie miss)
    //
    if (!sec->fUserEnable) {							// if no enabled username directive
        return DECLINED;							// then exit with a defer
    }										//
    TRC1("new transaction ==========");
    conn_rec *c			= r->connection;				//
    const char *pcszRemoteHost	= ap_get_remote_host ( r->connection, r->per_dir_config, REMOTE_NOLOOKUP, 0 );
    const char *cookie_data	= apr_table_get( r->headers_in, "Cookie");	//
    const char *type		= ap_auth_type(r);				// Basic, Digest, etc.
    const char *auth_line	= apr_table_get( r->headers_in, "Authorization");
    const char *pcszUser	= apr_pstrcat(r->pool, r->user, NULL, NULL);	//
    const char *pcszPassword	= 0;						//
    char  cookie_payload[8192]	= "";						// max total limit for browser cookies
    char  errstr[MAX_STRING_LEN]= "";						//
    char  *newCookie		= NULL;						//
    char  groupData[32]		= "";						//
    int   score = 0;								//
    int   junk = 0;
    char  default_realm[]	= "OpenVMS External Authentication";
    const char *base64_buf	= 0;
    const char *decode_buf	= 0;
    const char *cursor		= 0;
    //--------------------------------------------------------------------------
    TRC2("fCookieName: %s",sec->fCookieName);
    TRC2("fUserEnable: %d",sec->fUserEnable);
    TRC2("fGroupList : %s",sec->fGroupList);
    TRC2("fProgram   : %s",sec->fProgram);
    TRC2("fLogfile   : %s",sec->fLogfile);
    TRC2("fRealmName : %s",sec->fRealmName);
    TRC2(" LocalHost : %s",r->server->server_hostname);
    TRC2(" RemoteHost: %s",pcszRemoteHost);
    TRC2(" CookieData: %s",cookie_data);
    TRC2(" Authtype  : %s",type);
    TRC2(" Authorizat: %s",auth_line);
    TRC2(" USER      : %s",r->user);
    if (r->user!=0){								//
	junk = ap_get_basic_auth_pw (r, &pcszPassword);				//
    }										//
    TRC2(" PASS      : %s",pcszPassword);					//
    //--------------------------------------------------------------------------
    if ((auth_line)	&& 							// if we have an authorization line
       ((strlen(pcszUser)==0) || (strlen(pcszPassword)==0)))			// but no user or password
    {
	base64_buf = strstr(auth_line,"Basic ");				//
	if (base64_buf){
	    base64_buf += 6;
	}
	decode_buf = ap_pbase64decode(r->pool, base64_buf);			//
	TRC2("extracted info %s",decode_buf);					// user:pass
	cursor = decode_buf;							//
	pcszUser = ap_getword (r->pool, &cursor, ':');				//
	TRC2("extracted user %s",pcszUser);					//
	pcszPassword = ap_getword (r->pool, &cursor, ':');			//
	TRC2("extracted pass %s",pcszPassword);					//
    }

    //--------------------------------------------------------------------------
    //	if a cookie was specified, look for it
    //--------------------------------------------------------------------------
    if ((sec->fCookieName == NULL) || (cookie_data == NULL)) {			// no 'cookie specified' or 'no cookie data'
	TRC1("skipping cookie logic");						//
    }else{
	char *start_cookie, *end_cookie, *payload;				//
	if (start_cookie = strstr(cookie_data, sec->fCookieName)) {		// locate the desired cookie
	    start_cookie += strlen(sec->fCookieName) + 1;           		// slide past cookie name (and '=')
	    end_cookie = (char*) cookie_data + strlen(cookie_data);		//
	    for (char* i=start_cookie;i<=end_cookie;i++) {			//
		switch(*i) {							//
		    case ';':							//
		    case '\0':							//
			end_cookie=i;						//
                	break;							//
		    default:							//
		}								//
	    }									//
	    strncpy(cookie_payload, start_cookie, end_cookie-start_cookie);	//
	    cookie_payload[end_cookie-start_cookie] = '\0';			// ensure string is null terminated
	    TRC2("Cookie Payload: %s",cookie_payload);				//
	    if (strlen(cookie_payload)>0){					//
		score = 1;							// indicate we detected the named cookie
	    }									//
	}									//
    }										//

    //--------------------------------------------------------------------------
    //	if a username was specified and both username + password are present
    //--------------------------------------------------------------------------
    if ((!sec->fUserEnable)	||	// if not enabled
        (pcszPassword==0)	||	// or no password
	(pcszUser==0)		){	// or no username
	    TRC1("skipping user+pass logic");
    }else{
	score = score | 2;							// indicate we detected user + pass
    }

    if (score==0)
    //--------------------------------------------------------------------------
    //	if we have no data to send to the authenticator then exit now
    //--------------------------------------------------------------------------
    if (score==0) {								// we have nothing to work with
	if (!sec->fUserEnable) {						// if username not required
	    if (sec->fAuthoritative) {						// if authoritative
		TRC1("no data so HTTP_UNAUTHORIZED");				// the buck stops here
		return HTTP_UNAUTHORIZED;
	    }else{
		TRC1("no data so DECLINED");
		return DECLINED;
	    }
	}else{									// username required but not provided
	    //
	    //	this is how we command the client's browser to raise a user+pass dialog
	    //	(he will have a user+pass next time through here)
	    //
	    TRC1("no data so throw 401 (to get username+password)");
	    if (strlen(sec->fRealmName)<10) {
		apr_table_setn(r->err_headers_out,
		    (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
		                                    : "WWW-Authenticate",
		apr_pstrcat(r->pool, "Basic realm=\"", default_realm	, "\"", NULL));
	    }else{
		apr_table_setn(r->err_headers_out,
		    (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
		                                    : "WWW-Authenticate",
		apr_pstrcat(r->pool, "Basic realm=\"", sec->fRealmName	, "\"", NULL));
	    }
	    return HTTP_UNAUTHORIZED;						//
	}
    }

#if RMS_SESSION_CACHE==1
	if ((sec->fSessionCache!= NULL)	&&					// if sessionFs...
	    (strlen(cookie_payload)>0))	{					// and cookie
	    junk = session_cache_lookup(sec->fSessionCache, (char*)&cookie_payload, (char*)&groupData);
	    TRC2("-i-cache_lookup status: %d",junk);				//
	    if (junk==0) {							//					bf_105.3
		TRC2("-i-session-group-info : %s",groupData);			//
		cache_hit = 1;							// block overwriting of groupData below
		st = 1;								// simulate VMS-S-
		goto skip_spawn;						// don't hate me because I used "goto' :-)
	    }									//
	}									//
#endif

    //--------------------------------------------------------------------------
    //	call the external authenticator (lib$spawn is semi-expensive on Alpha)
    //--------------------------------------------------------------------------
    struct dsc$descriptor_s	user2,						// username
				pass2,						// password
				cook2,						// cookie
				lhost2,						// local host
				rhost2,						// remote host (for cookie security check)
				debug2,						// (optional) debug parameter
				spawn_command,					// dcl command string
				logfile;					// (optional) logfile spec
    if (sec->fProgram==0) {							// if no external dcl command
	TRC1("HTTP_NOT_IMPLEMENTED - no program");				//
	return HTTP_NOT_IMPLEMENTED;						// throw 501 (can't recover from this)
    }
    VMSIFY(spawn_command, sec->fProgram);					//
    //--------------------------------------------------
    $DESCRIPTOR( debug1,"VMSEXT_DEBUG");					// can only debug if we have a log file
    if (sec->fLogfile!=0){
	VMSIFY(logfile, sec->fLogfile);
	VMSIFY(debug2,"Y");
    }else{
        VMSIFY(logfile,"NL:");
	VMSIFY(debug2,"N");
    }
    st = lib$set_symbol( &debug1, &debug2, &2 );
    //--------------------------------------------------
    $DESCRIPTOR( lhost1,"VMSEXT_LHOST");
    VMSIFY(lhost2,r->server->server_hostname);
    st = lib$set_symbol( &lhost1, &lhost2, &2 );
    //--------------------------------------------------
    if (pcszRemoteHost!=0){
        $DESCRIPTOR( rhost1,"VMSEXT_RHOST");
	VMSIFY(rhost2,pcszRemoteHost);
	st = lib$set_symbol( &rhost1, &rhost2, &2 );
    }
    //--------------------------------------------------
    if (strlen(cookie_payload)>0){
        $DESCRIPTOR( cook1,"VMSEXT_COOKIE");
	VMSIFY(cook2,cookie_payload);
	st = lib$set_symbol( &cook1, &cook2, &2 );
    }
    //--------------------------------------------------
    if ((score & 2)==2){
	if (pcszUser !=0) {
	    $DESCRIPTOR( user1,"VMSEXT_USER");
	    VMSIFY(user2,pcszUser);
	    st = lib$set_symbol( &user1, &user2, &2 );
	}
	if (pcszPassword!=0) {
	    $DESCRIPTOR( pass1,"VMSEXT_PASS");
	    VMSIFY(pass2,pcszPassword);
	    st = lib$set_symbol( &pass1, &pass2, &2 );
	}
    }
    //--------------------------------------------------
    TRC1("calling lib$spawn()");
    rc = lib$spawn(
	&spawn_command,					// command-string
	0,						// input-file
	&logfile,					// output-file
	0,						// flags
	0,						// process-name
	0,						// process-id
	&st,						// completion-status-address
	0,						// byte-integer-event-flag-num
	0,						// AST-address
	0,						// varying-AST-argument
	0,						// prompt-string
	0,						// cli
	0);						// table
    //
    TRC2("lib$spawn-rc: %ld",rc);
    if ((st & 7)!=1) {					// if spawn failed
	TRC1("forbidden-0");				//
	return HTTP_FORBIDDEN;				// send back 403 (putting up a user+pass dialog would be pointless)
    }							//
    //--------------------------------------------------
    skip_spawn:;
    TRC2("exit status: %ld",st);
    //
    // note: according to the release notes for lib$spawn, a completion code of 0 is mapped to sys$normal (1)
    //
    switch(st){
    case 1:
    case 0:
	newCookie = getenv("VMSEXT_SETCOOKIE");					// this "might" be supplied by the authenticator
	if (newCookie==NULL){
	    TRC1("authenticator SETCOOKIE data: BLANK");
	}else{
	    TRC2("authenticator SETCOOKIE data: %s",newCookie);
	    if (sec->fCookieName==NULL) {
		TRC1("oops, NO COOKIE NAME defined");
	    }else{
		//
		// notes:
		// 1) we hope to end up with something like one of these (example two is for a "domain" session cookie)
		//    ICSIS_SESSION=abcdefghabcdefghabcdefghabcdefgh;
		//    ICSIS_SESSION=abcdefghabcdefghabcdefghabcdefgh; path=/; HttpOnly; Domain=kawc09.on.bell.ca;
		// 2) docs suggest using "apr_table_add()" rather than "apr_table_set()" so I did
		// 3) some website say to end with "\n" but that doesn't work here
		// 4) our authenticator is expected to send back all the cookie data including the trailing semi-colon
		//
		char cookie_hack[128];
		sprintf(cookie_hack,"%s%c%s",sec->fCookieName,'=',newCookie);
		TRC2("SETCOOKIE-HACK: %s",cookie_hack);
		apr_table_add(r->headers_out, "Set-Cookie", cookie_hack);
	    }
	}
	if (sec->fGroupList) {							// if a group list directive is present
	    if (cache_hit==0){							// if no cache hit, then call getenv
		char *temp;							//
		temp = getenv("VMSEXT_GROUP");					// this "might" be supplied by the authenticator
		if (temp==NULL) {						//
		    groupData[0]='\0';						//
		}else{								//
		    sprintf(groupData,temp);					// copy temp to group
		}								//
	    }
	    if (strlen(groupData)==0) {						// if blank...
		TRC1("authenticator GROUP data: BLANK");			//
		TRC1("forbidden-1");						//
		return HTTP_FORBIDDEN;						// send back 403
	    }else{								//
		TRC2("authenticator GROUP data: %s",groupData);			//
		if (!strstr(sec->fGroupList, groupData)) {			// if group is not in the defined list
		    TRC1("forbidden-2");					//
		    return HTTP_FORBIDDEN;					// send back 403
		}								//
	    }
	}
	TRC1("OK");								// yippee!
	return OK;								// yippee!
    default:
	//
	//	this is how we command the client's browser to raise a user+pass dialog
	//	(he will have a user+pass next time through here)
	//
	if (strlen(sec->fRealmName)<10){
		apr_table_setn(r->err_headers_out,
		    (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
	                                    : "WWW-Authenticate",
		apr_pstrcat(r->pool, "Basic realm=\"", default_realm	, "\"", NULL));
	}else{
		apr_table_setn(r->err_headers_out,
		    (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
	                                    : "WWW-Authenticate",
		apr_pstrcat(r->pool, "Basic realm=\"", sec->fRealmName	, "\"", NULL));
	}
	return HTTP_UNAUTHORIZED;						// send back 401
    }
}

//=====================================================================================================================
//	common apache module stuff
//=====================================================================================================================
static void register_hooks(apr_pool_t *p) {
    ap_hook_check_user_id(authenticate_vms_ext,NULL,NULL,APR_HOOK_FIRST);	// run before MOD_AUTH(_BASIC)
}

module AP_MODULE_DECLARE_DATA auth_vms_ext_module =
{
    STANDARD20_MODULE_STUFF,
    create_auth_vms_ext_cntxt,					// per dir config creater
    NULL,							// per dir merger --- default is to override
    NULL,							// server config
    NULL,							// merge server config
    auth_vms_ext_cmds,						// command apr_table_t
    register_hooks						// register hooks
};

#if RMS_SESSION_CACHE==1					// enable support for RMS-based session cache
//==============================================================
//	This stuff is for an optional RMS-based session cache
//	Note: RMS is an ISAM technology only availble on VMS and OpenVMS
//
//	technology:	ISAM databases		relational databases
//			==============		====================
//	terminology:	"keys"			"indexes"
//			"key options"		"contraints"
//==============================================================
//
//	need some more global variables
//
struct FAB	fab;						// file access block (an RMS structure)
struct RAB	rab;						// record access block (an RMS structure)
struct XABKEY	key0,						// extended access block (for each) key
		key1,						// first alternate key
		key2;						// second alternate key
struct {
    char icsis_session_id	[32];				// 128 bit session ID (cookie)	key #0 (primary)
    char icsis_session_pin	[ 7];				// employee pin			key #1 (alternate,dups,changes)
    char icsis_session_priv	[ 8];				// copied from profile-db at logon
    char icsis_session_title	[ 3];				// copied from priv at logon
    char icsis_session_ip	[15];				// ip address where the logon took place
    char icsis_session_timestamp[15];				// ccyymmddhhmmsst		key #2 (alternate,dups,changes)
    char icsis_session_grp	[ 3];				//
    char icsis_session_ini	[ 3];				//
    char icsis_session_org	[ 8];				//
    char icsis_session_name	[35];				//
    char icsis_session_script	[50];				//
    char icsis_session_region	[ 1];				// copied from d91_region
    char icsis_session_language	[ 1];				// copied from d91_language
    char icsis_session_jobcode	[ 3];				// copied from schedule at login
    char icsis_session_bu	[ 3];				//
    char icsis_session_room	[13];				//
} session_record;
//=====================================================================================================================
//	session cache lookup
//	it is assumed that another routine elsewhere is clearing out stale records
//=====================================================================================================================
long session_cache_lookup(char *fSessionCache, char *cookieData, char *groupData) {
   int junk;
   int rms_status;
   char temp1[32];

   if ((strlen(fSessionCache)==0)	||
       (strlen(cookieData)==0)	) {
	   TRC1("session_cache_lookup: failed in step 01");
	   return(-1);						// error
   }
   //-----------------------------------------------------------
   //	initialize rms
   //-----------------------------------------------------------
   fab		 = cc$rms_fab;					// Initialize FAB (be a good citizen)
   //
   //	now make changes to the just-initialized fab
   //
   fab.fab$b_bks = 4;						// blocksize:		4
   fab.fab$b_fac = FAB$M_GET	;				// intend to:		GET (read)
   fab.fab$l_fna = fSessionCache;				// file name addr:	filename.ext
   fab.fab$b_fns = strlen(fSessionCache);			// file name length:	whatever
// fab.fab$l_fop = FAB$M_CIF;					xx file operation:	create if (not found)
   fab.fab$w_mrs = sizeof(session_record);			// maximum record size:	whatever
   fab.fab$b_org = FAB$C_IDX;					// organization:	indexed
   fab.fab$b_rat = FAB$M_CR;					// record-attribute:	<CR>
   fab.fab$b_rfm = FAB$C_FIX;					// format:		fixed
   fab.fab$b_shr = FAB$M_NIL;					// share:		none
   fab.fab$l_xab = &key0;					// indicate our primary key
   //
   rab = cc$rms_rab;              				// Initialize RAB (be a good citizen)
   //
   //	now make changes to the just initialized rab
   //
   rab.rab$l_fab = &fab;					// now point our rab at our fab
   //
   key0 = cc$rms_xabkey;					// Initialize XAB for our key#0 (primary)
   //
   //	now make changes to the just initialized xab
   //
   key0.xab$b_ref = 0;						// reference:		this is key #0 (primary key)
   key0.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key0.xab$b_flg = 0;						// key options:		clear all flags (nodup, nochange, etc)
   key0.xab$w_pos0 = (char*) &session_record.icsis_session_id -	// start of segment0:	compute offset from start of record
		     (char*) &session_record;			//
   key0.xab$b_siz0= sizeof(session_record.icsis_session_id);	// length of segment0:  whatever
   key0.xab$l_nxt = &key1;					// next xab address:
   //
   key1 = cc$rms_xabkey;					// Initialize XAB for our key#1 (first alternate)
   //
   //	now make changes to the just initialized xab (this one is chained to the previous xab)
   //
   key1.xab$b_ref = 1;						// reference:		this is key #1
   key1.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key1.xab$b_flg = XAB$M_DUP | XAB$M_CHG; 			// key options:		DUP + CHANGE
   key1.xab$w_pos0 = (char*) &session_record.icsis_session_pin- // start of segment0:	offset from start of record
                     (char*) &session_record;			//
   key1.xab$b_siz0= sizeof(session_record.icsis_session_pin);	// length of segment0:	whatever
   key1.xab$l_nxt = &key2;					// next xab address:
   //
   key2 = cc$rms_xabkey;					// Initialize XAB for our key#2 (second alternate)
   //
   //	now make changes to the just initialized xab (this one is chained to the previous xab)
   //
   key2.xab$b_ref = 2;						// reference:		this is key #2
   key2.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key2.xab$b_flg = XAB$M_DUP | XAB$M_CHG; 			// key options:		DUP + CHANGE
   key2.xab$w_pos0 =						// start of segment0:	offset from start of record
	(char*) &session_record.icsis_session_timestamp	- 	//
	(char*) &session_record;				//
   key2.xab$b_siz0=						// length of segment0:  whatever
	sizeof(session_record.icsis_session_timestamp);
   //-----------------------------------------------------------
   //	now open the file
   //-----------------------------------------------------------
   rms_status = sys$open(&fab);					// use fab to open the file
   if (rms_status != RMS$_NORMAL){
	TRC2("session_cache open error: %d",rms_status);
	return(-1);
   }

   rms_status = sys$connect(&rab);				// connect rab to fab
   if (rms_status != RMS$_NORMAL){				// if some sort of error
	TRC2("session_cache connect error: %d",rms_status);
	return(-1);
   }
   //-----------------------------------------------------------
   //	now use the cookie to do a primary key lookup
   //-----------------------------------------------------------
   rab.rab$b_krf = 0;						// we want to use key#0 (primary)
   rab.rab$l_kbf = cookieData;					// key buf addr:	whatever
   rab.rab$b_ksz = sizeof(session_record.icsis_session_id); 	// key buf size:	whatever
   rab.rab$b_rac = RAB$C_KEY; 					// record access:	lookup-by-key
   rab.rab$l_rop = RAB$M_NLK;					// record operations:	no-lock
   rab.rab$l_ubf = (char *) &session_record;			// need for $get (but not $find)
   rab.rab$w_usz = sizeof(session_record);			// need for $get (but not $find)
   //
   rms_status = sys$get(&rab);					// get-by-key
   //
   switch(rms_status) {
   case RMS$_NORMAL:
	TRC1("session_cache_lookup worked (cookie found)");
	//
	strncpy(temp1, session_record.icsis_session_pin, sizeof(session_record.icsis_session_pin));
	temp1[ sizeof(session_record.icsis_session_pin) ] = '\0';
	TRC2("-i-session pin: %s", temp1);
	//
	//	in our system, business unit is used in group restriction
	//
	strncpy(temp1, session_record.icsis_session_bu, sizeof(session_record.icsis_session_bu));
	temp1[ sizeof(session_record.icsis_session_bu) ] = '\0';
	TRC2("-i-session bu : %s", temp1);
	//
	//	now save BU + GRP as comma-delimited data in the global variable "group"
	//
	strcpy(groupData,temp1);
	TRC2("-i-groupData  : %s",groupData);
	//
	junk = sys$close(&fab);					// at this point we will ALWAYS close the file
	return(0);						// zero means "no errors"
   case RMS$_RNF:
	TRC1("session_cache_lookup miss");
	junk = sys$close(&fab);					// at this point we will ALWAYS close the file
	return(1);						//
   default:
	TRC2("session_cache find error: %d",rms_status);
	junk = sys$close(&fab);					// at this point we will ALWAYS close the file
	return(1);
   }
}
#endif
//==============================================================