OpenVMS Source-Code Demos

MOD_AUTH_VMS_RMS

//================================================================================================================================
// title  : mod_auth_vms_rms.c
// author : Neil Rieck (Waterloo, Ontario, Canada) http://neilrieck.net/
// 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 my "mod_auth_vms_ext.c" which was based upon "mod_auth_openvms.c" from HP
//        : 2) I want to use "application authentication" rather than "SYSUAF authentication" (100% of our current >1400 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 including email addresses)
//        : 3) most places using this program will want to employ "domain cookies"
//	  : 4) VMS convention: exit with 1 = good; UNIX convention: exit with 0 = good; Since Apache was written to follow a UNIX
//		convention so most modules here will exit with 0 = good
// ver who when   what
// --- --- ------ ----------------------------------------------------------------------------------------------------------------
// 106 NSR 150615 0. copied from MOD_AUTH_VMS_EXT_105.C
//		  1. began replacing the lib$spawn section with additional RMS support
//     NSR 150616 2. more coding
//     NSR 151116 3. straighten out the confussion between VMS exit codes and UNIX exit codes (I can do these repairs now because
//		     the Itanium cutover has been delayed)
//     NSR 151117 4. the saga continues
//     NSR 151118 5. started inserting the encryption module we use to generate a new cookie (oops, I must have forgot this)
//     NSR 151210 6. 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_rms_module     modules/mod_auth_rms_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
//	AuthVmsRmsUser		On					off means plugin is disabled
//	AuthVmsRmsAuthoritative	On					off means exit with DECLINED rather than HTTP_UNAUTHORIZED
//	AuthVmsRmsCookie	ICSIS_SESSION				optional test (no value means test is disabled)
//	AuthVmsRmsGroupList	ATS,BRT,WST,POW,			optional test (no value means test is disabled)
//	AuthVmsRmsRealmName	"OpenVMS RMS-based Authentication"	optional, displayed during the password dialog
//	require valid-user
//	#---------------------------------------------------------
// 3) Simplified Flow Chart:
//
//	step-1	does this user have the desired cookie?				n	goto step-5
//				y
//		is the cookie in our session database?				y	goto step-7
//				n
//	step-5	has this user supplied a username+password			n	goto step-8
//				y
//		are user+pass found in the authentication database?		n	goto step-8
//				y
//		locate additional information in the profile database
//		create a new "domain" session cookie
//		save cookie in the session database for the next time
//		tell Apache to send the cookie back to the client
//				|
//	step-7	allow user to access the protected directory or document
//		exit with OK (will result in status: 200)
//
//	step-8	exit with 401 (put up a username+password dialog)
//================================================================================================================================
#define RMS_SESSION_CACHE	1					// enable support for RMS-based session cache
//
//	yep, hardcoded file specs for now (sorry about that but this may be more efficient than defining them in .htaccess)
//
char profile1_fs[]	= "csmis$dat:profiledb_authentication_100.dat";	// profile1 (contains usernames + password hashes)
char profile2_fs[]	= "csmis$dat:profiledb_employee_120.dat";	// profile2 (contains full employee profile data)
char profile3_fs[]	= "csmis$dat:profiledb_22_hist.dat";		// profile3 (contains event history)
char sessions_fs[]	= "csmis$dat:icsis_session_5.dat";		// sessions (usernames, cookies, IP addresses, date-time)
char k_key_file$[]	= "csmis$dat:web_key_validation.txt";		// a pass phrase used during encryption
//
//	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 <time.h>						// for time stamps
//
//	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
#include <gen64def.h>						// required for sys$hash_password
#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
char ccyymmdd[10];								// ccyymmdd
char spy_file[99];								//
void build_spy_stamps() {
	//----------------------------------------------------------------------
	struct	timeb	timebuffer;						// for ftime()
	struct	tm	*time_fields;						// for localtime()
	char		millisecs[5];						//
	char		my_date_time[20];					//
	//----------------------------------------------------------------------
	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
	strftime(	ccyymmdd,						//
			sizeof(ccyymmdd),					//
			"%Y%m%d",						//
			time_fields);						//
}
char trc_buf[MAX_STRING_LEN];							//
FILE *trc_file = NULL;								//
void TRC1(char *msg) {								// trace (one param)
    build_spy_stamps();								//
    sprintf(spy_file,"%s%s%s",							//
	"APACHE$COMMON:[000000]aaa_mod_auth_vms_rms_",				//
	ccyymmdd,								//
	".trc");								//
//  trc_file = fopen("APACHE$COMMON:[000000]aaa_mod_auth_vms_rms.trc", "a");	// open the trace file (old)
    trc_file = fopen(spy_file, "a");						// open the trace file (new)
    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    *fRealmName;				// authorization name (realm)		optional (to override default)
}
CTXBLK;

//
//  Function prototypes
//
extern module AP_MODULE_DECLARE_DATA auth_vms_rms_module;
#if RMS_SESSION_CACHE == 1				//
long sessions_lookup(char*, char*, char*);		// sessions (usernames, cookies, IP addresses, date-time)
long sessions_write(char*, char*);			// sessions (usernames, cookies, IP addresses, date-time)
#include "[.fun]WCSM_TEA_ENCRYPT.C"			// need this for cookie encryption (no associated h file)
char ccyymmddhhmmsst[15];				// ccyymmddhhmmsst (stamp for cookie)
char new_data[64]	= "";				//
char new_cookie[64]	= "";				// same size as variable in wcsm_c_encrypt
#endif							//
long profile1_lookup(char*, char*, char*);		// usernames + password hashes
long profile2_lookup(char*, char*, char*);		// full employee profile data
//
//	build cookie date+time stamp
//
void build_cookie_stamp() {
	//----------------------------------------------------------------------
	struct	timeb	timebuffer;						// for ftime()
	struct	tm	*time_fields;						// for localtime()
	char		millisecs[5];						//
	char		my_date_time[20];					//
	//----------------------------------------------------------------------
	ftime( &timebuffer );							// record current system time
	sprintf(millisecs, "%03hu", timebuffer.millitm);			// extract milliseconds as one char
	time_fields = localtime( &timebuffer.time );				// breakout other time fields
	strftime(	my_date_time,						// ccyymmddhhmmss
			sizeof(my_date_time),					//
			"%Y%m%d%H%M%S",						//
			time_fields );						//
	sprintf(	ccyymmddhhmmsst,					// ccyymmddhhmmsst
			"%s%c",							//
			my_date_time,						//
			millisecs[0]);						//
}

