/*
*   AUTHACL contains a routines to test whether a specified VMS username
*           has access to a given ACL (specified as a string) on behalf
*	    of ACL_AUTHENTICATOR.
*
*    Modified: 27-NOV-1996	DLJ- Set access check based upon method,
*				make ctxptr conditional upon OS version.
*    Modified:  3-DEC-1996	Incorporate changes from D. Smith.
*    Modified: 7-SEP-2000	Add extended ACL.
*    Modified:  8-OCT-2001	Add IDENTIFIER= to appacl.
*    Mofified:  9-DEC-2001	Add cert_identifier support to check_acl.
*/

#include <descrip.h>
#include <string.h>
#include <ctype.h>
#include <starlet.h>
#include <lib$routines.h>
#include <chpdef.h>
#include <prvdef.h>
#include <armdef.h>
#include <uaidef.h>
#include <stdio.h>
#include <acedef.h>
#include <acldef.h>
#include "authacl.h"
#include "authlib.h"

int ext_client_cert_debug;

#ifdef DEBUGACL1
/*	 
**  Test routine, for testing ACL routine interactively.
*/	 
int main ( int argc, char **argv )
{
    int allowed, parsed, i, j;
    char acl[ACLMAXBIN];		/* binary ACL */
    char username[13];
    char method[8];
    char log_message[200];
    int  acllen = 0;			/* ACL length */

    while (1) {

	acllen = 0;

/*	 
**  to test the routine, hardcode an ACL by calling authacl_parse_acl as
**  below, then compile with /DEFINE=DEBUGACL
*/	 
	if ( argc < 2 ) {
	parsed = authacl_parse_acl ( "(IDENT=SMITH_NP,ACCESS=WRITE+READ)", acl, &acllen, log_message );
	if (!parsed) printf ("%s\n", log_message);
	parsed = authacl_parse_acl ( "(IDENT=FCJVS_T,ACCESS=NONE)", acl, &acllen, log_message  );
	if (!parsed) printf ("%s\n", log_message);
	parsed = authacl_parse_acl ( "(IDENT=[30,*],ACCESS=READ)", acl, &acllen, log_message  );
	if (!parsed) printf ("%s\n", log_message);
	}

	printf ("Enter username: ");
	gets (username);
	if ( !(*username) ) break;
	printf ("Method (GET, POST, etc): ");
	gets (method);
	if ( !(*method) ) break;

	for ( i = 1; i < argc; i++ ) {
	    int oldlen;
	    oldlen = acllen;
	    if ( argv[i][0] == '(' ) {
	        parsed = authacl_parse_acl ( argv[i], acl, &acllen, 
			log_message );
	    } else {
		parsed = authacl_parse_appacl ( argv[i], acl, &acllen,
			log_message );
	    }
	    if ( !parsed ) printf ( "argv[%d]: %s\n", i, log_message );
	    else {
		struct {
		    unsigned char length, type;
		    unsigned short flags;
		    long mask;
		    char data[248];
		} ace;
		memcpy ( &ace, &acl[oldlen], acllen-oldlen );
		printf ( "argv[%d] parsed into ACE of size: %d\n",i,acllen-oldlen);
		printf("  ace hdr: l=%d, t=%d, flag=%x, msk=%x\n",
			ace.length,ace.type,ace.flags,ace.mask );
		for ( j = 0; j < ((acllen-oldlen)-8); j++ ) {
		    printf ( "   ace data[%d] = %d\n", j, ace.data[j] );
		}
	    }
	}
	allowed = authacl_check_acl( username, method, acl, acllen);

	if (allowed) {
	    printf ("User %s has access\n", username);
	} else {
	    printf ("User %s does not have access\n", username);
	}
    }
}

#endif
/*
 * Define structures for security-based system services.
 */
struct rights_holder { int uic; int zero; };
struct 	rightslist_entry { int id, attrib; };
/*
 * Get rights for UIC specified by caller.
 */
static int get_rightslist ( struct rights_holder *holder, 
	struct rightslist_entry *rights )
{
    int sts, rightscnt, contxt_id;

    rights[0].id = holder->uic;
    rights[0].attrib = 0;
    rightscnt=1;

    /* Get first held identifier */
    contxt_id = 0;
    sts = sys$find_held ( holder,  &rights[rightscnt].id, 
				    &rights[rightscnt].attrib, 
				    &contxt_id);
    while ( (sts & 1) ) {
	rightscnt++;
	/* Get rest of held identifiers */
	sts = sys$find_held ( holder,  &rights[rightscnt].id, 
					&rights[rightscnt].attrib, 
					&contxt_id);
    }

    return rightscnt;
}

