/*
 * Return user authorization information about a user:
 *	uic			User uic
 *	Login dir.		User's login directory (device + dir),
 *				converted to unix filename format.
 *
 * Revised:	10-AUG-1994	Fixed bug in checking for disable account.
 * Revised:	14-FEB-1995	Check OS version and use context arg in GETUAI.
 * Revised:	28-MAR-1995	Fixed bug in GETSYI call (length returned as short)
 * Revised:	29-JUL-1996	Ensure global lock around getuai calls.
 * Revised:	17-NOV-1996	Rework initialization code.
 * Revised:	21-NOV-1996	Rework cmexec code.
 */
#include <stdio.h>
#include <stdlib.h>
#include <descrip.h>
#include <uaidef.h>
#include <syidef.h>
#include <prvdef.h>
#include <ssdef.h>
#include "pthread_1c_np.h"
#include "tutil.h"

int http_log_level;		/* Logging control flag */
int SYS$GETUAI(), SYS$GETSYIW(), SYS$SETPRV(), SYS$CMEXEC(), tlog_putlog();
#define USERNAME_MAX 12

static pthread_once_t uaf_setup = PTHREAD_ONCE_INIT;
static int uai_version = 0;		/* 0- unknown, 1- old, 2 - new */
static int cmexec_serialization;	/* If true, use $CMEXEC */
static long context;
typedef struct { short length, code; char *buffer; int *ret_len; } item_list;
/***************************************************************************/
/* Serialized call to GETUAI, set and reset SYSPRV privilege bit.
 */
int do_getuai ( long *ctxptr, 
	struct dsc$descriptor_s *username_dx, item_list *item )
{
    int status, privs[2];
    privs[1] = 0; privs[0] = PRV$M_SYSPRV;
    SYS$SETPRV ( 1, privs, 0, 0 );
    status = SYS$GETUAI ( 0, ctxptr, username_dx, item, 0, 0, 0 );
    SYS$SETPRV ( 0, &privs, 0, 0 );
    return status;
}
/***************************************************************************/
/* One-time initialzation, check environment and set static flags to indicate
 * capabilities.
 */
static void user_info_init() {
    int status, length;
    long iosb[2], priv_mask[2], prev_privs[2];
    char version[64];
    item_list item[5];
    /*
     * Initialize uai_version flag to indicate whether this version of VMS
     * supports the context argument for the GETUAI call (added after 6.1).
     * Keeping the context when allowed save considerable I/O.
     */
    uai_version = 1; context = -1;		/* default value */
    item[0].code = SYI$_NODE_SWVERS;
    item[0].length = 8;
    item[0].buffer = version;
    item[0].ret_len = &length;
    item[1].code = item[1].length = 0;
    length = 0;
    status = SYS$GETSYIW ( 0, 0, 0, item, iosb, 0, 0 );
    if ( status&1 ) status = iosb[0];
    if ( status&1 ) {
	version[length] = '\0';
	if ( tu_strncmp ( version, "V6.1", 8 ) >= 0 ) uai_version = 2;
    }
    /*
     * Check privileges and set flag to determine method of invoking
     * $GETUAI: 1-priority, 2-cmexec.
     */
    cmexec_serialization = 0;			/* default to old method */
    priv_mask[1] = 0; priv_mask[0] = PRV$M_CMEXEC;
    status = SYS$SETPRV ( 1, priv_mask, 0, prev_privs );
    if ( (status == SS$_NORMAL) && (uai_version==2) ) cmexec_serialization = 1;

    tlog_putlog ( 10, 
	"GETUAI calls will !AZ context and !AZ serialzation.!/",
	uai_version==2 ? "use persistent" : "NOT use persistent",
	cmexec_serialization ? "CMEXEC" : "setscheduler" );
}
/***************************************************************************/
/* Primary function for returning username info.
 */
