/*  DWPROFILE.C -- Authorize/Profile/DECburger replacement     */
/*  Copyright © 1990,1991,1992 Bruce Tanner - Cerritos College */

/*
 *  Revision history:
 *   9/4/90   Version 1.0
 *   9/12/90  Version 1.1
 *            Added popup menu for access, privs boxes.
 *            Forced 12 point Helvetica Bold font.
 *            Moved lots of stuff to the resource file.
 *            Added date of last login, logfails.
 *   10/10/90 Version 2.0
 *            Create/delete directory.
 *            Add/delete disk quota.
 *            Grant/revoke identifiers.
 *            Take username from command line.
 *   11/28/90 Version 2.1
 *            Fixed: Dup UIC from calc_uic() on multiple adds from same record.
 *            Fixed: Identifier not changed on UIC change.
 *   8/21/91  Version 2.2
 *            Fixed: directory creation; add quota THEN create directory.
 *            Check for blank owner, account DEFAULT, defdir [USER]
 *            Fixed: New account created bad UIC.
 *   10/9/92  Version 2.3
 *            Keep list of identifiers up to date.
 *            Minor code cleanup.
 *   10/27/92 Version 3.0
 *            Converted to Motif.
 *   11/4/92  Version 3.1
 *            Added Select menu and Find Next button.
 *   11/13/92 Version 3.2
 *            Added Help menu item.
 *   11/30/92 Version 3.3
 *            Beta test bug fixes and the usual code randomizing.
 */

#define TITLE "DWProfile"
#module DWProfile "DWProfile V3.3"
#include descrip
#include string
#include ssdef
#include stdio
#include clidef
#include jpidef
#include prvdef
#include rmsdef
#include strdef
#include str$routines
#include <decw$cursor.h>
#include "dwpdef.h"
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/MwmUtil.h>
#include <Mrm/Mrm.h>
#include <Xm/RowColumn.h>
#include <Xm/MessageB.h>
#include <Xm/SelectioB.h>
#include <Xm/DrawingA.h>
#include <DXm/DECspecific.h>
#include <DXm/DXmHelpB.h>

static MrmHierarchy Hierarchy;
static MrmType class;
static XtAppContext context;
static Widget toplevel, dwprofile_main, access_box, privs_box,
    widget_array[MAX_WIDGET], flags_array[MAX_FLAGS],
    prime_array[MAX_PRIME], privs_array[MAX_PRIVS],
    button_array[MAX_MENUS], idents_box, accounts_box, work_box;
static void init_application(), get_gc(), update_prime(),
    update_time(Cardinal), update_strings(), update_flags(),
    update_privs(), set_translations(), set_handlers(),
    trans_uic(Cardinal), make_cursor(), build_popup(),
    update_held(), set_defaults(), build_accounts(), pos_functions(),
    DWPerror(String, String, int), DWPmsg(String, Cardinal),
    progress(ident *), read_identifiers(Boolean);
static Boolean get_record(String), parse_uic(int);
static XtEventHandler popup_handler(), access_handler();
static XtCallbackProc WidgetCreated(), WidgetChanged(),
    Quit(), Write(), Read(), Find(), Remove(), Access(),
    Privs(), Accounts(), Idents(),
    FlagsCreated(), FlagsChanged(),
    PrimeCreated(), PrimeChanged(),
    PrivsCreated(), PrivsChanged(),
    AccountChanged(), CautionCallback(),
    MessageCallback(), ReadIdentifiers(),
    ListSelect(), IdentsCancelCallback(),
    ButtonCreated(), Select(), FindCancelCallback(),
    Help(), DestroyHelpCallback();
static XtActionProc return_key(), select_key();
static MrmRegisterArg regvec[] = {
    {"WidgetCreated", WidgetCreated},
    {"WidgetChanged", WidgetChanged},
    {"Quit", Quit},
    {"Write", Write},
    {"Read", Read},
    {"Find", Find},
    {"Remove", Remove},
    {"Access", Access},
    {"Privs", Privs},
    {"Accounts", Accounts},
    {"FlagsCreated", FlagsCreated},
    {"FlagsChanged", FlagsChanged},
    {"PrimeCreated", PrimeCreated},
    {"PrimeChanged", PrimeChanged},
    {"PrivsCreated", PrivsCreated},
    {"PrivsChanged", PrivsChanged},
    {"ReadIdentifiers", ReadIdentifiers},
    {"Idents", Idents},
    {"ListSelect", ListSelect},
    {"ButtonCreated", ButtonCreated},
    {"Select", Select},
    {"Help", Help}
};
static char return_translation[] = "<Key>Return: return_key()\n\
    <Key>KP_Enter: return_key()",
    select_translation[] = "<Key>osfSelect: select_key()";
static XtActionsRec action_table[] = {
    {"return_key", return_key},
    {"select_key", select_key}
};                                             
static String fallbacks[] = {
    "*fontList: -Adobe-Helvetica-Bold-R-Normal--12-120-75-75-P-*-*-*",
    NULL
};
static String select_string, unpad(String);
static ApplicationData resource_data;
static struct {
    int idata;
    Boolean changed;
    char sdata[SDATA_LEN], odata[SDATA_LEN];
    } uafdata[MAX_WIDGET];
static struct itemlist3 itmlst[MAX_WIDGET];
static Display *top_display;
static Window top_window;
static GC gc_draw, gc_clear;
static Boolean setting_widgets, setting_prime, setting_flags, setting_privs;
static ident *ident_list = NULL, *select_ptr = NULL, *ident_ptr, *found_ptr,
     *found_add, found_list = {NULL, 0, 0, NULL};
static held_ident *held_list = NULL;
static Cardinal uic_count, account_count, ident_count, select_index;
static XtIntervalId timeout_id, progress_id;
static Cursor wait_cursor;
static XtWorkProcId idents_work_proc_id, find_work_proc_id;


main(unsigned int argc, char *argv[])
{
    Cardinal status, ac;
    Arg arglist[5];
    Pixmap icon_pixmap;

    init_application();
    MrmInitialize();
    DXmInitialize();
    ac = 0;
    XtSetArg(arglist[ac], XmNtitle, TITLE); ac++;
    XtSetArg(arglist[ac], XmNmwmDecorations,
             MWM_DECOR_MENU |MWM_DECOR_BORDER | MWM_DECOR_TITLE |
             MWM_DECOR_MINIMIZE); ac++;
    toplevel = XtAppInitialize(&context, "DWProfile", NULL, 0, &argc, argv,
                               fallbacks, arglist, ac);

    status = MrmOpenHierarchy(XtNumber(uidname), uidname, NULL, &Hierarchy);
    if (status != MrmSUCCESS) lib$signal(status);

    status = MrmRegisterNames(regvec, XtNumber(regvec));
    if (status != MrmSUCCESS) lib$signal(status);

    if ((MrmFetchWidget(Hierarchy, "dwprofile_main", toplevel,
                        &dwprofile_main, &class)) != MrmSUCCESS)
        XtAppError(context, "Error Fetching main widget");

    XtGetApplicationResources(toplevel, &resource_data, resources,
                              XtNumber(resources), NULL, 0);

    XtManageChild(dwprofile_main);
    XtRealizeWidget(toplevel);

    top_display = XtDisplay(toplevel);
    top_window = XtWindow(toplevel);
    icon_pixmap = XCreateBitmapFromData(top_display, top_window,
                                        dwp32_bits, dwp32_width, dwp32_height);

    /* Set up the WM icons via shell widget attributes */
    ac = 0;
    XtSetArg(arglist[ac], XmNiconPixmap, icon_pixmap); ac++;
    XtSetValues(toplevel, arglist, ac);

    make_cursor();             /* create wait_cursor */
    set_translations();        /* set text widget translations */
    build_popup();             /* build a popup menu */
    get_gc();                  /* set up stuff for access windows */
    pos_functions();           /* center the functions box */

    if (argc == 1)
        get_record("");        /* Initialize text fields */

    /* load username if given on command line */
    if (argc == 2)
        if (!get_record(argv[1]))
            if (get_record("DEFAULT"))
                set_defaults(argv[1]);
            else
                DWPerror("Main Init", "Cannot read username DEFAULT", 0);

    /* set up the windows based on resource file */
    if (resource_data.show_access) Access(NULL, NULL, NULL);
    if (resource_data.show_privs) Privs(NULL, NULL, NULL);
    if (resource_data.show_accounts) Accounts(NULL, NULL, NULL);
    if (resource_data.show_idents) Idents(NULL, NULL, NULL);

    /* traverse to the username widget */
    XmProcessTraversal(widget_array[UAI$_USERNAME], XmTRAVERSE_CURRENT);

    XtAppMainLoop(context);
}


/*** Initialization Routines ***/

/*
 *  Set sysprv
 *  Clear out widget arrays
 */

static void init_application()
{
    Cardinal ind, status, my_privs[2],
        jpicode = JPI$_AUTHPRIV, priv_mask[2] = {0, 0};

    status = lib$getjpi(&jpicode, 0, 0, &my_privs, 0, 0);
    if (status != SS$_NORMAL)
        XtAppError(context, "lib$getjpi failed");
       
    if (my_privs[0] & (PRV$M_SYSPRV | PRV$M_SETPRV)) {
        priv_mask[0] |= PRV$M_SYSPRV;
        status = sys$setprv(ON, &priv_mask, 0, 0);
        if (status != SS$_NORMAL)
            XtAppError(context, "sys$setprv failed");
    }
    else {
        printf("Insufficient privilege to see other than your own UAF record\n");
/*        exit(); */
    }

    for (ind = 0; ind < MAX_WIDGET; ind++) widget_array[ind] = NULL;
    for (ind = 0; ind < MAX_FLAGS; ind++) flags_array[ind] = NULL;
    for (ind = 0; ind < MAX_PRIVS; ind++) privs_array[ind] = NULL;
    for (ind = 0; ind < MAX_PRIME; ind++) prime_array[ind] = NULL;
}


/*
 *  Load the cursor font and allocate colors for the cursor
 */

static void make_cursor()
{
    Colormap cmap;
    XColor red, white, dummy;
    Font cursorfont;

    cmap = DefaultColormap(top_display, DefaultScreen(top_display));

    XAllocNamedColor(top_display, cmap, "white", &white, &dummy);
    XAllocNamedColor(top_display, cmap, "red", &red, &dummy);

    cursorfont = XLoadFont(top_display, "DECW$CURSOR");
    wait_cursor = XCreateGlyphCursor(top_display, cursorfont, cursorfont,
                  decw$c_wait_cursor, decw$c_wait_cursor+1, &red, &white);

}


/*
 *  Override the translation table of the Username, Account text widgets
 *  Override Select translation for selectable fields
 */

static void set_translations()
{
    Cardinal si;
    XtTranslations compiled_trans;

    XtAppAddActions(context, action_table, XtNumber(action_table));

    compiled_trans = XtParseTranslationTable(return_translation);
    XtOverrideTranslations(widget_array[UAI$_USERNAME], compiled_trans);
    XtOverrideTranslations(widget_array[UAI$_ACCOUNT], compiled_trans);

    compiled_trans = XtParseTranslationTable(select_translation);
    for(si = 0; select_data[si].code; si++)
        if (select_data[si].type != DWP_RINT)
            XtOverrideTranslations(widget_array[select_data[si].code], compiled_trans);
}


/*
 *  Set up event handlers for access windows
 */

static void set_handlers()
{
    Cardinal wi;

    for (wi = UAI$_NETWORK_ACCESS_P; wi <= UAI$_REMOTE_ACCESS_S; wi++)
        if (widget_array[wi])
            XtAddEventHandler(widget_array[wi],
                              ButtonPressMask | ButtonReleaseMask | ExposureMask |
                              Button1MotionMask, FALSE, access_handler, wi);
}


/*
 *  Build a MenuPopup widget and children
 */

