/*
 * Sample authenticator with extensions for access control lists, MD5, and
 * user-defined password checking.
 *
 * The setup file simply defines a list of usernames/hosts and passwords.
 * If a password of '*' is specified, the corresponding username is checked
 * against the SYSUAF file.  If the username specification includes
 * a host address (e.g. SMITH@128.146.235.*), the password applies to only
 * the matching hosts.  A null username with a host address will accept
 * any connect from that host with no password check.
 *
 * Summary:
 *  <tag> tag-args...		   -> Special configuration info:
 *					<realm> string
 *  @host    *	   	           -> Host address must match
 *  username[@host] password       -> must match both
 *  *[@host]        password       -> any username, must match password
 *  username[@host] *              -> must match username, password from SYSUAF
 *  *[@host]        *              -> any username, password from SYSUAF
 *
 * If host restrictions would deny access regardless of username/password
 * given, the authentication fails with a 403 status so client doesn't waste
 * its time prompting user for username/password.
 *
 * Environment variables:
 *   EXT_ALLOW_WD_MISSING	If defined, allow access to directories
 *				that are missing a 'with-document' style
 *				(name begins with period) protection file.
 *
 *   EXT_EXTENSION_IMAGE	If defined, the value is a logical name that
 *				translates to the shareable image to load 
 *				dynamically.  This image contains a
 *				check_password routine to be used if the
 *				setup file specifies a domain or the target
 *				username has the extauth bit set.
 *
 *				If this variable not defined, or the extension
 *				declines to handle extauth requests, then the
 *				UAI$M_EXTAUTH flag is ignored.
 *
 *   EXT_CLIENT_CERT_DEBUG	Display trace info if defined.
 *
 * MACRO symbols for compile-time constants:
 *    DNS_ENABLE	Enable remote address translation via external
 *		  	lookup_host() function (authhost.c).
 *    UAF_HTBL_SIZE	Size of hash table for UAF cache must be power of 2.
 *			Default is 256.
 *    UAF_CACHE_LIMIT	Absolute limit of number of cache blocks allocated.
 *			Default = 10000
 *    UAF_CACHE_TIMEOUT	Timeout on uaf cache entries, specified as delta time.
 *			default = "0 01:00:00" (1 hour).
 *
 * Author:      David Jones
 * Date:        29-MAY-1994
 * Revised:      5-FEB-1995	Fixes for DECC.  Support host address.
 * Revised:	 8-FEB-1995	Add caching of UAI information.
 *				Also check flags for disuser's account.
 * Revised:	13-FEB-1995	Bugfix from CEL, make context var. static.
 * Revised:	23-FEB-1995	Add auth_callback prototype for notosimple.
 * Revised:	7-SEP-1995	Report SYSPRV status in startup message.
 * Revised:	26-NOV-1996	Added Dave Smith's ACL enhancements.
 * Revised:	13-FEB-1997	Incorporated fixes to MD5 code from
 *				Martin Winter (martin.winter@dm.krinfo.ch).
 *				Cleaned up structure of nososimple().
 *				Modified setup to preserve username/password
 *				case when <digest> tag present in file.
 * Revised:	16-AUG-1997	Modified to call $SCAN_INTRUSION service
 *				if compiled on version 6.1 or higher and
 *				image installed with SECURITY privilege.
 * Revised:	12-SEP-1997	Fix bug in 'with-document' processing, add
 *				MD5_ALLOW_WD_MISSING configuration option.
 * Revised:	26-SEP-1997	Alloc optional port restriction on host mask.
 * Revised:	27-MAY-2000	Convert to use of authlib.
 * Revised:	8-JUN-2000	Add ext_extension_image.
 * Revised:	29-SEP-2000	Add missing local_port to check_password call.
 * Revised:	6-OCT-2000	ensure realm initialized.
 * Revised:	9-OCT-2001	Use file cache, bump version to 1.3
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <descrip.h>
#include "ctype_np.h"
#include <uaidef.h>
#include <prvdef.h>
#include "authlib.h"
#include "authacl.h"
#include "authutil.h"
#include "file_access.h"
#include "file_cache.h"
#include "md5.h"		/* Eric Young's MD5 digester */
typedef struct { int l; char *s; } string;

#define MAXLINE 1024

static struct AUTHINFO {
   struct AUTHINFO *next;
   char *domain;		/* computer/domain for user (NULL if none) */
   char *user;			/* Username */
   char *host;			/* Host restrictions in username */
   char *pass;			/* password */
   char lbuf[MAXLINE+64];	/* string storage (1 line + alignment pad.) */
   int port;			/* if non-zero, port restriction */
} *uhead, *utail, *ufree;
static struct {
    char setup_file[768];		/* Name of setup file */
    char realm[256];			/* realm (signature) for file */
    char digest[256];			/* Parameters for digest authenticate */
    char acl[ACLMAXBIN];		/* binary ACL for file */
    int  acllen;			/* ACL length */
    int  wd_missing;			/* Protect file with document missing? */
    char **ext_arg;			/* argument list for external auth. */
    int load_cert_info;			/* if true, decode CERT: header */
} suf;					/* current [s]et[u]p [f]ile */

/*
 * Provide default sizes for pwd cache.
 */
#ifndef UAF_HTBL_SIZE
#define UAF_HTBL_SIZE 256
#endif
#ifndef UAF_CACHE_LIMIT
#define UAF_CACHE_LIMIT 10000
#endif
#ifndef UAF_CACHE_TIMEOUT
#define UAF_CACHE_TIMEOUT "0 01:00:00"
#endif

static long priv_mask[2];
static long prev_privs[2];
static long *uai_context;
static int allow_wd_missing;	/* if true, missing with-document prot file,
				 * permits access. */