static int check_app_acl ( char *acl, int acllen, char *acc, int acclen,
	struct rights_holder *holder, 
	struct rightslist_entry *rights, int *rightscnt )
{
    int offset, size, type, access, fails, passes, i;
    struct acedef *ace;
    char *text, *match;
    /*
     * Trim padding from account name.
     */
#ifdef DEBUGACL
    printf("check_app_acl entered, acclen = %d, acc[0] = %d", acclen, acc[0]);
    acc[32] = '\0';
    printf("   account: '%s'\n", acc );
#endif
    match = acc;
    for ( i = acclen-1; i >= 0; --i ) if ( acc[i] != ' ' ) break;
    acc[i+1] = '\0';
    /*
     * Walk the list of aces and process the application aces.
     */
    fails = passes = 0;
    for ( offset = 0; offset < acllen; offset += size ) {
	ace = (struct acedef *) &acl[offset];
	size = ace->ace$b_size;
	type = ace->ace$b_type;
	if ( type != ACE$C_INFO ) continue;
	/*
	 * access field.
	 */
	access = ace->ace$l_info_flags;
	text = &ace->ace$t_info_start;
	switch ( access ) {
		/* "<APPACL> ACCOUNT=..." record */
	   case 1:
		if ( strncmp (acc, text, size) == 0 ) passes++; else fails++;
		break;
	   case 2:
		/* Allow a specific IDENTIFIER, get rightslist if first call.  */
		if ( *rightscnt == 0 )
			*rightscnt = get_rightslist ( holder, rights );

		if ( *rightscnt > 0 ) {
		    /* Lookup the named identifier and see if in */
		    static $DESCRIPTOR(idname_dx,"");
		    int id, attrib, status, i;
		    i = strnlen ( text, 31 );
		    idname_dx.dsc$a_pointer = text;
		    idname_dx.dsc$w_length = i;
		    status = sys$asctoid ( &idname_dx, &id, &attrib );
		    if ( (status&1) == 1 ) {
			for ( i = 0; i < *rightscnt; i++ ) {
			    if ( id == rights[i].id ) {
				passes++; break;
			    }
			}
			if ( i >= *rightscnt ) fails++;
		    } else {
			/*
			 * Identifer lookup failed.
			 */
		    }
		}
		break;
	    default:
#ifdef DEBUGACL
		printf("Unexpected ace flags: %x\n", access );
#endif
		break;
	}
    }
#ifdef DEBUGACL
    printf("app ace scan completed, passes: %d, fails: %d\n", passes, fails);
#endif
    if ( passes > 0 ) return 1;
    if ( fails > 0 ) return 0;
    return 1;
}

int authacl_parse_acl ( char *aclstr, char *acl, int *acllen, char *log_message)
{
    int	 sts;
    unsigned char *ace;
    short msglen, errpos;
    struct dsc$descriptor_s aclstr_dx = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
    struct dsc$descriptor_s ace_dx = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
    struct dsc$descriptor_s msg_dx = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};

    aclstr_dx.dsc$w_length = strlen(aclstr);
    aclstr_dx.dsc$a_pointer = aclstr;

    ace = (unsigned char *) (acl + *acllen);
    ace_dx.dsc$w_length = ACLMAXBIN - *acllen;
    ace_dx.dsc$a_pointer = (char *) ace;

    sts = sys$parse_acl ( &aclstr_dx, &ace_dx, &errpos, 0, 0);
    if (!(sts & 1)) {
        msg_dx.dsc$w_length = 200;
	msg_dx.dsc$a_pointer = log_message;
	lib$sys_getmsg ( &sts, &msglen, &msg_dx, 0, 0);
	msg_dx.dsc$a_pointer += msglen + 1;
	sprintf (msg_dx.dsc$a_pointer, 
		"\r\nACE: %s\r\n error at pos %hi ", aclstr, errpos);
	return 0;
    } else {
	*acllen += *ace;
	return 1;
    }
}

/* 
** Macros for inserting items into item lists 
*/
#define set_chplst(size,itemcode,buffadr)	    \
    chpcnt++;				    \
    chplst[chpcnt].len     =  size;	    \
    chplst[chpcnt].code    =  itemcode;	    \
    chplst[chpcnt].bufadr  =  buffadr;	    \
    chplst[chpcnt].retadr  =  0;	    

#define set_uailst(size,itemcode,buffadr, retlenadr)	    \
    uaicnt++;				    \
    uailst[uaicnt].len     =  size;	    \
    uailst[uaicnt].code    =  itemcode;	    \
    uailst[uaicnt].bufadr  =  buffadr;	    \
    uailst[uaicnt].retadr  =  retlenadr;	    