static void build_popup()
{
#define MENUS 4
    Arg arglist[2];
    Cardinal ac, ind;
    struct {
        String resource;
        String title;
        XtCallbackProc callback;
    } params[MENUS] = {
        {"access_button", "Access...", Access},
        {"privs_button", "Privs...", Privs},
        {"accounts_button", "Accounts...", Accounts},
        {"idents_button", "Identifiers...", Idents}
    };
    Widget menu_items[MENUS], popup_menu;

    popup_menu = XmCreatePopupMenu(dwprofile_main, "popup", NULL, 0);
    /* pass popup_menu as the client data for popup() */
    XtAddEventHandler(dwprofile_main, ButtonPressMask, FALSE, popup_handler, popup_menu);

    for (ind = 0; ind < MENUS; ind++) {
        ac = 0;
        XtSetArg(arglist[ac], XmNlabelString, XmStringCreateSimple(params[ind].title)); ac++;
        menu_items[ind] = XmCreatePushButtonGadget(popup_menu, params[ind].resource, arglist, ac);
        XtAddCallback(menu_items[ind], XmNactivateCallback, params[ind].callback, 0);
    }

    XtManageChildren(menu_items, ind);   /* just the buttons, not the menu */
}


/*
 *  Set up the graphic contexts gc_draw and gc_clear
 */

static void get_gc()
{
    Cardinal ac;
    Arg arglist[2];
    XGCValues gc_values;
    Pixel foreground, background;

    ac = 0;
    XtSetArg(arglist[ac], XmNforeground, &foreground);  ac++;
    XtSetArg(arglist[ac], XmNbackground, &background);  ac++;
    XtGetValues(dwprofile_main, arglist, ac);
    gc_values.foreground = foreground;
    gc_values.background = background;
    gc_draw = XtGetGC(dwprofile_main, GCForeground | GCBackground, &gc_values);
    gc_values.foreground = background;
    gc_clear = XtGetGC(dwprofile_main, GCForeground, &gc_values);
}



/*** Utility Routines ***/

/*
 *  Set the username, password and directory name
 *  Called by main, Read
 */

static void set_defaults(String user)
{
    Arg arg[1];
    char text[40];

    /* SetString sets .sdata and .changed */
    XmTextSetString(widget_array[UAI$_USERNAME], user);
    XmTextSetString(widget_array[UAI$_PWD], user);
    sprintf(uafdata[UAI$_DEFDIR].sdata, "[%s]", user);
    XmTextSetString(widget_array[UAI$_DEFDIR], uafdata[UAI$_DEFDIR].sdata);

    sprintf(text, "%s\n%s", TITLE, user);
    XtSetArg(arg[0], XmNiconName, text);
    XtSetValues(toplevel, arg, 1); /* update the icon name */
}


/*
 *  Center the functions box
 */

static void pos_functions()
{
  Arg arglist[5];
  Cardinal ac;
  Dimension funct_width, main_width, funct_x, new_x;

  ac = 0;
  XtSetArg(arglist[ac], XmNwidth, &funct_width); ac++;
  XtSetArg(arglist[ac], XmNx, &funct_x); ac++;
  XtGetValues(button_array[functions_menu], arglist, ac);
  ac = 0;
  XtSetArg(arglist[ac], XmNwidth, &main_width); ac++;
  XtGetValues(dwprofile_main, arglist, ac);
  new_x = (main_width - funct_width) / 2;
  ac = 0;
  XtSetArg(arglist[ac], XmNx, new_x); ac++;
  XtSetValues(button_array[functions_menu], arglist, ac);
}


/*
 *  Lookup a UAF record and fill those widgets
 *  Called by Read and program initialization
 */

static Boolean get_record(String acct)
{
    Cardinal status, ind, si, resid, rdbctx = 0, holder[2] = {0, 0};
    ushort namlen;
    ident *ptr;
    held_ident *hptr, *tptr;
    Arg arg[1];
    char text[40], nambuf[40];
    $DESCRIPTOR(acct_dsc, acct);
    $DESCRIPTOR(name_dsc, nambuf);

    /* reset all the data storage */
    for (ind = 0; ind < MAX_WIDGET; ind++) {
        memset(uafdata[ind].sdata, 0, SDATA_LEN);
        uafdata[ind].idata = 0;
        uafdata[ind].changed = FALSE;
    }

    /* acct = "" means clear form */
    if (strlen(acct)) { 
        acct_dsc.dsc$w_length = strlen(acct);
        for (ind = 0; ; ind++) {
            itmlst[ind].buflen = items[ind].size;
            itmlst[ind].itmcode = items[ind].code;
            if (!items[ind].code) break;    /* exit after copying zero entry */
            if (items[ind].size > 4)        /* anything that fits goes in .idata */
                itmlst[ind].bufadr = uafdata[items[ind].code].sdata;
            else
                itmlst[ind].bufadr = &uafdata[items[ind].code].idata;
            itmlst[ind].retadr = 0;
        }
        status = sys$getuai(0, 0, &acct_dsc, &itmlst, 0, 0, 0);

        if ((status & 7) != SS$_NORMAL)  /* often SS$_BUFFEROVF */
            return FALSE;    /* probably not found */

        for (ind = 0; items[ind].code; ind++)    /* reset changed flags */
            uafdata[items[ind].code].changed = FALSE;

        /* remember the old values of device and directory */
        memcpy(uafdata[UAI$_DEFDEV].odata, uafdata[UAI$_DEFDEV].sdata, 32);
        memcpy(uafdata[UAI$_DEFDIR].odata, uafdata[UAI$_DEFDIR].sdata, 64);
    }

    for (hptr = held_list; hptr;) { /* free old held list */
        tptr = hptr;
        hptr = hptr->next;
        XtFree(tptr);
    }
    held_list = NULL;

    if (strlen(acct)) {
        holder[0] = uafdata[UAI$_UIC].idata;
        for (hptr = held_list;;) {
            status = sys$find_held(&holder, &resid, 0, &rdbctx);
            if (status == SS$_NOSUCHID) break;
            if (status != SS$_NORMAL) {
                sys$finish_rdb(&rdbctx);
                DWPmsg("get_record", status);
                break;
            }
            if (hptr)
                hptr = hptr->next = XtNew(held_ident);
            else
                held_list = hptr = XtNew(held_ident);
            hptr->next = NULL;
            hptr->identifier = resid;
            hptr->status = original;
            status = sys$idtoasc(resid, &namlen, &name_dsc, 0, 0, 0);
            nambuf[namlen] = '\0';
            hptr->name = XtNewString(nambuf);
        }
    }

    uafdata[UAI$_PWD].sdata[0] = '\0';    /* discard password for Write()*/

    update_strings();      /* update the string widgets */
    update_flags();        /* update the flag toggle button widgets */
    update_prime();        /* update the primary days and time widgets */
    update_privs();        /* update the privs toggle button widgets */
    update_held();         /* update the list of held identifiers */

    if (strlen(acct)) {
        sprintf(text, "%s\n%s", TITLE, acct);
        XtSetArg(arg[0], XmNiconName, text);
        XtSetValues(toplevel, arg, 1); /* update the icon name */
    }
    return TRUE;
}


/*
 *  Find all UAI elements that have (text) widgets and update them
 */

static void update_strings()
{
    Cardinal ind, value, status;
    ushort length;
    char str[SDATA_LEN], *ptr;
    Arg arg[1];
    XmString xmstr;

    setting_widgets = TRUE;
    for (ind = 0; value = items[ind].code; ind++)
        if (widget_array[value])
            switch (items[ind].type) {
            case DWP_LALPHA:    /* string with length byte */
                /* can't use unpad() because owner field contains spaces */
                length = (ushort) uafdata[value].sdata[0];
                uafdata[value].sdata[length+1] = '\0';
                XmTextSetString(widget_array[value],
                                &uafdata[value].sdata[1]);
                break;
            case DWP_ALPHA:    /* string (username, account) */
                XmTextSetString(widget_array[value],
                    unpad(uafdata[value].sdata));
                break;
            case DWP_ENCR:    /* password */
                XmTextSetString(widget_array[value], "");  /* clear field */
                break;
            case DWP_DATE:    /* text date */
                {
                $DESCRIPTOR(time_dsc, str);

                sys$asctim(&length, &time_dsc, uafdata[value].sdata, 0);
                str[length] = '\0';
                ptr = strstr(str, ":00.00");
                if (ptr) *ptr = '\0';
                if (!strncmp(str, "17-NOV-1858", 11))
                    strcpy(str,"(none)");    /* handle 'none' */
                XmTextSetString(widget_array[value], str);
                break;
                }
            case DWP_RDATE:    /* label date */
                {
                $DESCRIPTOR(time_dsc, str);

                sys$asctim(&length, &time_dsc, uafdata[value].sdata, 0);
                str[length] = '\0';
                ptr = strrchr(str, ':');
                if (ptr) *ptr = '\0'; /* chop off :ss.cc */
                if (!strncmp(str, "17-NOV-1858", 11))
                    strcpy(str,"(none)");    /* handle 'none' */
                xmstr = XmStringCreateSimple(str);
                XtSetArg(arg[0], XmNlabelString, xmstr);
                XtSetValues(widget_array[value], arg, 1);
                XmStringFree(xmstr);
                break;
                }
            case DWP_CPU:    /* CPU time */
                if (!uafdata[value].idata)
                    strcpy(str, "(none)");
                else {
                    Cardinal dd, hh, mm, ss, cc, time = uafdata[value].idata;

                    dd =  time / 8640000;
                    time -= dd * 8640000;
                    hh =  time / 360000;
                    time -= hh * 360000;
                    mm =  time / 6000;
                    time -= mm * 6000;
                    ss =  time / 100;
                    time -= ss * 100;
                    cc =  time;
                    sprintf(str, "%d %02d:%02d:%02d.%02d",
                                  dd,  hh,  mm,  ss,  cc);
                }
                XmTextSetString(widget_array[value], str);
                break;
            case DWP_UIC:    /* UIC */
                sprintf(uafdata[value].sdata, "[%o,%o]",
                    uafdata[value].idata >> 16,
                    uafdata[value].idata & 0xFFFF);
                XmTextSetString(widget_array[value],
                    uafdata[value].sdata);
                trans_uic(uafdata[value].idata);
                break;
            case DWP_INT:    /* text integer */
                sprintf(str, "%d", uafdata[value].idata);
                XmTextSetString(widget_array[value], str);
                break;
            case DWP_RINT:    /* label integer */
                sprintf(str, "%d", uafdata[value].idata);
                xmstr = XmStringCreateSimple(str);
                XtSetArg(arg[0], XmNlabelString, xmstr);
                XtSetValues(widget_array[value], arg, 1);
                XmStringFree(xmstr);
                break;
            }
    setting_widgets = FALSE;
}


/*
 *  Update login flags toggle buttons according to UAI data
 */

static void update_flags()
{
    Cardinal ind;
    Boolean notify = NO;

    setting_flags = TRUE;
    for (ind = 0; ind < MAX_FLAGS; ind++)
        if (flags_array[ind])
            if (uafdata[UAI$_FLAGS].idata >> ind & 1)
                XmToggleButtonSetState(flags_array[ind], ON, notify);
            else
                XmToggleButtonSetState(flags_array[ind], OFF, notify);
    setting_flags = FALSE;
}


/*
 *  Set the buttons to reflect primedays
 *  Set the time map window widgets
 */

static void update_prime()
{
    Cardinal ind;
    Boolean notify = NO;

    setting_prime = TRUE;
    for (ind = 0; ind < MAX_PRIME; ind++)
        if (prime_array[ind])
            XmToggleButtonSetState(prime_array[ind],
                uafdata[UAI$_PRIMEDAYS].idata >> ind & 1 ^ 1, notify);
    setting_prime = FALSE;

    update_time(UAI$_NETWORK_ACCESS_P);
    update_time(UAI$_NETWORK_ACCESS_S);
    update_time(UAI$_BATCH_ACCESS_P);
    update_time(UAI$_BATCH_ACCESS_S);
    update_time(UAI$_LOCAL_ACCESS_P);
    update_time(UAI$_LOCAL_ACCESS_S);
    update_time(UAI$_DIALUP_ACCESS_P);
    update_time(UAI$_DIALUP_ACCESS_S);
    update_time(UAI$_REMOTE_ACCESS_P);
    update_time(UAI$_REMOTE_ACCESS_S);
}