int ext_client_cert_debug;
/*
 * Structures needed to handle dynamically loaded extensions.
 */
static int ext_flags;
static int ext_loaded;
static int ext_version;
static int ext_tlist_size;
static char **ext_tlist;
static struct tag_def {
    struct tag_def *next;
    char *name;				/* saved upcased */
    int first_arg;			/* index into ext_tag_arg */
    int last_arg;
    int cur_arg;			/* points to next free spot */
} *ext_tag_def;
char **ext_tag_arg;

/* prototypes for forward references */
int hash_realm(char *s);
int sys$setprv(), sys$getuai(), sys$hash_password();
int http_parse_elements ( int limit, char *line, string *elem );
static int expand_uaf_free ( int alloc_size );
auth_callback notsosimple;		/* typedef auth_callback in authlib.h*/
static int check_digest ( char *fields[8], char *method, char *uri, 
	unsigned char *remote_address, char *correct_password, 
	char **response, char *log_message );
/*
 * Main routine.
 */
int main ( int argc, char **argv )
{
    int status, i;
    int cache_size, cache_maxitem, cache_refresh;
    char log_message[1240];
    char ext_message[120];
    char *envval, *ext_auth_image;
    /*
     * turn off any initial SYSPRV
     */

    priv_mask[0] = PRV$M_SYSPRV; priv_mask[1] = 0;
    (void) sys$setprv ( 0, priv_mask, 0, prev_privs );
    /*
     * Initialize data structures needed to perform authentication.
     */
    uhead = utail = ufree = NULL;
    suf.setup_file[0] = '\0';
    suf.digest[0] = '\0';
    status = auth_init_vms_pwd ( 
	"EXT_AUTHENTICATOR", 		/* user for intrusion reporting */
	prev_privs,			/* installed privileges */
	UAF_HTBL_SIZE,			/* cache hash table size */
	UAF_CACHE_LIMIT,	
	UAF_CACHE_TIMEOUT );
    if ( 0 == (status&1) ) exit ( status );
    allow_wd_missing = 0;
    envval = getenv ( "EXT_ALLOW_WD_MISSING" );
    if ( envval ) allow_wd_missing = atoi ( envval );
    ext_client_cert_debug = 0;
    envval = getenv ( "EXT_CLIENT_CERT_DEBUG" );
    if ( envval ) ext_client_cert_debug = atoi ( envval );
    /*
     * Initialize the file cache for saving setup file info.
     */
    tf_initialize ( (char *) 0 );

    cache_refresh = 300;		/* 5 minutes */
    envval = getenv ( "EXT_CACHE_REFRESH" );
    if ( envval ) cache_refresh = atoi ( envval );
    cache_size = 10000000;		/* 1 megabyte */
    envval = getenv ( "EXT_CACHE_SIZE" );
    if ( envval ) cache_size = atoi ( envval );
    cache_maxitem = 10000;		/* Max per item */
    envval = getenv ( "EXT_CACHE_MAXITEM" );
    if ( envval ) cache_maxitem = atoi ( envval );

    status = tfc_init ( cache_refresh, cache_size, cache_maxitem );
    /*
     * Initialize extension authenticator.
     */
    ext_loaded = 0;
    ext_auth_image = getenv ( "EXT_EXTENSION_IMAGE" );
    ext_tag_def = (struct tag_def *) 0;
    strcpy ( ext_message, "" );
    if ( ext_auth_image ) {
	/*
	 * Attempt to load shareable image extension to authenticator.
	 */
	int i, j;
	char *name;
	struct tag_def *def;
	i = strlen(ext_auth_image);
	strcpy ( ext_message, "\r\n  extension " );
	if ( i+42 >= sizeof(ext_message) ) i = sizeof(ext_message)-42;
	strncpy ( &ext_message[14], ext_auth_image, i );
	ext_message[i+14] = '\0';

	status = auth_load_ext_handler ( ext_auth_image, &ext_flags,
		&ext_version, &ext_tlist, &ext_tlist_size );
	if ( (status&1) == 1 ) {
	    int tag_count;
	    /*
	     * Allocate an array to hold the tag list arguments
	     */
	    ext_tag_arg = (char **) malloc ( (ext_tlist_size+1) *
		sizeof(char *) );
	    /*
	     * Compute argument list indexes for the tags.
	     */
	    tag_count = 0;
	    for ( i = 0; i < ext_tlist_size; i++ ) {
		name = malloc ( strlen(ext_tlist[i]) + 1 );
		for ( j = 0; ext_tlist[i][j]; j++ ) {
		    name[j] = toupper ( ext_tlist[i][j] );
		}
		name[j] = '\0';
		if ( ext_tag_def ) {
		    if ( strcmp ( name, ext_tag_def->name ) == 0 ) {
			/*
			 * Name matches previous.
			 */
			ext_tag_def->last_arg = i;
			free ( name );
			continue;
		    }
		}
		def = (struct tag_def *) malloc (sizeof(struct tag_def) );
		tag_count++;
		def->next = ext_tag_def;
		def->name = name;
		def->first_arg = i;
		def->last_arg = i;
		def->cur_arg = i;
		ext_tag_def = def;
	    }
	    ext_loaded = 1;
	    sprintf ( &ext_message[strlen(ext_message)], " loaded (%d tag%s)",
		tag_count, (tag_count==1) ? "" : "s" );
	} else {
	    sprintf ( &ext_message[strlen(ext_message)], " failed: %d",
		status );
	}
#ifdef DEBUG
	printf("Status of ext. auth. image (%s) load: %d, tags: %d\n", 
	ext_auth_image, status, ext_tlist_size );
#endif
    }
    /*
     * Process server requests.  The auth_server routine connects to the
     * the HTTP server that created us and effecitively makes the authenticate
     * function (notsosimple()) a remote procedure call for the server.
     * The third argument is text to be placed in the server's log file.
     */
    sprintf ( log_message,
	"EXT Authenticator, V 1.4, initialized successfully, %s SYSPRV%s%s",
	(PRV$M_SYSPRV&prev_privs[0]) ? "has" : "does not have",
	ext_message, "\r\n  MD5 digest code by Eric Young" );

    status = auth_server ( argc, argv, log_message, 1, notsosimple );
    /*
     * Getting to this point means an error occurred since auth_server
     * loops to continuously get authentication requests.  Most likely the
     * the HTTP server exitted so we should run down as well.  Perform any
     * necessary cleanup and exit.
     */
    exit ( status );
}
/************************************************************************/
/*
 * Read protection setup file and store in global variable.  Store in parsed
 * form so reuse is more efficient.
 */
