From:	HENRY::IN%"LAV%brandeis.bitnet%wiscvm.wisc.edu%sri-kl.ARPA%relay.cs.net@rca.com" 25-MAR-1987 19:19
To:	info-vax@sri-kl.arpa
Subj:	Program to list unread mail messages

Here is a little program that makes plain old VMS Mail slightly
nicer: type UNREAD TOM,JOHN and it will list messages you've sent
to users TOM and JOHN which they haven't gotten around to
reading yet.

To generate it:
        cut out the following command procedure;
        trim off trailing blank spaces from every line (they have a
tendency to creep in, and they ruin the checksums);
        run the procedure;
        edit NODENAMES.MAR so that it lists all nodes in your
cluster or DECnet which use the same bunch of usernames;
        run MAKE.COM to compile and link the program;
        move UNREAD.EXE to the directory of your choice;
        edit UNREAD.CLD to specify that directory;
        run ADD.COM to add the UNREAD command to the system
command tables;
        install UNREAD.EXE with SYSPRV and see that it gets done
in your SYSTARTUP.COM as well;
        add UNREAD.HLP to the help library of your choice.


John Lavagnino (lav @ brandeis.bitnet)
Feldberg Computer Center, Brandeis University
415 South Street
Waltham, MA  02254   USA
(617)736-4594


$!----------------------------start of code------------------
$ create ADD.COM
$DECK
$!      Add the UNREAD command to the system command tables.
$!
$ SET COMMAND/TABLES=SYS$LIBRARY:DCLTABLES.EXE-
        /OUTPUT=SYS$LIBRARY:DCLTABLES.EXE   -
                unread.cld
$!
$ install = "$install/command"
$ install REPLACE SYS$LIBRARY:DCLTABLES.EXE
$EOD
$ checksum ADD.COM
$ bad = checksum$checksum .ne. 1396580781
$ if bad then write sys$output "Bad checksum for ADD.COM"
$ create MAKE.COM
$DECK
$ cc/nolis/nodebug/opt unread
$ macro/nolis/nodebug nodenames
$ link/nomap/notrace unread, nodenames, sys$input:/options
sys$share:vaxcrtl/share
$EOD
$ checksum MAKE.COM
$ bad = checksum$checksum .ne. 543718537
$ if bad then write sys$output "Bad checksum for MAKE.COM"
$ create NODENAMES.MAR
$DECK
        .TITLE  NODENAMES       Node names for UNREAD.C
        .IDENT  /1.0/

;  Lists allowed source nodes for messages from the current username.

        .PSECT  NODES, BYTE, PIC, SHR, NOEXE, RD, NOWRT

NODE_NAMES::

;  Put all allowed node names here, each in an ASCIC directive.

        .ASCIC  "BINAH"
        .ASCIC  "DIN"
        .ASCIC  "LOGOS"

;  This byte marks the end of the list.

        .BYTE   0

        .END
$EOD
$ checksum NODENAMES.MAR
$ bad = checksum$checksum .ne. 1965670439
$ if bad then write sys$output "Bad checksum for NODENAMES.MAR"
$ create UAFDEF.H
$DECK
/*  Adapted from $UAFDEF in LIB.MLB, VMS 4.5.  12/8/86.  */

#define UAF$S_USERNAME  32
#define UAF$S_DEFDEV    32
#define UAF$S_DEFDIR    64
$EOD
$ checksum UAFDEF.H
$ bad = checksum$checksum .ne. 873467748
$ if bad then write sys$output "Bad checksum for UAFDEF.H"
$ create UAIDEF.H
$DECK
/*  Adapted from $UAIDEF in STARLET.MLB, VMS 4.5.  12/5/86.  */

#define UAI$_DEFDEV     13
#define UAI$_DEFDIR     14
$EOD
$ checksum UAIDEF.H
$ bad = checksum$checksum .ne. 843012925
$ if bad then write sys$output "Bad checksum for UAIDEF.H"
$ create UNREAD.C
$DECK
#module         UNREAD          "V1.0"

