#module  SET_USER  "V2.1"

/*++SETUSER.C
 Facility: 
	Fermilab Accelerator Control System - VAX/VMS ACNET
	Privileged program for system management use.

 Abstract:
	Change UIC and username of process, usually to permit batch job to
	be submitted by one user (usually SYSTEM) to run under another user's
	name.

 Environment:
	Foreign DCL command, requires CMKRNL privilege.
--*/
/*
 Modification History:
 Author: F. Nagy

V1.0	22-Sep-83  FJN	Created for every day/week batch job usage.
V2.0	16-Jan-84  FJN	Adding capability to set account name; access UAF file
V2.0	29-Jan-84  FJN	Continue with UAF capability additions
V2.1	30-Jan-84  FJN	Added "A" option
*/

/* #define  DEBUG				/* Define for debugging */

/******************
 * Include Files: *
 ******************/
#include  stdio				/* Standard C I/O definitions */
#include  ctype				/* Character classifications */
#include  vaxtypes			/* VAX-11 C Type extensions */
#include  descrip			/* VAX-11 Descriptors */
#include  descrip2			/* Additional descriptor defs. */
#include  rms				/* VAX-11 RMS definitions */
#include  stsdef			/* Condition code fields */
#include  $uafdef			/* UAF record definition */

/********************************
 * Local definitions and types: *
 ********************************/
typedef unsigned long status;		/* Condition code status type */

#define  PARM_LEN	21		/* Parameter text buffer length */

#define  NUL		'\0'		/* NUL character (string terminator) */

#define  ERROR_EXIT	_exit( 0X10000002)

struct UIC {				/* UIC "longword" */
    unsigned short int mem;		/* Member number */
    unsigned short int grp;		/* Group number */
    };

/*****************************
 * Module Local Own Storage: *
 *****************************/

static struct UAF_RECORD uafrec;	/* User Authorization File record */

/**********************************************
 * Global Procedure and Function definitions: *
 **********************************************/

extern status SYS$CMKRNL();		/* Change mode to kernel service */
extern status SET_UIC_USER();		/* Kernel routine to actually change
					   the UIC and username */
extern status SYS$SETDDIR();		/* Set default directory service */
extern status LIB$SET_LOGICAL();	/* Set supervisor-mode logical name */


/*+0Main Program

 Functional Description:
	Change UIC and username.  Options allow changing account name and
	default device and directory.

 Calling Sequence:
	Install as a DCl foreign command:
		$ SETUSER :== $FERMI$EXE:SETUSER
	Execute to change UIC and username (account optional):
		$ SETUSER  username  uic  [account]
	Execute to change username, UIC and account (only if "A") using UAF:
		$ SETUSER  -U[A]  username
	Execute to change username, etc. and the default device/directory:
		$ SETUSER  -UD  username

 Side Effects:
	Changes the UIC in the PCB of the process and the username in the
	JIB and in P1 space.  Optionally changes the account name stored
	in the JIB.
-*/

