/* 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 <utcblkdef.h>
#include <acmedef.h>
#include <jpidef.h>
#include <efndef.h>
#include <descrip.h>
#include <string.h>
#include <smgdef.h>
#include <acmemsgdef.h>
#include <iledef.h>
#include <lib$routines.h>
#include <smg$routines.h>
#include <starlet.h>

#include "errchk.h"


/******************************************************************************/
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;
        }
            
        if (strchr (valid, (char)toupper (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) {
/*
** This code uses SYS$ACMW () to duplicate the SET PASSWORD command.
*/

static ILE3 acmitms[6];

static ACMESB acmesb;

static int r0_status;
static int jpi_code = JPI$_USERNAME;
static int i;
static int flags = ACMEPWDFLG$M_PASSWORD_1;
static int logon_type = ACME$K_LOCAL;

static char username[12];
static char old_pwd[31];
static char new_pwd1[31];
static char new_pwd2[31];

static struct dsc$descriptor_s username_d = { sizeof (username),
                                              DSC$K_DTYPE_T,
                                              DSC$K_CLASS_S,
                                              username };

static struct dsc$descriptor_s old_pwd_d = { sizeof (old_pwd),
                                             DSC$K_DTYPE_T,
                                             DSC$K_CLASS_S,
                                             old_pwd };

static struct dsc$descriptor_s new_pwd1_d = { sizeof (new_pwd1),
                                              DSC$K_DTYPE_T,
                                              DSC$K_CLASS_S,
                                              new_pwd1 };

static struct dsc$descriptor_s new_pwd2_d = { sizeof (new_pwd2),
                                              DSC$K_DTYPE_T,
                                              DSC$K_CLASS_S,
                                              new_pwd2 };

    /*
    ** Get the username of this process.
    */
    r0_status = lib$getjpi (&jpi_code,
                            0,
                            0,
                            0,
                            &username_d,
                            0);
    errchk_sig (r0_status);

    /*
    ** Fix the descriptor length,
    */
    for (i = 0; i < sizeof (username); i++) {
        if (isspace (username[i])) {
            username_d.dsc$w_length = i;
            break;
        }
    }

    /*
    ** Get old password.
    */
    (void)printf ("Old password: ");
    (void)fflush (stdout);
    r0_status = read_pwd (&old_pwd_d);
    errchk_sig (r0_status);

    /*
    ** Get new password.
    */
    (void)printf ("New password: ");
    (void)fflush (stdout);
    r0_status = read_pwd (&new_pwd1_d);
    errchk_sig (r0_status);

    /*
    ** Get new password again.
    */
    (void)printf ("Verification: ");
    (void)fflush (stdout);
    r0_status = read_pwd (&new_pwd2_d);
    errchk_sig (r0_status);

    /*
    ** Make sure the new password and the verification match.
    */
    if ((new_pwd1_d.dsc$w_length != new_pwd2_d.dsc$w_length) ||
        (memcmp (new_pwd1_d.dsc$a_pointer,
                 new_pwd2_d.dsc$a_pointer,
                 new_pwd1_d.dsc$w_length) != 0)) {
        (void)printf ("New password verification errror\n");
        exit (EXIT_FAILURE);
    }

    /*
    ** Set up the item list for the call to SYS$ACMW ()
    */
    acmitms[0].ile3$w_length = username_d.dsc$w_length;
    acmitms[0].ile3$w_code = ACME$_PRINCIPAL_NAME_IN;
    acmitms[0].ile3$ps_bufaddr = username;
    acmitms[0].ile3$ps_retlen_addr = NULL;
    acmitms[1].ile3$w_length = old_pwd_d.dsc$w_length;
    acmitms[1].ile3$w_code = ACME$_PASSWORD_1;
    acmitms[1].ile3$ps_bufaddr = old_pwd;
    acmitms[1].ile3$ps_retlen_addr = NULL;
    acmitms[2].ile3$w_length = new_pwd1_d.dsc$w_length;
    acmitms[2].ile3$w_code = ACME$_NEW_PASSWORD_1;
    acmitms[2].ile3$ps_bufaddr = new_pwd1;
    acmitms[2].ile3$ps_retlen_addr = NULL;
    acmitms[3].ile3$w_length = 4;
    acmitms[3].ile3$w_code = ACME$_NEW_PASSWORD_FLAGS;
    acmitms[3].ile3$ps_bufaddr = &flags;
    acmitms[3].ile3$ps_retlen_addr = NULL;
    acmitms[4].ile3$w_length = 4;
    acmitms[4].ile3$w_code = ACME$_LOGON_TYPE;
    acmitms[4].ile3$ps_bufaddr = &logon_type;
    acmitms[4].ile3$ps_retlen_addr = NULL;
    acmitms[5].ile3$w_length = 0;
    acmitms[5].ile3$w_code = 0;
    acmitms[5].ile3$ps_bufaddr = NULL;
    acmitms[5].ile3$ps_retlen_addr = NULL;

    /*
    ** Do the password change.
    */
    r0_status = sys$acmw (EFN$C_ENF,
                          ACME$_FC_CHANGE_PASSWORD,
                          0,
                          acmitms,
                          &acmesb,
                          0,
                          0);
    errchk_sig (r0_status);
    switch (acmesb.acmesb$l_status) {
        case SS$_INVPWDLEN :
        case SS$_PWDNOTDIF :
        case SS$_PWDINDIC :
        case SS$_PWDINHIS :
        case SS$_PWDWEAK :
        case ACME$_PWDINHISTORY :
        case ACME$_AUTHFAILURE :
        case ACME$_PWDTOOSHORT :
        case ACME$_INVNEWPWD :
            /*
            ** Possible error returns from change password function.
            */
            r0_status = acmesb.acmesb$l_status;
            break;
        default:
            errchk_sig (acmesb.acmesb$l_status);
            (void)printf ("Successfully changed password\n");
            r0_status = SS$_NORMAL;
            break;
    }
    exit (r0_status);
}

Back to the master examples list.