[mod.computers.vax] Program to list unread mail messages

LAV@BRANDEIS.BITNET.UUCP (03/25/87)

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;