/*  DWPROFILE.C -- Authorize/Profile/DECburger replacement */
/*  Copyright © 1990 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 account
*           Fixed: Identifier not changed on UIC change
*/

#include descrip
#include string
#include ssdef
#include stdio
#include clidef
#include jpidef
#include prvdef
#include rmsdef
#include <DECW$INCLUDE:DECwDwtApplProg.h>
#include <decw$cursor.h>
#include "dwpdef.h"

static char *uidname[] = {
    "DWPUID:DWPROFILE.UID",
    "DWPUID:ACCESS.UID",
    "DWPUID:PRIVS.UID",
    "DWPUID:IDENTS.UID",
};
static DRMHierarchy Hierarchy;
static DRMCode class;
static Widget toplevel, dwprofile_main, access_box, privs_box,
    menu, widget_array[MAX_WIDGET], flags_array[MAX_FLAGS],
    prime_array[MAX_PRIME], privs_array[MAX_PRIVS],
    idents_box;
static void init_application(), get_gc(), update_prime(),
    update_time(ulong), update_strings(), update_flags(),
    update_privs(), set_translations(), set_handlers(),
    build_popup(), trans_uic(ulong), make_cursor(),
    update_held(), set_defaults(),
    DWPerror(char *, char *, ulong), DWPmsg(char *, ulong);
static Boolean get_account(char *);
static Boolean read_identifiers(Boolean);
static XtEventHandler access_event_handler();
static XtCallbackProc WidgetCreated(), WidgetChanged(),
    Quit(), Write(), Read(), Remove(), Access(),
    Privs(), Accounts(), Idents(),
    FlagsCreated(), FlagsChanged(),
    PrimeCreated(), PrimeChanged(),
    PrivsCreated(), PrivsChanged(),
    AccountChanged(), CautionCallback(),
    MessageCallback(), ReadIdentifiers(),
    ListSelect();
static XtActionProc return_key(), popup();
static DRMRegisterArg regvec[] = {
    {"WidgetCreated", (caddr_t)WidgetCreated},
    {"WidgetChanged", (caddr_t)WidgetChanged},
    {"Quit", (caddr_t)Quit},
    {"Write", (caddr_t)Write},
    {"Read", (caddr_t)Read},
    {"Remove", (caddr_t)Remove},
    {"Access", (caddr_t)Access},
    {"Privs", (caddr_t)Privs},
    {"Accounts", (caddr_t)Accounts},
    {"FlagsCreated", (caddr_t)FlagsCreated},
    {"FlagsChanged", (caddr_t)FlagsChanged},
    {"PrimeCreated", (caddr_t)PrimeCreated},
    {"PrimeChanged", (caddr_t)PrimeChanged},
    {"PrivsCreated", (caddr_t)PrivsCreated},
    {"PrivsChanged", (caddr_t)PrivsChanged},
    {"ReadIdentifiers", (caddr_t)ReadIdentifiers},
    {"Idents", (caddr_t)Idents},
    {"ListSelect", (caddr_t)ListSelect}
};
static char return_translation[] = "<Key>Return: return_key()\n\
<Key>KP_Enter: return_key()",
    popup_translation[] = "<Btn2Down>: popup()";
static XtActionsRec action_table[] = {
    {"return_key", return_key},
    {"popup", popup}
};                                             
static XtResource resources[] = {
    {"defaultIdent", "DefaultIdent", XtRString, sizeof(char *),
     XtOffset(ApplicationDataPtr, default_ident), XtRString, ""},
    {"hourSize", "HourSize", XtRInt, sizeof(int),
     XtOffset(ApplicationDataPtr, hour_size), XtRString, "8"},
    {"notifyInterval", "NotifyInterval", XtRInt, sizeof(int),
     XtOffset(ApplicationDataPtr, notify_interval), XtRString, "0"},
    {"overdraft", "Overdraft", XtRInt, sizeof(int),
     XtOffset(ApplicationDataPtr, overdraft), XtRString, "100"},
    {"quota", "Quota", XtRInt, sizeof(int),
     XtOffset(ApplicationDataPtr, quota), XtRString, "1000"},
    {"showAccess", "ShowAccess", XtRBoolean, sizeof(Boolean),
     XtOffset(ApplicationDataPtr, show_access), XtRString, "False"},
    {"showAccounts", "ShowAccounts", XtRBoolean, sizeof(Boolean),
     XtOffset(ApplicationDataPtr, show_accounts), XtRString, "False"},
    {"showIdents", "ShowIdents", XtRBoolean, sizeof(Boolean),
     XtOffset(ApplicationDataPtr, show_idents), XtRString, "False"},
    {"showPrivs", "ShowPrivs", XtRBoolean, sizeof(Boolean),
     XtOffset(ApplicationDataPtr, show_privs), XtRString, "False"},
    {"showWorkBox", "ShowWorkBox", XtRBoolean, sizeof(Boolean),
     XtOffset(ApplicationDataPtr, show_work), XtRString, "True"},
    {"versionLimit", "VersionLimit", XtRInt, sizeof(int),
     XtOffset(ApplicationDataPtr, version_limit), XtRString, "0"},
};
static ApplicationData resource_data;
static struct {
    int idata;
    Boolean changed;
    char sdata[65];
    } uafdata[MAX_WIDGET];
static struct itemlist3 itmlst[MAX_WIDGET];
static Display *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;
static held_ident *held_list = NULL;
static ulong timeout_id;
static Cursor wait_cursor;