int authacl_check_acl ( char *username, char *method, char *acl, int acllen,
	int id_count, unsigned long *cert_identifiers )
/*
** This routine builds up a security object and user profile given the
** username and ACL and then calls $CHKPRO to see if the user has 
** access to the object.  
*/
{
    static int contxt = -1;
    static int contxt_id = 0;
    int sts, i;
    int chpcnt = -1, uaicnt = -1;
    int rightscnt = 0;
    long priv_mask[2];

    int chp_access;
    int app_acl_present, accnam_len;
    char account_name[36];

    int privused = 0;

    struct rights_holder holder = { 0, 0 };

    int uai_flags;
    long privs[2];
    struct rightslist_entry rights[256];
    static int owner = 0;

    short prot = 0xFF00;  /* (S:RWED, O:RWED, G:none, W:none) */

    struct {                   
		 short len, code;
		 void *bufadr;
		 int retadr;
    } chplst[ACLCHPSIZE];   

    struct {                   
		 short len, code;
		 void *bufadr;
		 int *retadr;
    } uailst[ACLUAISIZE];   

    $DESCRIPTOR (owner_dx,OWNERUIC);  /* Owner UIC of the object we're buiding */

    struct dsc$descriptor_s acl_dx = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};

    struct dsc$descriptor_s username_dx = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
    username_dx.dsc$w_length = strlen(username);
    username_dx.dsc$a_pointer = username;

    /* Convert owner id to binary UIC */

    if ( owner == 0 ) {
	sts = sys$asctoid (&owner_dx, &owner, 0);
	if (!(sts & 1)) lib$signal(sts);
    }

    /*
     * See if application ace present in ACL (must be first).
     */
    app_acl_present = 0;
    if ( acllen >= ACE$C_LENGTH ) if (acl[1] == ACE$C_INFO) app_acl_present=1;
    /*
     * Enable SYSPRV temporarily, get UAF items and rights and disable again.
     */
    priv_mask[0] = PRV$M_SYSPRV; 
    priv_mask[1] = 0;    
    sts = sys$setprv ( 1, priv_mask, 0, 0 );
    if (!(sts & 1)) return 0;

    set_uailst( 4, UAI$_UIC, &holder.uic, 0);
    set_uailst( 8, UAI$_PRIV, privs, 0);
    set_uailst( 4, UAI$_FLAGS, &uai_flags, 0);
    accnam_len = 0;
    if ( app_acl_present ) {
	set_uailst ( 32, UAI$_ACCOUNT, &account_name, &accnam_len );
    }
    set_uailst( 0, 0, 0, 0);

    sts = sys$getuai (0, auth_getuai_context(), &username_dx, uailst, 0, 0, 0);
#if  ACLREQUIREUSER
/* Valid username required, return failed access. */
    if (!(sts & 1))	{
        (void) sys$setprv ( 0, priv_mask, 0, 0 );
	return 0;
    }
#else
/* Valid username not required, return succesful access.  Assumes that a
   hardcoded username/password combination in protect file should allow
   access.
 */
    if (!(sts & 1)) {
	if ( id_count <= 0 ) {
	    (void) sys$setprv ( 0, priv_mask, 0, 0 );
	    return 1;
	}
	/*
	 * If the first identifier is 0, the client had none, so
	 * fake it.
	 */
	if ( cert_identifiers[0] == 0 ) {
	    (void) sys$setprv ( 0, priv_mask, 0, 0 );
	    if ( ext_client_cert_debug ) printf ( 
		"check_acl returning failed status due to null cert.\n" );
	    return 0;
	}
	/*
	 * Provide fake information from UAF call.
	 */
	accnam_len = 0;			/* Null acount name */
	uai_flags = 0;			/* Dummy account attributes */
	holder.uic = 0;
	privs[0] = PRV$M_TMPMBX; privs[1] = 0;
	rightscnt = (id_count>256) ? 256 : id_count;
	for ( i = 0; i < rightscnt; i++ ) {
	    rights[i].id = cert_identifiers[i];
	    rights[i].attrib = 0;	/* ignored by $chkpro */
	}
	if ( ext_client_cert_debug ) printf ( 
	    "Faking getuai for client cert., new rightscnt: %d %x\n",
	    rightscnt, rights[0].id );
	
    }