/*  List mail messages you've sent to specified users that haven't
    been read yet. */

/**********************************************************************/

/*  2/7/87      John Lavagnino, Brandeis University
                lav@brandeis.bitnet                                   */

/**********************************************************************/

#include        climsgdef
#include        descrip
#include        jpidef
#include        psldef
#include        rms
#include        ssdef

#include        "uafdef.h"
#include        "uaidef.h"

/*** Various username strings and their descriptors ***/

/*  "firstname" is the original username off the command line that
  we're after.  (Here and elsewhere, 1 is added to the length to avoid
  problems with those trailing nulls getting stuck on; for the most
  part, ordinary null-terminated strings are not being used in this code.)
    "username" is the current username that we're working on, possibly
  different from the original because of forwarding.  (To escape from
  a possible forwarding loop, we allow only MAX_FORWARDINGS forwarding
  steps before we give up on it.)
    The real lengths of these two names, with trailing blanks removed,
  are in firstname_len and username_len.
    The username of the process running the program is "ourname".
    "username_entity" specifies what we called the command-line username
  parameter in the .CLD for this program. */

static char username_buf[UAF$S_USERNAME + 1];
static char firstname_buf[UAF$S_USERNAME + 1];
static char ourname_buf[UAF$S_USERNAME + 1];

static short firstname_len, username_len;

static readonly struct dsc$descriptor_s username_desc = {
        UAF$S_USERNAME, 0, 0, &username_buf
};

static readonly struct dsc$descriptor_s firstname_desc = {
        UAF$S_USERNAME, 0, 0, &firstname_buf
};

#define MAX_FORWARDINGS         10
static int forwardings;

static readonly $DESCRIPTOR(username_entity, "USERNAME");


/*** The VMSMAIL.DAT file. ***/

/*  The following information is taken from the VMS 4.4 fiche,
    facility MAIL, file MAILDEF.SDL. */

/* Max size of a VMSMAIL record is a guess, chosen to be generous. */

#define VMSMAIL_NAME            "VMSMAIL"
#define VMSMAIL_DEFAULT         "SYS$SYSTEM:.DAT"
#define MAX_VMSMAIL_RECORD      512
#define VMSMAIL_KEY_SIZE        31

struct vmsmail_record {
        char vmsmail_username[VMSMAIL_KEY_SIZE];
        unsigned short flags;
        unsigned short mailcnt;         /* count of unread messages */
        char spare[30];
        unsigned char dirlng;           /* length of directory name */
        unsigned char fnmlng;           /* length of full username */
        unsigned char fwdlng;           /* length of forwarded name */
        char fwdnam;                    /* first byte of forwarded name */
};

static struct FAB vmsmail_fab;
static struct RAB vmsmail_rab;

/* Possible returns from routine that reads VMSMAIL record. */

typedef enum { no_such_record, no_messages, forwarded, normal } VMSMAIL_STAT;


/*** MAIL.MAI file ***/

/*  The following information is taken from the VMS 4.4 fiche,
    facility MAIL, file MAILDEF.SDL, and file MAIL$ISAM_SUBS,
    routine DECODE_MISCDATA. */

#define MAI_DEFAULT     "MAIL.MAI"
#define NEWMAIL_FOLDER  "NEWMAIL"
#define MAX_MAI_RECORD  2048

static struct FAB mai_fab;
static struct RAB mai_rab;

static char mai_file[NAM$C_MAXRSS + 1]; /* name of current mail file */

/*  The latter part of a record in MAIL.MAI (of the type we're going
   to be reading) contains a sequence of Mail Message Header (MMH)
   records, not necessarily in any special order, their contents
   identified by one of the MMH_C constants that follows. */

/* Selected Mail Message Header constants. */

#define MMH_C_FROM              0       /* From: */
#define MMH_C_SUBJ              2       /* Subject: */
#define MMHFLAGS_M_NEWMSG       1       /* New message? */