main(unsigned int argc, char *argv[])
{
    ulong status, ind = 0;
    Arg arglist[4];
    Pixmap icon_pixmap, iconify_pixmap;

    init_application();
    DwtInitializeDRM();
    toplevel = XtInitialize(TITLE, "DWProfile", NULL, 0, &argc, argv);

    XtSetArg(arglist[ind], DwtNx, 10); ind++;
    XtSetArg(arglist[ind], DwtNy, 50); ind++;
    XtSetArg(arglist[ind], DwtNallowShellResize, TRUE); ind++;
    XtSetValues(toplevel, arglist, ind);

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

    status = DwtRegisterDRMNames(regvec, XtNumber(regvec));
    if (status != DRMSuccess) lib$signal(status);

    if ((DwtFetchWidget(Hierarchy, "dwprofile_main", toplevel,
                        &dwprofile_main, &class)) != DRMSuccess)
        XtError("Error Fetching main widget");

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

    XtManageChild(dwprofile_main);
    XtRealizeWidget(toplevel);
    XtInstallAllAccelerators(dwprofile_main, dwprofile_main);

    top_display = XtDisplay(toplevel);
    top_window = XtWindow(toplevel);
    icon_pixmap = XCreateBitmapFromData(top_display, top_window,
                                        dwp32_bits, dwp32_width, dwp32_height);
    iconify_pixmap = XCreateBitmapFromData(top_display, top_window,
                                        dwp17_bits, dwp17_width, dwp17_height);

    /* Set up the WM icons via shell widget attributes */
    XtSetArg(arglist[ind], DwtNiconifyPixmap, iconify_pixmap); ind++;
    XtSetArg(arglist[ind], DwtNiconPixmap, icon_pixmap); ind++;
    XtSetValues(toplevel, arglist, ind);

    if ((DwtFetchWidget(Hierarchy, "access_box", toplevel,
                        &access_box, &class)) != DRMSuccess)
        XtError("Error Fetching Access main widget");

    make_cursor();             /* create wait_cursor */
    set_translations();        /* set text widget translations */
    set_handlers();            /* set window event handlers */
    build_popup();             /* no UIL for MenuPopup widget */
    get_gc();                  /* set up stuff for access windows */
    if (argc == 1 || !get_account(argv[1]))
        if (get_account("DEFAULT")) {
            if (argc == 2)
                set_defaults(argv[1]);
        }
        else
            DWPerror("Main Init", "Cannot read DEFAULT account", 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);

    XtMainLoop();
}


/*** initialization routines ***/