/*
 *  Set the priv buttons to reflect auth priv & default priv
 *  the buttons for default privs 0-38 are kept in privs_array[0-38]
 *  the buttons for auth privs 0-38 are kept in privs_array[40-78]
 *  (PRIVS_BIAS is 40)
 */

static void update_privs()
{
    Cardinal ind;
    Boolean notify = NO;

    setting_privs = TRUE;
    for (ind = 0; ind < MAX_PRIVS/2; ind++)
        if (privs_array[ind])
            if (uafdata[UAI$_PRIV].sdata[ind >> 3] >> (ind & 7) & 1)
                XmToggleButtonSetState(privs_array[ind], ON, notify);
            else
                XmToggleButtonSetState(privs_array[ind], OFF, notify);

    for (ind = PRIVS_BIAS; ind < MAX_PRIVS; ind++)
        if (privs_array[ind])
            if (uafdata[UAI$_DEF_PRIV].sdata[ind - PRIVS_BIAS >> 3] >>
                    (ind - PRIVS_BIAS & 7) & 1)
                XmToggleButtonSetState(privs_array[ind], ON, notify);
            else
                XmToggleButtonSetState(privs_array[ind], OFF, notify);
    setting_privs = FALSE;
}    


/*
 *  Update a time map window widget
 */

static void update_time(Cardinal code)
{
    Cardinal ind;
    Display *display;
    Window window;

    if (!widget_array[code]) return;          /* return if no widget */

    display = XtDisplay(widget_array[code]);  /* get display for XFill */
    window = XtWindow(widget_array[code]);    /* get window for XFill */
    if (!window) return;                      /* Just paranoia? */

    switch (uafdata[code].idata) {            /* speed up special cases */
    case 0:        /* full access */
        XFillRectangle(display, window, gc_draw,
                       0, 0, resource_data.hour_size * 24, k_hours_height);
        break;
    case 0xFFFFFF:    /* no access */
        XFillRectangle(display, window, gc_clear,
                       0, 0, resource_data.hour_size * 24, k_hours_height);
        break;
    default:
        XFillRectangle(display, window, gc_draw,
                       0, 0, resource_data.hour_size * 24, k_hours_height);
        for (ind = 0; ind < 24; ind++)
            if (uafdata[code].idata >> ind & 1)
                XFillRectangle(display, window, gc_clear,
                               ind * resource_data.hour_size, 0,
                               resource_data.hour_size, k_hours_height);
    }
}


/*
 *  Update the list of held identifiers
 */

static void update_held()
{
    Cardinal status, ac, ind, entries, position;
    Boolean already_granted;
    Arg arglist[5];
    ident *ptr;
    held_ident *hptr;
    XmString *items;

    /* quit if the idents box is not there, or still building ident_list */
    if (!idents_box || !XtIsManaged(idents_box) || idents_work_proc_id)
        return;

    items = (XmString *)XtCalloc(ident_count, sizeof(XmString));

    /* show the held idents list */
    for (hptr = held_list, ind = 0; hptr; hptr = hptr->next)
        if (hptr->status == original || hptr->status == add_ident)
            items[ind++] = XmStringCreateSimple(hptr->name);
    entries = ind;  /* save count of list entries */
    if (ind > ident_count)
        XtAppError(context, "held ident array overflow in update_held()");
    /* The Motif FAQ says that this will free memory used by the old list */
    ac = 0;
    XtSetArg(arglist[ac], XmNitems, items); ac++;
    XtSetArg(arglist[ac], XmNitemCount, ind); ac++;
    XtSetValues(widget_array[k_widget_held_list], arglist, ac);
    for (ind = 0; ind < entries; ind++)
        XmStringFree(items[ind]);

    /* show all the non-uic idents not already held */
    for (ptr = ident_list, ind = 0; ptr; ptr = ptr->next)
        if (ptr->status == user_ident) {
            already_granted = FALSE;
            for (hptr = held_list; hptr; hptr = hptr->next)
                already_granted |= (hptr->identifier == ptr->identifier) &&
                                   (hptr->status != del_orig);
            if (!already_granted) {  /* if user doesn't already have id */
                if (!strcmp(ptr->name, resource_data.default_ident))
                    position = ind;
                items[ind++] = XmStringCreateSimple(ptr->name);
            }
        }
    entries = ind;  /* save count of list entries */
    if (ind > ident_count)
        XtAppError(context, "all ident array overflow in update_held()");
    ac = 0;
    XtSetArg(arglist[ac], XmNitems, items); ac++;
    XtSetArg(arglist[ac], XmNitemCount, ind); ac++;
    XtSetArg(arglist[ac], XmNtopItemPosition, position + 1); ac++;
    XtSetValues(widget_array[k_widget_all_list], arglist, ac);
    for (ind = 0; ind < entries; ind++)
        XmStringFree(items[ind]);

    XtFree(items);
}


/*
 *  Force text to uppercase
 *  Called by WidgetChanged, AccountChanged
 */

static void uppercase(String str)
{
    String str2;

    for (str2 = str; *str2; str2++)
        *str2 = toupper(*str2);
}
    

/*
 *  Remove trailing blanks from a DWP_LALPHA data item
 *  Returns the address of the string
 */

static String unpad(String alpha)
{
    String ptr;

    ptr = strchr(alpha, ' ');
    if (ptr) *ptr = '\0';  /* if there is a trailing space, trim it */
    if ((Cardinal) alpha[0] < 32)
        return alpha + 1;  /* if there is a length byte, return the start of the string */
    else
        return alpha;
}


/*
 *  Signal a local error
 *  Interval = -2 means don't do anything
 *             -1 means just printf to stdout
 *              0 means popup message box and without timeout
 *              > 0 means popup message box with timeout in 'interval' ms
 */

static void DWPerror(String where, String what, int interval)
{
    Cardinal ac;
    Arg arglist[5];
    char msg[100];
    XmString xmstr;
    static Widget message_box = NULL;

    if (interval == -2)
        return;
    if (interval == -1) {
        printf("%s  (From: %s)\n", what, where);
        return;
    }
    if (!message_box) {
        ac = 0;
        xmstr = XmStringCreateSimple("DWProfile: Warning Message");
        XtSetArg(arglist[ac], XmNdialogTitle, xmstr); ac++;
	message_box = XmCreateMessageDialog(dwprofile_main, "message_box", arglist, ac);
        XmStringFree(xmstr);
        XtUnmanageChild(XmMessageBoxGetChild(message_box, XmDIALOG_HELP_BUTTON));
        XtUnmanageChild(XmMessageBoxGetChild(message_box, XmDIALOG_CANCEL_BUTTON));
	XtAddCallback(message_box, XmNokCallback, MessageCallback, NULL);
    }
    ac = 0;
    sprintf(msg, "%s\nFrom: %s", what, where);
    xmstr = XmStringCreateLtoR(msg,XmSTRING_DEFAULT_CHARSET);
    XtSetArg(arglist[ac], XmNmessageString, xmstr); ac++;
    XtSetValues(message_box, arglist, ac);
    XmStringFree(xmstr);
    XtManageChild(message_box);    /* popup message box */
    if (interval)
        timeout_id = XtAppAddTimeOut(context, interval, MessageCallback, message_box);
    XFlush(top_display);
}


/*
 *  Signal an external error
 */

static void DWPmsg(String where, Cardinal msg)
{
    char buffer[100];
    Cardinal status;
    ushort length;
    $DESCRIPTOR(txt_dsc, buffer);

    status = sys$getmsg(msg, &length, &txt_dsc, 0, 0);
    buffer[length] = '\0';
    if ((status & 7) == SS$_NORMAL)
        DWPerror(where, buffer, 0);
    else
        DWPerror(where, "Unknown", 0);
}


/*
 *  Do a DCL command
 *  Called by Write
 */

static void spawn_dcl(String str)
{
    Cardinal status, substatus = SS$_NORMAL, flags = CLI$M_NOCLISYM, /* | CLI$M_NOLOGNAM */
          devlen, ctx, count = 0;
    char *cptr, dev1[40] = "", dev2[40], dir[40], result[40], filespec[40];
    ident *iptr, *prev;
    FILE *file;
    $DESCRIPTOR(dcl_dsc, "DCL.TMP");
#ifdef DEBUG
    $DESCRIPTOR(out_dsc, "TT:");
#else
    $DESCRIPTOR(out_dsc, "NL:");  /* change to TT: for debugging command files */
#endif
    $DESCRIPTOR(dev1_dsc, dev1);
    $DESCRIPTOR(dev2_dsc, dev2);
    $DESCRIPTOR(res_dsc, result);
    $DESCRIPTOR(file_dsc, filespec);
    Boolean remove_command = !strncmp(str, "$ UAF REMOVE", 12);
    Boolean modify_uic = strstr(str, "$ UAF MODIFY") && strstr(str, "/UIC");

#ifdef DEBUG
    printf("spawn_dcl(%s)\n", str);
#endif

    /* set up dev1, dev2, dir */
    /* if from getuai, it has a length byte; if from text widget, not */
    strcpy(dev1, unpad(uafdata[UAI$_DEFDEV].sdata));
    if (dev1[strlen(dev1) - 1] == ':')
        dev1[strlen(dev1) - 1] = '\0'; /* strip off trailing ':' */
    dev1_dsc.dsc$w_length = strlen(dev1);
    status = lib$sys_trnlog(&dev1_dsc, &devlen, &dev2_dsc, 0, 0, 0);
    cptr = strchr(dev2, ' ');
    if (cptr) *cptr = '\0'; /* null terminate */
    cptr = strchr(dev2, ':');
    if (cptr) *cptr = '\0'; /* physical device without ':' */
    strcpy(dir, unpad(uafdata[UAI$_DEFDIR].sdata));
    if (dir[0] == '[')
        strcpy(dir, dir + 1);         /* strip off [ */
    if (dir[strlen(dir) - 1] == ']')
        dir[strlen(dir) - 1] = '\0';  /* strip off ] */

    file = fopen("DCL.TMP","w");
#ifdef DEBUG
    fprintf(file, "$ SET VERIFY\n");
    fprintf(file, "$ SHOW LOGICAL DWPUID\n");
#endif    
    fprintf(file, "$ DELETE DCL.TMP;*\n");
    fprintf(file, "$ PREVPRIV = F$SETPRV(\"ALL\")\n");
    fprintf(file, "$ DEFINE/USER SYSUAF SYS$SYSTEM:SYSUAF\n");
    fprintf(file, "$ UAF :== $AUTHORIZE\n");
    fprintf(file, "%s\n", str);

    /* if the command is REMOVE, delete mail profile */
    if (remove_command) {
        fprintf(file, "$ MAIL\n");
        fprintf(file, "%s\n", str);
        fprintf(file, "EXIT\n");
    }

    /* if the command is REMOVE, delete disk files and directory */
    if (remove_command && strlen(dev1) && strlen(dir))
        fprintf(file, "$ @DWPUID:DELTREE %s:[%s]\n", dev1, dir);

    /* add the quota and overdraft resources.
       The default (0) is to do nothing, i.e. use the [group,0] entry
       in quota.sys.  Set the resources 'quota' and 'overdraft' to use
       a fixed quota
    */
    /* if defdev exists and a fixed disk quota specified */
    if (strlen(dev1) && (resource_data.quota)) {
        sprintf(filespec, "%s:[000000]QUOTA.SYS", dev2);
        file_dsc.dsc$w_length = strlen(filespec);
        ctx = 0;
        status = lib$find_file(&file_dsc, &res_dsc, &ctx, 0, 0, 0, 0);
        lib$find_file_end(&ctx);
        if ((status & 7) == SS$_NORMAL) {  /* QUOTA.SYS exists */
            fprintf(file, "$ RUN SYS$SYSTEM:DISKQUOTA\n");
            fprintf(file, "USE %s\n", dev2);
            if (remove_command)
                fprintf(file, "REMOVE %s\n", uafdata[UAI$_UIC].sdata);
            else
                fprintf(file, "ADD %s /PERMQUOTA=%d /OVERDRAFT=%d\n",
                    uafdata[UAI$_UIC].sdata, resource_data.quota,
                    resource_data.overdraft);
            fprintf(file, "EXIT\n");
        }
    }    
    fclose(file);
    status = lib$spawn(0, &dcl_dsc, &out_dsc, &flags, 0, 0, &substatus, 0, 0, 0, 0, 0);
    if ((substatus & 7) != SS$_NORMAL)   /* probably CLI$_NORMAL */
        DWPmsg("Spawned process status", substatus);
    if (status != SS$_NORMAL)
        DWPmsg("Spawn_DCL", status);

    if (!remove_command && strlen(dev1) && strlen(dir)) {
        sprintf(filespec, "%s:[000000]%s.DIR", dev1, dir);
        file_dsc.dsc$w_length = strlen(filespec);
        ctx = 0;
        status = lib$find_file(&file_dsc, &res_dsc, &ctx, 0, 0, 0, 0);
        lib$find_file_end(&ctx);
        filespec[0] == '\0';
        if (status == RMS$_FNF) {
/*          setpriv(ON, PRV$M_BYPASS | PRV$M_EXQUOTA | PRV$M_SYSPRV); */
            sprintf(filespec, "%s:[%s]", dev1, dir);
            file_dsc.dsc$w_length = strlen(filespec);
            if (strlen(filespec)) {
                status = lib$create_dir(&file_dsc, &uafdata[UAI$_UIC].idata, 0, 0,
                    &resource_data.version_limit, 0);
#ifdef DEBUG
                printf("create_dir %s  status = %d\n", filespec, status);
#endif
                if (status != SS$_CREATED)
                    DWPmsg("Create Dir", status);
            }
        }
    }

    /* If REMOVE, remove identifier from ident_list */
    if (remove_command || modify_uic) {
        for (iptr = ident_list, prev = NULL; iptr; prev = iptr, iptr = iptr->next)
            if (!strcmp(iptr->name, uafdata[UAI$_USERNAME].sdata)) break;
        if (iptr) {
            if (prev)
                prev->next = iptr->next;
            else
                ident_list = iptr->next;
            XtFree(iptr->name);
            XtFree(iptr);
            uic_count--;
        }
        /* identifier may still be on found_list */
        for (iptr = found_list.next, prev = &found_list; iptr; prev = iptr, iptr = iptr->next)
            if (!strcmp(iptr->name, uafdata[UAI$_USERNAME].sdata)) break;
        if (iptr) {
            prev->next = iptr->next;
            XtFree(iptr->name);
            XtFree(iptr);
            if (iptr == found_ptr)
                found_ptr = prev;
            if (iptr == found_add)
                found_add = prev;
        }
        /* remove group identifier if no other uic in group */
        for (iptr = ident_list; iptr; iptr = iptr->next)
            if ((iptr->identifier >> 16) == (uafdata[UAI$_UIC].idata >> 16))
                count++;
        if (count == 1) {
            for (iptr = ident_list, prev = NULL; iptr; prev = iptr, iptr = iptr->next)
                if (iptr->identifier == (uafdata[UAI$_UIC].idata | 0xFFFF)) break;
            if (iptr) {
                if (prev)
                    prev->next = iptr->next;
                else
                    ident_list = iptr->next;
                XtFree(iptr->name);
                XtFree(iptr);
                account_count--;
            }
        }
        if (accounts_box && XtIsManaged(accounts_box)) {
            XtUnmanageChild(accounts_box);
            Accounts(NULL, NULL, NULL);  /* reload accounts select box */
        }
    }
}


