jonathan@cs.keele.ac.uk (Jonathan Knight) (10/23/89)
Here's a neat little program which detects whether users on your local machine have ready their mail recently. It reports when they last checked it and also whether any new mail has arrived since. Very useful if your local machine uses userids which are based on department or type of user or year of graduation. You may copy this program as often as you like - just don't pretend you wrote it. You may rip off any useful bits of code you like - a "thankyou" would be nice. #!/bin/sh # to extract, remove the header and type "sh filename" if `test ! -s ./Makefile` then echo "Writing ./Makefile" cat > ./Makefile << '\Rogue\Monster\' # Makefile for cmail - a mail checking program # # This is the CFLAGS I had to use under Dynix so that the normal # include files were found first, but regexp.h was found in the # att universe. # #CFLAGS = -O -I/usr/include -I/usr/att/usr/include # CFLAGS = -O LFLAGS = # Destination: # DEST - The destination for the cmail executeable # MDEST - The destination for the cmail.1 manual source DEST = /usr/local/bin MDEST = /usr/man/man1 # all: cmail cmail: cmail.c cmail.h cc $(CFLAGS) $(LFLAGS) -o cmail cmail.c install: cmail cmail.1 install -c -m 755 -s cmail $(DEST) install -c -m 644 cmail.1 $(MDEST) clean: rm -f cmail.o cmail core a.out \Rogue\Monster\ else echo "Will not over-write ./Makefile" fi if `test ! -s ./README` then echo "Writing ./README" cat > ./README << '\Rogue\Monster\' cmail - check when people have read their mail - Version 1.1 Cmail is useful if you want to check to see if a group of people have read their mail. It allows regular expressions as a list of usernames to check, so sites which have usernames derived from the department or project a user is involved in will benefit. Check the compile options in the Makefile and then take a look at cmail.h. Once these are correct run make. Cmail has been tested on the following systems: Ultrix 1.2A (BSD4.2) Dynix 3.0.15 (BSD4.2 ish) Microport 2.4 (SYSVR2) (R.I.P.) SunOS 3.5 (BSD4.2) (Yellow Pages) Bugs, etc can be sent to: ______ JANET :jonathan@uk.ac.keele.cs Jonathan Knight, / BITNET:jonathan%cs.kl.ac.uk@ukacrl Department of Computer Science / _ __ other :jonathan@cs.keele.ac.uk University of Keele, Keele, (_/ (_) / / UUCP :...!ukc!kl-cs!jonathan Staffordshire. ST5 5BG. U.K. \Rogue\Monster\ else echo "Will not over-write ./README" fi if `test ! -s ./cmail.1` then echo "Writing ./cmail.1" cat > ./cmail.1 << '\Rogue\Monster\' .TH CMAIL 1 "8 July 1989" "Local Commands" .SH NAME cmail \- check when users last checked their mail .SH SYNOPSIS cmail [-v] userlist... .SH DESCRIPTION .LP .I Cmail is a program for showing when users last checked their mail. It produces output given the user name, their full name (as given in the password file) and when they last checked their mail. If no system mailbox exists then no date is shown. .LP If mail has been added to the mailbox since it was last checked then .I cmail will print .B "New Mail" beside the date the user last read their mail. .LP The .I userlist is a list of restricted regular expressions. The regular expressions are compared to the usernames in the password file and any match is then used. The restriction on the regular expressions is that the comparison is only performed at the start of the user name (an implicit circumflex at the start of the regular expression). .LP Note that any command that scans the mailbox of a user will cause .I cmail to believe the user has checked their mailbox. So .IR from (1) and .IR mail (1) when run will cause cmail to believe all the mail has been checked. Programs like the auto-reply daemon of the elm mailing system will also make cmail believe the mail has been checked. .SH OPTIONS .IP "-v" Print the version and release date of cmail. .SH EXAMPLES .LP Check users who's usernames start with 'biy'. .IP cmail biy .LP Check users who's name has a 'd' as the third character. .IP cmail ..d .LP Check users who's username begins with csa or cca and check the username advisory. .IP cmail -v "c[sc]a" advisory$ .SH AUTHOR Jonathan@cs.keele.ac.uk \Rogue\Monster\ else echo "Will not over-write ./cmail.1" fi if `test ! -s ./cmail.c` then echo "Writing ./cmail.c" cat > ./cmail.c << '\Rogue\Monster\' /* Reports on who's got mail to read. Version 1.0 Initial release Version 1.1 Fixed manual page Usage: mailfor [-v] user1 user2 user3 .... Users can be given as regular expressions. */ /* Options you shouldn't change */ #define REGSIZE 100 #define UNAMESIZE 10 #define FNAMESIZE 25 #define PRINT1 "%-10.10s %-25.25s " #define VERSION "1.1" #define DATE "8 July 1989" #define YEAR "1989" /* Include files */ #include "cmail.h" #include <pwd.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #ifdef TIME #include <time.h> #else #include <sys/time.h> #endif #ifdef STRING #include <string.h> #else #include <strings.h> #endif #ifdef INDEX #define strchr(a, b) index(a, b) #define strrchr(a, b) rindex(a, b) #endif struct passwd *getpwent(); /* These are not declared for SYSVR2 */ void endpwent(); /* so we'll declare them here. */ /* Global data declarations */ char *myname; /* Now comes the initialisation of the regular expression package. We use this rather than the regexp(3) routines because then we can hold all the arguments in a compiled form and just do one pass of the password file. */ void regerr(err) int err; { fprintf(stderr, "%s: Regular expresion failure %d\n", myname, err); exit(255); } #define INIT register char *sp = instring; #define GETC() (*sp++) #define PEEKC() (*sp) #define UNGETC(c) (--sp) #define RETURN(c) return; #define ERROR(c) regerr(c) #include <regexp.h> /* Structures to hold the list of users and their full names after a scan of the password file. */ typedef struct userlist { char username[UNAMESIZE]; /* Username */ char fullname[FNAMESIZE]; /* Full name */ struct userlist *next; /* Next user in list */ } USERLIST; typedef struct userroot { char reguser[REGSIZE]; /* User name in regexp format */ char *argument; /* Original argument */ USERLIST *last; /* Last username in list */ USERLIST *first; /* First username in list */ } USERROOT; /* Functions we may want to use later */ USERROOT *build(); USERROOT *buildarray(); void buildlist(); void addtolist(); char *getusername(); void printarray(); void printstat(); void fatal(); char *malloc(); char *calloc(); /* Now the program */ main(argc, argv) char *argv[]; int argc; { USERROOT *array; /* Find out who I am */ myname=strrchr(*argv, '/'); if (myname == NULL) myname=(*argv); else ++myname; ++argv; --argc; /* If they've asked for a version then now is a good time to print it out. */ if (strcmp(*argv, "-v") == 0) { printf("\n%s Version %s, %s\n(C) Copyright %s Jonathan Knight\n\n", myname, VERSION, DATE, YEAR); ++argv; --argc; } /* Check for the obvious */ if (argc == 0) exit(0); /* Go to the spool directory. */ if (chdir(MSPOOL)) fatal("Can't access mail spool directory", errno); /* Create an array of users which match the arguments */ array=build(argc, argv); /* Print out the result */ printarray(argc, array); exit(0); } /* Build a list of users and their full names */ USERROOT *build(argc, argv) int argc; char *argv[]; { USERROOT *array; array=buildarray(argc, argv); if (array != NULL) buildlist(argc, array); return(array); } /* This function builds an array of the arguments passed ready for a scan of the password file */ USERROOT *buildarray(argc, argv) int argc; char *argv[]; { USERROOT *array; int i; array=(USERROOT *)calloc(argc, sizeof(USERROOT)); if (array == NULL) return(NULL); /* Array already initialised to 0 by the calloc call */ for (i=0; i<argc; ++i) { array[i].argument=argv[i]; (void)compile(argv[i], array[i].reguser, &array[i].reguser[REGSIZE], '\0'); } return(array); } /* This function scans the password file looking for users which match the list of users given. If they do, then a structure containing the username and their full name is stored. */ void buildlist(argc, array) int argc; USERROOT array[]; { struct passwd *pwent; int i; while ((pwent=getpwent()) != NULL) for (i=0; i < argc; ++i) if (advance(pwent->pw_name, array[i].reguser)) addtolist(&array[i], pwent); endpwent(); /* Report on any regexp's that didn't match anything */ for (i=0; i<argc; ++i) if (array[i].first == NULL) fprintf(stderr, "No users matched %s\n", array[i].argument); } /* Function to get a username from a GECOS field */ #ifdef FINGG char *getusername(gecos) char *gecos; { char *c; for (c=gecos; (*c != '\0') && (*c != ','); ++c); *c='\0'; return(gecos); } #else #ifdef SYSVG char *getusername(gecos) char *gecos; { char *s, *e; s=strchr(gecos, '-'); if (s==NULL) s=gecos; else ++s; e=strrchr(gecos, '('); if (e!=NULL) *e='\0'; return(s); } #else #define getusername(gecos) gecos #endif #endif void addtolist(el, pwent) USERROOT *el; struct passwd *pwent; { USERLIST *new; if ((new=(USERLIST *)malloc(sizeof(USERLIST))) == NULL) fatal("No memory for user list", errno); if (el->first == NULL) el->first = new; else el->last->next=new; el->last=new; new->next = NULL; strncpy(new->username, pwent->pw_name, UNAMESIZE); strncpy(new->fullname, getusername(pwent->pw_gecos), FNAMESIZE); } /* Now a function to print out all the users we have collected */ void printarray(argc, array) USERROOT array[]; { int i; USERLIST *p; if (array == NULL) return; for (i=0; i<argc; ++i) if (array[i].first != NULL) for (p=array[i].first; p != NULL; p=p->next) printstat(p); } /* Function to stat the mail file and print out some information. */ void printstat(p) USERLIST *p; { struct stat buf; printf(PRINT1, p->username, p->fullname); if (stat(p->username, &buf) == 0) { printf("%.19s", ctime(&buf.st_atime)); if ((buf.st_atime < buf.st_mtime) && (buf.st_size)) printf(" New Mail"); } putchar('\n'); fflush(stdout); } /* Function to perform a fatal error */ void fatal(mess, err) char *mess; int err; { fprintf(stderr, "%s: ", myname); perror(mess); exit(err); } \Rogue\Monster\ else echo "Will not over-write ./cmail.c" fi if `test ! -s ./cmail.h` then echo "Writing ./cmail.h" cat > ./cmail.h << '\Rogue\Monster\' /* Options: * *SYSVG - You have a SYSV gecos field in your passwd file "(000)-Name(0000)" * *FINGG - You have a BSD finger type GECOS field "name, office, ext, ..." * (If neither of the above is defined then the GECOS field is assumed * to only contain the user's name) * *STRING - If the file /usr/include/string.h exists. Otherwise * /usr/include/strings.h is used instead. * *TIME - time.h is in /usr/include and not /usr/include/sys * *INDEX - You have index and rindex rather that strchr and strrchr. * *MSPOOL - Where the system mailboxes are. */ #undef SYSVG #undef FINGG #undef STRING #undef TIME #define INDEX #define MSPOOL "/usr/spool/mail" \Rogue\Monster\ else echo "Will not over-write ./cmail.h" fi echo "Finished archive 1 of 1" exit -- ______ JANET :jonathan@uk.ac.keele.cs Jonathan Knight, / BITNET:jonathan%cs.kl.ac.uk@ukacrl Department of Computer Science / _ __ other :jonathan@cs.keele.ac.uk University of Keele, Keele, (_/ (_) / / UUCP :...!ukc!kl-cs!jonathan Staffordshire. ST5 5BG. U.K.
tchrist@convex.COM (Tom Christiansen) (10/24/89)
In article <1121@kl-cs.UUCP> jonathan@cs.keele.ac.uk (Jonathan Knight) writes: >Here's a neat little program which detects whether users on your >local machine have ready their mail recently. It reports when they >last checked it and also whether any new mail has arrived since. >Very useful if your local machine uses userids which are based on >department or type of user or year of graduation. Sigh. Yet another piece of system administrative hackery written in C. This one took over 300 lines and contains various and sundry data structures and subroutines. I don't mean to impune the competence of the original author. It *is* a neat little program. But it seems like terrific overkill to do this in C. So as an exercise, I rewrote the program in perl (version 3.0). It took *substantially* less code: around 15%. I'll bet it was faster to write and faster to debug. I'm sure it'll be faster to modify. Even if you don't have perl, check out my script here. I think you'll agree that it's **MILES** clearer than the corresponding C code. I wanted to do some timing comparisons, but found that the original program used SysV's regexp() routines rather than BSD's rexex(). So if anyone else wants to post timings to alt.sources.d, I'd be interested in seeing them. Meanwhile, both to demonstrate the marvelousness of perl and also to chase off the non-source-posting haranguers, here is the code. I have retained the basic structure, routine names, and variable names of the original code where reasonable to do so, perhaps more so than was optimal. Two comments on the original C code: doing getpwent()s until you run out is terribly innefficient on systems with long passwd files; mine is ~1300 lines long, and it would be better to do readdir()s on SPOOL and then getpwnam()s on the results. Secondly, the author has what is to me an odd style to his code (comment placement, argument placement, curley placement), so I ran it through indent to put it in KNF (kernel normal form) before conversion. This was purely aesthetic. Recall that you will need version 3 of perl. I include after the cmail.pl script code for the ctime() function, which is not a perl intrinsic. The ctime.pl code was posted to the net some time ago, but I've unfortunately lost the info on the original author. I thank him for his code and apologize for not having provided his proper credit. --tom #!/bin/sh # This is a shell archive. # Run the following text with /bin/sh to extract. echo x cmail.pl sed -e 's/^X//' << \EOFMARK > cmail.pl X#!/usr/local/bin/perl3 X X$PRINT1 = "%-10.10s %-25.25s "; X$VERSION = "1.2"; X$DATE = "23 October"; X$YEAR = "1989"; X$SPOOL = "/usr/spool/mail"; X Xdo 'ctime.pl'; X($myname = $0) =~ s%.*/%%; X Xif ($ARGV[0] eq "-v") { X printf "%s Version %.1f, %s %d; Tom Christiansen <tchrist@convex.com>\n", X $myname, $VERSION, $DATE, $YEAR; X shift; X} X Xexit 0 unless $#ARGV > $[-1; Xchdir $SPOOL || die "$myname: can't access mail spool directory: $!\n"; Xdo build(@ARGV); Xdo printarray(); Xexit 0; X Xsub build { X setpwent; Xpw: while (($name, $passwd, $uid, X $gid, $quota, $comment, X $gcos, $dir, $shell) = getpwent) X { Xrexpr: foreach $rexpr ( @_ ) { X next rexpr unless $name =~ /^$rexpr/; X $matched_rexprs{$rexpr}++; X ( $fullname = $gcos ) =~ s/,.*//; X $fullname{$name} = $fullname; X next pw; X } X } X endpwent; X X foreach $rexpr ( @_ ) { X next if $matched_rexprs{$rexpr}; X printf stderr "No users matched %s\n", $rexpr; X } X} X Xsub printarray { X foreach $user ( keys %fullname ) { X printf $PRINT1, $user, $fullname{$user}; X if ( -e $user ) { X ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, X $atime,$mtime,$ctime,$blksize,$blocks) = stat(_); X printf "%.19s", &ctime($atime); X print " New Mail" if $atime < $mtime && $size; X } X print "\n"; X } X} EOFMARK echo x ctime.pl sed -e 's/^X//' << \EOFMARK > ctime.pl X@DoW = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat'); X@MoY = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'); X Xsub ctime { X local($time) = @_; X local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst); X local($date); X X ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) X = localtime($time); X $year += ($year < 70)? 2000: 1900; X $date = sprintf("%s %s %2d %2d:%02d:%02d %s %4d\n", X $DoW[$wday], $MoY[$mon], $mday, $hour, $min, $sec, X $ENV{'TZ'}, $year); X return $date; X} EOFMARK Tom Christiansen {uunet,uiucdcs,sun}!convex!tchrist Convex Computer Corporation tchrist@convex.COM "EMACS belongs in <sys/errno.h>: Editor too big!"