static int load_setup ( char *setup_file, char *log_message, char *ident )
{
     int i, j, length, count, preserve_case, bpos, last_slash, tfc_status;
     struct AUTHINFO *u;
     void *fp;
     char line[MAXLINE];
     char *buf;
     struct tag_def *def;
     string token[10];
     char aclent[256];
     int setup_wd;  /* flag indicating setup file is with document */
     char wd_file[768]; /* path of 'with document' setup file */
     char *input_file; 
     struct tfc_context tfc;
#define ALIGN_OFFSET(x) ((x+(sizeof(int)-1))&(~(sizeof(int)-1)))
     /*
      * Construct actual filename to be opened.  Setup-file names that
      * begin with '.' are 'with document' setup files.
      */
    input_file = setup_file;

    setup_wd = FALSE;
    preserve_case = 0;
    if ( *setup_file == '.' )	{	/* file with document? */
	/*
	 * Construct setup file name using directory of current ident.
	 */
	setup_wd = TRUE;
	for ( i = 0; i < sizeof ( wd_file ); i++ ) {
	    wd_file[i] = ident[i];
	    if ( !wd_file[i] || (wd_file[i] == '*') ) break;
	}
	for (i = i-1;  (i > 0) && (wd_file[i] != '/');  i--);
	if ( (i + strlen(setup_file) + 1) < sizeof(wd_file ) ) {
	    strcpy (&wd_file[i+1], setup_file);
	} else {
	    strcpy ( wd_file, "0:file.too.long" );
	}
	input_file = wd_file;
    }
     /*
      * Attempt to open cached copy of file (type=1 (text)).
      */
     tfc_status = tfc_cached_open ( 1, input_file, &fp, line, &tfc );
     if ( tfc_status == 0 ) {
	sprintf(log_message,"unable to open setup file %s: %s",setup_file,
		line );
	if ( setup_wd && allow_wd_missing ) {
	    /*
	     * Make a dummy setup-file parse result if 'with document' setup 
	     * files are allowed to be missing.
	     */
	    suf.wd_missing = TRUE;
	    suf.ext_arg = (char **) 0;
	    suf.realm[0] = '\0';
	    suf.acllen = 0;
	    suf.load_cert_info = 0;
	    if ( utail != NULL ) utail->next = ufree;
	    ufree = uhead;
	    uhead = utail = NULL;
	    strcpy (suf.setup_file, input_file);	/* save failure */
	    return 1;
	} else {
	    return 0;
	}
     }
     /*
      * Check if setup already loaded, otherwise try to open it.
      * Cache can be disabled by defining DEBUGAUTH when debugging 
      * authenticator or .PROT files.
      */
#ifndef DEBUGAUTH
     if ( (tfc_status ==1) && (0 == strcmp ( setup_file, suf.setup_file )) ) {
	tfc_rundown_context ( &tfc, 0 );
	if ( fp ) tf_close ( fp );
	return 1;
     }
#endif
     /*
      * Free old UAF info blocks and init suf structure.
      */
     if ( utail != NULL ) {
	utail->next = ufree;
     }
     ufree = uhead;
     uhead = utail = NULL;
     suf.setup_file[0] = '\0';		/* mark as invalid */
     suf.digest[0] = '\0';
     suf.realm[0] = '\0';
     suf.acllen = 0;
     suf.wd_missing = 0;
     suf.load_cert_info = 0;

     if ( ext_loaded ) {		
	/* 
	 * reset the tag_arg list
	 */
	suf.ext_arg = ext_tag_arg;
	for ( def = ext_tag_def; def; def = def->next ) {
	    for ( j = def->first_arg; j <= def->cur_arg; j++ ) {
		free ( suf.ext_arg[j] );
	    }
	    def->cur_arg = def->first_arg;
	}
	memset ( suf.ext_arg, 0, ext_tlist_size * sizeof(char *) );
     } else {
	suf.ext_arg = (char **) 0;
     }
     /*
      * Read and parse suffix file, set file access to use just LF as
      * line delimiter.
      */
     if ( tfc_status == 2 ) tf_set_crlf_newline ( fp, 0 );
     for ( ; ; ) {
	if ( tfc_status == 1 ) {
	    /* Take next line from cache */
	    char *rec;
	    if ( 1 != tfc_get ( &tfc, (void **) &rec, &length ) ) break;
	    strncpy ( line, rec, length );
	    line[length] = '\0';
	} else {
	    /*
	     * New entry, read line and add to cache.
	     */
	    length = tf_getline ( fp, line, sizeof(line), 1 );
	    if ( length < 0 ) {
		tfc_rundown_context ( &tfc, 1 );
		if ( fp ) tf_close ( fp );
		sprintf ( log_message, "Error reading %s\n", setup_file );
		return 0;
	    } else if ( length == 0 ) {
		break;
	    }
	    if ( line[length-1] == '\n' ) length--;
	    line[length] = '\0';
	    for (j=0; (j<length); j++) {
                if ((line[j] == '#') || (line[j] == '\n')) {
                    line[j] = '\0';
                    break;
                }
            }
	    length = j;
	    tfc_put ( &tfc, line, length );
	}
	/*
	 * Break line into tokens and add to setup file structure.
	 */
        if (length > 0) {
            count = http_parse_elements(9,line,token);
	    if ( count == 1 ) if ( token[0].s[0] == '@' ) count = 2;
            if ( count > 1 ) {
		/*
		 * Check for special control tags.
		 */
		if ( (token[0].s[0] == '<') && 
			(token[0].s[token[0].l-1] == '>') ) {
		    for ( j = 0; token[0].s[j]; j++ ) 
			token[0].s[j] = UPPER_CASE(token[0].s[j]);
		    if ( strcmp ( token[0].s, "<REALM>" ) == 0 ) {
			/* Concatenate rest of tokens as realm name */
			for ( j=0, i = 1; i < count; i++ ) {
			    if ( j ) suf.realm[j++] = ' ';
			    if ( (token[i].l + j) >= sizeof(suf.realm) ) break;
			    strcpy ( &suf.realm[j], token[i].s );
			    j = j + token[i].l;
			}
			suf.realm[j] = '\0';
		    }
		    else if ( strcmp ( token[0].s, "<ACL>" ) == 0 ) {
			/* Concatenate rest of tokens as ACE */
			for ( j=0, i = 1; i < count; i++ ) {
			    if ( j ) aclent[j++] = ' ';
			    if ( (token[i].l + j) >= sizeof(aclent) ) break;
			    strcpy ( &aclent[j], token[i].s );
			    j = j + token[i].l;
			}
			aclent[j] = '\0';
			if ( !authacl_parse_acl ( aclent, 
						  suf.acl, 
						  &suf.acllen,
						  log_message ) ) {
			    if ( fp ) tf_close ( fp );
			    tfc_rundown_context ( &tfc, 1 );
			    return 0;
			}
		    }
		    else if ( strcmp ( token[0].s, "<APPACL>" ) == 0 ) {
			/* Argument is formatted as application ACE. */
			if ( !authacl_parse_appacl ( token[1].s, suf.acl,
				&suf.acllen, log_message ) ) {
			    if ( fp ) tf_close ( fp );
			    tfc_rundown_context ( &tfc, 1 );
			    return 0;
			}
		    }
		    else if ( strcmp ( token[0].s, "<CERTMAP>" ) == 0 ) {
			/* Get list of identifiers to represent user */
		  	suf.load_cert_info = 1;
		    }
		    else if ( strcmp ( token[0].s, "<DIGEST>" ) == 0 ) {
			/* Concatenate rest of tokens as digest arguments */
			for ( j=0, i = 1; i < count; i++ ) {
			    if ( j ) suf.digest[j++] = ' ';
			    if ((token[i].l + j) >= sizeof(suf.digest)) break;
			    strcpy ( &suf.digest[j], token[i].s );
			    j = j + token[i].l;
			}
			suf.digest[j] = '\0';
			/* all following names are case sensitive */
			preserve_case = 1;	
		    } else if ( strcmp ( token[0].s, "<CACHE>" ) == 0 ) {
			/*
			 * Set the cache expiration time for this file's
			 * cache entry.  A time of 0 means each access checks
			 * the file's modification date against cached entry.
			 */
			if ( tfc_status == 2 ) tfc_update_expiration (
				&tfc,  atoi(token[1].s) );
		    } else if ( ext_loaded ) {
			/*
			 * Search tags.
			 */
			for ( def = ext_tag_def; def; def = def->next ) {
			    if ( strcmp(token[0].s, def->name) == 0 ) {
				/*
				 * Matched tag, take next slot.
				 */
				if ( def->cur_arg <= def->last_arg ) {
				    suf.ext_arg[def->cur_arg] = malloc ( 
					token[1].l + 1 );
				    strcpy ( suf.ext_arg[def->cur_arg],
					token[1].s );
				    def->cur_arg++;
				} else { /* no more slots */
				}
				break;
			    }
			}
		    }
		    continue;
		}
		/*
		 * Allocate block and set 
		 */
		if ( ufree == NULL ) {
                    u = (struct AUTHINFO *)malloc(sizeof(struct AUTHINFO));
		    if ( u == NULL ) {
			if ( fp ) tf_close ( fp );
		        tfc_rundown_context ( &tfc, 1 );
			return 0;
		    };
 		} else {
		    u = ufree;			/* Get from lookaside list */
		    ufree = u->next;
  		}
		/*
		 * Parse token[0] into domain, username, and host:
		 *    [\\domain\]username[@host]
		 */
		buf = u->lbuf;
		bpos = 0;
		u->domain = NULL;
		u->user = NULL;
		u->host = NULL;
		if ( (token[0].l > 2) && (token[0].s[0] == '\\') &&
			(token[0].s[1] == '\\') ) {
		    /*
		     * Domain present, locate the last backslash.
		     * And make domain name everything before it.
		     */
		    last_slash = 1;
		    u->domain = &buf[bpos];
		    for ( i = 2; i < token[0].l; i++ ) {
			if ( token[0].s[i] == '\\' ) last_slash = i;
			if ( token[0].s[i] == '@' ) break;
		    }
		    if ( last_slash > 1 ) {
			memcpy ( &buf[bpos], &token[0].s[2], last_slash-2 );
			bpos += (last_slash-2);
		    }
		    buf[bpos++] = '\0';
		    bpos = ALIGN_OFFSET(bpos);
		} else last_slash = -1;

		u->user = &buf[bpos];
		for ( i = last_slash + 1; i < token[0].l; i++ ) {
		    buf[bpos] = (preserve_case && (u->host==NULL) ) ?
			(token[0].s)[i] : UPPER_CASE((token[0].s)[i]);
		    if ( buf[bpos] == '@' ) {
			/* Terminate username and make remainder host string */
			buf[bpos] = '\0';
			u->host = (&buf[bpos+1]);
		    }
		    bpos++;
		}
		buf[bpos++] = '\0';
		bpos = ALIGN_OFFSET(bpos);
		/*
		 * Strip port number off host string.
		 */
		u->port = 0;
		if ( u->host ) {
		    for ( i = 0; (u->host)[i]; i++ ) if ((u->host)[i] == ':') {
			(u->host)[i] = '\0';
			u->port = atoi ( &(u->host)[i+1] );
			break;
		    }
		}
		/*
		 * Copy token[1] into lbuf.
		 */
		u->pass = &buf[bpos];
                for (i=0; i<token[1].l; i++) 
			(u->pass)[i] = preserve_case ?
			(token[1].s)[i] : UPPER_CASE((token[1].s)[i]);
		bpos += token[1].l;
                (u->pass)[token[1].l] = '\0';
		/*
		 * Add to linked list of authinfo structures.
		 */
                u->next = NULL;
		if ( utail == NULL ) uhead = u;
		else utail->next = u;
		utail = u;
            }
        }
     }
     /*
     * Close file/cache entry and save name.
     */
     if ( fp ) tf_close ( fp );
     tfc_rundown_context ( &tfc, 0 );
     strcpy ( suf.setup_file, input_file );
     return 1;
}
/************************************************************************/
/* Dummy routine for returning hostname for address.  To replace, compile
 * module DNS_ENABLE defined.
 */