/*
 *  Calculate the next available UIC in the group that matches the account
 *   or finds the next available group if there is no match
 *  Updates the UIC widget and uafdata fields.
 *  Called when an account is selected via selector widget or from return_key
 *   when in the account text widget
 */

static void calc_uic()
{
    Cardinal status, maxgroup = resource_data.minimum_group,
          groupid = 0, uic = 0;
    ushort length;
    ident *ptr;
    $DESCRIPTOR(group_dsc, uafdata[UAI$_ACCOUNT].sdata);
    
    /* read in text widget */
    if (!parse_uic(UAI$_UIC))
        DWPerror("calc_uic","Illegal UIC format", 0);
    if (uafdata[UAI$_UIC].idata) return;    /* don't recalc if UIC exists */
    read_identifiers(FALSE);
    group_dsc.dsc$w_length = strlen(uafdata[UAI$_ACCOUNT].sdata);
    status = sys$asctoid(&group_dsc, &groupid, 0);
    for (ptr = ident_list; ptr; ptr = ptr->next) {
        /* if groups match and not in [n,*] and higher than old uic, save it */
        if (((ptr->identifier >> 16) == (groupid >> 16)) &&
            ((ptr->identifier & 0xFFFF) != 0xFFFF))
            uic = (ptr->identifier > uic) ? ptr->identifier : uic;
        /* if this is a new group name, remember highest [n,*] ident */
        if (!groupid &&
            (ptr->identifier < 0x80000000) &&
            (ptr->identifier < (resource_data.maximum_group << 16)) &&
            (ptr->identifier > maxgroup))
            maxgroup = ptr->identifier;
    }
    if (!groupid)    /* new group name */
        groupid = maxgroup + 0x10000;  /* highest group + 1 */
    if (!uic)    /* if only [x,177777] in UAF, give [x,101] as uic */
        uic = (groupid & 0x7FFF0000) + 0100;
    uafdata[UAI$_UIC].idata = ++uic;
    sprintf(uafdata[UAI$_UIC].sdata, "[%o,%o]", uic >> 16, uic & 0xFFFF);
    XmTextSetString(widget_array[UAI$_UIC], uafdata[UAI$_UIC].sdata);
}


/*
 *  Translate UIC to identifier format, if identifiers are loaded
 *  Called by update_strings, return key in Account widget, and Accounts selector
 */

static void trans_uic(Cardinal uic)
{
    ident *ptr;
    char group[32] = "", member[32] = "", suic[32];
    Arg arg[1];
    XmString xmstr;

    for (ptr = ident_list; ptr; ptr = ptr->next)
        if (ptr->identifier == uic)
            strcpy(member, ptr->name);
        else if (ptr->identifier == (uic & 0x7FFF0000 | 0xFFFF))
            strcpy(group, ptr->name);
    if (member[0])
        if (group[0])
            sprintf(suic, "([%s,%s])", group, member);
        else
            sprintf(suic, "([%s])", member);
    else
        sprintf(suic, "([%o,%o])", uic >> 16, uic & 0xFFFF);

    xmstr = XmStringCreateSimple(suic);
    XtSetArg(arg[0], XmNlabelString, xmstr);
    XtSetValues(widget_array[k_widget_uic_trans], arg, 1);
    XmStringFree(xmstr);
}
    

/*
 *  Close the work_box dialog box
 */

static void close_work_box()
{
        if (XtIsManaged(work_box))
            XtUnmanageChild(work_box);
        XUndefineCursor(top_display, top_window);
}


/*
 *  WorkProc to put one identifier on the idents list
 */

static Boolean idents_work_proc(XtPointer data)
{
    Cardinal status, resid, ac = 0, pct;
    static int id, count, rdbctx;
    Arg arglist[5];
    ushort namlen;
    XmString xmstr;
    char idbuf[35], str[30];
    Pixmap gague_pixmap;
    char gague[100];
    $DESCRIPTOR(name_dsc, idbuf);

    if (!ident_ptr) {  /* init count, id, context on first & subsequent calls */
        count = 0;
        id = -1;
        sys$finish_rdb(&rdbctx);
    }
    count++;
    if (((count % 100) == 0) && (count >= 200)) {
        sprintf(str, "Reading identifier  %d", count);
        ac = 0;
        xmstr = XmStringCreateSimple(str);
        XtSetArg(arglist[ac], XmNmessageString, xmstr); ac++;
        XtSetValues(work_box, arglist, ac);
        XmStringFree(xmstr);
    }

    status = sys$idtoasc(id, &namlen, &name_dsc, &resid, 0, &rdbctx);
    if (status == SS$_NOSUCHID) {
        close_work_box();
        idents_work_proc_id = (XtWorkProcId) 0;
        build_accounts();
        update_held();
        return TRUE;
    }
    if (status != SS$_NORMAL) {
        sys$finish_rdb(&rdbctx);
        DWPmsg("read_identifiers", status);
        close_work_box();
        idents_work_proc_id = (XtWorkProcId) 0;
        return TRUE;
    }
    if (ident_ptr)
        ident_ptr = ident_ptr->next = XtNew(ident);
    else
        ident_list = ident_ptr = XtNew(ident);
    idbuf[namlen] = '\0';
    ident_ptr->name = XtNewString(idbuf);
    ident_ptr->next = NULL;
    ident_ptr->identifier = resid;
    if ((resid > 0x80010000) &&    /* if not VMS defined idents */
            strncmp(idbuf, "SYS$NODE", 8)) { /* and not SYS$NODE_xxx */
        ident_ptr->status = user_ident;      /* it's a regular ident */
        ident_count++;
    }
    else if (resid > 0x80000000)  /* if a non-uic ident and not a user ident */
        ident_ptr->status = intern_ident;    /* it's a internal ident */
    else if ((resid & 0xFFFF) == 0xFFFF) {
        ident_ptr->status = account;         /* accounts end in FFFF */
        account_count++;
    }
    else {
        ident_ptr->status = uic_ident;
        uic_count++;
    }
    return FALSE;
}


/*
 *  Build list of identifiers if list is empty or if 'force' true
 */

static void read_identifiers(Boolean force)
{
    Cardinal ac;
    Arg arglist[10];
    XmString xmstr;
    ident *ptr, *tptr;

    if (!idents_work_proc_id && (!ident_list || force)) {
        if (!work_box) {
            ac = 0;
            XtSetArg(arglist[ac], XmNheight, 120); ac++;
            XtSetArg(arglist[ac], XmNwidth, 220); ac++;
            XtSetArg(arglist[ac], XmNresizePolicy, XmRESIZE_NONE); ac++;
            xmstr = XmStringCreateSimple("DWProfile: Reading Identifiers");
            XtSetArg(arglist[ac], XmNdialogTitle, xmstr); ac++;
            work_box = XmCreateWorkingDialog(dwprofile_main, "work_box", arglist, ac);
            XmStringFree(xmstr);
            XtUnmanageChild(XmMessageBoxGetChild(work_box, XmDIALOG_OK_BUTTON));
            XtUnmanageChild(XmMessageBoxGetChild(work_box, XmDIALOG_HELP_BUTTON));
            XtAddCallback(work_box, XmNcancelCallback, IdentsCancelCallback, NULL);
        }
        ac = 0;
        xmstr = XmStringCreateSimple("Reading identifiers");
        XtSetArg(arglist[ac], XmNmessageString, xmstr); ac++;
        XtSetValues(work_box, arglist, ac);
        XmStringFree(xmstr);
        if (resource_data.show_work && !XtIsManaged(work_box))
            XtManageChild(work_box);    /* pop up work box */

        for (ptr = ident_list; ptr;) { /* free old ident list */
            XtFree(ptr->name);
            tptr = ptr;
            ptr = ptr->next;
            XtFree(tptr);
        }
        ident_list = ident_ptr = select_ptr = NULL;
        account_count = uic_count = ident_count = 0;
        idents_work_proc_id = XtAppAddWorkProc(context, idents_work_proc, NULL);
    }
}


/*
 *  Check each uic identifier for match against select_string
 *  Update found_add (the end of found_list)
 */

