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;