#ifndef DNS_ENABLE
void lookup_hostname ( unsigned char *address, char *remote_host )
{
    strcpy ( remote_host, "-1.-1.-1.-1" );
}
#endif
/************************************************************************/
/* Compare host string with IP address, return 1 if it matches. 
 */
static int check_host ( char *host, unsigned char *address )
{
    static char octet_str[256][4];
    static int octet_str_init = 0;

    if ( ( *host >= '0' ) && ( *host <= '9' ) ) {
	/*
	 * Numeric host address.  Compare octet by octet, host string can
	 * substitute * for an octet.
	 */
        char number[16], *d, *octet;
        int i, j, len;
	if ( !octet_str_init ) {
	    /* First time, init numeric strings */
	    for ( i = 0; i < 256; i++ ) sprintf ( octet_str[i], "%d", i );
	    octet_str_init = 1;
	}

        for ( d = host, i = 0; i < 4; i++ ) {
	    if ( (*d == '*') && ((d[1]=='.')||(d[1]=='\0')) ) {
		d += 2;
	    } else {
		octet = octet_str[address[i]];
		for ( len = 0; d[len] && (d[len] != '.'); len++ ) {
		    if ( d[len] != octet[len] ) break;		/* mismatch */
		}

		if ( octet[len] != '\0' ) break;
		else if ( i < 3 ) {
		    if ( d[len] != '.' ) break;		/* wrong delimiter */
		} else {
		    if ( d[len] != '\0' ) break;
		}
		d += len + 1;
	    }
	}
	if ( i == 4 ) return 1;	/* we have a match, 4 fields matched */

    } else {
	/*
	 * Compare domain name.
	 */
	char *d;
        int i, j, k, len, rhost_len;
	char remote_host[512];
        void lookup_hostname();

	lookup_hostname ( address, remote_host );
	rhost_len = strlen ( remote_host );
	remote_host[rhost_len++] = '.';

	/* Break hostname into separate labels */
	d = host;
	for (j=i=0; i <= rhost_len; i++ ) if (remote_host[i]=='.') {
	    /* 
	     * String goes from j to i, ending in period.  Determine
	     * length (k) of next label in pattern string (d)
	     */
	    for ( k=0; d[k] && (d[k]!='.'); k++ );
	    len = i - j;
	    if ( (k != 1) || (d[0] != '*') ) {

	        if ( k != len) break;	/* mismatch */
	        if ( strncmp(d,&remote_host[j],len) ) break;
	    }
	    d += k; if ( *d ) d++;
	    j = i+1;
	}
	/* We pass if If all labels matched and no more value */
	if ( (i > rhost_len) && !*d ) return 1;
    }
    return 0;
}
/************************************************************************/
/*
 * access checking routine.
 *  check against username/remote_address pairs stored in linked list
 *  and return password if it is necessary for the final access check
 *  several cases:
 *
 *  @host    *	   	           -> Host address must match
 *  username[@host] password       -> must match both
 *  *[@host]        password       -> any username, must match password
 *  username[@host] *   	   -> must match username, password from SYSUAF
 *  *[@host]        *              -> any username, password from SYSUAF
 *  \\domain\username[@host] *	   -> must match username, call external check.
 *  \\domain\*[@host]        *     -> any username, call external check rtn.
 *
 *  Return values:
 *	1		Access granted.
 *	0		No match, username required.
 *	-1		No match, setup file specified no username checks
 *			for remote (or all) host so authorization will not help.
 *      -2              Access ok, if password matches returned password
 *      -3              Access ok, if VMS-password/ext. auth. check succeeds
 */
