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;