static Boolean find_work_proc(XtPointer data)
{
    char message[80], match_string[80];
    Boolean matched;
    Cardinal status, uic;
    ushort length, logfails;
    String sptr;
    struct dsc$descriptor_s ident_dsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
    $DESCRIPTOR(select_dsc, select_string);
    $DESCRIPTOR(match_dsc, match_string);

    select_dsc.dsc$w_length = strlen(select_string);
    if (!ident_list) {    /* No idents read, this is bad and shouldn't happen */
        find_work_proc_id = (XtWorkProcId) 0;
        return TRUE;
    }
    if (!select_ptr)  /* select_ptr is the workproc control variable */
        select_ptr = ident_list;
    if (select_ptr->status != uic_ident) {
        select_ptr = select_ptr->next;  /* only look at uic type idents */
        return FALSE;
    }
    ident_dsc.dsc$a_pointer = select_ptr->name;
    ident_dsc.dsc$w_length = strlen(select_ptr->name);
    switch (select_data[select_index].type) {
    case DWP_ALPHA:
        if (select_data[select_index].code == UAI$_USERNAME) {
            matched = (str$match_wild(&ident_dsc, &select_dsc) == STR$_MATCH);
            break;
        }
        else {
            itmlst[0].buflen = select_data[select_index].size;
            itmlst[0].itmcode = select_data[select_index].code;
            itmlst[0].bufadr = match_string;
            itmlst[0].retadr = 0;
            itmlst[1].buflen = 0;
            itmlst[1].itmcode = 0;
            status = sys$getuai(0, 0, &ident_dsc, &itmlst, 0, 0, 0);
            if ((status & 7) != SS$_NORMAL) {
                sprintf(message, "Username %s does not exist", select_ptr->name);
                DWPerror("Find", message, resource_data.find_interval);
                break;
            }
            match_string[8] = ' ';    /* make sure ' ' */
            match_string[9] = '\0';   /* make sure null */
            sptr = strchr(match_string, ' ');
            if (sptr) *sptr = '\0';
            match_dsc.dsc$w_length = strlen(match_string);
            matched = (str$match_wild(&match_dsc, &select_dsc) == STR$_MATCH);
            break;
        }
    case DWP_LALPHA:
        itmlst[0].buflen = select_data[select_index].size;
        itmlst[0].itmcode = select_data[select_index].code;
        itmlst[0].bufadr = match_string;
        itmlst[0].retadr = &length;
        itmlst[1].buflen = 0;
        itmlst[1].itmcode = 0;
        status = sys$getuai(0, 0, &ident_dsc, &itmlst, 0, 0, 0);
        if ((status & 7) != SS$_NORMAL) {
            sprintf(message, "Username %s does not exist", select_ptr->name);
            DWPerror("Find", message, resource_data.find_interval);
            break;
        }
        match_string[length] = ' ';    /* make sure ' ' */
        match_string[length+1] = '\0';   /* make sure null */
        strcpy(match_string, unpad(match_string));  /* get rid of length byte */
        match_dsc.dsc$w_length = strlen(match_string);
        matched = (str$match_wild(&match_dsc, &select_dsc) == STR$_MATCH);
        break;
    case DWP_UIC:
        itmlst[0].buflen = select_data[select_index].size;
        itmlst[0].itmcode = select_data[select_index].code;
        itmlst[0].bufadr = &uic;
        itmlst[0].retadr = &length;
        itmlst[1].buflen = 0;
        itmlst[1].itmcode = 0;
        status = sys$getuai(0, 0, &ident_dsc, &itmlst, 0, 0, 0);
        if ((status & 7) != SS$_NORMAL) {
            sprintf(message, "Username %s does not exist", select_ptr->name);
            DWPerror("Find", message, resource_data.find_interval);
            break;
        }
        /* convert UIC to text and do text wildcard match */
        sprintf(match_string, "[%o,%o]", uic >> 16, uic & 0xFFFF);
        match_dsc.dsc$w_length = strlen(match_string);
        matched = (str$match_wild(&match_dsc, &select_dsc) == STR$_MATCH);
        break;
    case DWP_DATE:
        itmlst[0].buflen = select_data[select_index].size;
        itmlst[0].itmcode = select_data[select_index].code;
        itmlst[0].bufadr = match_string;
        itmlst[0].retadr = 0;
        itmlst[1].buflen = 0;
        itmlst[1].itmcode = 0;
        status = sys$getuai(0, 0, &ident_dsc, &itmlst, 0, 0, 0);
        if ((status & 7) != SS$_NORMAL) {
            sprintf(message, "Username %s does not exist", select_ptr->name);
            DWPerror("Find", message, resource_data.find_interval);
            break;
        }
        matched = (!memcmp(match_string, select_string,
                           select_data[select_index].size));
        break;
    case DWP_RINT:
        /* special test - the only test of a RINT is logfails > 0 */
        itmlst[0].buflen = select_data[select_index].size;
        itmlst[0].itmcode = select_data[select_index].code;
        itmlst[0].bufadr = &logfails;
        itmlst[0].retadr = &length;
        itmlst[1].buflen = 0;
        itmlst[1].itmcode = 0;
        status = sys$getuai(0, 0, &ident_dsc, &itmlst, 0, 0, 0);
        if ((status & 7) != SS$_NORMAL) {
            sprintf(message, "Username %s does not exist", select_ptr->name);
            DWPerror("Find", message, resource_data.find_interval);
            break;
        }
        matched = logfails;
        break;
    }
    if (matched) {
        if (found_add)
            found_add = found_add->next = XtNew(ident);
        else
            found_add = found_list.next = XtNew(ident);
        found_add->name = XtNewString(select_ptr->name);
        found_add->next = NULL;
    }
    if (select_ptr = select_ptr->next)
        return FALSE;
    else {
        if (!found_ptr->next) {  /* no more to display */
            XtSetSensitive(button_array[find_button], OFF);  /* 'find' button is inactive */
            XtSetSensitive(button_array[find_menu], OFF);
        }
        progress(NULL);
        find_work_proc_id = (XtWorkProcId) 0;
        return TRUE;
    }
}


/*
 *  Parse textual date to 64 bit binary value 
 *  Returns false if illegal date
 */

static Boolean parse_date(Cardinal value)
{
    Cardinal status, count = 0;
    String str;
    $DESCRIPTOR(time_dsc, uafdata[value].sdata);

    if (!strlen(uafdata[value].sdata)) { /* empty date means (none) */
        memset(uafdata[value].sdata, 0, 8);  /* make date 'none' */
        return TRUE;
    }
    /* default expiration date to midnight instead of current time */
    for (str = uafdata[value].sdata; str;) {
        if((str = strchr(str, ':')) == NULL)
            break;  /* no more colons */
        count++;    /* count the colons in date */
        str++;      /* skip over the colon we just counted */
    }
    if (count < 1)              /* no hh:mm:ss.cc */
        strcat(uafdata[value].sdata, " 00:00:00.00");
    else if (count < 2)         /* no ss.cc */
        strcat(uafdata[value].sdata, ":00.00");
    time_dsc.dsc$w_length = strlen(uafdata[value].sdata);
    status = sys$bintim(&time_dsc, &uafdata[value].sdata);
    /* .sdata will now contain the 64-bit system time */
    return (status == SS$_NORMAL);
}


/*
 *  Parse textual UIC to binary value 
 *  Returns false if illegal UIC format
 */

static Boolean parse_uic(int code)
{
    Cardinal status, grp = 0, mem = 0;

    status = sscanf(uafdata[code].sdata, "[%o,%o]", &grp, &mem);
    if (status != 2) /* try without [] */
        status = sscanf(uafdata[code].sdata, "%o,%o", &grp, &mem);
    uafdata[UAI$_UIC].idata = (grp << 16) + mem;
    return (status == 2);
}


/*
 *  Build the list of accounts for accounts_box
 */

static void build_accounts()
{
    ident *ptr;
    XmString *items;
    Widget list;
    Cardinal ind;

    if (!accounts_box || !XtIsManaged(accounts_box))
        return; /* box isn't there */

    list = XmSelectionBoxGetChild(accounts_box, XmDIALOG_LIST);
    XmListDeleteAllItems(list);
    items = (XmString *)XtCalloc(account_count, sizeof(XmString));
    for (ptr = ident_list, ind = 0; ptr; ptr = ptr->next)
        if (ptr->status == account)
            items[ind++] = XmStringCreateSimple(ptr->name);
    XmListAddItems(list, items, ind, 0);
    for (ind = 0; ind < account_count; ind++)
        XmStringFree(items[ind]);
    XtFree(items);
}


/*
 *  Show the progress of 'Find Next'
 */

static void progress(ident *location)
{
    static Widget progress_box = NULL;
    XmString xmstr;
    char str[80];
    Cardinal ac, count, pct;
    static int old_pct = -1;
    Arg arglist[10];
    ident *ptr;
    Display *display;
    Window window;

    if (progress_id) {
        XtRemoveTimeOut(progress_id);   /* clear out any old timers */
        progress_id = (XtIntervalId) 0;
    }

    if (!location) {  /* location = null means pop down box */
        if (progress_box && XtIsManaged(progress_box))
            XtUnmanageChild(progress_box);
        return;
    }

    if (!progress_box) {
        ac = 0;
        xmstr = XmStringCreateSimple("DWProfile: Find progress");
        XtSetArg(arglist[ac], XmNheight, 112); ac++;
        XtSetArg(arglist[ac], XmNwidth, 224); ac++;
        XtSetArg(arglist[ac], XmNresizePolicy, XmRESIZE_NONE); ac++;
        XtSetArg(arglist[ac], XmNdialogTitle, xmstr); ac++;
        progress_box = XmCreateWorkingDialog(dwprofile_main, "progress_box", arglist, ac);
        XmStringFree(xmstr);
        XtUnmanageChild(XmMessageBoxGetChild(progress_box, XmDIALOG_OK_BUTTON));
        XtUnmanageChild(XmMessageBoxGetChild(progress_box, XmDIALOG_HELP_BUTTON));
        XtAddCallback(progress_box, XmNcancelCallback, FindCancelCallback, NULL);
    }

    if (!XtIsManaged(progress_box))
        XtManageChild(progress_box);    /* pop up progress box */

    if (location->next) {
        for (ptr = ident_list, count = 0; ptr != location; ptr = ptr->next)
            if (ptr->status == uic_ident)
                count++;   /* count idents read so far */
        pct = (count * 100) / uic_count;  /* idents read as a percent */
        if (pct != old_pct) {
            old_pct = pct;
            sprintf(str, "%d%% of identifiers searched", pct);
            xmstr = XmStringCreateSimple(str);
            ac = 0;
            XtSetArg(arglist[ac], XmNmessageString, xmstr); ac++;
            XtSetValues(progress_box, arglist, ac);
            XmStringFree(xmstr);
        }
        /* leave this dialog box up, call Find in 1 second */
        progress_id = XtAppAddTimeOut(context, 1000, Find, NULL);

    }
    else  /* this is the last item, pop down the box */
        XtUnmanageChild(progress_box);
}



/*** Event Handlers ***/

/*
 *  Called by mouse button and exposure events in access windows
 */

static XtEventHandler access_handler(Widget w, XtPointer data,
                                     XButtonEvent *event, Boolean *dispatch)
{
    Cardinal wi, hour;
    static Boolean mode;

    /* find the uai code for this event */
    for (wi = UAI$_NETWORK_ACCESS_P;
        widget_array[wi] != w && wi <= UAI$_REMOTE_ACCESS_S; wi++)
        /* just calc index */ ;

    switch (event->type) {
    case Expose :
        update_time(wi);    /* update the exposed window */
        break;
    case ButtonPress :
        uafdata[wi].changed = TRUE;
        hour = event->x / resource_data.hour_size;
        mode = uafdata[wi].idata >> hour & 1 ^ 1;    /* flip current */
    case MotionNotify:         /* common code for ButtonPress, Motion */
        hour = event->x / resource_data.hour_size;
        XFillRectangle(event->display, event->window,
                       mode ? gc_clear : gc_draw,
                       hour * resource_data.hour_size, 0,
                       resource_data.hour_size, k_hours_height);
        if (mode)
            uafdata[wi].idata |= 1 << hour;
        else
            uafdata[wi].idata &= ~(1 << hour);
        break;
    }
}


/*
 *  Called by event handler on MB3 push
 */

static XtEventHandler popup_handler(Widget widget, Widget popup_menu,
                                    XButtonPressedEvent *event, Boolean *dispatch)
{
    if (event->button != Button3)
        return;
    XmMenuPosition(popup_menu, event);     /* position popup menu */
    XtManageChild(popup_menu);             /* manage it */
}


/*
 *  Called when a return is pressed in a text widget
 */