static int check_access ( char *username, unsigned char *remote_address,
	char *password, int max_pwd, int local_port )
{
    struct AUTHINFO *u;
    int user_check;

    user_check = -1;
    for ( u = uhead; u != NULL; u = u->next ) {
	if ( u->host ) {
	    /* Check host and port , go to next authinfo if check fails. */
	    if ( u->port ) if ( u->port != local_port ) continue;
	    if ( !check_host ( u->host, remote_address ) ) continue;

	    /* Host passed, Accept uncondionally if username null. */
	    if ( u->user[0] == '\0' ) return 1;
	}
	user_check = 0;
	/*fprintf(stderr,"checking user '%s'@'%s', password '%s'\n", u->user,
	    u->host ? u->host : "*", u->pass ); */
        if (strcmp(u->user,"*") == 0) {
            if (strcmp(u->pass,"*") == 0) {
		if ( u->domain && !ext_loaded ) continue;
                return -3;
	    } else if ( u->domain ) {
		/* Explicit password not allowed with domain, ignore line */
		continue;
            } else {
                strncpy(password,u->pass,max_pwd-1);
		password[max_pwd-1] = '\0';
                return -2;
            }
        } else if (strcmp(u->user,username) == 0) {
            if (strcmp(u->pass,"*") == 0) {
		if ( u->domain && !ext_loaded ) continue;
                return -3;
	    } else if ( u->domain ) {
		/* Explicit password not allowed with domain, ignore line */
		continue;
            } else {
                strncpy(password,u->pass,max_pwd-1);
		password[max_pwd-1] = '\0';
                return -2;
            }
        }
    }
    return user_check;
}
/************************************************************************/
/*
 * password checking routine.
 *  check against username/password pairs stored in linked list
 *  several cases:
 *
 *  @host    *	   	           -> Host address must match
 *  username[@host] password       -> must match both
 *  *[@host]        password       -> any username, must match password
 *  username[@host] *              -> must match username, password from SYSUAF
 *  *[@host]        *              -> any username, password from SYSUAF
 *
 *  Return values:
 *	1		Access granted.
 *	0		No match, username required.
 *	-1		No match, setup file specified no username checks
 *			for remote (or all) host so authorization will not help.
 *
 * Note that domain pointer returned points to dynamic data, string should
 * be copied to private storage if not used immediately.
 */