#endif
    /*
     * Process application aces at begining of ACL.
     */
    if ( app_acl_present ) {
	sts = check_app_acl ( acl, acllen, account_name, accnam_len,
		&holder, rights, &rightscnt );
	if ( (sts&1) == 0 ) {
	    (void) sys$setprv ( 0, priv_mask, 0, 0 );
	    return 0;		/* denied */
	}
    }

    if ( rightscnt == 0 ) {
	rightscnt = get_rightslist ( &holder, rights );
	if ( ext_client_cert_debug ) printf ( 
		"appending %d cert. rights to existing %d\n",
		id_count, rightscnt );
	for ( i = 0;  i < id_count; i++ ) if ( rightscnt < 256 ) {
	    rights[rightscnt].id = cert_identifiers[i];
	    rights[rightscnt].attrib = 0;
	    rightscnt++;
	}
    }

    /* Disable SYSPRV */
    (void) sys$setprv ( 0, priv_mask, 0, 0 );

    if ( ( uai_flags & UAI$M_DISACNT ) )  return 0;	    /* Disuser'd? */

    /*
     * Check method and change access to READ for GET and HEAD operations.
     */
    chp_access = CHP$M_WRITE;
    if ( *method == 'G' ) {
	if ( strcmp(method,"GET") == 0 ) chp_access = CHP$M_READ;
    } else if ( *method = 'H' ) {
	if ( strcmp(method,"HEAD") == 0 ) chp_access = CHP$M_READ;
#ifdef CHP$M_DELETE
    } else if ( *method == 'D' ) {
	/* (If delete bit unknown, we will use default WRITE access) */
	if ( strcmp(method,"DELETE") == 0 ) chp_access = CHP$M_DELETE;
#endif
    }

    set_chplst ( 4, CHP$_ACCESS, &chp_access );
    set_chplst ( (rightscnt * 8), CHP$_RIGHTS, rights);
    set_chplst ( 8, CHP$_PRIV, privs);
    set_chplst ( 4, CHP$_OWNER, &owner );
    set_chplst ( 2, CHP$_PROT, &prot );
    set_chplst ( acllen, CHP$_ACL, acl );
    set_chplst ( 4, CHP$_PRIVUSED, &privused );
    
    set_chplst ( 0, CHP$_END, 0 )

    sts = sys$chkpro ( chplst );
    if ( ext_client_cert_debug ) printf(
	"chpro result: %d, rightscnt: %d\n", sts, rightscnt );

    if (sts & 1) {
	return 1;
    } else {
	return 0;
    }

}
/*
 * Format data.
 */
static char *app_keywords[] = { "ACCOUNT", "IDENTIFIER", (char *) 0 };
int authacl_parse_appacl ( 
	char *aclstr, 
	char *acl, 
	int *acllen, char *log_message)
{
    char keyword[80];
    int info[128];
    char acestr[768];
    int length, i, flags, pos, code, info_size;
    /*
     * Scan aclstr and break into keyword and raw int data, parsing on
     * the first '='.
     */
    length = strlen ( aclstr );
    info_size = 0;
    for ( i = 0; i < length; i++ ) {
	if ( i >= sizeof(keyword) ) {
	    strcpy ( log_message, 
		"Bad format for keyword=data, keyword too long" );
	    return 0;
	}
	keyword[i] = _toupper(aclstr[i]);
	if ( keyword[i] == '=' ) {
	    keyword[i] = '\0';
	    length = length - i;		/* will include trailin null */
	    if ( length > sizeof(info) ) {
		strcpy ( log_message,
		   "Too much data" );
		return 0;
	    }
	    memcpy ( info, &aclstr[i+1], length );
	    info_size = (length+sizeof(int)-1)/sizeof(int);
	    break;
	}
    }
    if ( info_size == 0 ) {
	strcpy ( log_message, "Bad syntax for keyword=data" );
	return 0;
    }
    /*
     * Translate keyword into code value that will be stored in access field.
     */
    for ( code = 0; app_keywords[code]; code++ ) {
	if ( strcmp ( app_keywords[code], keyword ) == 0 ) break;
    }
    if ( !app_keywords[code] ) {
	strcpy ( log_message, "Bad format for keyword=data, unknown keyword" );
	return 0;
    }
    code++;		/* shift code numbers to start at 1. */
    /*
     * construct an ACE entry for parsing.
     */
    flags = ACE$C_CUST;
    sprintf ( acestr, "(APPLICATION,SIZE=%d,FLAGS=%d,ACCESS=%d,DATA=",
	(info_size*sizeof(int)) + ACE$C_LENGTH, flags, code );
    pos = strlen ( acestr );
    for ( i = 0; i < info_size; i++ ) {
	sprintf ( &acestr[pos], "%%X%x,", info[i] );
	pos += strlen ( &acestr[pos] );
    }
    strcpy ( &acestr[pos-1], ")" );
    /*
     * Take over.
     */
    return authacl_parse_acl ( acestr, acl, acllen, log_message );
}
