/*
 * C wrapper functions for VMS persona services (6.2 or higher).  The
 * persona_authenticate routine validates the username against the SYSUAF
 * using the password.
 *
 * typedef long persona;
 *
 * int persona_authenticate ( char *user, char *pwd, persona *pers );
 * int persona_create ( char *user, persona *pers );
 * int persona_assume ( persona * );
 * int persona_revert ( );
 * int persona_delete ( persona * );
 *
 * Author:  David Jones
 * Date:    31-May-1998
 */
#include <impdef.h>			/* VMS ipersonate services */
#include <descrip.h>			/* VMS string descriptors */
#include <uaidef.h>			/* VMS user auth. info. */
#include <ssdef.h>
#include <ciadef.h>
#include <jpidef.h>

#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

int SYS$PERSONA_CREATE(), SYS$PERSONA_ASSUME(), SYS$PERSONA_DELETE();
int SYS$HASH_PASSWORD(), SYS$GETUAI(), SYS$SCAN_INTRUSION();
#define PRV$M_SECURITY 64
#ifdef VAXC
globalref int secsrv$_intruder;
#else
extern int secsrv$_intruder;
#endif

#include "persona.h"			/* verify prototypes */

typedef struct { short length, code; char *buffer; int *ret_len; } item_list;

int persona_create ( char *username, persona *new_persona )
{
    char compare_string[36];
    int status, i, match;
    struct dsc$descriptor username_dx;
    /*
     * Upcase the string.
     */
    for ( i = 0; i < sizeof(compare_string)-1; i++ ) {
	if ( !username[i] ) break;
	compare_string[i] = toupper(username[i]);
    }
    compare_string[i] = '\0';
    /*
     * Build string descriptor and make system service call.
     */
    username_dx.dsc$a_pointer = compare_string;
    username_dx.dsc$w_length = i;
    status = SYS$PERSONA_CREATE ( new_persona, &username_dx, 0);

    return status;
}
/************************************************************************/
int persona_assume ( persona *target_persona )
{
    int status, flags;

    flags = IMP$M_ASSUME_SECURITY;
    status = SYS$PERSONA_ASSUME ( target_persona, flags );
    return status;
}
/************************************************************************/
int persona_delete ( persona *target_persona )
{
    int status;

    status = SYS$PERSONA_DELETE ( target_persona );
    return status;
}
/***************************************************************************/
int persona_revert ( )
{
    int status;
    static persona original_persona;
    original_persona = 1;
    return persona_assume ( &original_persona );
}
/***************************************************************************/
/* Scan intrustion database, return 0 if login should be denied, or original
 * login status (success or failure) if no matching intrusion data.
 */
static int scan_intrusion ( int login_status, int username_valid,
	struct dsc$descriptor_s *username )
{
    int status, flags, len;
    static $DESCRIPTOR(default_user,"INVALID");
    $DESCRIPTOR(remote_node_dx, "");
    struct dsc$descriptor_s *source_user;
    /*
     * Check that process has SECURITY privilege, do null operation if not.
     */
    /* if ( (PRV$M_SECURITY&cur_privs[1]) == 0 ) return login_status; */
    /*
     * Determine source user string to use for scan, use "INVALID" if
     * username not valid.
     */
    source_user = &default_user;
    if ( username_valid ) source_user = username;
    /*
     * Build descriptors and call system service to do intrusion DB scan.
     */
    remote_node_dx.dsc$w_length = 10;
    remote_node_dx.dsc$a_pointer = "WEB_SCRIPT";
    flags = (username_valid) ? CIA$M_REAL_USERNAME : 0;

    status = SYS$SCAN_INTRUSION ( login_status, username, JPI$K_REMOTE,
	0, &remote_node_dx, source_user, 0, 0, 0, 0, flags );
    /*
     * Fail request if intruder detected, otherwise return original
     * login status.
     */
    if ( status == (int) &secsrv$_intruder ) return 0;

    return (1&login_status);
}

/***************************************************************************/
int persona_authenticate ( char *user, char *pwd, persona *new_persona )
{
    item_list item[10];
    int i, flags, alg, salt, status;
    long uic, hash[2], test_hash[2];
    struct dsc$descriptor_s user_dx, pwd_dx;
    char username[40], password[32];
    /*
     * make descriptors, upcase username and password.
     */
    user_dx.dsc$b_dtype = user_dx.dsc$b_class = 0;
    for ( i = 0; i < sizeof(username); i++ ) {
	if ( !user[i] ) break;
	username[i] = toupper(user[i]);
    }
    user_dx.dsc$w_length = i;
    user_dx.dsc$a_pointer = username;
    pwd_dx.dsc$b_dtype = pwd_dx.dsc$b_class = 0;
    for ( i = 0; i < sizeof(password); i++ ) {
	if ( !pwd[i] ) break;
	password[i] = toupper(pwd[i]);
    }
    pwd_dx.dsc$w_length = i;
    pwd_dx.dsc$a_pointer = password;
    /*
     * Lookup user information needed to test password.
     */
    item[0].code = UAI$_FLAGS; item[0].length = sizeof(flags);
    item[0].buffer = (char *) &flags; item[0].ret_len = (int *) 0;

    item[1].code = UAI$_PWD; item[1].length = 8;
    item[1].buffer = (char *) hash; item[1].ret_len = (int *) 0;
    hash[0] = 0;  hash[1] = 0;

    item[2].code = UAI$_SALT; item[2].length = 2;
    item[2].buffer = (char *) &salt; item[2].ret_len = (int *) 0;
    salt = 0;

    item[3].code = UAI$_UIC; item[3].length = sizeof(int);
    item[3].buffer = (char *) &uic; item[3].ret_len = (int *) 0;

    item[4].code = UAI$_ENCRYPT; item[4].length = 1;
    item[4].buffer = (char *) &alg; item[4].ret_len = (int *) 0;
    alg = 0;

    item[5].code = 0; item[5].length = 0;	/* terminate list */
    status = SYS$GETUAI ( 0, 0, &user_dx, item, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    /*
     * Hash password and compare.
     */
    status = SYS$HASH_PASSWORD ( &pwd_dx, alg, salt, &user_dx, test_hash );
    if ( (status&1) == 0 ) return status;

    if ( ((flags&UAI$M_DISACNT) == 0) &&
	   (hash[0] == test_hash[0]) && (hash[1]==test_hash[1]) ) {
	/*
	 * make sure not in intrusion database.
	 */
	status = scan_intrusion ( 1, 1, &user_dx );
	/*
	 * Password validated.
	 */
	status = persona_assume ( new_persona );
    } else {
	/*
	 * Add record to intrusion database.
	 */
	scan_intrusion ( 0, 1, &user_dx );
	status = SS$_INVLOGIN;
    }
    return status;
}
#ifdef TEST_PROG
int main ( int argc, char **argv )
{
    int status;
    persona target;
    if ( argc < 3 ) { printf("usage: persona user pwd\n"); exit(1); }

    status = persona_create ( argv[1], &target );
    printf("Status of persona_create: %d, handle: %x\n", status, target );
    if ( (status&1) == 0 ) return status;

    status = persona_authenticate ( argv[1], argv[2], &target );
    printf("Status of persona_auth: %d\n", status );

    persona_revert();
    return status;
}
#endif