static int check_password ( char *username, char *password, char **tag_table,
	unsigned char *remote_address, int local_port, char **domain )
{
    struct AUTHINFO *u;
    int user_check;

    user_check = -1;
    *domain = (char *) 0;
    for ( u = uhead; u != NULL; u = u->next ) {
	if ( u->host ) {
	    /* Check host, go to next authinfo if check fails. */
	    if ( u->port ) if ( u->port != local_port ) continue;
	    if ( !check_host ( u->host, remote_address ) ) continue;

	    /* Host passed, Accept uncondionally if username null. */
	    if ( u->user[0] == '\0' ) return 1;
	}
	user_check = 0;
	if ( ext_client_cert_debug ) fprintf ( stderr,
	   "checking user '%s'@'%s domain: %s', password '%s'\n", 
	    u->user,
	    u->host ? u->host : "*", u->domain ? u->domain : "<>", u->pass );

        if (strcmp(u->user,"*") == 0) {
            if (strcmp(u->pass,"*") == 0) {
		if ( u->domain ) {
		    if ( !ext_loaded ) 	continue;	/* no handler present*/
		    if ( auth_check_ext_password ( username, password,
				u->domain, tag_table, remote_address, 0 ) ) {
			*domain = u->domain;
			return 1;
		    }
                } else {
		    if ( auth_check_vms_password (
			username, password, remote_address, 0) ) return 1;
		}
	    } else if ( u->domain ) {
		/* Explicit password not allowed with domain, ignore line */
		continue;
            } else {
                if (strcmp(u->pass,password) == 0) return 1;
            }
        } else if (strcmp(u->user,username) == 0) {
            if (strcmp(u->pass,"*") == 0) {
		if ( u->domain ) {
		    if ( !ext_loaded ) 	continue;	/* no handler present*/
		    if ( auth_check_ext_password ( username, password,
				u->domain, tag_table, remote_address, 0 ) ) {
			*domain = u->domain;
			return 1;
		    }
                } else {
                    if ( auth_check_vms_password (
			username, password, remote_address, 0)) return 1;
		}
	    } else if ( u->domain ) {
		/* Explicit password not allowed with domain, ignore line */
		continue;
            } else {
                if (strcmp(u->pass,password) == 0) return 1;
            }
        }
    }
    return user_check;
}


/*************************************************************************/
/* Authenticator callback routine for auth_server().  This routine is
 * called to process an authentication request sent by the server.
 *
 * This routine determines whether access to the requested object (ident)
 * with the specified method is to be granted, validating the authorization
 * information against the protections defined by the setup file.
 *
 * return values:
 *      0       Access to the object is denied.
 *      1       Access to the object is permitted.
 *
 * When access is denied, this routine sets the response argument to point
 * to a statically allocated string containing the HTTP error response headers
 * to send to the client, sans the HTTP version (i.e. "403 Access denied\r\n").
 * The newlines are included.  If a 401 status is returned, the response header
 * includes the WWW-authenticate headers for the authorization schemes that
 * this routine understands.
 */