/* Format of an MMH record.  mmh_data is only the start of the data:
   the mmh_length field specifies the data's length, in bytes.  In
   FROM, TO, and SUBJ records the data is a string of varying length;
   in NREC records the data is a longword integer.  I haven't seen
   records of the other types.  Add mmh_length to the address of
   mmh_data to get the start of the next MMH record. */

struct mmh_record {
        short mmh_type;
        short mmh_length;
        char mmh_data;
};

/*  Format of the entire MAIL.MAI record.  msgtime is the time of the
    message (also used to identify the message within the file: this
    is key 0).  folder is the folder name, and is key 1.  mmh1 is
    only the first MMH-format record in the MAIL.MAI record: there may
    be any number, and the only way to detect the end of the series
    is to know the length of the MAIL.MAI record and notice when you
    reach its end. */

struct mai_record {
        long msgtime[2];
        unsigned char foldersize;
        char folder[39];
        unsigned short mai_flags;
        unsigned char flagsiz;
        unsigned long flagval;
        unsigned char hdspare;
        long datid[2];
        struct mmh_record mmh1;
};


/*** Miscellaneous global variables ***/

/* header_line_printed indicates whether we've printed a header or
   error message line for the current username. */

static int header_line_printed;

/* NODE_NAMES is the start of a string that lists the nodes that
   messages from us might be from.  In a homogeneous cluster, our
   username really stands for the same individual on several nodes;
   but in an ordinary DECnet network, you may not have such
   consistency.  This string is defined in an external module, as
   it's site-specific and the site may not have a C compiler.
   It consists of counted ASCII strings, concatenated, with a null
   byte at the end. */

globalref char NODE_NAMES;
/**************************/
main()
{
        long cli$get_value();

        get_ourname();
        open_vmsmail();

        /* Main loop, executed once for each user specified. */

        while (cli$get_value (&username_entity,
                &firstname_desc, &firstname_len) != CLI$_ABSENT ) {

                /* If a username is too long for the buffer,
                   CLI$GET_VALUE truncates it but returns the full
                   length in firstname_len. */

                if (firstname_len > firstname_desc.dsc$w_length) {
                        printf("\nUsername %.*s... is too long.\n",
                                firstname_desc.dsc$w_length,
                                firstname_desc.dsc$a_pointer);
                        continue;
                }

                if (firstname_len <= 0)         /* ignore null usernames */
                        continue;

                /* Find out where the user's mail file is (complicated
                   by the possibility of forwarding). */

                str$upcase(&firstname_desc, &firstname_desc);

                str$copy_dx(&username_desc, &firstname_desc);

                forwardings = 0;
                username_len = 0;

                while (!find_mai_file()) {
                        if (++forwardings > MAX_FORWARDINGS) {
                                printf(
                        "\nToo many forwardings for user %.*s.\n",
                                        firstname_len,
                                        firstname_desc.dsc$a_pointer);
                                break;
                        }
                }

                /* Read the mail file and print a report. */

                if (mai_file[0] != '\0') {
                        header_line_printed = 0;
                        scan_mail();
                        if (header_line_printed == 0)
                                none_unread();
                }
        }
}
/**************************/
/*  Get the username of the process that's running this program. */

get_ourname()
{
        static struct {
                short buffer_length;
                short item_code;
                char *buffer_address;
                char *return_length_address;
                long null;
        } itemlist = {
                UAF$S_USERNAME, JPI$_USERNAME, 0, 0, 0
        };

        itemlist.buffer_address = ourname_buf;

        sys$getjpiw ( 0, 0, 0, &itemlist, 0, 0, 0 );
}
/**************************/
/*  Open the VMSMAIL.DAT file.  Aborts the program if unsuccessful,
    since we can't do anything without this file. */

