/* Copyright 2003-2023 James F. Duff */
/* License and disclaimer: http://www.eight-cubed.com/disclaimer.html */
#define __NEW_STARLET 1
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <ssdef.h>
#include <stsdef.h>
#include <uaidef.h>
#include <jpidef.h>
#include <efndef.h>
#include <descrip.h>
#include <string.h>
#include <smgdef.h>
#include <iosbdef.h>
#include <gen64def.h>
#include <lib$routines.h>
#include <smg$routines.h>
#include <starlet.h>
#include "errchk.h"
struct itm3 {
unsigned short int len;
unsigned short int code;
void *bufadr;
void *buflen;
};
/******************************************************************************/
static int read_pwd (struct dsc$descriptor_s *output_d) {
/*
** Bonus function: how to read a single character or single keypress at a
** time with no echo on VMS.
** This routine uses the SMG$ API, which allows you to do device independant
** terminal I/O on VMS very efficiently (similar to Curses on Un*x).
*/
static char *p;
static int r0_status;
static unsigned int kbid;
static int i;
static unsigned short int key;
static short int max_len;
static char done;
static const char valid[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890$_";
/*
** Create a virtual keyboard to read input.
*/
r0_status = smg$create_virtual_keyboard (&kbid,
0,
0,
0);
errchk_sig (r0_status);
max_len = output_d->dsc$w_length;
p = output_d->dsc$a_pointer;
i = 0;
done = FALSE;
while (!done) {
if (i == max_len) {
done = TRUE;
continue;
}
r0_status = smg$read_keystroke (&kbid,
&key,
0,
0,
0,
0,
0);
if (r0_status == SMG$_EOF) {
/*
** Handle ctrl-z just like return.
*/
output_d->dsc$w_length = i;
done = TRUE;
continue;
} else {
/*
** Return unexpected errors to our caller.
*/
errchk_ret (r0_status);
}
if (key == '\r') {
/*
** User hit return. Update length of output and exit loop
*/
output_d->dsc$w_length = i;
done = TRUE;
continue;
}
key = toupper (key);
if (strchr (valid, (char)key) == NULL) {
if (key == SMG$K_TRM_DELETE) {
/*
** If the key is delete, then backspace and overwrite our last
** star unless we are at position zero, in which case, just
** beep.
*/
if (i > 0) {
(void)printf ("\b \b");
i--;
*p--;
} else {
(void)printf ("\a");
}
} else {
/*
** An invalid character for passwords, just beep. Of course,
** in a production program, the code would accept the invalid
** character and eventually just return a status indicating
** the entire password was invalid (without bothering to call
** $hash_password), as telling the possible black hat that the
** individual character is invalid is giving them info about
** the system...
*/
(void)printf ("\a");
if (i > 0) {
i--;
*p--;
}
}
} else {
/*
** Add the valid character to the output string and print out a
** star so the user knows they hit a key.
*/
(void)printf ("*");
(void)fflush (stdout);
*p++ = (char)key;
i++;
}
}
(void)printf ("\n");
r0_status = smg$delete_virtual_keyboard (&kbid);
errchk_sig (r0_status);
return SS$_NORMAL;
}
/******************************************************************************/
int main (void) {
static IOSB iosb;
static GENERIC_64 pwd;
static GENERIC_64 hash_result;
static int r0_status;
static unsigned short int salt;
static char algorithm;
static char username[12];
static char password[31];
static struct itm3 jpiitms[] =
{ sizeof (username), JPI$_USERNAME, username, NULL,
0, 0, NULL, NULL };
static struct itm3 uaiitms[] =
{ sizeof (salt), UAI$_SALT, &salt, NULL,
sizeof (algorithm), UAI$_ENCRYPT, &algorithm, NULL,
sizeof (pwd), UAI$_PWD, &pwd, NULL,
0, 0, NULL, NULL };
static struct dsc$descriptor_s username_d = { 0,
DSC$K_DTYPE_T,
DSC$K_CLASS_S,
username };
static struct dsc$descriptor_s password_d = { sizeof (password),
DSC$K_DTYPE_T,
DSC$K_CLASS_S,
password };
/*
** Get our username.
*/
r0_status = sys$getjpiw (EFN$C_ENF,
0,
0,
jpiitms,
&iosb,
0,
0);
errchk_sig (r0_status);
errchk_sig (iosb.iosb$l_getxxi_status);
/*
** Fix the descriptor.
*/
for (int i = 0; i < sizeof (username); i++) {
if (isspace (username[i])) {
username_d.dsc$w_length = i;
break;
}
}
/*
** Get the salt, the encryption algorithm, and the hashed password for
** our account. Note you don't need privilege to look up your own info,
** but if you wish to modify this code to check the password for other
** usernames, you need either grpprv or sysprv (or read access to SYSUAF).
*/
r0_status = sys$getuai (0,
0,
&username_d,
uaiitms,
0,
0,
0);
errchk_sig (r0_status);
(void)printf ("Enter password for %-.*s: ",
username_d.dsc$w_length,
username_d.dsc$a_pointer);
(void)fflush (stdout);
/*
** Read the password with no echo, validating the charaters as we go.
*/
r0_status = read_pwd (&password_d);
errchk_sig (r0_status);
/*
** Hash the password the user just typed using the salt and encryption
** algorithm we got via $GETUAI previously.
*/
r0_status = sys$hash_password (&password_d,
algorithm,
salt,
&username_d,
&hash_result);
errchk_sig (r0_status);
/*
** See if the new hash matches the one we got via $GETUAI
*/
if (pwd.gen64$q_quadword == hash_result.gen64$q_quadword) {
(void)printf ("Correct password entered\n");
} else {
(void)printf ("Incorrect password entered\n");
}
}