main( argc, argv )
int argc;				/* Command line argument count */
char *argv[];				/* Command line arguments text */
{
char aparam[PARM_LEN];			/* Parameter text buffer */
char *pp, *tp;				/* Parameter text pointers */
struct {				/* SET_UIC_USER argument list */
    ulong count;			/* Argument count */
    char *userp;			/* Username text pointer */
    struct UIC *uicp;			/* UIC longword pointer */
    char *acctp;			/* Optional account name pointer */
    } args = { 3, uafrec.uaf$t_username, &uafrec.uaf$l_uic, NULL };
int strlen();				/* Returns NUL-term. string length */
int sscanf();				/* Scan and convert string */
status sts;				/* Completion status */
ulong ugrp, umem;			/* For conversion arguments */
int set_default = FALSE;		/* Flag to do implicit SET DEFAULT */
int set_account = FALSE;		/* Flag to set account name from UAF */
$DESCRIPTOR0( d_string);		/* Generic string descriptor */
$DESCRIPTOR( d_sysdisk, "SYS$DISK");	/* Default device logical name string */

/*
  If there is at least one parameter and the first character of the first
  parameter is "-", then assume we have the "-U" format call and will get
  the username and then access the UAF for everything else.
*/
if ((argc > 1) && (argv[1][0] == '-'))
    {
    /*
	Parameter #1 is the options list "-U", possibly with "A" and/or "D".
    */
    umem = FALSE;			/* Flag for "U" not yet seen */
    for (pp = argv[1]++; *argv[1] != NUL; ++argv[1])
	if ((_toupper(*argv[1])) == 'U')
	    umem = TRUE;
	else if ((_toupper(*argv[1])) == 'A')
	    set_account = TRUE;		/* "-UA" changes account also */
	else if ((_toupper(*argv[1])) == 'D')
	    set_default = TRUE;		/* "-UD" does "SET DEFAULT" also */
	else if (*argv[1] != NUL)
	    {
	    fprintf( stderr, "%%SETUSER-E-UNKOPT, unknown option '%c'\n",
		     *argv[1]);
	    ERROR_EXIT;
	    }
    if (!umem)
	{
	fprintf( stderr, "%%SETUSER-E-BADOPTS, bad option list '%s'\n", pp);
	ERROR_EXIT;
	}

    /*
	Parameter #2 is the username text, if no such parameter, prompt for its
	entry and keep doing so until it is entered.
    */
    if (argc < 3)
	do
	    printf( "Username: ");
	    while (((pp = gets( aparam)) == NULL) || (*pp == '\0'));
    else
	pp = argv[2];			/* Entered on command line */

    /*
	Check the username length (must be < 12 characters) and uppercase it.
    */
    if (strlen( pp) > UAF$S_USERNAME)
	{
	fprintf( stderr, "%%SETUSER-E-USRTOOLNG, username too long '%s'\n", pp);
	ERROR_EXIT;
	}
    tp = pp;				/* Set temporary text pointer */
    do
	*tp = _toupper( *tp);
	while (*++tp != NUL);

#ifdef DEBUG
    printf( "UAF key: %s\n", pp);
#endif					/* DEBUG */
    /*
	Now just read the record from the User Authorization File using the
	username as the index key.  Change account name from UAF only if the
	"A" option was seen.
    */
    access_uaf( pp);
    if (set_account)
	args.acctp = uafrec.uaf$t_account;

    }
/*
  Here we assume we have the non-UAF form where at least the username and
  UIC are explicitly provided.
*/
else		
   {
    /*
	Parameter #1 is the username text, if no such parameter, prompt for its
	entry and keep doing so until it is entered.
    */
    if (argc < 2)
	do
	    printf( "Username: ");
	    while (((pp = gets( aparam)) == NULL) || (*pp == '\0'));
    else
	pp = argv[1];			/* Entered on command line */

    /*
	Check the username length (must be < 12 characters) and copy to fixed
	buffer (padding with blanks and uppercasing it).
    */
    if (strlen( pp) > UAF$S_USERNAME)
	{
	fprintf( stderr, "%%SETUSER-E-USRTOOLNG, username too long '%s'\n", pp);
	ERROR_EXIT;
	}
    padded_copy_toupper( pp, UAF$S_USERNAME, uafrec.uaf$t_username);

    /*
	Parameter #2 is the UIC text, if no such parameter, prompt for its
	entry and keep doing so until it is entered.
    */
    if (argc < 3)
	do
	    printf( "Uic: ");
	    while (((pp = gets( aparam)) == NULL) || (*pp == '\0'));
    else
	pp = argv[2];			/* Entered on command line */

    /*
	Convert UIC from text format of "[ggg,mmm]" into binary.  The group and
	member numbers ("ggg" and "mmm") are assumed to be in octal.
    */
    if (sscanf( pp, "[%o,%o]", &ugrp, &umem) < 2)
	{
	fprintf( stderr, "%%SETUSER-E-UICFMT, UIC format error '%s'\n", pp);
	ERROR_EXIT;
	}
    /*
	Combine UIC parts into single UIC longword.
    */
    uafrec.uaf$w_grp = ugrp;
    uafrec.uaf$w_mem = umem;

    /*
	Parameter #3 is the optional account name text.  If found, copy to
	fixed buffer and uppercase it, then modify the kernel-mode routine's
	argument list.
    */
    if (argc > 3)
	{
	if (strlen( argv[3]) > UAF$S_ACCOUNT)
	    {
	    fprintf( stderr, "%%SETUSER-E-ACTTOOLNG, account too long '%s'\n",
		     argv[3]);
	    ERROR_EXIT;
	    }
	args.acctp = uafrec.uaf$t_account;
	padded_copy_toupper( argv[3], UAF$S_ACCOUNT, uafrec.uaf$t_account);
	}
    }

/*
  Now execute kernel mode routine to change UIC and username (and optionally
  the account name).
*/
#ifdef DEBUG
fix_printf( "Username: %s", args.userp, UAF$S_USERNAME);
printf( "  UIC: [%o,%o]", args.uicp->grp, args.uicp->mem);
if (args.acctp == NULL)
    printf("  Account: <none>\n");
else
    fix_printf("  Account: %s\n", args.acctp, UAF$S_ACCOUNT);
#else					/* NOT DEBUG */
sts = SYS$CMKRNL( SET_UIC_USER, &args);
ABORT_ON_FAILURE( sts);
#endif					/* DEBUG */

/*
  If optionally doing an implicit "SET DEFAULT", change the definition of the
  supervisor-mode (DCL) logical name "SYS$DISK" to change the default device
  and explicitly change the default directory.
*/
if (set_default)
    {
    /*
	Setup string descriptor for new default device name using the counted
	text string in the UAF record area.
    */
    pp = uafrec.uaf$t_defdev;
    $LENGTH(d_string) = *pp++;		/* Get string length, point to text */
    $POINTER(d_string) = pp;		/* Set pointer to string text */
#ifdef  DEBUG
    fix_printf( "Default device: %s\n", $POINTER(d_string), $LENGTH(d_string));
#else					/* NOT DEBUG */
    sts = LIB$SET_LOGICAL( &d_sysdisk, &d_string);
    ABORT_ON_FAILURE( sts);
#endif					/* DEBUG */

    /*
	Setup string descriptor for new default directory using the counted
	text string in the UAF record area.
    */
    pp = uafrec.uaf$t_defdir;
    $LENGTH(d_string) = *pp++;		/* Get string length, point to text */
    $POINTER(d_string) = pp;		/* Set pointer to string text */
#ifdef  DEBUG
    fix_printf( "Default directory: %s\n", $POINTER(d_string),
					   $LENGTH(d_string));
#else					/* NOT DEBUG */
    sts = SYS$SETDDIR( &d_string, 0, 0);
    ABORT_ON_FAILURE( sts);
#endif					/* DEBUG */

    }

_exit( 1);
}