int notsosimple (
        int local_port,                 /* Local port # of TCP/IP connection */
        int remote_port,                /* Remote TCP/IP port of client */
        unsigned char remote_address[4],/* Remote binary IP address of client*/
        char *method,                   /* Requested method (GET, PUT, etc); */
        char *setup_file,               /* Protection setup file name */
        char *ident,                    /* Ident portion of translated URL */
        char *authorization,            /* Auth. headers included in request */
        char **response,                /* HTTP error response headers */
        char **log )                    /* Optional log message */
{
    int status, allowed, auth_type, i;
    char basic_username[40], correct_password[40], password[40];
    int cert_rec_num, id_count;
    unsigned long identifiers[32];
    /*
     * Note that response and log buffers must be statically allocated.
     * Fail and succeed are pointers to string constants (implicitly static),
     * if you make them char arrays be to all make them static storage class.
     */
    static char log_message[2000];
    static char fail[200];
    char *username, *pwd_domain, *fields[8];
    char *succeed = "200 access to protected file granted\r\n";
    char *setup_failure = "500 failed to open protection db file\r\n";
    char *host_failure = "403 Forbidden, authorization will not help\r\n";
    char http_uri[256], field_store[512];
    /*
     * Read the protection setup file that defines
     * the protections for the object.  If this operation fails, we
     * return failure (0) with a "500" error status line.
     */
     log_message[0] = 0;
     if ( !load_setup ( setup_file, log_message, ident ) ) {
        *response = setup_failure;
        *log = log_message;
        return 0;
     }
    /*
     * Set default response to the "unauthorized" error response headers string
     */
    *response = fail;
    *log = log_message;
    /*
     * Get the identifer(s) mapped to any X509 certificate passed by client.
     */
    id_count = 0;
    if ( suf.load_cert_info ) {
	status = auth_extract_cert_info ( authorization, &cert_rec_num, 
		&id_count, identifiers, sizeof(identifiers) );

	if ( (status&1) == 0 ) {
	    /* Client presented no certificate, fake a bad identifier list
	     * so check ACL will fail.
	     */
	    id_count = 1;
	    identifiers[0] = 0;
	    if ( ext_client_cert_debug ) printf ( 
		"Client without certificate" );
	} else if ( ext_client_cert_debug ) {
	    printf ( "Client has certificate: rec=%x cnt=%d id[0]=%x\n",
		cert_rec_num, id_count, identifiers[0] );
	}
    }
    /*
     * determine authentication available in request: 0-none, 1-md5, 2-basic
     * Set username to that indicated in the request.
     */
    auth_type = 0;
    username = "";
    password[0] = correct_password[0] = '\0';
    if ( suf.digest[0] ) {
        /*
         * Load file has MD5 flag, look for digest authentication
         */
	status = auth_digest_authorization ( authorization,
		http_uri, fields, field_store );
	if ( status&1 ) { auth_type = 1; username = fields[0]; }
	if ( !username ) username = "";
    }
    if ( auth_type == 0 ) {
	/* 
	 * Look for basic authorization header, only upcase the username
	 * since external auth allows mixed case.
	 */
	if ( auth_basic_authorization ( authorization, 
	    basic_username, sizeof(basic_username), 
	    password, sizeof(password), 2 ) ) {
	    auth_type = 2;
	    username = basic_username;
	}
    }
    /*
     * Do access check against the protection setup file or allow access
     * if wd_missing flag set (no with-document).
     */
    if ( suf.wd_missing ) allowed = 1;
    else allowed = check_access ( username, remote_address, correct_password,
	sizeof(correct_password), local_port );
    if ( ext_client_cert_debug ) fprintf ( stdout,
	"Check access result: %d, auth_type: %d, acllen: %d\n", 
	allowed,auth_type, suf.acllen);

    pwd_domain = (char *) 0;
    if ( allowed == -2 ) {
	/*
	 * Password in request must match password supplied in setup file.
	 */
	if ( auth_type == 1 ) {
	    /* 
	     * MD5 authentication 
	     */
	    allowed = check_digest ( fields, method, http_uri, remote_address,
		correct_password, response, log_message );

	} else if ( auth_type == 2 ) {  
	    /* 
	     * basic authorization, do case-blind compare.
	     */
	    for ( i = 0; correct_password[i]; i++ )
		correct_password[i] = UPPER_CASE(correct_password[i]);
	    for ( i = 0; password[i]; i++ )
		password[i] = UPPER_CASE(password[i]);
	    if ( 0 == strcmp ( password, correct_password ) ) allowed = 1;
	    else allowed = 0;

	} else allowed = 0; 
    } else if ( allowed == -3 ) {
	/*
	 * authentication password must match user's SYSUAF password or
	 * external domain user/password (basic authentication only)
	 */
	if ( auth_type == 2 ) {
	    allowed = check_password (	username, password, 
			suf.ext_arg, remote_address, local_port, &pwd_domain );
	} else allowed = 0;
    }
    /*
     * At this point, allowed flag should have one of 3 possible values:
     *    1 - Access provisionally granted.
     *    0 - Access denied, retry with different username/pass may work.
     *   -1 - Access denied for reason other than invalid  username/pwd.
     *
     * If access granted and acl present, verify against the ACL.
     */
    if ( (allowed == 1) && (suf.acllen != 0) ) {
	allowed = authacl_check_acl ( username, method, suf.acl, suf.acllen,
		id_count, identifiers );

	if ( ext_client_cert_debug ) printf ( 
		"Result of check_acl: %d\n", allowed );

    }
    /*
     * Generate a message for the server's log file.
     * The server checks if the log line begins with "[username]" and
     * makes 'username' the local user in the access.log file.
     */
    if ( allowed < 0 && *username) {
	/*
	 * Make last chance check using null username and log as default access.
	 */
	allowed = check_password ( "", "", suf.ext_arg,
		remote_address, local_port, &pwd_domain );
	sprintf(log_message,"Default access authentication %s",
		(allowed==1) ? "succeeded" : "failed" );
    } else {
        sprintf(log_message,
	    "[%s]%sAuthentication for user='%s' pass='%s' %s%s%s.",
            allowed ? username : "", (auth_type==1) ? "MD5-" : "", username, 
            "[CENSORED]", allowed ? "succeeded" : "failed",
	    pwd_domain ? ", Domain: " : "", pwd_domain ? pwd_domain : "" );
    }
    /*
     * Set the HTTP response line to send back to the client (currently fail).
     * Don't override if a previous step explicitly set response line.
     */
    if ( allowed < 0 ) {
	/* Host checks failed, send 403 status. */
	*response = host_failure;
	allowed = 0;

    } else if ( allowed == 1 ) {
	/* Access was granted */
	if ( *response == fail ) *response = succeed;

    } else if ( *response == fail ) {
	/* 
	 * Generate authorization failure status line with authenticate header
	 * Synthesize realm name if none in setup file.
	 */
	if ( suf.realm[0] == '\0' )
		sprintf ( suf.realm, "CEL%08X",	hash_realm(suf.setup_file) );

	strcpy ( fail, "401 Unauthorized\r\n" );
	if ( suf.digest[0] == '\0' ) {
	    /* No digest tag in setup file, use basic */
	    sprintf ( &fail[18], "WWW-Authenticate: Basic realm=\"%s\"\r\n",
		suf.realm );
	} else {
	    /* Create www-authenticate header for digest method */
	    auth_digest_challenge ( &fail[18], sizeof(fail)-18,
		suf.realm, http_uri );
	}
    }
    return allowed;
}