static XtActionProc return_key(Widget widget, XKeyPressedEvent *event,     
                               String *str, Cardinal *args)
{
    if (widget == widget_array[UAI$_USERNAME])
        Read(NULL, NULL, NULL); /* read new username */
    else if (widget == widget_array[UAI$_ACCOUNT]) {
        calc_uic();                         /* figure out a new uic */
        trans_uic(uafdata[UAI$_UIC].idata); /* in case calc_uic didn't */
    }
}


/*
 * Select menu shortcut
 * Set up select_data index for call to Select()
 */

static XtActionProc select_key(Widget widget, XKeyPressedEvent *event,     
                               String *str, Cardinal *args)
{
    Cardinal wi, si;

    /* find the widget_array index */
    for(wi = 0; items[wi].code; wi++)
        if (widget == widget_array[wi])
            break;
    /* find the select_data index */
    for(si = 0; select_data[si].code; si++)
        if (wi == select_data[si].code)
            break;
    /* call Select() with the index */
    if (select_data[si].code)
        Select(NULL, &si, NULL);
}



/*** Callbacks ***/

/*
 *  Called by 'Quit' menu item
 */

static XtCallbackProc Quit(Widget widget, XtPointer data,     
            XmPushButtonCallbackStruct *reason)
{
    exit();
}


/*
 *  Read in the uaf entry specified by username; if error, load DEFAULT
 *  Called by 'Read' menu or return_key from username text widget
 */

static XtCallbackProc Read(Widget widget, XtPointer data,
            XmPushButtonCallbackStruct *reason)
{
    String str;

    XDefineCursor(top_display, top_window, wait_cursor);
    XFlush(top_display);
    str = XmTextGetString(widget_array[UAI$_USERNAME]);
    if (!get_record(str)) {
        if (get_record("DEFAULT")) {
            set_defaults(str);
            DWPerror("Read", "Username does not exist, defaults applied",
                     resource_data.read_interval);
        }
        else
            DWPerror("Read", "Cannot read username DEFAULT", 0);
    }
    XtFree(str);
    XUndefineCursor(top_display, top_window);
}


/*
 *  Find the next UAF entry that matches the current criteria
 *
 *  Note: UAF entry is defined as a UIC-type identifier.
 *  If a UAF entry does not have an identifier of the same
 *   name, it will not be searched.
 *
 *  Called by 'Find' menu item
 */

static XtCallbackProc Find(Widget widget, XtPointer data,
            XmPushButtonCallbackStruct *reason)
{

    /* if nothing found yet and still looking, mark time */
    if (!found_ptr->next && find_work_proc_id) {
        progress(select_ptr);
        return;
    }

#ifdef DEBUG
    if (!XtIsSensitive(button_array[find_button])) {  /* stray timer callback */
        printf("Find() callback with button insensitive\n");
        return;
    }
#endif

    progress(NULL);  /* something is found or search done, get rid of progress box */

    /* get next found ident and display it */
    if (found_ptr->next) {
        found_ptr = found_ptr->next;
        get_record(found_ptr->name);
        if (!found_ptr->next && !find_work_proc_id) {        /* last item on found list? */
            XtSetSensitive(button_array[find_button], OFF);
            XtSetSensitive(button_array[find_menu], OFF);
        }
    }
    /* we get here when something allows a Find Next with nothing to get */
    else {
#ifdef DEBUG
        printf("This shouldn't happen, tell me\n");
#endif
        XtSetSensitive(button_array[find_button], OFF);  /* turn off button */
        XtSetSensitive(button_array[find_menu], OFF);
    }
}


/*
 *  Update the UAF record for 'username'
 *  Called by 'Write' menu item
 */

static XtCallbackProc Write(Widget widget, XtPointer data,
            XmPushButtonCallbackStruct *reason)
{
    Cardinal ind, pad, item, value, status, dd, hh, mm, ss, cc,
             holder[2] = {0, 0};
    ident *ptr, *prev, *next;
    held_ident *hptr, *prev_hptr, *tmp_hptr;
    char command[200] = "", temp[100];
    $DESCRIPTOR(acct_dsc, uafdata[UAI$_USERNAME].sdata);

    XDefineCursor(top_display, top_window, wait_cursor);
    XFlush(top_display);

    /* make sure uafdata[uai$_uic].idata matches the text */
    if (!parse_uic(UAI$_UIC))  /* if not good uic */
        uafdata[UAI$_UIC].idata = 0;  /* force error */
    
    if (!strlen(uafdata[UAI$_OWNER].sdata) ||
        !strncmp(uafdata[UAI$_ACCOUNT].sdata, "DEFAULT", 7) ||
        !strncmp(uafdata[UAI$_DEFDIR].sdata, "[USER]", 6) ||
        !strlen(unpad(uafdata[UAI$_DEFDEV].sdata)) ||
        !strlen(unpad(uafdata[UAI$_DEFDIR].sdata)) ||
        !uafdata[UAI$_UIC].idata) {
            DWPerror("Write","Bad Owner, Account, UIC or Directory name", 0);
            XUndefineCursor(top_display, top_window);
            return;
    }
    /* item code UAI$_USERNAME is never used by sys$setuai */
    if (uafdata[UAI$_USERNAME].changed) {
        if (uafdata[UAI$_USERNAME].sdata[0] &&
                uafdata[UAI$_UIC].idata) {    /* create a new user */
            sprintf(temp, "$ UAF ADD %s/UIC=%s", uafdata[UAI$_USERNAME].sdata,
                    uafdata[UAI$_UIC].sdata);
            strcpy(command, temp);
            uafdata[UAI$_USERNAME].changed = FALSE;
            uafdata[UAI$_UIC].changed = FALSE;
            if (uafdata[UAI$_ACCOUNT].sdata[0]) {
                sprintf(temp, "/ACCOUNT=%s", uafdata[UAI$_ACCOUNT].sdata);
                strcat(command, temp);
                uafdata[UAI$_ACCOUNT].changed = FALSE;
            }
            if (uafdata[UAI$_PWD].sdata[0]) {
                sprintf(temp, "/PASSWORD=%s", uafdata[UAI$_PWD].sdata);
                strcat(command, temp);
                uafdata[UAI$_PWD].changed = FALSE;
            }
            spawn_dcl(command);
        }
        else {
            DWPerror("Write","No username or no UIC", 0);
            XUndefineCursor(top_display, top_window);
            return;
        }
    }
    else {
        /* check for password change; is setuai able to do this now? */
        if (uafdata[UAI$_PWD].changed &&
          uafdata[UAI$_USERNAME].sdata[0] &&
          uafdata[UAI$_PWD].sdata[0]) {
            sprintf(temp, "$ UAF MODIFY %s/PASSWORD=%s\n",
                    uafdata[UAI$_USERNAME].sdata, uafdata[UAI$_PWD].sdata);
            strcat(command, temp);
            uafdata[UAI$_PWD].changed = FALSE;
        }
        /* check for device/directory change; the directory tree needs to be moved */
        if ((uafdata[UAI$_DEFDEV].changed && strlen(uafdata[UAI$_DEFDEV].sdata))
            || (uafdata[UAI$_DEFDIR].changed && strlen(uafdata[UAI$_DEFDIR].sdata))) {
            sprintf(temp, "$ @DWPUID:MOVE_DIRECTORY %s %s %s %s\n",
                    unpad(uafdata[UAI$_DEFDEV].odata),
                    unpad(uafdata[UAI$_DEFDIR].odata),
                    unpad(uafdata[UAI$_DEFDEV].sdata),
                    unpad(uafdata[UAI$_DEFDIR].sdata));
            strcat(command, temp);
            /* update the old values */
            strcpy(uafdata[UAI$_DEFDEV].odata, unpad(uafdata[UAI$_DEFDEV].sdata));
            strcpy(uafdata[UAI$_DEFDIR].odata, unpad(uafdata[UAI$_DEFDIR].sdata));
        }
        /* check for UIC change;  old identifier must be deleted, new ident created */
        /* let authorize handle it.  new UIC will be added to idents list below */
        if (uafdata[UAI$_UIC].changed &&
          uafdata[UAI$_UIC].idata) {
            sprintf(temp, "$ UAF MODIFY %s/UIC=%s\n",
                    uafdata[UAI$_USERNAME].sdata, uafdata[UAI$_UIC].sdata);
            strcat(command, temp);
            uafdata[UAI$_UIC].changed = FALSE;
            sprintf(temp, "$ @DWPUID:RESETOWNER %s%s %s\n",
                    unpad(uafdata[UAI$_DEFDEV].sdata),
                    unpad(uafdata[UAI$_DEFDIR].sdata),
                    uafdata[UAI$_USERNAME].sdata);
            strcat(command, temp);
        }
        /* any of these generated a DCL command? */
        if (strlen(command))
            spawn_dcl(command);
    }
    item = 0;
    for (ind = 0; value = items[ind].code; ind++) {
        if (value && uafdata[value].changed) {
            uafdata[value].changed = FALSE;
            itmlst[item].itmcode = value;
            itmlst[item].buflen = items[ind].size;
            switch (items[ind].type) {
            case DWP_LALPHA: /* if it's been changed, it has no length byte */
                if (value == UAI$_DEFDIR && strlen(uafdata[value].sdata)) {
                    if (uafdata[value].sdata[0] != '[') {
                        sprintf(temp, "[%s", uafdata[value].sdata);
                        strcpy(uafdata[value].sdata, temp);
                    }
                    if (uafdata[value].sdata[strlen(uafdata[value].sdata)-1]
                        != ']')
                        strcat(uafdata[value].sdata, "]");    /* dir has [] */
                }
                if (value == UAI$_DEFDEV &&
                        strlen(uafdata[value].sdata) &&
                        uafdata[value].sdata[strlen(uafdata[value].sdata)-1]
                        != ':')
                    strcat(uafdata[value].sdata, ":");    /* ends with ':' */
                strcpy(temp, uafdata[value].sdata);
                uafdata[value].sdata[0] = strlen(temp);   /* make length byte */
                for (pad = strlen(temp); pad < items[ind].size; pad++)
                    temp[pad] = ' ';     /* pad with spaces, no null */
                strncpy(&uafdata[value].sdata[1], temp, items[ind].size);
                itmlst[item].bufadr = uafdata[value].sdata;
                break;
            case DWP_ALPHA:   /* account */
                for (pad = 0; pad < items[ind].size && uafdata[value].sdata[pad];)
                    pad++;  /* skip over string */
                for (; pad < items[ind].size; pad++)
                    uafdata[value].sdata[pad] = ' '; /* pad with spaces, no null */
            case DWP_PRIV:    /* common code for all alphabetic, priv */
                itmlst[item].bufadr = uafdata[value].sdata;
                break;
            case DWP_INT:
                sscanf(uafdata[value].sdata, "%d", &uafdata[value].idata);
            case DWP_TIME:    /* these have correct data in .idata */
            case DWP_FLAG:
            case DWP_PRIME:
                itmlst[item].bufadr = &uafdata[value].idata;
                break;
            case DWP_CPU:
                dd = hh = mm = ss = cc = 0;
                status = sscanf(uafdata[value].sdata, "%d %02d:%02d:%02d.%02d",
                                                      &dd, &hh, &mm, &ss, &cc);
                if (status < 4) { /* try hh:mm:ss */
                    dd = hh = mm = ss = cc = 0;
                    status = sscanf(uafdata[value].sdata, "%02d:%02d:%02d.%02d",
                                                            &hh, &mm, &ss, &cc);
                }
                if (status < 3) { /* try mm:ss */
                    dd = hh = mm = ss = cc = 0;
                    status = sscanf(uafdata[value].sdata, "%02d:%02d.%02d",
                                                            &mm, &ss, &cc);
                }
                if (status < 2) { /* try ss */
                    dd = hh = mm = ss = cc = 0;
                    status = sscanf(uafdata[value].sdata, "%d.%02d",
                                                            &ss, &cc);
                }
                uafdata[value].idata = dd * 8640000 + hh * 360000 +
                                       mm * 6000 + ss * 100 + cc;
                itmlst[item].bufadr = &uafdata[value].idata;
                break;
            case DWP_DATE: {
                /* This one's kinda tricky, .sdata can contain a 64-bit */
                /* system time or a string.  At this point it should contain */
                /* a string, since .changed is set only in WidgetChanged */
                if (parse_date(value))
                    itmlst[item].bufadr = &uafdata[value].sdata;
                else {
                    DWPerror("Write","Illegal date format", 0);
                    --item;
                }
                break;
            }
            case DWP_UIC:
                itmlst[item].bufadr = &uafdata[value].idata;
                break;
            }
            itmlst[item].retadr = 0;
            item++;
        }
    }
    itmlst[item].itmcode = itmlst[item].buflen = 0;    /* end the list */
    acct_dsc.dsc$w_length = strlen(uafdata[UAI$_USERNAME].sdata);
    if (item) {
        status = sys$setuai(0, 0, &acct_dsc, &itmlst, 0, 0, 0);               
        if (status != SS$_NORMAL)
            DWPmsg("Write/SETUAI", status);
        else
            DWPerror("Write", "SETUAI successful", resource_data.write_interval);
    }
    update_strings();    /* undo the padding of strings done above */

    holder[0] = uafdata[UAI$_UIC].idata;
    for (hptr = held_list, prev_hptr = NULL; hptr;) {
        tmp_hptr = NULL;
        switch (hptr->status) {
        case add_ident:  /* ident flagged for pending add */
            status = sys$add_holder(hptr->identifier, &holder, 0);
            if (status != SS$_NORMAL)
                DWPmsg("Write/ADD_HOLDER", status);
            else
                DWPerror("Write","ADD_HOLDER successful", resource_data.write_interval);
            hptr->status = original;  /* ident is now permanently held */
            break;
        case del_orig:  /* ident flagged for pending delete */
            status = sys$rem_holder(hptr->identifier, &holder);
            if (status != SS$_NORMAL)
                DWPmsg("Write/REM_HOLDER", status);
            else
                DWPerror("Write","REM_HOLDER successful", resource_data.write_interval);
            if (prev_hptr)
                prev_hptr->next = hptr->next;  /* remove node from list */
            else
               held_list = hptr->next;
            tmp_hptr = hptr;
            break;
        case original: /* originally held id, no need to add it again */
            break;
        }
        prev_hptr = hptr;
        hptr = hptr->next;
        if (tmp_hptr)
            XtFree(tmp_hptr);
    }

        /* add identifier to list */
        for (ptr = ident_list, prev = NULL; ptr; prev = ptr, ptr = ptr->next)
            if (strcmp(ptr->name, uafdata[UAI$_USERNAME].sdata) >= 0) break;
        /* if ptr = 0, no identifier was higher than username
           if strcmp > 0, ptr->name is the next higher name
           if strcmp = 0, names matched; don't insert
        */
        if (!ptr || strcmp(ptr->name, uafdata[UAI$_USERNAME].sdata)) {
            next = ptr;
            ptr = XtNew(ident);
            if (prev)
                prev->next = ptr;
            else
                ident_list = ptr;
            ptr->name = XtNewString(uafdata[UAI$_USERNAME].sdata);
            ptr->next = next;
            ptr->identifier = uafdata[UAI$_UIC].idata;
            ptr->status = uic_ident;
            uic_count++;
        }
        /* add group identifier to list */
        for (ptr = ident_list, prev = NULL; ptr; prev = ptr, ptr = ptr->next) 
            if (strcmp(ptr->name, uafdata[UAI$_ACCOUNT].sdata) >= 0) break;
        if (!ptr || strcmp(ptr->name, uafdata[UAI$_ACCOUNT].sdata)) {
            next = ptr;
            ptr = XtNew(ident);
            if (prev)
                prev->next = ptr;
            else
                ident_list = ptr;
            ptr->name = XtNewString(uafdata[UAI$_ACCOUNT].sdata);
            ptr->next = next;
            ptr->identifier = (uafdata[UAI$_UIC].idata | 0xFFFF);
            ptr->status = account;
            account_count++;
            if (accounts_box && XtIsManaged(accounts_box)) {
               build_accounts();  /* reload accounts info */
            }
        }

    XUndefineCursor(top_display, top_window);
}