open_vmsmail()
{
        long status;

        vmsmail_fab = cc$rms_fab;
        vmsmail_fab.fab$b_fac = FAB$M_GET;
        vmsmail_fab.fab$l_fna = &VMSMAIL_NAME;
        vmsmail_fab.fab$b_fns = sizeof ( VMSMAIL_NAME );
        vmsmail_fab.fab$l_dna = &VMSMAIL_DEFAULT;
        vmsmail_fab.fab$b_dns = sizeof ( VMSMAIL_DEFAULT );
        vmsmail_fab.fab$b_acmodes = PSL$C_EXEC << FAB$V_LNM_MODE;
        vmsmail_fab.fab$b_org = FAB$C_IDX;
        vmsmail_fab.fab$b_shr = FAB$M_SHRPUT | FAB$M_SHRGET |
                                FAB$M_SHRDEL | FAB$M_SHRUPD;

        status = sys$open(&vmsmail_fab);

        if (status != RMS$_NORMAL)
                exit(status);

        vmsmail_rab = cc$rms_rab;
        vmsmail_rab.rab$l_fab = &vmsmail_fab;
        vmsmail_rab.rab$l_rop = RAB$M_LOC | RAB$M_NLK | RAB$M_RRL;
        vmsmail_rab.rab$b_krf = 0;
        vmsmail_rab.rab$l_kbf = username_buf;
        vmsmail_rab.rab$b_ksz = VMSMAIL_KEY_SIZE;
        vmsmail_rab.rab$b_rac = RAB$C_KEY;
        vmsmail_rab.rab$w_usz = MAX_VMSMAIL_RECORD;

        status = sys$connect(&vmsmail_rab);

        if (status != RMS$_NORMAL)
                exit(status);
}
/**************************/
/*  Given global variable "username", looks for the MAIL.MAI file.
    Returns 0 if it should be called again to process a forwarded
    username, 1 if all done for whatever reason. */

find_mai_file()
{
        char vmsmail_dir[NAM$C_MAXRSS + 1];
        VMSMAIL_STAT get_vmsmail_data();

        *mai_file = '\0';

        /* The only non-obvious case is "no such record": this is
           perfectly legal if the user in question has never sent
           or received mail.
             The message count in VMSMAIL.DAT can have the wrong value,
           but the error is usually that it's > 0 when there are no
           messages to be read.  Thus it's generally safe to return
           without even bothering to open MAIL.MAI if that count is
           zero, as we do here. */

        switch (get_vmsmail_data(vmsmail_dir)) {
                case no_such_record:
                        if (assemble_mai_file_name(vmsmail_dir))
                                none_unread();
                        else
                                no_such_user();
                        *mai_file = '\0';
                        return(1);
                        break;
                case no_messages:
                        none_unread();
                        return(1);
                        break;
                case forwarded:
                        return(0);
                        break;
                case normal:
                        break;
        }

        if (!assemble_mai_file_name(vmsmail_dir)) {
                no_such_user();
                *mai_file = '\0';
                return(1);
        }

        return(1);
}
/**************************/
/*  Read the VMSMAIL record for a given username. */

VMSMAIL_STAT get_vmsmail_data(vmsmail_dir)
char *vmsmail_dir;
{
        char vmsmail_data[MAX_VMSMAIL_RECORD + 1];
        struct vmsmail_record *data;
        long status, new_len;

        *vmsmail_dir = '\0';

        vmsmail_rab.rab$l_ubf = vmsmail_data;

        status = sys$get(&vmsmail_rab);

        if (status == RMS$_RNF)
                return(no_such_record);

        if (status != RMS$_NORMAL && status != RMS$_OK_RRL)
                exit(status);

        /* fwdnam in a VMSMAIL record is the start of
           forwarded_username + full_name + devdir, concatenated;
           we pick out the pieces we want using the length bytes. */

        data = (struct vmsmail_record *)(vmsmail_rab.rab$l_rbf);

        if (data->fwdlng == 0) {                /* no forwarding */
                if (data->mailcnt == 0)
                        return(no_messages);

                if (data->dirlng != 0) {
                        strncpy(vmsmail_dir,
                                &(data->fwdnam) + data->fnmlng,
                                data->dirlng);
                        vmsmail_dir[data->dirlng] = '\0';
                }
                return(normal);
        }
        else {                  /* user is forwarded */
                sprintf(username_buf, "%-*.*s", UAF$S_USERNAME,
                                        data->fwdlng, &(data->fwdnam));
                username_len = data->fwdlng > UAF$S_USERNAME ?
                                        UAF$S_USERNAME : data->fwdlng;
                return(forwarded);
        }
}
/**************************/
/*  Looks the user up in the UAF and fills in mai_file.  Returns 1
    if all okay, 0 if the user doesn't exist in the UAF.
      Note that this is done after checking VMSMAIL.  It's possible
    for someone to be in VMSMAIL but not the UAF, legally, if the
    name is simply forwarded on to a real person. */