/*+/ACCESS_UAF

 Functional Description:
	Get record from User Authorization File, key on username.

 Input Parameters:
	username - NUL-terminated string with user name key.

 Implicit Inputs:
	NONE

 Output Parameters:
	NONE

 Implicit Outputs:
	uafrec - buffer into which the UAF record is read.

 Condition Codes:
	All errors are signalled.

 Side Effects:
	NONE
-*/

#define RMS_ABORT( sts, stv)	if ($VMS_FAILURE( sts)) LIB$STOP( sts, stv);

access_uaf( username )
char *username;				/* Username for key (NUL-term) */
{
struct FAB uaf_fab;			/* UAF file access block */
struct RAB uaf_rab;			/* UAF record access block */
int strlen();				/* Return length of NUL-term string */
status sts;				/* Completion status */
status SYS$OPEN();			/* RMS open file service */
status SYS$CONNECT();			/* RMS record stream setup service */
status SYS$GET();			/* RMS record input service */
status SYS$CLOSE();			/* RMS close file service */
int try_count = 10;			/* Record read retry count */
status SYS$SCHDWK();			/* Scheduled wakeup service */
static readonly char filename[]		/* UAF file name or logical name */
		= "SYSUAF";
static readonly char file_defaults[]	/* Defaults for UAF file */
		= "SYS$SYSTEM:.DAT";
static readonly vmstime asecond		/* One second in system time units */
		= { -(10*1000*1000), -1 };

/*
  Initialize the RMS structures.
*/
uaf_fab = cc$rms_fab;
uaf_rab = cc$rms_rab;

/*
  Fill in the File Access Block.
*/
uaf_fab.fab$l_dna = file_defaults;	/* Default file string address/size */
uaf_fab.fab$b_dns = (sizeof file_defaults) - 1;
uaf_fab.fab$l_fna = filename;		/* File name string address/size */
uaf_fab.fab$b_fns = (sizeof filename) - 1;
uaf_fab.fab$b_fac = FAB$M_GET;		/* Readonly */
uaf_fab.fab$b_shr =			/* Maximal sharing with others */
		FAB$M_GET|FAB$M_PUT|FAB$M_DEL|FAB$M_UPD;

/*
  Fill in the Record Access Block.
*/
uaf_rab.rab$l_fab = &uaf_fab;		/* Pointer to File Access Block */
uaf_rab.rab$l_kbf = username;		/* Key buffer address/size */
uaf_rab.rab$b_ksz = strlen( username);
uaf_rab.rab$b_krf = 0;			/* Key #0 */
uaf_rab.rab$b_rac = RAB$C_KEY;		/* Access by key */
uaf_rab.rab$l_rop = RAB$M_NLK;		/* Do not lock just-read record */
uaf_rab.rab$l_ubf = &uafrec;		/* Record buffer address/size */
uaf_rab.rab$w_usz = sizeof uafrec;

/*
  Open User Authorization File and setup record stream.
*/
sts = SYS$OPEN( &uaf_fab);
RMS_ABORT( sts, uaf_fab.fab$l_stv);
sts = SYS$CONNECT( &uaf_rab);
RMS_ABORT( sts, uaf_rab.rab$l_stv);

/*
  Read record, retry several times if record is locked.
*/
do {
    sts = SYS$GET( &uaf_rab);
    if ($VMS_FAILURE( sts))
	if (sts == RMS$_RLK)
	    { /* Sleep for one second before retry-ing record read */
	    sts = SYS$SCHDWK( 0, 0, &asecond, 0);
	    ABORT_ON_FAILURE( sts);
	    SYS$HIBER();
	    }
	else if (sts == RMS$_RTB)
	    sts |= STS$K_SUCCESS;	/* Long record is OK! */ 
	else if (sts == RMS$_RNF)
	    {
	    fprintf( stderr, "%%SETUSER-F-NOUSER, no such user '%s' in UAF\n",
		     username);
	    ERROR_EXIT;
	    }
	else
	    LIB$STOP( sts, uaf_rab.rab$l_stv);
    } while ($VMS_FAILURE( uaf_rab.rab$l_sts) && (--try_count > 0));
RMS_ABORT( uaf_rab.rab$l_sts, uaf_rab.rab$l_stv);

/*
  Done with UAF, close file.
*/
sts = SYS$CLOSE( &uaf_fab);
RMS_ABORT( sts, uaf_fab.fab$l_stv);

}