/*
 *  Delete a UAF record
 *  Called by 'Remove' menu item
 */

static XtCallbackProc Remove(Widget widget, XtPointer data,
            XmPushButtonCallbackStruct *reason)
{
    Cardinal ac;
    Arg arglist[5];
    char msg[30];
    XmString xmstr;
    String str;
    static Widget caution_box = NULL;

    str = XmTextGetString(widget_array[UAI$_USERNAME]);
    strcpy(msg, str);
    XtFree(str);
    if (!get_record(msg)) {
        sprintf(msg, "User %s doesn't exist", uafdata[UAI$_USERNAME].sdata);
        DWPerror("Remove", msg, 0);
        return;
    }
    if (!caution_box) {
        ac = 0;
        xmstr = XmStringCreateSimple("DWProfile: Remove");
        XtSetArg(arglist[ac], XmNdialogTitle, xmstr); ac++;
        XtSetArg(arglist[ac], XmNdialogStyle, XmDIALOG_APPLICATION_MODAL); ac++;
        caution_box = XmCreateWarningDialog(dwprofile_main, "caution_box", arglist, ac);
        XmStringFree(xmstr);
        XtUnmanageChild(XmMessageBoxGetChild(caution_box, XmDIALOG_HELP_BUTTON));
        XtAddCallback(caution_box, XmNokCallback, CautionCallback, NULL);
    }
    ac = 0;
    sprintf(msg, "Remove user %s?", uafdata[UAI$_USERNAME].sdata);
    xmstr = XmStringCreateSimple(msg);
    XtSetArg(arglist[ac], XmNmessageString, xmstr); ac++;
    XtSetValues(caution_box, arglist, ac);
    XmStringFree(xmstr);
    XtManageChild(caution_box);    /* popup modal caution box */
}


/*
 *  Issue REMOVE UAF command if OK button pressed in caution box
 */

static XtCallbackProc CautionCallback(Widget widget, XtPointer data,
            XmPushButtonCallbackStruct *reason)
{
    char command[20];

    XtUnmanageChild(widget);
    XDefineCursor(top_display, top_window, wait_cursor);
    XFlush(top_display);
    if (reason->reason == XmCR_OK
            && uafdata[UAI$_USERNAME].sdata[0]) {
        sprintf(command, "$ UAF REMOVE %s", uafdata[UAI$_USERNAME].sdata);
        spawn_dcl(command);
        get_record("");
    }
    XUndefineCursor(top_display, top_window);
}


/*
 *  Remove the error message widget when ack'ed or timed out
 */

static XtTimerCallbackProc MessageCallback(Widget widget, XtPointer data)
{
    XtUnmanageChild(widget); /* popdown message box */
    XtRemoveTimeOut(timeout_id); /* clear out timer */
}


/*
 *  Stop the idents work proc when 'reading idents' WorkingDialog widget is cancelled
 */

static XtCallbackProc IdentsCancelCallback(Widget widget, XtPointer data,
            XmPushButtonCallbackStruct *reason)
{
    XtRemoveWorkProc(idents_work_proc_id);
    idents_work_proc_id = (XtWorkProcId) 0;
}


/*
 *  Stop find() when 'find progress' WorkingDialog widget is cancelled
 */

static XtCallbackProc FindCancelCallback(Widget widget, XtPointer data,
            XmPushButtonCallbackStruct *reason)
{
    XtRemoveWorkProc(find_work_proc_id);
    find_work_proc_id = (XtWorkProcId) 0;
    if (progress_id) {
        XtRemoveTimeOut(progress_id);        /* cancel pending timeout */
        progress_id = (XtIntervalId) 0;
    }

    if (!found_ptr->next) {        /* nothing left on found list? */
        XtSetSensitive(button_array[find_button], OFF);
        XtSetSensitive(button_array[find_menu], OFF);
    }
}


/*
 *  Pop up (or remove) the access dialog box
 *  Called by 'Access' menu item
 */

static XtCallbackProc Access(Widget widget, XtPointer data,
                XmToggleButtonCallbackStruct *reason)
{
    Cardinal wi, ac;
    Arg arglist[2];

    if (!access_box) {
        if ((MrmFetchWidget(Hierarchy, "access_box", toplevel,
             &access_box, &class)) != MrmSUCCESS)
            XtAppError(context, "Error Fetching Access main widget");
        /* adjust drawing area using hourSize resource */
        for (wi = UAI$_NETWORK_ACCESS_P; wi <= UAI$_REMOTE_ACCESS_S; wi++) {
            ac = 0;
            XtSetArg(arglist[ac], XmNwidth, resource_data.hour_size * 24); ac++;   
            if (widget_array[wi])
                XtSetValues(widget_array[wi], arglist, ac);
        }
    }
    if (XtIsManaged(access_box)) {
        XtUnmanageChild(access_box);
        XmToggleButtonSetState(button_array[access_menu], OFF, NO);
    }
    else {
        XtManageChild(access_box);
        XmToggleButtonSetState(button_array[access_menu], ON, NO);
        update_prime();       /* may not have existed */
        set_handlers();       /* set window event handlers */
    }
}


/*
 *  Pop up (or remove) the privs dialog box
 *  Called by 'Privs' menu item
 */

static XtCallbackProc Privs(Widget widget, XtPointer data,
                XmToggleButtonCallbackStruct *reason)
{
    if (!privs_box)
        if ((MrmFetchWidget(Hierarchy, "privs_box", toplevel,
             &privs_box, &class)) != MrmSUCCESS)
            XtAppError(context, "Error Fetching Privs main widget");
    if (XtIsManaged(privs_box)) {
        XtUnmanageChild(privs_box);
        XmToggleButtonSetState(button_array[privs_menu], OFF, NO);
    }
    else {
        XtManageChild(privs_box);
        XmToggleButtonSetState(button_array[privs_menu], ON, NO);
        update_privs();        /* may not have existed */
    }
}


/*
 *  Activation callback on Select menu item
 *  also called by select_key()
 *  widget, reason not referenced
 */

static XtCallbackProc Select(Widget widget, int *data,
                XmPushButtonCallbackStruct *reason)
{
    Cardinal ind, ac;
    Arg arglist[10];
    ident *ptr, *tptr;
    char str[80];
    XmString xmstr;

    if (found_list.next || *data < 100) {  /* skip if 100 and nothing on list */
        XtSetSensitive(button_array[find_button], ON);  /* 'find' button is now active */
        XtSetSensitive(button_array[find_menu], ON);    /* 'find' menu item is now active */
    }
    if (*data == 100) {  /* 100 means re-enable found_list */
        found_ptr = &found_list;
        return;
    }

    sprintf(str, "Reset %s search", select_data[*data].name);
    xmstr = XmStringCreateSimple(str);
    ac = 0;
    XtSetArg(arglist[ac], XmNlabelString, xmstr); ac++;
    XtSetArg(arglist[ac], XmNsensitive, TRUE); ac++;
    XtSetValues(button_array[restart_button], arglist, ac);
    XmStringFree(xmstr);

    for (ptr = found_list.next; ptr;) { /* free old found list */
        XtFree(ptr->name);
        tptr = ptr;
        ptr = ptr->next;
        XtFree(tptr);
    }
    select_index = *data;       /* which field to match */
    if (select_string)
        XtFree(select_string);  /* free memory used by old string */
    switch (select_data[*data].type) {
    case DWP_ALPHA:  /* username, account */
    case DWP_LALPHA: /* other text fields */
    case DWP_UIC:    /* use the text of UIC */
        /* we could parse_dev(), parse_dir(), etc. here to ensure match */
        select_string = XtNewString(unpad(uafdata[select_data[*data].code].sdata));
        break;
    case DWP_DATE:  /* pwdlifetime, expiration; text string or 64 bit date */
        if (uafdata[select_data[*data].code].changed)  /* text was entered */
            if (!parse_date(select_data[*data].code))  /* convert text to binary */
                DWPerror("Select","Illegal date format", 0);
        select_string = XtMalloc(select_data[*data].size);
        memcpy(select_string, uafdata[select_data[*data].code].sdata,
               select_data[*data].size);
        break;
    default:
        select_string = XtNewString("");  /* so find_work_proc won't blow up */
    }
    select_ptr = found_add = NULL;  /* restart find from the beginning */
    found_list.next = NULL;
    found_ptr = &found_list;
    if (find_work_proc_id)  /* is there one going on now? */
        XtRemoveWorkProc(find_work_proc_id);  /* stop it */
    find_work_proc_id = XtAppAddWorkProc(context, find_work_proc, NULL);
    /* note: to be run first, read_identifiers must be called after AppAddWorkProc */
    read_identifiers(FALSE);        /* set up ident_list if necessary */
}