int assemble_mai_file_name(vmsmail_dir)
char *vmsmail_dir;
{
        char defdev[UAF$S_DEFDEV + 1], defdir[UAF$S_DEFDIR + 1];

        static struct {
                short   buflen1;
                short   itmcode1;
                char    *bufadr1;
                long    retlenadr1;
                short   buflen2;
                short   itmcode2;
                char    *bufadr2;
                long    retlenadr2;
                long    blank;
        } uai_itemlist = { UAF$S_DEFDEV, UAI$_DEFDEV, 0, 0,
                UAF$S_DEFDIR, UAI$_DEFDIR, 0, 0, 0 };

        long status, dev_len, dir_len;


        uai_itemlist.bufadr1 = defdev;
        uai_itemlist.bufadr2 = defdir;

        status = sys$getuai(0, 0, &username_desc, &uai_itemlist,
                                        0, 0, 0);

        if (status == RMS$_RNF)         /* there's no such user */
                return(0);

        if (status != SS$_NORMAL)
                exit(status);

        /* Put together the device and directory from the UAF. */

        dev_len = defdev[0];    /* these are counted strings */
        dir_len = defdir[0];
        strncpy(mai_file, defdev + 1, dev_len);
        strncpy(mai_file + dev_len, defdir + 1, dir_len);
        mai_file[dev_len + dir_len] = '\0';

        /* Add on the subdirectory, if any, specified in VMSMAIL.
           It's assumed that this is of the form [.MAIL] (braces
           included): we eliminate the final ] in the mail-file-name
           so far, and then tack on the subdirectory from VMSMAIL
           minus the [ at its start.  (We get RMS to tack on the
           MAIL.MAI part for us.) */

        if (*vmsmail_dir != '\0') {
                mai_file[strlen(mai_file) - 1] = '\0';
                strcat(mai_file, vmsmail_dir + 1);
        }

        return(1);
}
/**************************/
/* Do all the processing for one MAIL.MAI file. */

scan_mail()
{
        char mai_data[MAX_MAI_RECORD + 1];
        struct mai_record *mai;
        long status;

        if (!open_mail())   /* missing mail file, or no NEWMAIL folder */
                return;

        mai_rab.rab$l_ubf = mai_data;

        /* Keep reading records until we run out. */

        while (1) {
                status = sys$get(&mai_rab);

                if (status == RMS$_OK_LIM || status == RMS$_RNF ||
                                        status == RMS$_EOF)
                        break;

                if (status != RMS$_NORMAL)
                        exit(status);

                mai = (struct mai_record *)(mai_rab.rab$l_rbf);

                analyze_mai_record(mai, mai_rab.rab$w_rsz);
        }

        sys$close(&mai_fab);
}
/**************************/
/*  Open the mail file.

    Based on information from the VMS 4.4 fiche, facility MAIL,
    file MAIL$ISAM_SUBS, routine SELECT_MSGS. */