//
void *create_auth_vms_rms_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->fRealmName	= 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_rms_cmds[] =
{
    { "AuthVmsRmsAuthoritative",
	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" },
    { "AuthVmsRmsUser",
	ap_set_flag_slot,
	(void *) APR_XtOffsetOf(CTXBLK,fUserEnable),
	OR_AUTHCFG,
	FLAG,
	"OpenVMS user authentication/authorization on/off" },
    { "AuthVmsRmsGroupList",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fGroupList),
	OR_AUTHCFG,
	TAKE1,
	"comma-delimited list of authorized groups (blank=disabled)" },
    { "AuthVmsRmsCookie",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fCookieName),
	OR_AUTHCFG,
	TAKE1,
	"desired cookie (blank=disabled)" },
    { "AuthVmsRmsRealmName",
	ap_set_string_slot,
	(void*)APR_OFFSETOF(CTXBLK,fRealmName),
	OR_AUTHCFG,
	TAKE1,
	"realm name (eg. \"OpenVMS External Authentication\")" },
    { 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) Never do plain text Basic authentication over port 80; use SSL encryption via port 443
//======================================================================================================================
//
//	<<< Local RMS Authentication >>>
//
//	pass cookie and remote host to the authenticator
//	if the authenticator says 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 create a new session cookie which will be passed back to the browser for use on the next
//		pass through here)
//======================================================================================================================
INTERNAL int authenticate_vms_ext (request_rec *r)
{
    unsigned int st;								//
    unsigned int rc;								//
    CTXBLK *sec			= (CTXBLK *) ap_get_module_config (r->per_dir_config, &auth_vms_rms_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 *remote_host	= 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");
    char  cookie_payload[8192]	= "";						// max total limit for browser cookies
    char  username[99]		= "";						// big enough for email addresses
    char  password[33]		= "";						//
    char  errstr[MAX_STRING_LEN]= "";						//
    char  *temp;								//
    char  groupData[32]		= "";						// from sessions or profile2
    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("fRealmName : %s",sec->fRealmName);
    TRC2(" LocalHost : %s",r->server->server_hostname);				// needed to prevent cookie spoofing
    TRC2(" RemoteHost: %s",remote_host);					// needed for domain cookie
    TRC2(" CookieData: %s",cookie_data);					// inbound cookie string (could be huge)
    TRC2(" Authtype  : %s",type);
    TRC2(" Authorizat: %s",auth_line);
    //--------------------------------------------------------------------------
    if (auth_line){						 		// if we have an authorization line
	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;							//
	temp = ap_getword (r->pool, &cursor, ':');				//
	strcpy(username,temp);
	TRC2("extracted user %s",username);					//
	temp = ap_getword (r->pool, &cursor, ':');				//
	strcpy(password,temp);							//
	TRC2("extracted pass %s",password);					//
    }
    for (int i=0; i<strlen(username); i++)					// on VMS we always upcase: username
	username[i] = toupper(username[i]);					//
    //
    // synonyms for the developers
    //
    if (strcasecmp(username,"NEIL")==0)
	sprintf(username,"%s","NS_RIECK");
    if (strcasecmp(username,"DAVE")==0)
	sprintf(username,"%s","DG_MCNEIL");
    if (strcasecmp(username,"STEVE")==0)
	sprintf(username,"%s","SM_KENNEL");
    if (strcasecmp(username,"KARIM")==0)
	sprintf(username,"%s","KA_MACKLAI");

    //--------------------------------------------------------------------------
    //	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
        (strlen(password)==0)		||					// or no password
	(strlen(username)==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)");
	    apr_table_set(r->headers_out, "Cache-Control", "no-cache, no-store");
	    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 (strlen(cookie_payload)>0) {						//
	    junk = sessions_lookup(	(char*)&cookie_payload,
					(char*)&groupData,
					(char*)remote_host);			//
	    TRC2("-i-cache_lookup status: %d",junk);				//
	    if (junk==0) {							// 0 means good (unix style)
		TRC2("-i-session-group-info : %s",groupData);			//
		st = 0;								// simulate UNIX good
		goto skip_user_pass;						// don't hate me because I used "goto' :-)
	    }									//
	}									//
#endif
    //
    //	lookup username + password in our profile database
    //
//  new_cookie[0] = '\0';							//
    junk = profile1_lookup(username, password, (char*)remote_host);		// are user+pass valid? (0=good)
    TRC2("-i-profile1_lookup: %d",junk);					//
    if (junk==0) {								// if no error
	junk = profile2_lookup(	username,
				(char*)&groupData,
				(char*)remote_host);				// lookup to create new cookie etc.
	TRC2("-i-profile2_lookup: %d",junk);					//
    }
    st = junk;									//
    //--------------------------------------------------
    skip_user_pass:;
    TRC2("-i-status: %ld",st);
    //
    switch(st){									//
    case 0:									// good
	if (strlen(new_cookie)==0){
	    TRC1("SETCOOKIE data: BLANK");
	}else{
	    TRC2("SETCOOKIE data: %s",new_cookie);
	    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
		//
		char cookie_hack[128];
		sprintf(cookie_hack,"%s%c%s%s%s%s", sec->fCookieName, '=', new_cookie,"; Domain=",
			r->server->server_hostname,"; path=/; HttpOnly");
		TRC2("SETCOOKIE-HACK: %s",cookie_hack);
		apr_table_add(r->headers_out, "Set-Cookie", cookie_hack);
		apr_table_set(r->headers_out, "Cache-Control", "no-cache, no-store");
	    }
	}
	if (sec->fGroupList) {							// if a group list directive is present
	    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_rms_module =
{
    STANDARD20_MODULE_STUFF,
    create_auth_vms_rms_cntxt,					// per dir config creater
    NULL,							// per dir merger --- default is to override
    NULL,							// server config
    NULL,							// merge server config
    auth_vms_rms_cmds,						// command apr_table_t
    register_hooks						// register hooks
};


//trek1
//===============================================================================================================================
//	support for: profile1
//	this file contains usernames + password hashes (no, we are not dumb enough to store raw passwords)
//
//	open ( default_node$ + k_fs_pro_auth$ ) as #90  ! profile authentication        &
//		,access         modify                                                  &
//		,allow          modify                                                  &
//		,organization   indexed                                                 &
//		,map            profile_auth                                            &
//		,primary   key   d90_username
//===============================================================================================================================
//
//	global variables for file 1
//
struct FAB	fab1;						// file access block (an RMS structure)
struct RAB	rab1;						// record access block (an RMS structure)
struct XABKEY	key10;						// extended access block (for each) key
//
#pragma __member_alignment __save
#pragma __nomember_alignment
struct {
    char		d90_username		[99];		// username (space for user=email)	key #0 (primary)
    GENERIC_64		d90_enc_pwd	;			// hashed password (quadword)
    unsigned short	d90_salt	;			// salt
    unsigned char	d90_enc_type	;			// algorithm
    char		d90_datetime_pwc	[14];		// ccyymmddhhmmss
    char		d90_datetime_login	[14];		// ccyymmddhhmmsst
    char		d90_mixed_case		[ 1];		//
    char		d90_room_to_grow	[98];		//
} user_pw_record;						//
#pragma __member_alignment __restore
//=======================================================================================
//	profile1_lookup
//	exit: 0 = good (unix convention)
//=======================================================================================
long profile1_lookup(char *username, char *password, char *remote_address) {
   int junk;
   int rms_status;
   static GENERIC_64		trial_pw_hash;			//
   struct dsc$descriptor_s	user2;				//
   struct dsc$descriptor_s	pass2;				//
   //
   if (strlen(username)==0){
	TRC1("profile1_lookup: failed in step 01");
	return(-1);						// error
   }
   if (strlen(password)==0){
	TRC1("profile1_lookup: failed in step 02");
	return(-1);						// error
   }
   TRC2("user: %s",username);					//
   //-----------------------------------------------------------
   //	initialize rms (profile1)
   //-----------------------------------------------------------
   fab1	= cc$rms_fab;						// Initialize FAB (be a good citizen)
   rab1	= cc$rms_rab;              				// Initialize RAB (be a good citizen)
   key10 = cc$rms_xabkey;					// Initialize XAB for our key#0 (primary)
   //
   //	now make changes to the just-initialized fab
   //
// fab1.fab$b_bks = 4;						// blocksize:		4
   fab1.fab$b_fac = FAB$M_GET;					// intend to:		GET (read)
   fab1.fab$l_fna = (char*) &profile1_fs;			// file name addr:	filename.ext
   fab1.fab$b_fns = strlen(profile1_fs);			// file name length:	whatever
// fab1.fab$l_fop = FAB$M_CIF;					// file operation:	create if (not found)
   fab1.fab$w_mrs = sizeof(user_pw_record);			// maximum record size:	whatever
   fab1.fab$b_org = FAB$C_IDX;					// organization:	indexed
   fab1.fab$b_rat = FAB$M_CR;					// record-attribute:	<CR>
// fab1.fab$b_rfm = FAB$C_FIX;					// format:		fixed
// fab1.fab$b_rfm = FAB$C_VAR;					// format:		variable
   fab1.fab$b_shr = FAB$M_NIL;					// share:		none
   fab1.fab$l_xab = &key10;					// indicate our primary key
   //
   rab1.rab$l_fab = &fab1;					// now point our rab at our fab
   //
   //	key stuff
   //
   key10.xab$b_ref = 0;						// reference:		this is key #0 (primary key)
   key10.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key10.xab$b_flg = 0;						// key options:		clear all flags (nodup, nochange, etc)
   key10.xab$w_pos0= (char*) &user_pw_record.d90_username -	// start of segment0:	compute offset from start of record
		     (char*) &user_pw_record;			//
   key10.xab$b_siz0= sizeof(user_pw_record.d90_username);	// length of segment0:  whatever
   //-----------------------------------------------------------
   //	now open the file
   //-----------------------------------------------------------
   rms_status = sys$open(&fab1);				// use fab to open the file
   if (rms_status != RMS$_NORMAL){
	TRC2("profile1 open error: %d",rms_status);
	return(-1);
   }

   rms_status = sys$connect(&rab1);				// connect rab to fab
   if (rms_status != RMS$_NORMAL){				// if some sort of error
	TRC2("profile1 connect error: %d",rms_status);
	return(-1);
   }
   //-----------------------------------------------------------
   //	now use the username to do a primary key lookup
   //-----------------------------------------------------------
   strcpy(user_pw_record.d90_username, username);		// copy desired data into this buffer
   for (long i=strlen(username); i<sizeof(user_pw_record.d90_username); i++) {
	user_pw_record.d90_username[i] = ' ';			// pad with spaces
   }
   rab1.rab$b_krf = 0;						// we want to use key#0 (primary)
   rab1.rab$l_kbf = (char *) &user_pw_record.d90_username;	// key buf addr:	whatever
   rab1.rab$b_ksz = sizeof(user_pw_record.d90_username); 	// key buf size:	whatever
   rab1.rab$b_rac = RAB$C_KEY; 					// record access:	lookup-by-key
   rab1.rab$l_rop = RAB$M_NLK;					// record operations:	no-lock
   rab1.rab$l_ubf = (char *) &user_pw_record;			// need for $get (but not $find)
   rab1.rab$w_usz = sizeof(user_pw_record);			// need for $get (but not $find)
   //
   rms_status = sys$get(&rab1);					// get-by-key
   //
   switch(rms_status) {
   case RMS$_NORMAL:
	TRC1("profile1 lookup success");
	break;							// fall thru to hash generate + compare
   case RMS$_RNF:
	TRC1("profile1_lookup miss");
	junk = sys$close(&fab1);				// at this point we will ALWAYS close the file
	return(1);						//
   default:
	TRC2("profile1_lookup error: %d",rms_status);
	junk = sys$close(&fab1);				// at this point we will ALWAYS close the file
	return(1);
   }
   //
   //	If we get here then the record was found. So use the supplied username + password
   //	along with the encryption_type and salt to compute a trial_pw_hash
   //
   junk = sys$close(&fab1);					// at this point we will ALWAYS close the file
#define SEE_RECORD 1
#if     SEE_RECORD==1
   user_pw_record.d90_username[98] = '\0';					// null terminate this mapped string for C
   TRC2("d90_username  : %s",	user_pw_record.d90_username	);
   TRC2("d90_enc_type  : %d",	user_pw_record.d90_enc_type	);
   TRC2("d90_salt      : %d",	user_pw_record.d90_salt		);
   TRC2("d90_mixed_case: %c",	atoi(user_pw_record.d90_mixed_case)	);
   TRC2("d90_enc_pwd   : %lld", user_pw_record.d90_enc_pwd.gen64$q_quadword);	// yep, this line is for real
#endif
// if (d90_mixed_case!='Y')					// we DO NOT support mixed case passwords at this time
	for (int i=0; i<strlen(password); i++)			// upcase the password
	    password[i] = toupper(password[i]);			//
   //
   //	brain damaged moment: be sure to call VMSIFY on password after we upcase it
   //
   VMSIFY(user2,username);					// sys$hash_password requires string descriptors
   TRC2("pass: %s",password);					// sys$hash_password requires string descriptors
   VMSIFY(pass2,password);					//
   junk = sys$hash_password(	&pass2,
				user_pw_record.d90_enc_type,
				user_pw_record.d90_salt,
				&user2,
				&trial_pw_hash);
   TRC2("rc: %d", junk);
   TRC2("d90_enc_pwd : %lld",   trial_pw_hash.gen64$q_quadword);// yep, this line is for real
//spock
   if (user_pw_record.d90_enc_pwd.gen64$q_quadword ==
                    trial_pw_hash.gen64$q_quadword		)
   {
      TRC1("password is valid");
      junk = sessions_write(username,remote_address);		// generate a new COOKIE, etc.
      return(0);						// zero means "no errors" (unix convention)
   }else{
      TRC1("password is not valid");
      return(2);						// two means: vms-e-
   }
}

//trek2
//===============================================================================================================================
//	support for: profile2
//	this file contains user profile data
//
//	open ( default_node$ + k_fs_pro_empl$ )  as #92         ! employee-db                           &
//		,access         modify                                                                  &
//		,allow          modify                                                                  &
//		,organization   indexed                                                                 &
//		,map            my_disk                                                                 &
//		,primary   key  (d91_last_name  ,d91_grp        )       duplicates                      &
//		,alternate key  (d91_last_name  ,d91_pin        )                       changes         &
//		,alternate key  (d91_org_code   ,d91_last_name  )       duplicates      changes         &
//		,alternate key  (d91_pin                        )                       changes         &
//		,alternate key  (d91_grp        ,d91_init       )                       changes         &
//		,alternate key  (d91_cell_phone                 )       duplicates      changes         &
//		,alternate key  (d91_csmis_id                   )       duplicates      changes
//===============================================================================================================================
//
//	global variables for file 2
//
struct FAB	fab2;						// file access block (an RMS structure)
struct RAB	rab2;						// record access block (an RMS structure)
struct XABKEY	key20,						// extended access block (for each) key
		key21,
		key22,
		key23,
		key24,
		key25,
		key26;
//
struct {
    char d91_grp		[ 3];				// csm group name
    char d91_csmis_id		[12];				// csmis id
    char d91_last_name		[20];				// given name
    char d91_first_name		[15];				// surname
    char d91_init		[ 3];				// initials
    char d91_inactive		[ 1];				// inactive flag
    char d91_pin		[ 7];				// pin
    char d91_labour		[ 5];				// labour rate (now: S.O.T.)
    char d91_job_code		[ 3];				// job code from nomas
    char d91_pref_prog		[ 3];				// what program to run
    char d91_alerts		[ 5];				// alert option bits
    char d91_work_phone		[10];				// work phone 10 digits
    char d91_home_phone		[10];				// home phone 10 digits
    char d91_cell_phone		[10];				// cell phone 10 digits
    char d91_pager_num		[10];				// pager      10 digits
    char d91_pager_type		[ 1];				// type of pager a/n/t
    char d91_ardis_num   	[ 8];				// mobile term num (T.O.D.)
    char d91_ardis_type		[ 1];				// hand held term (NOMAS Flag)
    char d91_org_code		[ 8];				// org code
    char d91_title		[ 3];				// csmis title
    char d91_report_centre	[12];				// home office
    char d91_safety		[ 1];				// auto safety check
    char d91_printer		[ 3];				// default printer
    char d91_yada		[ 5];				// application privs (20-bits)
    char d91_language		[ 1];				// default language
    char d91_mail_service	[ 3];				// service of choice
    char d91_mail_id		[39];				// service id
    char d91_system		[20];				// system
    char d91_skill		[ 1];				// skills
    char d91_callout		[ 1];				// callout a.....z
    char d91_trained		[ 8];				// date last trained
    char d91_notes		[42];				// notes
    char d91_region		[ 1]; 				// O/ntario, Q/uebec, A/liant
    char d91_tier		[ 1];				// level: E/C/D
    char d91_reports_to		[ 7];				// PIN of manager
    char d91_bu			[ 3];				// primpary business unit
    char d91_priv		[ 8];				// common app privs 32-bits
    char d91_priv_js		[10];				// common app privs 32-bits
    char d91_vehicle		[10]; 				// tech's vehicle number
    char d91_vehicle_park	[30];				// where normally parked
    char d91_filler		[20];				// room to grow ...
} employee_record;						//

//=======================================================================================
//	profile2_lookup
//	entry:	username 	(used in the primary key lookup)
//		remote_address	(user when creating the new cookie)
//=======================================================================================
long profile2_lookup(char *username, char *groupData, char *remote_address) {
   int junk;
   int rms_status;

   if (strlen(username)==0){					//
	TRC1("profile2_lookup: failed in step 01");		//
	return(-1);						// error
   }
   //-----------------------------------------------------------
   //	initialize rms (profile 2)
   //-----------------------------------------------------------
   fab2	= cc$rms_fab;						// Initialize FAB (be a good citizen)
   rab2	= cc$rms_rab;              				// Initialize RAB (be a good citizen)
   key20 = cc$rms_xabkey;					// Initialize XAB for our key#0 (primary)
   key21 = cc$rms_xabkey;					// Initialize XAB for our key#1 (alternate)
   key22 = cc$rms_xabkey;					// Initialize XAB for our key#2 (alternate)
   key23 = cc$rms_xabkey;					// Initialize XAB for our key#3 (alternate)
   key24 = cc$rms_xabkey;					// Initialize XAB for our key#4 (alternate)
   key25 = cc$rms_xabkey;					// Initialize XAB for our key#5 (alternate)
   key26 = cc$rms_xabkey;					// Initialize XAB for our key#6 (alternate)

   //
   //	now make changes to the just-initialized fab
   //
// fab2.fab$b_bks = 4;						// blocksize:		4
   fab2.fab$b_fac = FAB$M_GET;					// intend to:		GET (read)
   fab2.fab$l_fna = (char*) &profile2_fs;			// file name addr:	filename.ext
   fab2.fab$b_fns = strlen(profile2_fs);			// file name length:	whatever
// fab2.fab$l_fop = FAB$M_CIF;					xx file operation:	create if (not found)
   fab2.fab$w_mrs = sizeof(employee_record);			// maximum record size:	whatever
   fab2.fab$b_org = FAB$C_IDX;					// organization:	indexed
   fab2.fab$b_rat = FAB$M_CR;					// record-attribute:	<CR>
// fab2.fab$b_rfm = FAB$C_FIX;					// format:		fixed
   fab2.fab$b_shr = FAB$M_NIL;					// share:		none
   fab2.fab$l_xab = &key10;					// indicate our primary key
   //
   rab2.rab$l_fab = &fab2;					// now point our rab at our fab
   //
   //	key stuff
   //
   key20.xab$b_ref = 0;						// reference:		this is key #0 (primary key)
   key20.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key20.xab$b_flg = XAB$M_DUP;					// key options:		dups
   key20.xab$w_pos0= (char*) &employee_record.d91_last_name -	// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key20.xab$b_siz0= sizeof(employee_record.d91_last_name);	// length of segment0:  whatever
   key20.xab$w_pos1= (char*) &employee_record.d91_grp -		// start of segmentt1:	whatever
		     (char*) &employee_record;			//
   key20.xab$b_siz1= sizeof(employee_record.d91_grp);		// length of segment1:  whatever
   key20.xab$l_nxt = &key21;					// next xab address
   //
   key21.xab$b_ref = 1;						// reference:		this is key #1 (alternate)
   key21.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key21.xab$b_flg = XAB$M_CHG;					// key options:		changes
   key21.xab$w_pos0= (char*) &employee_record.d91_last_name -	// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key21.xab$b_siz0= sizeof(employee_record.d91_last_name);	// length of segment0:  whatever
   key21.xab$w_pos1= (char*) &employee_record.d91_pin -		// start of segmentt1:	whatever
		     (char*) &employee_record;			//
   key21.xab$b_siz1= sizeof(employee_record.d91_pin);		// length of segment1:  whatever
   key21.xab$l_nxt = &key22;					// next xab address
   //
   key22.xab$b_ref = 2;						// reference:		this is key #2 (alternate)
   key22.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key22.xab$b_flg = XAB$M_DUP | XAB$M_CHG;			// key options:		duplicates + changes
   key22.xab$w_pos0= (char*) &employee_record.d91_org_code -	// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key22.xab$b_siz0= sizeof(employee_record.d91_org_code);	// length of segment0:  whatever
   key22.xab$w_pos1= (char*) &employee_record.d91_last_name -	// start of segmentt1:	whatever
		     (char*) &employee_record;			//
   key22.xab$b_siz1= sizeof(employee_record.d91_last_name);	// length of segment1:  whatever
   key22.xab$l_nxt = &key23;					// next xab address
   //
   key23.xab$b_ref = 3;						// reference:		this is key #3 (alternate)
   key23.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key23.xab$b_flg = XAB$M_CHG;					// key options:		changes
   key23.xab$w_pos0= (char*) &employee_record.d91_pin -		// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key23.xab$b_siz0= sizeof(employee_record.d91_pin);		// length of segment0:  whatever
   key23.xab$l_nxt = &key24;					// next xab address
   //
   key24.xab$b_ref = 4;						// reference:		this is key #4 (alternate)
   key24.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key24.xab$b_flg = XAB$M_CHG;					// key options:		changes
   key24.xab$w_pos0= (char*) &employee_record.d91_grp -		// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key24.xab$b_siz0= sizeof(employee_record.d91_grp);		// length of segment0:  whatever
   key24.xab$w_pos1= (char*) &employee_record.d91_init -	// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key24.xab$b_siz1= sizeof(employee_record.d91_init);		// length of segment0:  whatever
   key24.xab$l_nxt = &key25;					// next xab address
   //
   key25.xab$b_ref = 5;						// reference:		this is key #5 (alternate)
   key25.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key25.xab$b_flg = XAB$M_DUP | XAB$M_CHG;			// key options:		duplicates + changes
   key25.xab$w_pos0= (char*) &employee_record.d91_cell_phone -	// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key25.xab$b_siz0= sizeof(employee_record.d91_cell_phone);	// length of segment0:  whatever
   key25.xab$l_nxt = &key26;					// next xab address
   //
   key26.xab$b_ref = 6;						// reference:		this is key #6 (alternate)
   key26.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key26.xab$b_flg = XAB$M_DUP | XAB$M_CHG;			// key options:		duplicates + changes
   key26.xab$w_pos0= (char*) &employee_record.d91_csmis_id -	// start of segment0:	compute offset from start of record
		     (char*) &employee_record;			//
   key26.xab$b_siz0= sizeof(employee_record.d91_csmis_id);	// length of segment0:  whatever

   //-----------------------------------------------------------
   //	now open the file
   //-----------------------------------------------------------
   rms_status = sys$open(&fab2);				// use fab to open the file
   if (rms_status != RMS$_NORMAL){
	TRC2("profile2 open error: %d",rms_status);
	return(-1);
   }

   rms_status = sys$connect(&rab2);				// connect rab to fab
   if (rms_status != RMS$_NORMAL){				// if some sort of error
	TRC2("profile2 connect error: %d",rms_status);
	return(-1);
   }
   //-----------------------------------------------------------
   //	now use username to do a key-6 lookup
   //-----------------------------------------------------------
   strcpy(employee_record.d91_csmis_id,username);		// copy desired data into this buffer
   for (int i=strlen(username); i<sizeof(employee_record.d91_csmis_id); i++)
	employee_record.d91_csmis_id[i] = ' ';			// pad with spaces
   rab2.rab$b_krf = 6;						// we want to use key#6 (alternate)
   rab2.rab$l_kbf = (char *) &employee_record.d91_csmis_id;	// key buf addr:	whatever
   rab2.rab$b_ksz = sizeof(employee_record.d91_csmis_id); 	// key buf size:	whatever
   rab2.rab$b_rac = RAB$C_KEY; 					// record access:	lookup-by-key
   rab2.rab$l_rop = RAB$M_NLK;					// record operations:	no-lock
   rab2.rab$l_ubf = (char *) &employee_record;			// need for $get (but not $find)
   rab2.rab$w_usz = sizeof(employee_record);			// need for $get (but not $find)
   //
   rms_status = sys$get(&rab2);					// get-by-key
   //
   switch(rms_status) {
   case RMS$_NORMAL:
	TRC1("profile2 lookup success");
	//
	//	in our system, business unit is used in group restriction
	//
	strncpy(groupData, employee_record.d91_bu, sizeof(employee_record.d91_bu));
	groupData[ sizeof(employee_record.d91_bu) ] = '\0';	//
	TRC2("-i-session bu : %s", groupData);			//
	//
	junk = sys$close(&fab2);				// at this point we will ALWAYS close the file
	return(0);						// zero means "no errors" (unix convention)
   case RMS$_RNF:
	TRC1("profile2_lookup miss");
	junk = sys$close(&fab2);				// at this point we will ALWAYS close the file
	return(1);						//
   default:
	TRC2("profile2_lookup error: %d",rms_status);
	junk = sys$close(&fab2);				// at this point we will ALWAYS close the file
	return(1);
   }
}

#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"
//===============================================================================================================================
//
//	global variables for file 0
//
struct FAB	fab0;						// file access block (an RMS structure)
struct RAB	rab0;						// record access block (an RMS structure)
struct XABKEY	key00,						// extended access block (for each) key
		key01,						// first alternate key
		key02;						// second alternate key
//
//	the is the record layout for our sessions database
//
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 d91_priv			(cached)
    char icsis_session_title	[ 3];				// copied from d91_title		(cached)
    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];				// copied from d91_grp			(cached)
    char icsis_session_ini	[ 3];				// copied from d91_ini			(cached)
    char icsis_session_org	[ 8];				// copied from d91_org_code		(cached)
    char icsis_session_name	[35];				// copied from d91_first_name +" "+ d91_last_name
    char icsis_session_script	[50];				//
    char icsis_session_region	[ 1];				// copied from d91_region		(cached)
    char icsis_session_language	[ 1];				// copied from d91_language		(cached)
    char icsis_session_jobcode	[ 3];				// copied from schedule at login	(cached)
    char icsis_session_bu	[ 3];				// copied from d91_bu			(cached)
    char icsis_session_room	[13];				// room too grow
} session_record;

//=======================================================================================
//	sessions lookup (cookie cache)
//	it is assumed that another routine elsewhere is clearing out stale records
//=======================================================================================
long sessions_lookup(char *cookieData, char *groupData, char *remote_address) {
   int junk;
   int rms_status;
   char temp1[32];

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

   rms_status = sys$connect(&rab0);				// connect rab to fab
   if (rms_status != RMS$_NORMAL){				// if some sort of error
	TRC2("sessions_lookup connect error: %d",rms_status);
	return(-1);
   }
   //-----------------------------------------------------------
   //	now use the cookie to do a primary key lookup
   //-----------------------------------------------------------
   rab0.rab$b_krf = 0;						// we want to use key#0 (primary)
   rab0.rab$l_kbf = cookieData;					// key buf addr:	whatever
   rab0.rab$b_ksz = sizeof(session_record.icsis_session_id); 	// key buf size:	whatever
   rab0.rab$b_rac = RAB$C_KEY; 					// record access:	lookup-by-key
   rab0.rab$l_rop = RAB$M_NLK;					// record operations:	no-lock
   rab0.rab$l_ubf = (char *) &session_record;			// need for $get (but not $find)
   rab0.rab$w_usz = sizeof(session_record);			// need for $get (but not $find)
   //
   rms_status = sys$get(&rab0);					// get-by-key
   //
   switch(rms_status) {
   case RMS$_NORMAL:
	TRC1("sessions_lookup success (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(&fab0);				// at this point we will ALWAYS close the file
	return(0);						// zero means "no errors"; OKAY TO SERVE REQUEST
   case RMS$_RNF:
	TRC1("sessions_lookup failed");
	junk = sys$close(&fab0);				// at this point we will ALWAYS close the file
	return(1);						//
   default:
	TRC2("sessions_lookup error: %d",rms_status);
	junk = sys$close(&fab0);				// at this point we will ALWAYS close the file
	return(1);
   }
}

//=======================================================================================
//	sessions write (create a cookie then save it in the sessions database
//=======================================================================================
long sessions_write(char *uname, char *remote_address) {
   char *p1,*p2, *p3;
   int junk;
   int rms_status;
   char temp1[99];						// large enough for various things
   //
   if (strlen(uname)==0) {
	TRC1("sessions_write: failed in step 01");
	return(-1);						// error
   }
   //-----------------------------------------------------------
   //	initialize rms
   //-----------------------------------------------------------
   fab0	= cc$rms_fab;						// Initialize FAB (be a good citizen)
   rab0	= cc$rms_rab;              				// Initialize RAB (be a good citizen)
   key00 = cc$rms_xabkey;					// Initialize XAB for our key#0 (primary)
   key01 = cc$rms_xabkey;					// Initialize XAB for our key#1 (alternate)
   key02 = cc$rms_xabkey;					// Initialize XAB for our key#2 (alternate)
   //
   //	now make changes to the just-initialized fab
   //
// fab0.fab$b_bks = 4;						// blocksize:		4
   fab0.fab$b_fac = FAB$M_PUT	;				// intend to:		PUT (write)
   fab0.fab$l_fna = (char*) &sessions_fs;			// file name addr:	filename.ext
   fab0.fab$b_fns = strlen(sessions_fs);			// file name length:	whatever
// fab0.fab$l_fop = FAB$M_CIF;					xx file operation:	create if (not found)
   fab0.fab$w_mrs = sizeof(session_record);			// maximum record size:	whatever
   fab0.fab$b_org = FAB$C_IDX;					// organization:	indexed
   fab0.fab$b_rat = FAB$M_CR;					// record-attribute:	<CR>
// fab0.fab$b_rfm = FAB$C_FIX;					// format:		fixed
   fab0.fab$b_shr = FAB$M_NIL;					// share:		none
   fab0.fab$l_xab = &key00;					// indicate our primary key
   //
   rab0.rab$l_fab = &fab0;					// now point our rab at our fab
   //
   //	key stuff
   //
   key00.xab$b_ref = 0;						// reference:		this is key #0 (primary key)
   key00.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key00.xab$b_flg = 0;						// key options:		clear all flags (nodup, nochange, etc)
   key00.xab$w_pos0= (char*) &session_record.icsis_session_id -	// start of segment0:	compute offset from start of record
		     (char*) &session_record;			//
   key00.xab$b_siz0= sizeof(session_record.icsis_session_id);	// length of segment0:  whatever
   key00.xab$l_nxt = &key01;					// next xab address:
   //
   //	now make changes to the just initialized xab (this one is chained to the previous xab)
   //
   key01.xab$b_ref = 1;						// reference:		this is key #1
   key01.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key01.xab$b_flg = XAB$M_DUP | XAB$M_CHG; 			// key options:		DUP + CHANGE
   key01.xab$w_pos0 = (char*) &session_record.icsis_session_pin- // start of segment0:	offset from start of record
                     (char*) &session_record;			//
   key01.xab$b_siz0= sizeof(session_record.icsis_session_pin);	// length of segment0:	whatever
   key01.xab$l_nxt = &key02;					// next xab address:
   //
   //	now make changes to the just initialized xab (this one is chained to the previous xab)
   //
   key02.xab$b_ref = 2;						// reference:		this is key #2
   key02.xab$b_dtp = XAB$C_STG;					// key data type:	string
   key02.xab$b_flg = XAB$M_DUP | XAB$M_CHG; 			// key options:		DUP + CHANGE
   key02.xab$w_pos0 =						// start of segment0:	offset from start of record
	(char*) &session_record.icsis_session_timestamp	- 	//
	(char*) &session_record;				//
   key02.xab$b_siz0=						// length of segment0:  whatever
	sizeof(session_record.icsis_session_timestamp);
   //-----------------------------------------------------------
   //	now open the file
   //-----------------------------------------------------------
   rms_status = sys$open(&fab0);				// use fab to open the file
   if (rms_status != RMS$_NORMAL){
	TRC2("sessions_write open error: %d",rms_status);
	return(-1);
   }

   rms_status = sys$connect(&rab0);				// connect rab to fab
   if (rms_status != RMS$_NORMAL){				// if some sort of error
	TRC2("sessions_write connect error: %d",rms_status);
	return(-1);
   }
   //-----------------------------------------------------------
   //	now prepare to write a record
   //-----------------------------------------------------------
   //
   //	setup data for presentation to TEA encrypt (must be 16 bytes for now)
   //   note:	the encoded data string is currently 16 bytes long; the first bytes consist of the username
   //		whilst the remainder consist of the last chunk of a 15-byte time stamp
   //
   build_cookie_stamp();					// populate ccyymmddhhmmsst
   junk = strlen(uname);					// this could be any length up to 15 bytes
   strcpy(temp1,ccyymmddhhmmsst+junk-1);			// middle to the end
   sprintf(new_data,"%s%s",uname,temp1);			//
   TRC2("hack-1 %s",uname);
   TRC2("hack-2 %s",ccyymmddhhmmsst);
   TRC2("hack-3 %s",temp1);
   TRC2("hack-4 %s",new_data);
   junk = wcsm_c_encrypt(new_data,new_cookie,k_key_file$);	//
   if (junk!=1){
	TRC2("wcsm_c_encrypt status: %d",junk);
	TRC2("wcsm_c_encrypt msg: %s",new_cookie);
   }else{
	TRC2("new_cookie: %s",new_cookie);
   }
   //
   //	now data-fill the record
   //
   for (int i=0; i<sizeof(session_record); i++)			//
	session_record.icsis_session_id[i]=' ';			// blank this record with spaces (BASIC-style map)
   memcpy(session_record.icsis_session_id	,new_cookie			,sizeof(new_cookie)			);
   memcpy(session_record.icsis_session_pin	,employee_record.d91_pin	,sizeof(employee_record.d91_pin)	);
   memcpy(session_record.icsis_session_priv	,employee_record.d91_priv	,sizeof(employee_record.d91_priv)	);
   memcpy(session_record.icsis_session_title	,employee_record.d91_title	,sizeof(employee_record.d91_title)	);
   memcpy(session_record.icsis_session_ip	,remote_address			,strlen(remote_address)			);
   memcpy(session_record.icsis_session_timestamp,ccyymmddhhmmsst		,sizeof(ccyymmddhhmmsst)		);
   memcpy(session_record.icsis_session_grp	,employee_record.d91_grp	,sizeof(employee_record.d91_grp)	);
   memcpy(session_record.icsis_session_ini	,employee_record.d91_init	,sizeof(employee_record.d91_init)	);
   memcpy(session_record.icsis_session_org	,employee_record.d91_org_code	,sizeof(employee_record.d91_org_code)	);
   //
   // now copy space-padded FIRST NAME and space-padded LAST NAME
   //
   temp1[0] = '\0';						// init
   p1 = (char*) &temp1;						// init
   p2 = (char*) &employee_record.d91_first_name;		// init
   p3 = strstr(employee_record.d91_first_name," ");		// locate the first <space>
   if (p3!=NULL){						// if found
	p3--;
	while (p1<p3) {
	    *p1++ = *p2++;
	}
   }
   *p1++ = ' ';
   p2 = (char*) &employee_record.d91_last_name;			//
   p3 = strstr(employee_record.d91_last_name," ");		//
   if (p3!=NULL){						// if found
	p3--;
	while (p1<p3) {
	    *p1++ = *p2++;
	}
   }
   *p1++ = '\0';
   memcpy(session_record.icsis_session_name	,temp1				,strlen(temp1)				);
   memcpy(session_record.icsis_session_script	,"mod_auth_vms_rms.c"		,18					);
   memcpy(session_record.icsis_session_region	,employee_record.d91_region	,sizeof(employee_record.d91_region)	);
   memcpy(session_record.icsis_session_language	,employee_record.d91_language	,sizeof(employee_record.d91_language)	);
   memcpy(session_record.icsis_session_jobcode	,employee_record.d91_job_code	,sizeof(employee_record.d91_job_code)	);
   memcpy(session_record.icsis_session_bu	,employee_record.d91_bu		,sizeof(employee_record.d91_bu)		);
// memcpy(session_record.icsis_session_room	room to grow
   //
   rab0.rab$l_rbf = (char *) &session_record;
   rab0.rab$w_rsz = sizeof(session_record);
   //
   rms_status = sys$put(&rab0);					// get-by-key
   //
   switch(rms_status) {
   case RMS$_NORMAL:						// no issues
   case RMS$_OK_DUP:						// one key was a duplicate but this is not an error
	TRC1("sessions_write success (cookie stored)");
	junk = sys$close(&fab0);				// at this point we will ALWAYS close the file
	return(0);						// zero means "no errors"; OKAY TO SERVE REQUEST
   default:
	TRC2("session_write error: %d",rms_status);
	junk = sys$close(&fab0);				// at this point we will ALWAYS close the file
	return(1);
   }
}
#endif