/*
 *  Creation callback;  save the widget using the UAI code as the index
 */

static XtCallbackProc WidgetCreated(Widget widget, XtPointer data,
                XmAnyCallbackStruct *reason)
{
    widget_array[*data] = widget;
}


/*
 *  Creation callback;  save the save menu button widget in button_array
 */

static XtCallbackProc ButtonCreated(Widget widget, XtPointer data,
                XmAnyCallbackStruct *reason)
{
    button_array[*data] = widget;
}


/*
 *  Called whenever a key is pressed in a text widget
 */

static XtCallbackProc WidgetChanged(Widget widget, XtPointer data,
                XmTextVerifyCallbackStruct *reason)
{
    String str;
    Cardinal pos;

    if (setting_widgets) return;
    pos = XmTextGetInsertionPosition(widget);
    str = XmTextGetString(widget);
    uafdata[*data].changed = TRUE;    /* element has changed */
    if (*data != UAI$_OWNER)
            uppercase(str);
    strcpy(uafdata[*data].sdata, str);
    setting_widgets = TRUE;        /* Don't allow recursion */
    XmTextSetString(widget, str);
    XmTextSetInsertionPosition(widget, pos);
    setting_widgets = FALSE;
    XtFree(str);
}


/*
 *  Called when flag toggle button is created
 */

static XtCallbackProc FlagsCreated(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    flags_array[*data] = widget;
}


/*
 *  Called whenever a flag toggle button is pressed
 */

static XtCallbackProc FlagsChanged(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    if (setting_flags) return;
    uafdata[UAI$_FLAGS].changed = TRUE;        /* element has changed */
    uafdata[UAI$_FLAGS].idata ^= 1 << *data;    /* toggle flag bit */
}


/*
 *  Called when a primary day toggle button is created
 */

static XtCallbackProc PrimeCreated(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    prime_array[*data] = widget;
}


/*
 *  Called whenever a prime day toggle button is pressed
 */

static XtCallbackProc PrimeChanged(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    if (setting_prime) return;
    uafdata[UAI$_PRIMEDAYS].changed = TRUE;        /* element has changed */
    uafdata[UAI$_PRIMEDAYS].idata ^= 1 << *data;    /* toggle bit in primedays */
}


/*
 *  Called when a privs toggle button is created
 */

static XtCallbackProc PrivsCreated(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    privs_array[*data] = widget;
}


/*
 *  Called whenever a privs toggle button is pressed
 */

static XtCallbackProc PrivsChanged(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    if (setting_privs) return;
    if (*data < PRIVS_BIAS) {
        uafdata[UAI$_PRIV].sdata[*data >> 3] ^= 1 << (*data & 7);
        uafdata[UAI$_PRIV].changed = TRUE;    /* element has changed */
        }
    else {
        uafdata[UAI$_DEF_PRIV].sdata[(*data - PRIVS_BIAS) >> 3] ^=
            1 << (*data - PRIVS_BIAS & 7);
        uafdata[UAI$_DEF_PRIV].changed = TRUE;
    }
}


/*
 *  Read selection box widget to get account name
 *  Create new UIC
 */

static XtCallbackProc AccountChanged(Widget widget, XtPointer data,
            XmSelectionBoxCallbackStruct *reason)
{
    String str;
    Cardinal ind;

    if (reason->reason == XmCR_CANCEL) {
        XmTextSetString(widget_array[UAI$_ACCOUNT], "");
        uafdata[UAI$_ACCOUNT].changed = TRUE;
        strcpy(uafdata[UAI$_ACCOUNT].sdata, "");
        return;
    }

    XmStringGetLtoR(reason->value, XmSTRING_DEFAULT_CHARSET, &str);
    XmTextSetString(widget_array[UAI$_ACCOUNT], str);
    uafdata[UAI$_ACCOUNT].changed = TRUE;
    strcpy(uafdata[UAI$_ACCOUNT].sdata, str);
    uppercase(uafdata[UAI$_ACCOUNT].sdata);
    XtFree(str);
    strcpy(uafdata[UAI$_UIC].sdata, "[0,0]");    /* force UIC recalc */
    calc_uic();
    trans_uic(uafdata[UAI$_UIC].idata);
}


/*
 *  Called by 'Read Identifiers' menu item
 */

static XtCallbackProc ReadIdentifiers(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    read_identifiers(TRUE);
}


/*
 *  Pop up accounts selection box widget
 *  Called by 'Accounts' menu item
 */

static XtCallbackProc Accounts(Widget widget, XtPointer data,
            XmAnyCallbackStruct *reason)
{
    Cardinal ac;
    Arg arglist[5];
    XmString xmstr;

    /* make accounts_box */
    /* this could be fetched from a UID file, but it's easier here */
    if (!accounts_box) {
        ac = 0;
        xmstr = XmStringCreateSimple("DWProfile: Accounts");
        XtSetArg(arglist[ac], XmNdialogTitle, xmstr); ac++;
	XtSetArg(arglist[ac], XmNdefaultPosition, FALSE); ac++;
        accounts_box = XmCreateSelectionDialog(toplevel, "accounts_box", arglist, ac);
        XmStringFree(xmstr);
        XtAddCallback(accounts_box, XmNapplyCallback, AccountChanged, NULL);
        XtAddCallback(accounts_box, XmNhelpCallback, Help, "menus windows accounts");
        XtUnmanageChild(XmSelectionBoxGetChild(accounts_box, XmDIALOG_OK_BUTTON));
        XtUnmanageChild(XmSelectionBoxGetChild(accounts_box, XmDIALOG_CANCEL_BUTTON));
    }
    if (XtIsManaged(accounts_box)) {
        XtUnmanageChild(accounts_box);
        XmToggleButtonSetState(button_array[accounts_menu], OFF, NO);
        return;
    }
    XtManageChild(accounts_box);
    XmToggleButtonSetState(button_array[accounts_menu], ON, NO);
    if (ident_list)
        build_accounts();  /* build list from idents */
    else
        read_identifiers(FALSE); /* get idents, build list when done */
}


/*
 *  Pop up (or remove) the idents dialog box
 *  Called by 'Idents' menu item
 */

static XtCallbackProc Idents(Widget widget, XtPointer data,
                XmAnyCallbackStruct *reason)
{
    if (!idents_box)
        if ((MrmFetchWidget(Hierarchy, "idents_box", toplevel,
             &idents_box, &class)) != MrmSUCCESS)
            XtAppError(context, "Error Fetching Idents main widget");
    if (XtIsManaged(idents_box)) {
        XtUnmanageChild(idents_box);
        XmToggleButtonSetState(button_array[idents_menu], OFF, NO);
        return;
    }
    XtManageChild(idents_box);
    XmToggleButtonSetState(button_array[idents_menu], ON, NO);
    if (ident_list)
        update_held();  /* build list from idents */
    else
        read_identifiers(FALSE);  /* get idents, build list when done */
}


/*
 *  Add (or remove) the identifier for the user
 *  Called by selecting identifier from list box
 */

static XtCallbackProc ListSelect(Widget widget, XtPointer data,
                XmListCallbackStruct *list)
{
    Cardinal count, ind;
    ident *ptr;
    held_ident *hptr, *prev_hptr, *tmp_hptr;
    String str;

    switch(*data) {
    case k_widget_all_list:
        XmStringGetLtoR(list->item, XmSTRING_DEFAULT_CHARSET, &str);
        /* find the identifier that matches the selected item */
        for (ptr = ident_list; ptr; ptr = ptr->next)
            if (!strcmp(ptr->name, str)) break;
        XtFree(str);
        for (hptr = held_list, prev_hptr = NULL; hptr; prev_hptr = hptr, hptr = hptr->next) /* find deleted id or end */
            if (hptr->identifier == ptr->identifier) break;
        if (!hptr) {   /* not in held list, make new node */
            hptr = XtNew(held_ident);
            hptr->name = XtNewString(ptr->name);
            hptr->identifier = NULL;
            hptr->next = NULL;
            if (prev_hptr)
                prev_hptr->next = hptr;  /* point prev node to new node */
            else
                held_list = hptr;        /* or start list with first new node */
        }
        if (!hptr->identifier) {
            hptr->status = add_ident;    /* not found */
            hptr->identifier = ptr->identifier;
        }
        else
            hptr->status = original; /* orig ident deleted then added back */
        break;
    case k_widget_held_list:
        count = 0;
        for (hptr = held_list, prev_hptr = NULL; hptr;) {
            tmp_hptr = NULL;
            if (hptr->status == original ||
                hptr->status == add_ident) count++;
            if (count == list->item_position) {   /* is this the list item selected ? */
                if (hptr->status == add_ident) {  /* if this is a pending add */
                    if (prev_hptr)                /* remove it from held_list */
                        prev_hptr->next = hptr->next;
                    else
                        held_list = hptr->next;
                    tmp_hptr = hptr;              /* and flag it for delete */
                }
                hptr->status = del_orig; /* original ident marked for removal */
                break;
            }
            prev_hptr = hptr;
            hptr = hptr->next;
            if (tmp_hptr)
                XtFree(tmp_hptr);
        }
        break;
    }
    update_held();
}

 
/*
 *  Activation callback on a help menu or help callback
 */

static XtCallbackProc Help(Widget widget, String data,
                XmPushButtonCallbackStruct *reason)
{

    Cardinal ac;
    Arg arglist[10];
    XmString appname, glossarytopic, overviewtopic, libspec, topic;
    static Widget help_box = NULL;
    Widget temp_help_box;

    if (!strcmp(data, "context")) {
        /* context sensitive help mode, we'll get back here via helpcallback */
        DXmHelpOnContext(toplevel, FALSE);	
        return;
    }

    topic = XmStringCreateSimple(data);

    if (!help_box || XtIsManaged(help_box)) {
        /* We're going to create a help widget */
        ac = 0;
        appname = XmStringCreateSimple("DWProfile");
        overviewtopic = XmStringCreateSimple("overview");
        libspec = XmStringCreateSimple(HELP_LIBRARY);
        XtSetArg(arglist[ac], DXmNapplicationName, appname); ac++;
        XtSetArg(arglist[ac], DXmNoverviewTopic, overviewtopic); ac++;
        XtSetArg(arglist[ac], DXmNlibrarySpec, libspec); ac++;
    }

    if (!help_box) {  /* create the help box */
        help_box = DXmCreateHelpDialog (toplevel, "help_box", arglist, ac);
        XmStringFree(appname);
        XmStringFree(overviewtopic);
        XmStringFree(libspec);
    }

    if (XtIsManaged(help_box)) {  /* there's already a help box up, make another one */
        XtSetArg(arglist[ac], DXmNfirstTopic, topic); ac++;
        temp_help_box = DXmCreateHelpDialog (toplevel, "help_box", arglist, ac);
        XtAddCallback(temp_help_box, XmNunmapCallback, DestroyHelpCallback, NULL);
        XmStringFree(appname);
        XmStringFree(overviewtopic);
        XmStringFree(libspec);
        XmStringFree(topic);
        XtManageChild(temp_help_box);
        return;
    }
    else { /* exists but is unmanaged */
        ac = 0;
        XtSetArg(arglist[ac], DXmNfirstTopic, topic); ac++;
        XtSetValues(help_box, arglist, ac);
    }

    XmStringFree(topic);
    XtManageChild(help_box);
}


/*
 *  Destroy generic help widget when it gets unmapped
 */

static XtCallbackProc DestroyHelpCallback(Widget widget, XtPointer data,
                XmAnyCallbackStruct *reason)
{
    XtDestroyWidget(widget);
}