open_mail()
{
        long status;

        mai_fab = cc$rms_fab;
        mai_fab.fab$b_fac = FAB$M_GET;
        mai_fab.fab$l_fna = mai_file;
        mai_fab.fab$b_fns = strlen ( mai_file );
        mai_fab.fab$l_dna = &MAI_DEFAULT;
        mai_fab.fab$b_dns = sizeof ( MAI_DEFAULT );
        mai_fab.fab$b_acmodes = PSL$C_EXEC << FAB$V_LNM_MODE;
        mai_fab.fab$b_org = FAB$C_IDX;
        mai_fab.fab$b_shr = FAB$M_SHRPUT | FAB$M_SHRGET |
                            FAB$M_SHRDEL | FAB$M_SHRUPD;

        status = sys$open(&mai_fab);

        if (status == RMS$_FNF) {       /* no mail file */
                lost_mail_file();
                header_line_printed = 1;
                return(0);
        }

        if (status != RMS$_NORMAL)
                exit(status);

        mai_rab = cc$rms_rab;
        mai_rab.rab$l_fab = &mai_fab;
        mai_rab.rab$l_rop = RAB$M_LOC | RAB$M_NLK | RAB$M_LIM | RAB$M_WAT;
        mai_rab.rab$b_krf = 1;
        mai_rab.rab$l_kbf = &NEWMAIL_FOLDER;
        mai_rab.rab$b_ksz = sizeof ( NEWMAIL_FOLDER );
        mai_rab.rab$b_rac = RAB$C_KEY;
        mai_rab.rab$w_usz = MAX_MAI_RECORD;

        status = sys$connect(&mai_rab);

        if (status != RMS$_NORMAL) {
                sys$close(&mai_fab);
                exit(status);
        }

        status = sys$find(&mai_rab);

        if (status == RMS$_RNF) {       /* no NEWMAIL folder */
                sys$close(&mai_fab);
                return(0);
        }

        if (status != RMS$_NORMAL) {
                sys$close(&mai_fab);
                exit(status);
        }

        mai_rab.rab$b_rac = RAB$C_SEQ;

        return(1);
}
/**************************/
/*  Analyze one MAIL.MAI record and decide if it's a match. */

analyze_mai_record(mai, mai_size)
struct mai_record *mai;
int mai_size;
{
        struct mmh_record *mmh;
        int matched = 0;
        char *subject;
        int subject_length;

        if ((mai->mai_flags & MMHFLAGS_M_NEWMSG) == 0)
                return;

        mmh = (struct mmh_record *)(&(mai->mmh1));

        while (mmh < (char *)mai + mai_size) {

                if (mmh->mmh_type == MMH_C_FROM) {
                        if (test_from(&(mmh->mmh_data), mmh->mmh_length))
                                matched = 1;
                        else
                                return;
                }
                else if (mmh->mmh_type == MMH_C_SUBJ) {
                        subject = &(mmh->mmh_data);
                        subject_length = mmh->mmh_length;
                }

                mmh = (struct mmh_record *)
                                (&(mmh->mmh_data) + mmh->mmh_length);
        }

        if (matched)
                display_mai(&(mai->msgtime[0]), subject, subject_length);
}
/**************************/
/*  Break the "from" string into a node name and username, and test
    both for a match. */

int test_from(from, from_length)
char *from;
int from_length;
{
        int i;
        int node_size = 0;

        /* Find first space.  The "from" string often includes a
           "personal name", which is separated from the username
           by some spaces. */

        for (i = 0; i < from_length; i++)
                if (from[i] == ' ')
                        break;
        from_length = i;

        /* Find :: between node name and username.  We actually seek
           only a :, since anything that contains a single colon
           is something weird which we shouldn't match. */

        for (i = 0; i < from_length; i++)
                if (from[i] == ':')
                        break;

        /* If we found a node name, test it. */

        if (i < from_length) {
                if (!test_node(from, i))
                        return(0);
                i += 2;         /* add length of :: to length of nodename */
                from += i;
                from_length -= i;
                if (from_length < 0)
                        return(0);
        }

        /* Now test the username from the "from" field and see if it's
           the same as that of the current process. */

        if (from_length > UAF$S_USERNAME)
                return(0);

        for (i = 0; i < from_length; i++)
                if (ourname_buf[i] != from[i])
                        return(0);

        if (from_length < UAF$S_USERNAME &&
                ourname_buf[from_length] != ' ')
                        return(0);

        return(1);
}
/**************************/
/*  Test a node name to see if it's one of those allowed. */