#ifdef  DEBUG
fix_printf( fmt, str, len)
char *fmt;				/* Format string */
char *str;				/* Pointer to fixed-length string */
int len;				/* String length */
{
char buffer[PARM_LEN];			/* Temporary string buffer */
char *bp;				/* Buffer pointer */

strncpy( buffer, str, len);
*(bp = &buffer[len]) = ' ';
while (*bp == ' ')
    *bp-- = NUL;
printf( fmt, buffer);
}
#endif

/*+/PADDED_COPY_TOUPPER

 Functional Description:
	Copy NUL-terminated string to fixed-length output buffer, padding
	with blanks to the full output length.  Characters from the input
	string are converted to uppercase before being stored in the output
	buffer.

 Input Parameters:
	istring - pointer to input string (NUL-terminated).
	fixlen - length of output string buffer.

 Implicit Inputs:
	NONE

 Output Parameters:
	ostring - pointer to fixed-length (not NUL-terminated) output string
		  buffer.

 Implicit Outputs:
	NONE

 Condition Codes:
	NONE

 Side Effects:
	Output string is all in uppercase.
-*/

padded_copy_toupper( istring, fixlen, ostring)
char *istring;				/* Input NUL-terminated string */
int fixlen;				/* Length of output buffer */
char *ostring;				/* Fixed-length output string */
{
char toupper();				/* Uppercasing function */

/*
  Loop over entire output buffer by running down (local copy) of the buffer
  length.  Once the NUL character terminating the input string is seen, just
  put blanks into the output string, else convert input character to uppercase
  and put into the output string.
*/
for (; fixlen > 0; fixlen--)
    if (*istring == NUL)
	*ostring++ = ' ';		/* Put blank and advance pointer */
    else
	*ostring++ = toupper( *istring++);

}