/* following swiped from rule_file.c ... we don't want the rest of the
 * baggage though.
 */

/*
 * Parse line into whitespace-delimited tokens, trimming leading and trailing
 * whitespace.  Each token parse is explicitly null-terminataed.  Function
 * value returned is number of elements found.  May be 1 more than limit if
 * last element does not end line.
 */
int http_parse_elements (
        int limit,          /* Max number of element to delimit */
        char *line,         /* input line to parse */
        string *elem )      /* Output array. */
{
    int tcnt, in_token, length, i;
    char *ptr;
    /* Delimit up to limit tokens */
    for ( in_token = tcnt = length = 0, ptr = line; *ptr; ptr++ ) {
        if ( in_token ) {
            if ( isspace ( *ptr ) ) {
                /* End current token */
                *ptr = '\0';  /* terminate string */
                elem[tcnt++].l = length;
                in_token = 0;
            }
            else length++;
        } else {
            if ( !isspace ( *ptr ) ) {
                /* start next token */
                if ( tcnt >= limit ) {
                    /* more tokens than expected */
                    tcnt++;
                    break;
                }
                elem[tcnt].s = ptr;
                in_token = 1;
                length = 1;
            }
        }
    }
    /*
     * Make final adjust to element count and make remaining elment null.
     */
    if ( in_token ) { elem[tcnt++].l = length; }
    for ( i = tcnt; i < limit; i++ ) { elem[i].l = 0; elem[i].s = ptr; }
    return tcnt;
}


/*
    just a simple minded hash of ascii -> 32 bit integer
    so that we can (hopefully) tell one setup file from another
    and assign them unique realms without giving away too much
    information about where the setup files actually live.
*/

int hash_realm(char *s)
{
    char c;
    int i = 0;

    while (c = *s++) {
        i = ((i>>24)&0x3F) ^ c ^ (i<<6);
    }
    return i;
}
/*************************************************************************/
/*  Perform digest authentication.
 *
 * Input:
 *    fields	 	array of arguments parsed from authorzation: header.
 *    uri		URL path parsed from HTTP request line.
 *    remote_address	Binary IP address of remote host.
 *
 * Output:
 *    response		Receives pointer to static array holding HTTP response
 *			line.
 *    log_message	Receives text to be added to server's trace log.
 *
 * Return value:
 *      1		Success.
 *	0		Authentication failed due to bad password.
 *     -1		Authentication permanently failed.
 */
int check_digest (  
	char *fields[8], 	/* fields from authenticate header */
	char *method, 		/* method from HTTP request */
	char *uri, 		/* URI specified in HTTP request */
	unsigned char *remote_address,	/* IP address of remote client */
	char *correct_password, 	/* valid password from setup file */
	char **response, char *log_message )   /* Return HTTP status line */
{
    static char http_status[512];
    int status;
    char result[33], pwd_hash[33], uri_hash[33];
    /*
     * lookup or compute hash for username:realm:password.
     */
#ifdef DEBUG
    fprintf(stdout,"supplied fields: '%s' '%s' '%s' '%s' '%s'\n",
	fields[0], fields[1], fields[2], fields[3], fields[4] );
#endif
    auth_digest_md5(pwd_hash, "%s:%s:%s", fields[0] ? fields[0] : "", 
	suf.realm, correct_password );                   
    /*
     * Compute response using our secret.
     */
    auth_digest_md5(result,"%m:%s:%m", pwd_hash, fields[2],
	auth_digest_md5(uri_hash, "%s:%s", method, fields[3]) );
#ifdef DEBUG
    fprintf(stdout,"request method: '%s', URI: '%s', realm: '%s', result hash: '%s'\n",
    method, uri, suf.realm, result );
#endif
    if ( strncmp(result,fields[4],32) == 0 ) {
	/* Password check passed, now verify nonce and uri match. */
        /* compare URI without leading "/" */
	if ( strcmp(&uri[1],fields[3]) != 0 ) return 0;
	strcpy ( http_status, "200 OK" );
	return 1;
    } else {
	return 0;
    }
    return 0;
}