int test_node(node, node_length)
char *node;
int node_length;
{
        int i, testnamesize;
        char *testname;

        testname = &NODE_NAMES;

        while (*testname != 0) {
                testnamesize = *(testname++);
                if (testnamesize == node_length) {
                        for (i = 0; i < node_length; i++)
                                if (node[i] != testname[i])
                                        break;
                        if (i == node_length)
                                return(1);
                }
                testname += testnamesize;
        }

        return(0);
}
/**************************/
/*  Print date/time and subject of this message, with a nice header
    line if this is the first message to this user. */

display_mai(time, subject, subject_length)
long *time;             /* pointer to 64-bit time */
char *subject;          /* pointer to subject string */
int subject_length;     /* length of subject string */
{
#define TIME_LEN        23

        static struct dsc$descriptor_s time_desc = {
                TIME_LEN, 0, 0, 0
        };
        char time_buf[TIME_LEN];

        time_desc.dsc$a_pointer = time_buf;
        sys$asctim(0, &time_desc, time, 0);
        time_buf[17] = '\0';

        if (header_line_printed == 0) {
                if (forwardings == 0)
                        printf("\nUnread messages to %.*s:\n\n",
                                firstname_len, firstname_desc.dsc$a_pointer);
                else {
                        printf(
                        "\nUnread messages to %.*s (forwarded to %.*s):\n\n",
                                firstname_len, firstname_desc.dsc$a_pointer,
                                username_len, username_desc.dsc$a_pointer);
                }
        }

        printf("%s  %.*s\n", time_buf,
                subject_length > 60 ? 60 : subject_length, subject);

        header_line_printed++;
}
/**************************/
/*  Print messages for various other cases. */

none_unread()
{
        printf("\nNo unread messages to %.*s.\n",
                        firstname_len, firstname_desc.dsc$a_pointer);
}


no_such_user()
{
        if (forwardings == 0)
                printf("\nNo such user %.*s.\n",
                        firstname_len, firstname_desc.dsc$a_pointer);
        else
                printf(
                "\nUsername %.*s is forwarded to nonexistent user %.*s.\n",
                        firstname_len, firstname_desc.dsc$a_pointer,
                        username_len, username_desc.dsc$a_pointer);
}


lost_mail_file()
{
        printf("\nUnable to open mail file for %.*s.\n",
                        firstname_len, firstname_desc.dsc$a_pointer);
}
$EOD
$ checksum UNREAD.C
$ bad = checksum$checksum .ne. 152461070
$ if bad then write sys$output "Bad checksum for UNREAD.C"
$ create UNREAD.CLD
$DECK
define verb unread
        image "sys$util:unread"
        parameter p1, prompt="To",
                value ( required, list ), label=username
$EOD
$ checksum UNREAD.CLD
$ bad = checksum$checksum .ne. 1998141651
$ if bad then write sys$output "Bad checksum for UNREAD.CLD"
$ create UNREAD.HLP
$DECK
1 UNREAD
The UNREAD command will tell you if mail you've sent to a specified
user or users has been read yet.  For example,

     $ unread solomon, magoon

will list, by date, time and subject, all the messages you've sent
to SOLOMON and MAGOON that they haven't read.  Bear in mind that,
although it involves the MAIL system, you can't actually issue
this command from within MAIL; you must first exit to DCL.

Format:

     UNREAD   username[,...]
$EOD
$ checksum UNREAD.HLP
$ bad = checksum$checksum .ne. 1260810105
$ if bad then write sys$output "Bad checksum for UNREAD.HLP"
$!-----------------end of code------------------------------------