int http_user_information ( char *username, int user_len,
	int *uic,
	char *login_dir, int maxlen )
{
    int i, flags, devlen, dirlen, status, length;
    int delta, priority, scheduler;
    item_list item[5];
    long *ctxptr;
    pthread_t self;
    char device[40], defdir[64], userkey[USERNAME_MAX+1];
    $DESCRIPTOR (username_dx,"");    
    struct sched_param param;
    /*
     * Determine if safe to use context argument.
     */
    pthread_once ( &uaf_setup, user_info_init );
    ctxptr = (uai_version == 2) ? &context : (long *) 0;
    /*
     * Build upcased descriptor for username.
     */
    if ( user_len > USERNAME_MAX ) return 20;	/* invalid name */
    if ( user_len <= 0 ) return 20;			/* Null username */

    tu_strnzcpy ( userkey, username, user_len );
    tu_strupcase ( userkey, userkey );
    username_dx.dsc$w_length = user_len;
    username_dx.dsc$a_pointer = userkey;
    /*
     * Make item list for getuai.
     */
    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$_DEFDEV; item[1].length = sizeof(device);
    item[1].buffer = device; item[1].ret_len = &devlen; 
    devlen = 0; device[0] = '\0';

    item[2].code = UAI$_DEFDIR; item[2].length = sizeof(defdir);
    item[2].buffer = defdir; item[2].ret_len = &dirlen; 
    dirlen = 0; defdir[0] = '\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 = 0; item[4].length = 0;	/* terminate list */
    /*
     * Fetch information abour user.  Block other activity and set privilege.
     */
    if ( cmexec_serialization ) {
	/*
	 * Do getuai call from exec mode to ensure no other threads get
	 * benefit of privilege elevation.
	 */
	long arg_list[4] = { 3, 0, 0, 0 };
	arg_list[1] = (long) ctxptr;
	arg_list[2] = (long) &username_dx;
	arg_list[3] = (long) item;
	status = SYS$CMEXEC ( do_getuai, arg_list );
    } else {
	/*
	 * Don't have cmexec, next best thing is to raise priority to FIFO
	 * level to block as many alternate threads as possible from executing.
	 */
	self = pthread_self();
#ifdef PTHREAD_USE_D4
	priority = pthread_getprio ( self );
	scheduler = pthread_getscheduler ( self );
	status = pthread_setscheduler ( self, SCHED_FIFO, PRI_FIFO_MAX );
#else
	pthread_getschedparam ( self, &scheduler, &param );  /* save current */
	priority = param.sched_priority; 
	param.sched_priority = PRI_FIFO_MAX;
	status = pthread_setschedparam ( self, SCHED_FIFO, &param );
#endif
	pthread_lock_global_np();
	status = do_getuai ( ctxptr, &username_dx, item );
	pthread_unlock_global_np();
#ifdef PTHREAD_USE_D4
	i = pthread_setscheduler ( self, scheduler, priority );
#else
	param.sched_priority = priority;
	i = pthread_setschedparam ( self, scheduler, &param );
#endif
    }
    /*
     * Check if logging enabled to this level and place info in log file.
     */
    if ( http_log_level > 10 ) {
	tlog_putlog ( 10,"userdir username: '!AS', getuai status: !SL (!SL, !SL)!/", 
		&username_dx, status, devlen, dirlen );
	tlog_putlog ( 10, "flags: !XL, device: '!AC', dir: '!AC'!/", flags,device, defdir );
    }
    /*
     * Return if error or account not enabled.
     */
    if ( (status&1) == 0 ) return status;		/* GETUAI error */
    if ( (flags&UAI$M_DISACNT) != 0 ) return 20;	/* Disusered account */
    /*
     * Fixup punctuation for conversion to unix filepath format.
     */
    devlen = device[0];		/* counted ascii */
    if ( device[devlen] == ':' ) devlen = devlen - 1;	/* trim colon */
    dirlen = defdir[0];
    if ( (defdir[dirlen] == ']') || (defdir[dirlen] == '>') ) {
	defdir[1] = '.';	/* Convert leading bracket */
	defdir[dirlen] = '.';	/* Convert trailing bracket */
    }
    for ( i = 1; i <= dirlen; i++ ) if ( defdir[i] == '.' ) defdir[i] = '/';
    /*
     * Load into users output.
     */
    if ( (devlen + dirlen + 2) > maxlen ) return 20;
    login_dir[0] = '/';
    tu_strncpy ( &login_dir[1], &device[1], devlen );
    tu_strnzcpy ( &login_dir[1+devlen], &defdir[1], dirlen );

    return 1;
}