/*
* Set sysprv
* Clear out widget arrays
*/
static void init_application()
{
    ulong 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) {
        XtError("lib$getjpi failed");
        exit(status);
    }
       
    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) {
            XtError("sys$setprv failed");
            exit(status);
        }
    }
    else {
        XtError("Insufficient privilege");
        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 all the text widgets
*/
static void set_translations()
{
    ulong ind;

    /* set up the translation table */
    XtAddActions(action_table, XtNumber(action_table));

    for (ind=0; items[ind].code; ind++)
        if (widget_array[items[ind].code] &&
                items[ind].type == DWP_LALPHA ||
                items[ind].type == DWP_ENCR   ||
                items[ind].type == DWP_ALPHA  ||
                items[ind].type == DWP_DATE   ||
                items[ind].type == DWP_CPU    ||
                items[ind].type == DWP_UIC    ||
                items[ind].type == DWP_INT)
            XtOverrideTranslations(widget_array[items[ind].code],
                                   XtParseTranslationTable(return_translation));

    /* Indicate that there is a popup menu routine */
    XtOverrideTranslations(dwprofile_main,
                           XtParseTranslationTable(popup_translation));
}


/*
* set up event handlers for access windows
*/
static void set_handlers()
{
    ulong ind;

    for (ind = UAI$_NETWORK_ACCESS_P; ind <= UAI$_REMOTE_ACCESS_S; ind++)
        XtAddEventHandler(widget_array[ind],
                          ButtonPressMask | ButtonReleaseMask | ExposureMask |
                          Button1MotionMask, NULL, access_event_handler, ind);
}


/*
* build a MenuPopup widget and children
*/
static void build_popup()
{
#define MENUS 4
    Arg arg;
    ulong count = 0;
    DwtCallback callbacks[] = {
        {0, 0},
        {NULL, NULL}
    };
    struct {
        char *resource;
        VoidProc callback;
    } params[MENUS] = {
        {"access_button", (VoidProc)Access},
        {"privs_button", (VoidProc)Privs},
        {"accounts_button", (VoidProc)Accounts},
        {"idents_button", (VoidProc)Idents}
    };
    Widget menu_items[MENUS];

    menu = DwtMenuPopupCreate(dwprofile_main, "popup", NULL, 0);

    for (count = 0; count < MENUS; count++) {
        callbacks[0].proc = params[count].callback;
        XtSetArg(arg, DwtNactivateCallback, callbacks);
        menu_items[count] = DwtPushButtonCreate(menu, params[count].resource, &arg, 1);
    }

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


/*
* set up the graphic contexts gc_draw and gc_clear
*/
static void get_gc()
{
    Window window;
    Arg arglist[2];
    XGCValues gc_values;
    Pixel foreground, background;
    ulong ind = 0;

    display = XtDisplay(dwprofile_main);
    window = XtWindow(dwprofile_main);

    XtSetArg(arglist[ind], DwtNforeground, &foreground);  ind++;
    XtSetArg(arglist[ind], DwtNbackground, &background);  ind++;
    XtGetValues(dwprofile_main, arglist, ind);
    gc_values.foreground = foreground;
    gc_values.background = background;
    gc_draw = XCreateGC(display, window,
                        GCForeground | GCBackground, &gc_values);
    gc_values.foreground = background;
    gc_clear = XCreateGC(display, window, GCForeground, &gc_values);
}



/*** Utility routines ***/

/*
* Set the username, password and directory name
* Called by main, Read
*/ 
static void set_defaults(char *user)
{
    Arg arg;
    char text[40];

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

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


/*
* flush out the current backlog of events
*/
static void flush_events()
{
    XEvent event;

    while (XtPending()) {
        XtNextEvent(&event);
        XtDispatchEvent(&event);
    }
}


/*
* Lookup an account and fill those widgets
* Called by Read and program initialization
*/
static Boolean get_account(char *acct)
{
    ulong status, ind, resid, context = 0, holder[2] = {0, 0};
    ident *ptr;
    held_ident *hptr, *tptr;
    Arg arg;
    char text[40];
    $DESCRIPTOR(acct_dsc, 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;
    }
    for (ind = 0; ind < MAX_WIDGET; ind++) {
        uafdata[ind].sdata[0] = '\0';
        uafdata[ind].idata = 0;
        uafdata[ind].changed = FALSE;
    }
    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;

    holder[0] = uafdata[UAI$_UIC].idata;
    for (hptr = held_list; hptr; hptr = hptr->next) { /* free old held list */
        tptr = hptr;
        free(tptr);
    }
    held_list = NULL;
    for (hptr = held_list;;) {
        status = sys$find_held(&holder, &resid, 0, &context);
        if (status == SS$_NOSUCHID) break;
        if (status != SS$_NORMAL) {
            sys$finish_rdb(&context);
            DWPmsg("get_account", status);
            break;
        }
        if (hptr) {
            hptr->next = (held_ident *)malloc(sizeof(held_ident));
            hptr = hptr->next;
        }
        else
            held_list = hptr = (held_ident *)malloc(sizeof(held_ident));
        hptr->next = NULL;
        hptr->identifier = resid;
        hptr->status = original;
    }
    
    for (ptr = ident_list; ptr; ptr = ptr->next)
        if (ptr->status == del_ident)
            ptr->status = add_ident;

    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 */

    sprintf(text, "%s\n%s", TITLE, acct);
    XtSetArg(arg, DwtNiconName, 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()
{
    ulong ind, value, length;
    char str[65], *ptr;
    Arg arg;
    DwtCompString dwtstr;

    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 */
                length = (ulong) uafdata[value].sdata[0];
                uafdata[value].sdata[length+1] = '\0';
                DwtSTextSetString(widget_array[value],
                    &uafdata[value].sdata[1]);
                break;
            case DWP_ALPHA:    /* string */
                uafdata[value].sdata[12] = ' ';    /* make sure ' ' */
                uafdata[value].sdata[13] = '\0';   /* make sure null */
                ptr = strchr(uafdata[value].sdata, ' ');
                if (ptr) *ptr = '\0';
                DwtSTextSetString(widget_array[value],
                    uafdata[value].sdata);
                break;
            case DWP_ENCR:    /* password */
                DwtSTextSetString(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' */
                DwtSTextSetString(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' */
                dwtstr = DwtLatin1String(str);
                XtSetArg(arg, DwtNlabel, dwtstr);
                XtSetValues(widget_array[value], &arg, 1);
                XtFree(dwtstr);
                break;
                }
            case DWP_CPU:    /* CPU time */
                if (!uafdata[value].idata)
                    strcpy(str, "(none)");
                else {
                    ulong 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);
                }
                DwtSTextSetString(widget_array[value], str);
                break;
            case DWP_UIC:    /* UIC */
                sprintf(uafdata[value].sdata, "[%o,%o]",
                    uafdata[value].idata >> 16,
                    uafdata[value].idata & 0xFFFF);
                DwtSTextSetString(widget_array[value],
                    uafdata[value].sdata);
                trans_uic(uafdata[value].idata);
                break;
            case DWP_INT:    /* text integer */
                sprintf(str, "%d", uafdata[value].idata);
                DwtSTextSetString(widget_array[value], str);
                break;
            case DWP_RINT:    /* label integer */
                sprintf(str, "%d", uafdata[value].idata);
                dwtstr = DwtLatin1String(str);
                XtSetArg(arg, DwtNlabel, dwtstr);
                XtSetValues(widget_array[value], &arg, 1);
                XtFree(dwtstr);
                break;
            }
    setting_widgets = FALSE;
}


/*
* Update login flags toggle buttons according to UAI data
*/
static void update_flags()
{
    ulong ind;
    Boolean notify;

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


/*
* Set the buttons to reflect primedays
* Set the time map window widgets
*/
static void update_prime()
{
    ulong ind;
    Boolean notify;

    setting_prime = TRUE;
    for (ind = 0; ind < MAX_PRIME; ind++)
        if (prime_array[ind])
            DwtToggleButtonSetState(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()
{
    ulong ind;
    Boolean notify;

    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)
                DwtToggleButtonSetState(privs_array[ind], ON, notify);
            else
                DwtToggleButtonSetState(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)
                DwtToggleButtonSetState(privs_array[ind], ON, notify);
            else
                DwtToggleButtonSetState(privs_array[ind], OFF, notify);
    setting_privs = FALSE;
}    


/*
* Update a time map window widget
*/
static void update_time(ulong code)
{
    ulong ind;
    Window window;

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

    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()
{
    ulong ind, count, status;
    Boolean already_granted;
    Arg arglist[3];
    unsigned short int namlen;
    ident *ptr;
    held_ident *hptr;
    DwtCompString *id_list, default_item;
    char nambuf[35];
    $DESCRIPTOR(name_dsc, nambuf);

    if (!widget_array[k_widget_held_list])
        return;

    /* display the held idents list */
    count = 0;
    for (hptr = held_list; hptr; hptr = hptr->next)
        if (hptr->status == original ||
            hptr->status == add) count++;
    id_list = calloc(count, sizeof(DwtCompString));
    ind = 0;
    for (hptr = held_list; hptr; hptr = hptr->next)
        if (hptr->status == original ||
            hptr->status == add) {
            status = sys$idtoasc(hptr->identifier, &namlen, &name_dsc, 0, 0, 0);
            nambuf[namlen] = '\0';
            id_list[ind++] = DwtLatin1String(nambuf);
        }
    ind = 0;
    XtSetArg(arglist[ind], DwtNitems, id_list); ind++;
    XtSetArg(arglist[ind], DwtNitemsCount, count); ind++;
    XtSetArg(arglist[ind], DwtNselectedItemsCount, 0); ind++;
    XtSetValues(widget_array[k_widget_held_list], arglist, ind);
    for (ind = 0; ind < count; ind++)
        XtFree(id_list[ind]);
    free(id_list);

    /* display all the non-uic idents not already held */
    count = 0;
    for (ptr = ident_list; ptr; ptr = ptr->next)
        if (ptr->status == add_ident) count++;
    id_list = calloc(count, sizeof(DwtCompString)); /* upper bound on id names */
    ind = 0;
    for (ptr = ident_list; ptr; ptr = ptr->next)
        if (ptr->status == add_ident || ptr->status == del_ident) {
            already_granted = FALSE;
            ptr->status = del_ident;  /* assume user already has id */
            for (hptr = held_list; hptr; hptr = hptr->next)
                already_granted |= (hptr->identifier == ptr->identifier) &
                                   (hptr->status != delete) &
                                   (hptr->status != del_orig);
            if (!already_granted) {  /* if user doesn't already have id */
                id_list[ind++] = DwtLatin1String(ptr->name);
                ptr->status = add_ident;
            }
        }
    count = ind;
    ind = 0;
    XtSetArg(arglist[ind], DwtNitems, id_list); ind++;
    XtSetArg(arglist[ind], DwtNitemsCount, count); ind++;
    XtSetArg(arglist[ind], DwtNselectedItemsCount, 0); ind++;
    XtSetValues(widget_array[k_widget_all_list], arglist, ind);
    for (ind = 0; ind < count; ind++)
        XtFree(id_list[ind]);
    free(id_list);
    default_item = DwtLatin1String(resource_data.default_ident);
    DwtListBoxSetItem(widget_array[k_widget_all_list], default_item);
    XtFree(default_item);
}


/*
* Convert a compound string to asciz string
*/
static char *get_comp_string(DwtCompString str)
{
    ulong status, dir;
    DwtCompStringContext context;
    char *text;
    ulong charset, lang;
    DwtRendMask rend;

    status = DwtInitGetSegment(&context, str);
    if (status != SS$_NORMAL)
        DWPmsg("get_comp_string", status);
    status = DwtGetNextSegment(&context, &text, &charset, &dir, &lang, &rend);
    if (status != SS$_NORMAL)
        DWPmsg("get_comp_string", status);
    DwtStringFreeContext(&context);
    return text;
}


/*
* Force text to uppercase
* Called by WidgetChanged
*/
static void uppercase(char *ptr)
{
    char *ptr2;

    for (ptr2 = ptr; *ptr2; ptr2++)
        *ptr2 = toupper(*ptr2);
}
    

/*
* Signal a local error
*/
static void DWPerror(char *where, char *what, ulong interval)
{
    Arg arg;
    char msg[100];
    DwtCompString string;
    static Widget message_box = NULL;
    DwtCallback callbacks[] = {
        {(VoidProc)MessageCallback, 0},
        {NULL, NULL}
    };

    if (!message_box) {
        string = DwtLatin1String(what);
        message_box = DwtMessageBox(dwprofile_main, "message_box", TRUE, 0, 0,
                                    DwtModal, string, NULL, callbacks, NULL);
        XtFree(string);
    }
    sprintf(msg, "%s\nFrom: %s", what, where);
    string = DwtLatin1String(msg);
    XtSetArg(arg, DwtNlabel, string);
    XtSetValues(message_box, &arg, 1);
    XtFree(string);
    XtManageChild(message_box);    /* popup modal message box */
    if (interval)
        timeout_id = XtAddTimeOut(interval, MessageCallback, message_box);
}


/*
* Signal an external error
*/
static void DWPmsg(char *where, ulong msg)
{
    char buffer[40];
    ulong status, length = 0;
    $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(char *str)
{
    ulong status, substatus = SS$_NORMAL, flags = CLI$M_NOCLISYM | CLI$M_NOLOGNAM,
          devlen, context;
    char *ptr, dev1[40] = "", dev2[40], dir[40], result[40], filespec[40];
    FILE *file;
    $DESCRIPTOR(dcl_dsc, "DCL.TMP");
    $DESCRIPTOR(nl_dsc, "NL:");
    $DESCRIPTOR(dev1_dsc, dev1);
    $DESCRIPTOR(dev2_dsc, dev2);
    $DESCRIPTOR(dir_dsc, dir);
    $DESCRIPTOR(res_dsc, result);
    $DESCRIPTOR(file_dsc, filespec);

    file = fopen("DCL.TMP","w");
    fprintf(file, "$ DELETE DCL.TMP;*\n");
    fprintf(file, "$ PREVPRIV = F$SETPRV(\"ALL\")\n");
    fprintf(file, "$ DEFINE/USER SYSUAF SYS$SYSTEM:SYSUAF\n");
    fprintf(file, "$ RUN SYS$SYSTEM:AUTHORIZE\n");
    fprintf(file, "%s\n", str);
    fprintf(file, "EXIT\n");

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

    /* add the quota and overdraft resources.  default 1000, 100 */
    if (strlen(uafdata[UAI$_DEFDEV].sdata)) {  /* if defdev exists */
        if ((ulong) uafdata[UAI$_DEFDEV].sdata[0] < 32)
            strcpy(dev1, uafdata[UAI$_DEFDEV].sdata + 1); /* skip length byte */
        else
            strcpy(dev1, 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);
        ptr = strchr(dev2, ' ');
        if (ptr) *ptr = '\0'; /* null terminate */
        ptr = strchr(dev2, ':');
        if (ptr) *ptr = '\0'; /* strip off : */
        sprintf(filespec, "%s:[0,0]QUOTA.SYS", dev2);
        file_dsc.dsc$w_length = strlen(filespec);
        context = 0;
        status = lib$find_file(&file_dsc, &res_dsc, &context, 0, 0, 0, 0);
        lib$find_file_end(&context);
        if ((status & 7) == SS$_NORMAL) {
            fprintf(file, "$ RUN SYS$SYSTEM:DISKQUOTA\n");
            fprintf(file, "USE %s\n", dev2);
            if (strncmp(str, "REMOVE", 6))
                fprintf(file, "ADD %s /PERMQUOTA=%d /OVERDRAFT=%d\n",
                    uafdata[UAI$_UIC].sdata, resource_data.quota,
                    resource_data.overdraft);
            else
                fprintf(file, "REMOVE %s\n", uafdata[UAI$_UIC].sdata);
            fprintf(file, "EXIT\n");
        }
    }    

    if (strlen(dev1) && strlen(uafdata[UAI$_DEFDIR].sdata)) {
        if ((ulong) uafdata[UAI$_DEFDIR].sdata[0] < 32)  /* length byte */
            strcpy(dir, uafdata[UAI$_DEFDIR].sdata + 1);
        else
            strcpy(dir, 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 ] */
        sprintf(filespec, "%s:[0,0]%s.DIR", dev1, dir);
        file_dsc.dsc$w_length = strlen(filespec);
        context = 0;
        status = lib$find_file(&file_dsc, &res_dsc, &context, 0, 0, 0, 0);
        lib$find_file_end(&context);
        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);
            status = lib$create_dir(&file_dsc, &uafdata[UAI$_UIC].idata, 0, 0,
                                    &resource_data.version_limit, 0);
            if (status != SS$_CREATED)
                DWPmsg("Create Dir", status);
        }
        /* if the command is REMOVE, delete disk files and directory */
        if (!strncmp(str, "REMOVE", 6))
            fprintf(file, "$ @DWPUID:DELTREE %s:[%s]\n", dev1, dir);
    }


    fclose(file);
    status = lib$spawn(0, &dcl_dsc, &nl_dsc, &flags, 0, 0, &substatus, 0, 0, 0, 0, 0);
    if ((substatus & 7) != SS$_NORMAL)   /* probably CLI$_NORMAL */
        DWPmsg("Spawned process", substatus);
    if (status != SS$_NORMAL)
        DWPmsg("Spawn_DCL", status);
}


/*
* 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()
{
    ulong status, length, maxgroup = 0, groupid = 0, uic = 0, grp, mem;
    Boolean reload;
    ident *ptr;
    $DESCRIPTOR(group_dsc, uafdata[UAI$_ACCOUNT].sdata);
    
    /* read in text widget */
    status = sscanf(uafdata[UAI$_UIC].sdata, "[%o,%o]", &grp, &mem);
    if (status != 2) /* try without [] */
        status = sscanf(uafdata[UAI$_UIC].sdata, "%o,%o", &grp, &mem);
    uafdata[UAI$_UIC].idata = (grp << 16) + mem;
    if (uafdata[UAI$_UIC].idata) return;    /* don't recalc if UIC exists */
    reload = 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 (((ptr->identifier >> 16) == (groupid >> 16)) &&
            ((ptr->identifier & 0xFFFF) != 0xFFFF))
            uic = (ptr->identifier > uic) ? ptr->identifier : uic;
        if (!groupid && ptr->identifier > maxgroup)
            maxgroup = ptr->identifier;
    }
    if (!groupid)    /* new group name */
        groupid = maxgroup + 1;
    if (!uic)    /* if only [x,177777] in UAF, give [x,101] as uic */
        uic = (groupid & 0x3FFF0000) + 0100;
    uafdata[UAI$_UIC].idata = ++uic;
    sprintf(uafdata[UAI$_UIC].sdata, "[%o,%o]", uic >> 16, uic & 0XFFFF);
    DwtSTextSetString(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(ulong uic)
{
    ident *ptr;
    char group[32] = "", member[32] = "", suic[32];
    Arg arg;
    DwtCompString string;

    for (ptr = ident_list; ptr; ptr = ptr->next)
        if (ptr->identifier == uic)
            strcpy(member, ptr->name);
        else if (ptr->identifier == (uic & 0x3FFF0000 | 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);

    string = DwtLatin1String(suic);
    XtSetArg(arg, DwtNlabel, string);
    XtSetValues(widget_array[k_widget_uic_trans], &arg, 1);
    XtFree(string);
}
    

/*
* Build list of identifiers; returns true if new list built
*/
static Boolean read_identifiers(Boolean force)
{
    ulong status, resid, context = 0, ind = 0, group;
    int id = -1;
    short namlen;
    char idbuf[35];
    static Widget work_box = NULL;
    Arg arg;
    ident *ptr, *tptr;
    $DESCRIPTOR(name_dsc, idbuf);

    if (!work_box)
        work_box = DwtWorkBoxCreate(dwprofile_main, "work_box", NULL, 0);

    if (!ident_list || force) {
        if (resource_data.show_work && !XtIsManaged(work_box))
            XtManageChild(work_box);    /* pop up work box */
        XDefineCursor(top_display, top_window, wait_cursor);
        flush_events();
        for (ptr = ident_list; ptr; ptr = ptr->next) { /* free old ident list */
            free(ptr->name);
            tptr = ptr;
            free(tptr);
        }
        for (;;) {
            flush_events();
            status = sys$idtoasc(id, &namlen, &name_dsc, &resid, 0, &context);
            if (status == SS$_NOSUCHID) break;
            if (status != SS$_NORMAL) {
                sys$finish_rdb(&context);
                DWPmsg("read_identifiers", status);
                break;
            }
            idbuf[namlen] = '\0';
            if (ptr) {
                ptr->next = (ident *)malloc(sizeof(ident));
                ptr = ptr->next;
            }
            else
                ident_list = ptr = (ident *)malloc(sizeof(ident));
            ptr->name = (char *)malloc(namlen + 1);
            strcpy(ptr->name, idbuf);
            ptr->next = NULL;
            ptr->identifier = resid;
            if ((resid > 0x80010000) &&    /* skip VMS defined idents */
                    strncmp(idbuf, "SYS$NODE", 8)) /* and SYS$NODE_xxx */
                ptr->status = add_ident;
            else if ((resid & 0xFFFF) == 0xFFFF)
                ptr->status = account;
            else
                ptr->status = uic;
        }
        if (XtIsManaged(work_box))
            XtUnmanageChild(work_box);
        XUndefineCursor(top_display, top_window);
    }
}



/*** Event handlers ***/

/*
* Called by mouse button and exposure events in access windows
*/
static XtEventHandler access_event_handler(Widget w, char *tag,
                                           XButtonEvent *event)
{
    ulong ind, hour;
    static Boolean mode;

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

    switch (event->type) {
    case Expose :
        update_time(ind);    /* update the exposed window */
        break;
    case ButtonPress :
        uafdata[ind].changed = TRUE;
        hour = event->x / resource_data.hour_size;
        mode = uafdata[ind].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[ind].idata |= 1 << hour;
        else
            uafdata[ind].idata &= ~(1 << hour);
        break;
    }
}


/*
* Called by event handler on MB2 push
*/
static XtActionProc popup(Widget widget, XButtonPressedEvent *event,
            char **params, ulong num)
{
    DwtMenuPosition(menu, event);    /* position menu */
    XtManageChild(menu);             /* map it */
}

    
/*
* Called when a return is pressed in a text widget
*/
static XtActionProc return_key(Widget widget, char *tag,     
            DwtAnyCallbackStruct *reason)
{
    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 */
    }
}



/*** callbacks ***/

/*
* Called by 'Quit' menu item
*/
static XtCallbackProc Quit()
{
    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, int *tag,
            DwtAnyCallbackStruct *reason)
{
    char *ptr;

    XDefineCursor(top_display, top_window, wait_cursor);
    flush_events();
    ptr = DwtSTextGetString(widget_array[UAI$_USERNAME]);
    if (!get_account(ptr)) {
        if (get_account("DEFAULT"))
            set_defaults(ptr);
        else
            DWPerror("Read", "Cannot read DEFAULT account", 0);
    }
    XtFree(ptr);
    XUndefineCursor(top_display, top_window);
}


/*
* Called by 'Write' menu item
*/
static XtCallbackProc Write(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    ulong ind, pad, item, value, status, dd, hh, mm, ss, cc,
                      grp, mem, holder[2] = {0, 0};
    ident *ptr;
    held_ident *hptr;
    char command[200], temp[100];
    $DESCRIPTOR(acct_dsc, uafdata[UAI$_USERNAME].sdata);

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

    /* 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, "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;
        }
    }
    /* check for password change; is setuai able to do this now? */
    else if (uafdata[UAI$_PWD].changed &&
         uafdata[UAI$_USERNAME].sdata[0] &&
         uafdata[UAI$_PWD].sdata[0]) {
             sprintf(command, "MOD %s/PASSWORD=%s",
                uafdata[UAI$_USERNAME].sdata, uafdata[UAI$_PWD].sdata);
            uafdata[UAI$_PWD].changed = FALSE;
            spawn_dcl(command);
    }
    /* 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 */
    else if (uafdata[UAI$_UIC].changed &&
         uafdata[UAI$_UIC].idata) {
             sprintf(command, "MOD %s/UIC=%s",
                uafdata[UAI$_USERNAME].sdata, uafdata[UAI$_UIC].sdata);
            uafdata[UAI$_UIC].changed = FALSE;
            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 its 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);
                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 */
                $DESCRIPTOR(time_dsc, uafdata[value].sdata);
                ulong scount = 0;
                char *str;

                if (!strlen(uafdata[value].sdata)) { /* empty date means (none) */
                    memset(uafdata[value].sdata, 0, 8);  /* make date 'none' */
                    itmlst[item].bufadr = &uafdata[value].sdata;
                    break;
                }
                /* default expiration date to midnight */
                for (str = uafdata[value].sdata; str;) {
                    if((str = strchr(str, ':')) == NULL)
                        break;  /* isn't this how at&t got into trouble? */
                    scount++;   /* count the colons in date */
                    str++;      /* skip over the colon we just counted */
                }
                if (scount < 1)              /* no hh:mm:ss.cc */
                    strcat(uafdata[value].sdata, " 00:00:00.00");
                else if (scount < 2)         /* no ss.cc */
                    strcat(uafdata[value].sdata, ":00.00");
                time_dsc.dsc$w_length = strlen(uafdata[value].sdata);
                /* .sdata will now contain the 64-bit system time */
                status = sys$bintim(&time_dsc, &uafdata[value].sdata);
                if (status != SS$_NORMAL) {
                    DWPerror("Write","Illegal date format", 0);
                    --item;
                }
                else
                    itmlst[item].bufadr = &uafdata[value].sdata;
                break;
            }
            case DWP_UIC:
                status = sscanf(uafdata[value].sdata, "[%o,%o]", &grp, &mem);
                if (status != 2) /* try without [] */
                    status = sscanf(uafdata[UAI$_UIC].sdata, "%o,%o", &grp, &mem);
                uafdata[value].idata = (grp << 16) + mem;
                if (status != 2) {
                    DWPerror("Write","Illegal UIC format", 0);
                    --item;
                }
                else
                    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 if (resource_data.notify_interval)
            DWPerror("Write", "SETUAI successful", resource_data.notify_interval);
    }

    holder[0] = uafdata[UAI$_UIC].idata;
    for (hptr = held_list; hptr; hptr = hptr->next)
        switch (hptr->status) {
        case add:
            status = sys$add_holder(hptr->identifier, &holder, 0);
            if (status != SS$_NORMAL)
                DWPmsg("Write/ADD_HOLDER", status);
            hptr->status = original;
            break;
        case del_orig:
            status = sys$rem_holder(hptr->identifier, &holder);
            if (status != SS$_NORMAL)
                DWPmsg("Write/REM_HOLDER", status);
            hptr->status = delete;
            break;
        case original: /* originally held id */
        case delete:   /* identifier added then deleted */
            break;
        }
        /* add identifier to list, may be a dup - but that won't hurt anything */
        for (ptr = ident_list; ptr && ptr->next;)
            ptr = ptr->next; /* find end of list */
        if (ptr) {
            ptr->next = (ident *)malloc(sizeof(ident));
            ptr = ptr->next;
        }
        else
            ident_list = ptr = (ident *)malloc(sizeof(ident));
        ptr->name = (char *)malloc(strlen(uafdata[UAI$_USERNAME].sdata) + 1);
        strcpy(ptr->name, uafdata[UAI$_USERNAME].sdata);
        ptr->next = NULL;
        ptr->identifier = uafdata[UAI$_UIC].idata;
        ptr->status = uic;

    XUndefineCursor(top_display, top_window);
}


/*
* Called by 'Remove' menu item
*/
static XtCallbackProc Remove(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    Arg arg;
    char msg[30];
    DwtCompString string;
    static Widget caution_box = NULL;
    DwtCallback callbacks[] = {
        {(VoidProc)CautionCallback, 0},
        {NULL, NULL}
    };

    if (!caution_box) {
        string = DwtLatin1String("");
        caution_box = DwtCautionBox(dwprofile_main, "caution_box",
            TRUE, 0, 0, DwtModal, NULL, NULL, NULL,
            string, NULL, callbacks, NULL);
        XtFree(string);
    }
    sprintf(msg, "Remove account %s?", uafdata[UAI$_USERNAME].sdata);
    string = DwtLatin1String(msg);
    XtSetArg(arg, DwtNlabel, string);
    XtSetValues(caution_box, &arg, 1);
    XtFree(string);
    XtManageChild(caution_box);    /* popup modal caution box */
}


/*
* Issue REMOVE UAF command if OK button pressed in caution box
*/
static XtCallbackProc CautionCallback(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    ulong ind;
    char command[20];

    XtUnmanageChild(widget);
    XDefineCursor(top_display, top_window, wait_cursor);
    flush_events();
    if (reason->reason != DwtCRCancel
            && reason->reason != DwtCRNo
            && uafdata[UAI$_USERNAME].sdata[0]) {
        sprintf(command, "REMOVE %s", uafdata[UAI$_USERNAME].sdata);
        spawn_dcl(command);
        if (!get_account("DEFAULT"))
            DWPerror("Remove", "Cannot read DEFAULT account", 0);
    }
    XUndefineCursor(top_display, top_window);
}


/*
* Remove the error message widget when ack'ed or timed out
*/
static XtCallbackProc MessageCallback(Widget widget, Widget tag,
            DwtAnyCallbackStruct *reason)
{
    if (!widget)
        XtUnmanageChild(tag); /* timeout, popdown passed widget */
    else
        XtUnmanageChild(widget); /* popdown message box */
    XtRemoveTimeOut(timeout_id); /* clear out timer */
}


/*
* Pop up (or remove) the access dialog box
* Called by 'Access' menu item
*/
static XtCallbackProc Access(Widget widget, int *tag,
                DwtAnyCallbackStruct *reason)
{
    if (XtIsManaged(access_box))
        XtUnmanageChild(access_box);
    else
        XtManageChild(access_box);
}


/*
* Pop up (or remove) the privs dialog box
* Called by 'Privs' menu item
*/
static XtCallbackProc Privs(Widget widget, int *tag,
                DwtAnyCallbackStruct *reason)
{
    if (!privs_box)
        if ((DwtFetchWidget(Hierarchy, "privs_box", toplevel,
             &privs_box, &class)) != DRMSuccess)
            XtError("Error Fetching Privs main widget");
    if (XtIsManaged(privs_box))
        XtUnmanageChild(privs_box);
    else {
        XtManageChild(privs_box);
        update_privs();        /* may not have existed */
    }
}


/*
* creation callback;  save the widget using the UAI code as the index
*/
static XtCallbackProc WidgetCreated(Widget widget, int *tag,
                DwtAnyCallbackStruct *reason)
{
    widget_array[*tag] = widget;
}


/*
* Called whenever a key is pressed in a text widget
*/
static XtCallbackProc WidgetChanged(Widget widget, int *tag,
                DwtAnyCallbackStruct *reason)
{
    char *ptr;

    if (setting_widgets) return;
    ptr = DwtSTextGetString(widget);
    uafdata[*tag].changed = TRUE;    /* element has changed */
    if (*tag != UAI$_OWNER)
            uppercase(ptr);
    strcpy(uafdata[*tag].sdata, ptr);
    setting_widgets = TRUE;        /* Don't allow recursion */
    DwtSTextSetString(widget, ptr);
    setting_widgets = FALSE;
    XtFree(ptr);
}


/*
* Called when flag toggle button is created
*/
static XtCallbackProc FlagsCreated(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    flags_array[*tag] = widget;
}


/*
* Called whenever a flag toggle button is pressed
*/
static XtCallbackProc FlagsChanged(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    if (setting_flags) return;
    uafdata[UAI$_FLAGS].changed = TRUE;        /* element has changed */
    uafdata[UAI$_FLAGS].idata ^= 1 << *tag;    /* toggle flag bit */
}


/*
* Called when a primary day toggle button is created
*/
static XtCallbackProc PrimeCreated(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    prime_array[*tag] = widget;
}


/*
* Called whenever a prime day toggle button is pressed
*/
static XtCallbackProc PrimeChanged(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    if (setting_prime) return;
    uafdata[UAI$_PRIMEDAYS].changed = TRUE;        /* element has changed */
    uafdata[UAI$_PRIMEDAYS].idata ^= 1 << *tag;    /* toggle bit in primedays */
}


/*
* Called when a privs toggle button is created
*/
static XtCallbackProc PrivsCreated(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    privs_array[*tag] = widget;
}


/*
* Called whenever a privs toggle button is pressed
*/
static XtCallbackProc PrivsChanged(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    if (setting_privs) return;
    if (*tag < PRIVS_BIAS) {
        uafdata[UAI$_PRIV].sdata[*tag >> 3] ^= 1 << (*tag & 7);
        uafdata[UAI$_PRIV].changed = TRUE;    /* element has changed */
        }
    else {
        uafdata[UAI$_DEF_PRIV].sdata[(*tag - PRIVS_BIAS) >> 3] ^=
            1 << (*tag - PRIVS_BIAS & 7);
        uafdata[UAI$_DEF_PRIV].changed = TRUE;
    }
}


/*
* read selection widget to get account name
* create new UIC
*/
static XtCallbackProc AccountChanged(Widget widget, int *tag,
            DwtSelectionCallbackStruct *reason)
{
    char *text;

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

    text = get_comp_string(reason->value);
    DwtSTextSetString(widget_array[UAI$_ACCOUNT], text);
    uafdata[UAI$_ACCOUNT].changed = TRUE;
    strcpy(uafdata[UAI$_ACCOUNT].sdata, text);
    XtFree(text);
    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, int *tag,
            DwtAnyCallbackStruct *reason)
{
    read_identifiers(TRUE);
}


/*
* Pop up accounts selection widget
* Called by 'Accounts' menu item
*/
static XtCallbackProc Accounts(Widget widget, int *tag,
            DwtAnyCallbackStruct *reason)
{
    ulong ind, count = 0;
    DwtCompString *account_list;
    ident *ptr;
    static Widget select_box = NULL;
    Arg arglist[2];
    DwtCallback callback[] = {
        {(VoidProc)AccountChanged, 0},
        {NULL, NULL}
    };

    /* if selection box is already there, remove it */
    if (select_box && XtIsManaged(select_box)) {
        XtUnmanageChild(select_box);
        return;
    }

    read_identifiers(FALSE);
    for (ptr = ident_list; ptr; ptr = ptr->next)
        if (ptr->status == account) count++;
    account_list = calloc(count, sizeof(DwtCompString));
    ind = 0;
    for (ptr = ident_list; ptr; ptr = ptr->next)
        if (ptr->status == account)
            account_list[ind++] = DwtLatin1String(ptr->name);
    if (!select_box) {
        select_box = DwtSelectionCreate(toplevel, "select_box", NULL, 0);
        ind = 0;
        XtSetArg(arglist[ind], DwtNcancelCallback, callback); ind++;
        XtSetArg(arglist[ind], DwtNactivateCallback, callback); ind++;
        XtSetValues(select_box, arglist, ind);
    }
    ind = 0;
    XtSetArg(arglist[ind], DwtNitems, account_list); ind++;
    XtSetArg(arglist[ind], DwtNitemsCount, count); ind++;
    XtSetValues(select_box, arglist, ind);
    XtManageChild(select_box);
    for (ind = 0; ind < count; ind++)
        XtFree(account_list[ind]);
    free(account_list);
}


/*
* Pop up (or remove) the idents dialog box
* Called by 'Idents' menu item
*/
static XtCallbackProc Idents(Widget widget, int *tag,
                DwtAnyCallbackStruct *reason)
{
    ulong ind, count = 0;
    DwtCompString *identstr;
    ident *ptr;
    Arg arglist[2];

    if (!idents_box)
        if ((DwtFetchWidget(Hierarchy, "idents_box", toplevel,
             &idents_box, &class)) != DRMSuccess)
            XtError("Error Fetching Idents main widget");
    if (XtIsManaged(idents_box)) {
        XtUnmanageChild(idents_box);
        return;
    }

    read_identifiers(FALSE);
    update_held();
    XtManageChild(idents_box);
}


/*
* Add (or remove) the identifier for the user
* Called by selecting identifier from list box
*/
static XtCallbackProc ListSelect(Widget widget, int *tag,
                DwtListBoxCallbackStruct *list)
{
    ulong count, ind;
    ident *ptr;
    held_ident *hptr, *tptr;
    char *text;

    switch(*tag) {
    case k_widget_all_list:
        text = get_comp_string(list->item);
        for (ptr = ident_list; ptr; ptr = ptr->next)
            if (!strcmp(ptr->name, text)) break;
        XtFree(text);
        tptr = NULL;
        for (hptr = held_list; hptr; tptr = hptr, hptr = hptr->next) /* find deleted id or end */
            if (hptr->identifier == ptr->identifier) break;
        if (!hptr) {
            hptr = (held_ident *)malloc(sizeof(held_ident));
            hptr->identifier = NULL;
            hptr->next = NULL;
            if (tptr)
                tptr->next = hptr;
            else
                held_list = hptr;
        }
        if (!hptr->identifier || hptr->status == delete)
            hptr->status = add;
        else
            hptr->status = original; /* status must be del_orig */
        hptr->identifier = ptr->identifier;
        break;
    case k_widget_held_list:
        /* ptr->status will be reset to add_ident in update_held() */
        count = 0;
        for (hptr = held_list; hptr; hptr = hptr->next) {
            if (hptr->status == original ||
                hptr->status == add) count++;
            if (count == list->item_number) {
                if (hptr->status == add)
                    hptr->status = delete;   /* could reclaim entry... */
                else
                    hptr->status = del_orig; /* status must be original */
                break;
            }
        }
        break;
    }
    update_held();
}
