[comp.os.minix] Final version of the MAIL program

waltje@relay.eu.net (06/03/90)

Here is a complete version of the "mail" (also /bin/mail) program
Peter Housel posted a long time ago.  I fixed some bugs, added the
missing things (nothing more, as the comments requested!) and re-
edited it to MSS style.

I have it on my disk as "lmail" and "binmail", both in /usr/bin.
Since it is small, it is an ideal "local mail delivery agent" for
my U-MAIL mailer (hence "lmail").  Some people like its interface
(sorry, I don't dare calling it a user-interface...), so I did
include it as a "normal" mailer as well (hence "binmail").

However, I recommend W-MAIL or ELM for "real" mailing work.
Since ELM is kinda large, I am working on a version of W-MAIL that
uses the WINDOW package, thus making it a full-screen mail reading
program, much like VN does it with news articles.

If there are any problems, please let me know.

Enjoy,
	Fred van Kempen
	MINIX User Group Holland
----------------------------- cut here ----------------------------------
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
#		"End of archive"
# Contents:
#	README        	mail.doc      	Makefile      	Makefile.unix 
#	mail.c        
#
# Wrapped by root@minixug on Sat Apr 28 14:47:59 1990
#
PATH=/bin:/usr/bin:/usr/local/bin:/usr/ucb:/tmp ; export PATH
if test -f 'README' -a "${1}" != "-c"
then
   echo "$0: Will not overwrite existing file: 'README'"
else
   echo "x - README"
sed 's/^X//' <<\END_OF_SHAR >'README'
XHere is a version of /bin/mail. If you've ever actually used /bin/mail
Xunder Unix, you know it is archaic and has a poor user interface. This
Xversion duplicates these characteristics quite faithfully.
X
XHowever, it is the standard delivery agent on most Unix systems, even
Xthose with better user interfaces. (The UCB "Mail" program is freely
Xredistributable, if anyone wants to port it, but it is big and depends
Xon reliable signals and file locking. One of these days I intend to write
Xa simple MH-clone in sh-scripts.)
X
XIf you have a "network transport agent" available, such as smail or
Xsendmail (!), then you can uncomment the line defining a MAILER. Without
Xsuch an agent, the program is still useful for reading and sending local
Xmail between users on your local Minix system.
X
XThe program is suid-root, so there could potentially be a sercurity hole
Xor two. I was careful, but if you find any problems please tell me.
X
XInstall using "make install". Basically, put it in /usr/bin, owned by
Xroot, mode 4755 (set-uid). The link named "/usr/bin/lmail" is for the
X"smail" and "umail" mailers.
X
X-Peter S. Housel-	housel@ecn.purdue.edu		...!pur-ee!housel
X Fred van Kempen	waltje@kyber.uucp
X
END_OF_SHAR
fi
if test -f 'mail.doc' -a "${1}" != "-c"
then
   echo "$0: Will not overwrite existing file: 'mail.doc'"
else
   echo "x - mail.doc"
sed 's/^X//' <<\END_OF_SHAR >'mail.doc'
X
XMAIL(1)             MINIX Programmer's Manual             MAIL(1)
X
XNAME     
X     mail - send or read electronic mail
X
XSYNOPSIS     
X     mail [ -tv ] user [ ... ]
X     mail [ -epqr ] [ -f file ]
X
XDESCRIPTION     
X     mail is used to send or read electronic mail. In  the  first
X     form,  a letter is copied from standard input (terminated by
X     end-of-file or '.' on a line by itself),  and  sent  to  the
X     maildrops  of  each  of  the  users  specified.   A postmark
X     telling  the  sender  and  date  of  delivery  precedes  the
X     message,  and  a  blank  line  is  placed  at the end of the
X     message as it is being delivered.  Lines in the message that
X     look  like  postmarks  (i.e.   which begin with "From ") are
X     copied with a ">" prepended. 
X     If the -t flag is specified, a line with "To:" plus a comma-
X     separated list of all specified addressees will be added.
X
X     In the second form, the user's maildrop is printed in  last-
X     in,  first-out  order. The -f flag may be used to specify an
X     alternate mailbox,  and  -r  may  be  used  to  reverse  the
X     printing  order.  If  -p  is  given,  the  entire mailbox is
X     printed and the program exits. Otherwise, after each message
X     a command is prompted for. The commands are:
X
X     newline    Go on to the next letter.
X
X     d    Delete the current letter and go on to the next.
X
X     p       Print the current letter again. - Print the previous
X          letter. s [ file ... ] Save in the named files.
X
X     !command    Execute the given command.
X
X     q	    Update the mailbox and exit.
X
X     EOF    Same as q
X
X     x    Exit without updating the mailbox. 
X
X     If  the -q flag is given, an in interrupt will cause mail to
X     exit. Otherwise, when a letter is printed interactively,  an
X     interrupt will abort the printing of the current letter.
X
XDELIVERY     
X     The  mail program can serve as both a user agent and a local
X     delivery agent. If the program is  compiled  without  MAILER
X     defined,  or  the -d flag is given, or the program is called
X     as lmail, then mail will do the  delivery  itself,  and  the
X     recipients  must  be  local  users.  No  header  (except for
X     the postmark and/or the "To: "-line) is added.  If the local
X     delivery fails, the message will be dumped in a file  called
X     "dead.letter"  in  the current working  directory,  and some
X     diagnostics will be sent to standard error. 
X
X
X
X				- 1 -
X
X
X
X     Otherwise, if a MAILER is available and the addressee is not
X     a  local user,  it is used to send the message to that user.
X     Examples of suitable MAILERs are the Smail UUCP mail router,
X     and the U-MAIL internetwork mail router. For larger systems,
X     the  Berkely SendMail internetwork  mail router can be used.
X     These programs are  capable  of  expanding  aliases,  adding
X     standard  mail headers, and routing letters to remote hosts.
X     Neither of these programs are designed to do local delivery,
X     and  will  use  another  program  (such  as  mail itself) to
X     deliver to local users.
X
XAUTHORS     
X     Peter S. Housel
X     Fred van Kempen
X
XFILES     
X     /usr/spool/mail/user	maildrop file
X     /usr/spool/mail/user.lock	lock for maildrop files
X     /etc/passwd		to map userid's into usernames
X     /tmp/mail*         	temporary file
X     mbox          		default save file
X     dead.letter		the message after an error
X
XSEE ALSO    
X     smail(8), sendmail(8), umail(8), wmail(8)
X
XBUGS     
X     Races are possible when two mailers  try  to  simultaneously
X     deliver  to  a  box that doesn't exist yet. This is probably
X     not very likely however.
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X				- 1 -
X
END_OF_SHAR
fi
if test -f 'Makefile' -a "${1}" != "-c"
then
   echo "$0: Will not overwrite existing file: 'Makefile'"
else
   echo "x - Makefile"
sed 's/^X//' <<\END_OF_SHAR >'Makefile'
X#
X# Makefile for binmail(1)	(MINIX)
X#
XCFLAGS	= -D_MINIX -D_POSIX_SOURCE
XLDFLAGS	= -i
X
Xall:	mail
X
Xmail:	mail.c
X	cc $(CFLAGS) $(LDFLAGS) -o mail mail.c
X	@chmem =8192 mail
X
Xinstall: mail
X	mv mail /usr/bin
X	rm -f /usr/bin/lmail
X	ln /usr/bin/mail /usr/bin/lmail
X	chown root /usr/bin/mail
X	chmod 4755 /usr/bin/mail
X
Xshar:
X	@bshar -Cvuo mail.shar README mail.doc Makefile* mail.c
X
Xtar:
X	@tar cfvo mail.tar README mail.doc Makefile* mail.c
X
END_OF_SHAR
fi
if test -f 'Makefile.unix' -a "${1}" != "-c"
then
   echo "$0: Will not overwrite existing file: 'Makefile.unix'"
else
   echo "x - Makefile.unix"
sed 's/^X//' <<\END_OF_SHAR >'Makefile.unix'
X#
X# Makefile for binmail(1)	(UNIX)
X#
XCFLAGS	= -D_UNIX
XLDFLAGS	= -O
X
Xall:	mail
X
Xmail:	mail.c
X	cc $(CFLAGS) $(LDFLAGS) -o mail mail.c
X
Xinstall: mail
X	mv mail /usr/bin
X	rm -f /usr/bin/lmail
X	ln /usr/bin/mail /usr/bin/lmail
X	chown root /usr/bin/mail
X	chmod 4755 /usr/bin/mail
X
Xshar:
X	@shar -Cvuo mail.shar README mail.doc Makefile* mail.c
X
Xtar:
X	@tar cfvo mail.tar README mail.doc Makefile* mail.c
X
X
END_OF_SHAR
fi
if test -f 'mail.c' -a "${1}" != "-c"
then
   echo "$0: Will not overwrite existing file: 'mail.c'"
else
   echo "x - mail.c"
sed 's/^X//' <<\END_OF_SHAR >'mail.c'
X/* binmail - the dumb mail handling program.     Author: Peter S. Housel */
X
X/*
X * binmail - 	standard UNIX mail reading program.
X *
X * Usage:	mail [-tv] user [...]
X *		mail [-epqr] [-f file]
X *
X * Version:	1.8	04/28/90
X *
X * Authors:	Peter S. Housel
X *		Fred van Kempen
X *
X * Revisions:
X *
X * 	08/16/88 1.0	Peter S. Housel		Initial release.
X *	01/06/89 1.1	Peter S. Housel
X *	08/03/89 1.2	Peter S. Housel
X *	11/09/89 1.3	Fred van Kempen		Edited for the MSS.
X *						Implemented -e option.
X *						Implemented -t option.
X *						Hacked the MAILER code.
X *	12/04/89 1.4	Fred van Kempen		Fixed all known bugs.
X *	12/16/89 1.5	Fred van Kempen		Cleanup (lint).
X *	12/30/89 1.6	Fred van Kempen		Cleanup for POSIX (MINIX 1.5)
X *	02/17/90 1.7	Fred van Kempen		Cleanup for release.
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <errno.h>
X#include <pwd.h>
X#include <setjmp.h>
X#include <signal.h>
X#include <string.h>
X#include <time.h>
X#include <unistd.h>
X#include <stdio.h>
X
X
X#ifdef _UNIX
X#	define LOCKNAME	   "/usr/mail/%s.lock"
X#	define DROPNAME    "/usr/mail/%s"
X#	define MAILER	   "/usr/bin/mailx"	/* "smart" mailer" */
X#endif
X
X#ifdef _MINIX
X#	define LOCKNAME	   "/usr/spool/mail/%s.lock"
X#	define DROPNAME    "/usr/spool/mail/%s"
X#	define MAILER	   "/usr/bin/mailx"	/* "smart" mailer" */
X#endif
X
X#define DEADLETTER	"dead.letter"		/* emergency file */
X#define SHELL		"/bin/sh"
X#define MBOX		"mbox"
X#define PROMPT		"? "
X#define LOCKWAIT	  5		/* seconds to wait after collision */
X#define LOCKTRIES	  4			/* maximum # of collisions */
X#define PATHLEN		 80
X#define MAXRCPT		100			/* maximum # of recipients */
X#define LINELEN		512
X#define UNREAD		  1			/* 'not read yet' status */
X#define DELETED		  2			/* 'deleted' status */
X#define READ		  3			/* 'has been read' status */
X#define FALSE		  0
X#define TRUE		  1
X
X
Xtypedef struct _letter {
X  struct _letter *prev, *next;		/* linked letter list */
X  int status;				/* letter status */
X  off_t location;			/* location within mailbox file */
X} LET;
X#define NIL_LET (LET *)NULL
X
X
Xstatic char *Version = "@(#) binmail 1.8 (04/28/90)";
X
X
Xchar tempname[PATHLEN] = "/tmp/mailXXXXXX";	/* temporary file */
Xchar mailbox[PATHLEN];			/* user's mailbox/maildrop */
Xjmp_buf printjump;			/* for quitting out of letters */
XLET *firstlet, *lastlet;		/* letter pointers */
XFILE *boxfp = NULL;			/* mailbox file */
Xunsigned oldmask;			/* saved umask() */
Xint sayall = FALSE;			/* add list of addressees */
Xint usemailer = TRUE;			/* use MAILER to deliver (if any) */
Xint printmode = FALSE;			/* print-and-exit mode */
Xint quitmode = FALSE;			/* take interrupts */
Xint reversemode = FALSE;		/* print mailbox in reverse order */
Xint usedrop = TRUE;			/* read the maildrop (no -f given) */
Xint verbose = FALSE;			/* pass "-v" flag on to mailer */
Xint needupdate = FALSE;			/* need to update mailbox */
Xint old_uid, old_gid;			/* for security-reasons */
X
X
Xextern int getopt(), optind;			/* from getopt(3) */
Xextern char *optarg;
X
Xextern FILE *freopen(), *fdopen();
Xextern struct passwd *getpwnam(), *getpwuid();
Xextern char *getenv();
X
Xextern int errno;				/* from libc.a */
X
X
Xint onint()
X{
X  longjmp(printjump, 1);
X}
X
X
X/*
X * Get the caller's user name.
X */
Xchar *whoami()
X{
X  struct passwd *pw;
X
X  if ((pw = getpwuid(old_uid)) == (struct passwd *)NULL) return("nobody");
X    else return(pw->pw_name);
X}
X
X
X/*
X * Strip off the directories from a pathname.
X */
Xchar *basename(name)
Xchar *name;
X{
X  char *p;
X
X  if ((p = strrchr(name, '/')) == (char *)NULL) return(name);
X    else return(++p);
X}
X
X
X/*
X * Show the contents of letter 'let' onto file 'fp'.
X */
Xvoid printlet(let, tofp)
XLET *let;
Xregister FILE *tofp;
X{
X  off_t current, limit;
X  register int c;
X
X  fseek(boxfp, (current = let->location), SEEK_SET);
X  limit = (let->next != NIL_LET) ? let->next->location : -1L;
X
X  while (current != limit && (c = getc(boxfp)) != EOF) {
X	fputc(c, tofp);
X	++current;
X  }
X}
X
X
X/*
X * Execute a command.
X * Do this as a child process, so we can turn off
X * the SetUID ROOT bit first...
X */
Xvoid doshell(command)
Xchar *command;
X{
X  int waitstat, pid;
X  char *shell;
X
X  if ((shell = getenv("SHELL")) == (char *)NULL) shell = SHELL;
X
X  if ((pid = fork()) < 0) {
X	perror("mail: couldn't fork");
X	return;
X  } else if (pid != 0) {	/* parent */
X	wait(&waitstat);
X	return;
X  } else {			/* child */
X	  setgid(old_gid);
X	  setuid(old_uid);
X	  umask(oldmask);
X
X	  execl(shell, shell, "-c", command, (char *)NULL);
X	  fprintf(stderr, "can't exec shell");
X	  exit(127);
X  }
X}
X
X
X/*
X * Check if we may perform operation 'mode' on
X * file 'name'. System Security!
X * If the error is 'ENOENT', then test the parent
X * directory for the desired access.
X */
Xint allowed(name, mode)
Xchar *name;			/* name of file to be checked */
Xunsigned short mode;		/* mode to check (R=4, W=2, X=1) */
X{
X  char abuf[1024];		/* temp. buf for filename */
X  struct stat stb;
X  char *p;
X
X  /* Is this 'The Master' calling? */
X  if (old_uid == 0) return(TRUE);
X
X  if (stat(name, &stb) < 0) {
X	if (errno == ENOENT) {			/* file does not exist */
X		strcpy(abuf, name);		/* so check its parent dir */
X		p = strrchr(abuf, '/');	
X		if (p == (char *)NULL)		/* plain filename, */
X			getcwd(abuf, 1023);	/* get current dir */
X		  else *p = '\0';		/* strip 'file' part */
X		if (stat(abuf, &stb) < 0) return(FALSE);	/* error? */
X	} else return(FALSE);			/* it exists, other error! */
X  }
X
X  /* We now have the status of the file or its parent dir. */
X  if (stb.st_uid == old_uid) {			/* we are owner! */
X	if ((stb.st_mode >> 6) & mode)
X				 return(TRUE);	/* OK, we may do it. */
X  	  else return(FALSE);			/* Alas... */
X  } else if (stb.st_uid == old_gid) {		/* are we the same group? */
X	if ((stb.st_mode >>3) & mode)
X				 return(TRUE);	/* OK, we may do it. */
X  	  else return(FALSE);			/* Alas... */
X  } else if (stb.st_mode & mode)		/* we are 'others' */
X				 return(TRUE);	/* OK, we may do it. */
X  return(FALSE);				/* Alas... */
X}
X
X
X/*
X * 'stdin' isn't rewindable. Make a temp file that is.
X * Note that if one wanted to catch SIGINT and write a
X * '~/dead.letter' for interactive mails, this might be
X * the place to do it (though the case where a MAILER is
X * being used would also need to be handled).
X */
XFILE *makerewindable(void)
X{
X  register FILE *tempfp;		/* temp file used for copy */
X  register int c;			/* character being copied */
X  register int state;			/* ".\n" detection state */
X
X  if ((tempfp = fopen(tempname, "w")) == (FILE *)NULL) {
X	fprintf(stderr, "mail: can't create temporary file");
X	return((FILE *)NULL);
X  }
X
X  /*
X   * Here we copy until we reach the end of the letter (end
X   * of file or a line containing only a '.'), painstakingly
X   * avoiding setting a line length limit.
X   */
X  state = '\n';
X
X  while ((c = getc(stdin)) != EOF) switch(state) {
X	case '\n':
X		if (c == '.') state = '.';
X		  else {
X			if (c != '\n') state = '\0';
X			fputc(c, tempfp);
X		}
X		break;
X	case '.':
X		if (c == '\n') goto done1;
X		state = '\0';
X		fputc('.', tempfp);
X		fputc(c, tempfp);
X		break;
X	default:
X		state = (c == '\n') ? '\n' : '\0';
X		fputc(c, tempfp);
X  }
X
Xdone1:
X
X  if (ferror(tempfp) || fclose(tempfp)) {
X	fprintf(stderr, "mail: couldn't copy letter to temporary file\n");
X	return((FILE *)NULL);
X  }
X  tempfp = freopen(tempname, "r", stdin);
X  unlink(tempname);	/* unlink name; file lingers on in limbo */
X  return(tempfp);
X}
X
X
X/*
X * Copy a message from file 'fromfp' to 'tofp'.
X */
Xcopy(fromfp, tofp)
XFILE *fromfp, *tofp;
X{
X  static char *postmark = "From ";
X  register char *p, *q;		/* fast scanners */
X  register int c;		/* character being copied */
X  register int state;		/* ".\n" and postmark detection state */
X  int blankline = FALSE;	/* was most recently line a blank? */
X
X  /*
X   * Here we copy until we reach the end of the letter (end of file
X   * or a line containing only a '.'). Postmarks (lines beginning
X   * with "From ") are copied with a ">" prepended. Here we also
X   * complicate things by not setting a line limit.
X   */
X  state = '\n';
X  p = postmark;
X
X  while ((c = getc(fromfp)) != EOF) {
X	switch(state) {
X		case '\n':
X			if (c == '.') state = '.';	/* '.' at BOL */
X			  else if (c == *p) {	/* start of postmark */
X					++p;
X					state = 'P';
X			} else {			/* anything else */
X				if (c == '\n') blankline = TRUE;
X				  else {
X					state = '\0';
X					blankline = FALSE;
X				}
X				fputc(c, tofp);
X			}
X			break;
X		case '.':
X			if (c == '\n') goto done2;
X			state = '\0';
X			fputc('.', tofp);
X			fputc(c, tofp);
X			break;
X		case 'P':
X			if (c == *p) {
X				/* Successfully reached end of PM. */
X				if (*++p == '\0') {
X					p = postmark;
X					fputc('>', tofp);
X					fputs(postmark, tofp);
X					state = '\0';
X					break;
X				}
X				break;		/* not there yet */
X			}
X			state = (c == '\n') ? '\n' : '\0';
X			for (q = postmark; q < p; ++q)
X						fputc(*q, tofp);
X			fputc(c, tofp);
X			blankline = FALSE;
X			p = postmark;
X			break;
X		default:
X			state = ('\n' == c) ? '\n' : '\0';
X			fputc(c, tofp);
X	}
X  }
X  if (state != '\n') fputc('\n', tofp);
X
Xdone2:
X
X  if (!blankline) fputc('\n', tofp);
X  if (ferror(tofp)) return(-1);
X  return(0);
X}
X
X
X/*
X * Read the contents of the mailbox, and create
X * a linked list of message frames (headers).
X */
Xvoid readbox()
X{
X  char linebuf[512];
X  LET *let;
X  off_t current;
X 
X  firstlet = lastlet = NIL_LET;
X
X  if (allowed(mailbox, 04) == FALSE) {
X	fprintf(stderr, "mail: can't access mailbox ");
X	perror(mailbox);
X	exit(1);
X  }
X  boxfp = fopen(mailbox, "r");
X
X  current = 0L;
X  while(TRUE) {
X	if (fgets(linebuf, sizeof linebuf, boxfp) == (char *)NULL) break;
X       
X	if (strncmp(linebuf, "From ", 5)==0) {
X		if ( (let = (LET *)malloc(sizeof(LET))) == NIL_LET) {
X			fprintf(stderr, "Out of memory.\n");
X			exit(1);
X		}
X        	if (lastlet == NIL_LET) {
X			firstlet = let;
X			let->prev = NIL_LET;
X		} else {
X			let->prev = lastlet;
X			lastlet->next = let;
X		}
X		lastlet = let;
X		let->next = NIL_LET;
X
X		let->status = UNREAD;
X		let->location = current;
X	}
X	current += strlen(linebuf);
X  }
X}
X
X
X/*
X * Write the contents of letter 'let' to file 'savefile'.
X */
Xvoid savelet(let, savefile)
XLET *let;
Xchar *savefile;
X{
X  FILE *savefp;
X
X  if (allowed(savefile, 04) == TRUE) {
X	if ((savefp = fopen(savefile, "a")) == (FILE *)NULL) {
X		perror(savefile);
X		exit(0);
X	}
X	printlet(let, savefp);
X	if ((ferror(savefp) != 0) || (fclose(savefp) != 0)) {
X		fprintf(stderr, "savefile write error:");
X		perror(savefile);
X	}
X  } else {
X	fprintf(stderr, "mail: cannot write to file \"%s\"\n", savefile);
X  }
X}
X
X
Xvoid updatebox()
X{
X  char lockname[PATHLEN];		/* maildrop lock */
X  LET *let;				/* current letter */
X  register FILE *tempfp;		/* fp for tempfile */
X  int locktries = 0;			/* tries when box is locked */
X  register int c;			/* copying character */
X
X  sprintf(lockname, LOCKNAME, whoami());
X
X  if ((tempfp = fopen(tempname, "w")) == (FILE *)NULL) {
X	perror("mail: can't create temporary file");
X	return;
X  }
X
X  for (let = firstlet; let != NIL_LET; let = let->next) {
X	if (let->status != DELETED) printlet(let, tempfp);
X  }
X
X  if (ferror(tempfp) || (tempfp = freopen(tempname, "r", tempfp))
X							== (FILE *)NULL) {
X	perror("mail: temporary file write error");
X	unlink(tempname);
X	return;
X  }
X
X  /* Shut off signals during the update. */
X  signal(SIGINT, SIG_IGN);
X  signal(SIGHUP, SIG_IGN);
X  signal(SIGQUIT, SIG_IGN);
X
X  if (usedrop) while(link(mailbox, lockname) != 0) {
X	if (++locktries >= LOCKTRIES) {
X		fprintf(stderr, "mail: couldn't lock maildrop for update\n");
X		return;
X	}
X	sleep(LOCKWAIT);
X  }
X
X  if ((boxfp = freopen(mailbox, "w", boxfp)) == (FILE *)NULL) {
X	perror("mail: couldn't reopen maildrop");
X	fprintf(stderr, "mail may have been lost; look in %s\n", tempname);
X	if (usedrop) unlink(lockname);
X	return;
X  }
X
X  unlink(tempname);
X
X  while ((c = getc(tempfp)) != EOF) fputc(c, boxfp);
X
X  fclose(boxfp);
X
X  if (usedrop) unlink(lockname);
X}
X
X
X/*
X * This is the interactive command interpreter.
X */
Xvoid interact()
X{
X  char linebuf[512];		/* user input line */
X  LET *let, *next;		/* current and next letter */
X  int interrupted = FALSE;	/* SIGINT hit during letter print */
X  int needprint = TRUE;		/* need to print this letter */
X  char *savefile;		/* filename to save into */
X
X  if (firstlet == NIL_LET) {
X	printf("No mail.\n");
X	return;
X  }
X
X  let = reversemode ? firstlet : lastlet;
X
X  while(TRUE) {
X	next = reversemode ? let->next : let->prev;
X	if (next == NIL_LET) next = let;
X
X	if (!quitmode) {
X		interrupted = setjmp(printjump);
X		signal(SIGINT, onint);
X	}
X
X	if (!interrupted && needprint) {
X		if (DELETED != let->status) let->status = READ;
X		printlet(let, stdout);
X	}
X
X	if (interrupted) putchar('\n');
X	needprint = FALSE;
X	fputs(PROMPT, stdout);
X	fflush(stdout);
X
X	if (fgets(linebuf, sizeof linebuf, stdin) == (char *)NULL) break;
X
X	if (!quitmode) signal(SIGINT, SIG_IGN);
X
X	switch(linebuf[0]) {
X		case '\n':
X			let = next;
X			needprint = TRUE;
X			continue;
X		case 'd':
X			let->status = DELETED;
X			if (next != let) needprint = TRUE;
X			needupdate = TRUE;
X			let = next;
X			continue;
X		case 'p':
X			needprint = TRUE;
X			continue;
X		case '-':
X			next = reversemode ? let->prev : let->next;
X			if (next == NIL_LET) next = let;
X			let = next;
X			needprint = TRUE;
X			continue;
X		case 's':
X			for (savefile = strtok(linebuf + 1, " \t\n");
X				savefile != (char *)NULL;
X				savefile = strtok((char *)NULL, " \t\n")) {
X					savelet(let, savefile);
X			}
X			continue;
X		case '!':
X			doshell(&linebuf[1]);
X			continue;
X		case 'q':
X			return;
X		case 'x':
X			exit(0);
X		default:
X			fprintf(stderr, "Illegal command\n");
X			continue;
X	}
X  }   
X}
X
X
X#ifdef MAILER
X/*
X * Send a message to a remote (non-local) user
X * through the MAILER agent.
X */
Xvoid sendremote(who)
Xchar *who;
X{
X  char *argvec[5];		/* string array for execv() */
X  char **argp;			/* index into array */
X  int waitstat, pid;
X
X  if ((pid = fork()) < 0) {
X	perror("mail: couldn't fork");
X	return;
X  } else if (pid != 0) {		/* parent */
X	wait(&waitstat);
X	return;
X  } else {				/* child */
X	  setgid(old_gid);
X	  setuid(old_uid);
X
X	  argp = argvec;		/* create the arguments for exec() */
X	  *argp++ = "send-mail";
X	  if (verbose) *argp++ = "-v";	/* Pass the -v option ... */
X	  *argp++ = who;		/* the recipient */
X
X	  *argp = (char *)NULL;		/* terminate the array with a NULL */
X
X	  execv(MAILER, argvec);	/* execute the mailer and exit */
X
X	  fprintf(stderr, "mail: couldn't exec %s\n", MAILER);
X	  exit(-1);
X  }
X}
X#endif MAILER
X
X
X/* 
X * Save the current message to file 'dead.letter'.
X */
Xvoid deadletter(void)
X{
X  char *fname = DEADLETTER;
X  register FILE *inf, *outf;
X
X  if (allowed(fname, 02) == TRUE) {
X	inf = fopen(tempname, "r");
X	if (inf == (FILE *)NULL) {
X		fprintf(stderr, "mail: cannot open \"%s\"\n", tempname);
X		return;
X	}
X
X	outf = fopen(fname, "w");
X	if (outf == (FILE *)NULL) {
X		fprintf(stderr, "mail: cannot create \"%s\"\n", fname);
X		return;
X	}
X
X	/* Copy temp. file to dead.letter. */
X	copy(inf, outf);
X
X	fclose(inf);
X	fclose(outf);
X
X	setuid(getuid());
X	chown(fname, getuid(), getgid());
X	fprintf(stderr, "mail: dumped message on file \"%s\"\n", fname);
X  } else {
X	fprintf(stderr, "mail: cannot write to file \"%s\"\n", fname);
X  }
X}
X
X
X/*
X * Deliver a message in old-style (V6/V7) format.
X */
Xint deliver(count, vec)
Xint count; char *vec[];
X{
X  int (*sigint)(), (*sighup)(), (*sigquit)();	/* saving signal state */
X  char lockname[PATHLEN];		/* maildrop lock */
X  char addressees[1024];			/* list of addressees */
X  char sender[32];			/* sender's login name */
X  struct stat stb;			/* for checking drop modes, owners */
X  time_t now;				/* for datestamping the postmark */
X  struct passwd *pw;			/* sender and recipent */
X  FILE *mailfp;				/* fp for mail */
X  int errs = 0;				/* count of errors */
X  int dropfd;				/* file descriptor for user's drop */
X  int created = 0;			/* true if we created the maildrop */
X  int locktries;			/* tries when box is locked */
X  int i;
X
X  if (count > MAXRCPT) {
X	fprintf(stderr, "mail: too many recipients\n");
X	return(-1);
X  }
X
X  if ((pw = getpwuid(old_uid)) == (struct passwd *)NULL) {
X	fprintf(stderr, "mail: unknown sender\n");
X	return(-1);
X  }
X  strcpy(sender, pw->pw_name);
X
X  /* If we need to rewind stdin and it isn't rewindable, make a copy. */
X  if (isatty(0) || (count > 1 && lseek(0, 0L, SEEK_SET) < 0L)) {
X	mailfp = makerewindable();
X  } else mailfp = stdin;
X
X  /* Create a list of addressees if necessary. */
X  if (sayall) {
X	strcpy(addressees, "");
X	for (i=0; i<count; i++) {
X		strcat(addressees, vec[i]);
X		/* RFC-822: separate with comma */
X		strcat(addressees, ", ");
X  	}
X  	addressees[strlen(addressees) -2] = '\0';  /* kill last comma */
X  }
X
X  /* Shut off signals during the delivery. */
X  sigint = signal(SIGINT, SIG_IGN);
X  sighup = signal(SIGHUP, SIG_IGN);
X  sigquit = signal(SIGQUIT, SIG_IGN);
X
X  /* Deliver the messages. Do this on a per-user basis. */
X  for (i = 0; i < count; ++i) {
X	if (count > 1) rewind(mailfp);
X
X#ifdef MAILER
X	if ((strchr(vec[i], '!') || strchr(vec[i], '@')) && usemailer) {
X		sendremote(vec[i]);
X		continue;
X	}
X#endif MAILER
X
X	if ((pw = getpwnam(vec[i])) == (struct passwd *)NULL) {
X		fprintf(stderr, "mail: user %s not known\n", vec[i]);
X		++errs;
X		continue;
X	}
X
X	sprintf(mailbox, DROPNAME, pw->pw_name);
X	sprintf(lockname, LOCKNAME, pw->pw_name);
X
X	/*
X	 * Lock the maildrop while we're messing with it.
X	 * Races are possible (though not very likely) when
X	 * we have to create the maildrop, but not otherwise.
X	 * If the box is already locked, wait awhile and try
X	 * again.
X	 */
X	locktries = created = 0;
Xtrylock:
X	if (link(mailbox, lockname) != 0) {
X		if (errno == ENOENT) {	/* user doesn't have a drop yet */
X			if ((dropfd = creat(mailbox, 0600)) < 0) {
X				fprintf(stderr,
X				"mail: couln't create a maildrop for user %s\n",
X								vec[i]);
X				++errs;
X				continue;
X			}
X			++created;
X			goto trylock;
X		} else {    /* somebody else has it locked, it seems - wait */
X			if (++locktries >= LOCKTRIES) {
X				fprintf(stderr,
X				"mail: couldn't lock maildrop for user %s\n",
X								vec[i]);
X				++errs;
X				continue;
X			}
X	   		sleep(LOCKWAIT);
X	  		goto trylock;
X		}
X	}
X
X	if (created) {
X		(void) chown(mailbox, pw->pw_uid, pw->pw_gid);
X		boxfp = fdopen(dropfd, "a");
X	} else boxfp = fopen(mailbox, "a");
X
X	if (boxfp == (FILE *)NULL || stat(mailbox, &stb) < 0) {
X		fprintf(stderr, "mail: serious maildrop problems for %s\n",
X								vec[i]);
X		unlink(lockname);
X		++errs;
X		continue;
X       	}
X
X	if (stb.st_uid != pw->pw_uid || (stb.st_mode & S_IFMT) != S_IFREG) {
X		fprintf(stderr, "mail: mailbox for user %s is illegal\n",
X								vec[i]);
X		unlink(lockname);
X		++errs;
X		continue;
X       	}
X
X	(void) time(&now);
X	fprintf(boxfp, "From %s %24.24s\n", sender, ctime(&now));
X	if (sayall) fprintf(boxfp, "To: %s\n", addressees);
X	fprintf(boxfp, "\n");	/* empty line marks end-of-header! */
X
X	if ((copy(mailfp, boxfp) < 0) | (fclose(boxfp) != 0)) {
X		fprintf(stderr, "mail: error delivering to user %s", vec[i]);
X		perror("");
X		++errs;
X       	}
X	unlink(lockname);
X  }
X
X  fclose(mailfp);
X
X  /* Put signals back the way they were. */
X  signal(SIGINT, sigint);
X  signal(SIGHUP, sighup);
X  signal(SIGQUIT, sigquit);
X
X  return((errs == 0) ? 0 : -1);
X}
X
X
X/*
X * Print all letters and exit.
X */
Xvoid printall()
X{
X  register LET *let;
X
X  let = reversemode ? firstlet : lastlet;
X
X  if (let == NIL_LET) {
X	printf("No mail.\n");
X	return;
X  }
X
X  while (let != NIL_LET) {
X	printlet(let, stdout);
X	let = reversemode ? let->next : let->prev;
X  }
X}
X
X
X/*
X * Check if there is any mail for the calling user.
X * Return 0 if there is mail, or 1 for NO MAIL.
X */
Xint chk_mail(void)
X{
X  char temp[512];
X  FILE *fp;
X
X  if ((fp = fopen(mailbox, "r")) != (FILE *)NULL) {
X	if (fgets(temp, sizeof(temp), fp) == (char *)NULL) {
X		fclose(fp);	/* empty mailbox, no mail! */
X		return(1);
X	}
X      	if (!strncmp(temp, "Forward to ", 11)) {
X		fclose(fp);	/* FORWARD line in mailbox */
X		return(2);	/* so no mail. */
X	}
X	fclose(fp);	/* another line, so we have mail! */
X	return(0);
X  }
X  return(1);
X}
X
X
Xvoid usage()
X{
X  fprintf(stderr, "usage: mail [-v] user [...]\n");
X  fprintf(stderr, "       mail [-epqr] [-f file] [-t arg]\n");
X}
X
X
Xint main(argc, argv)
Xint argc;
Xchar *argv[];
X{
X  int c;
X
X  /* If we are a link to 'lmail', let's deliver it ourself. */
X  if ((basename(argv[0]))[0] == 'l') usemailer = FALSE;
X
X  mktemp(tempname);		/* name the temp file */
X
X  oldmask = umask(022);			/* change umask for security */
X  old_uid = getuid();
X  old_gid = getgid();
X  setgid(getegid());
X  setuid(geteuid());
X
X  while ((c = getopt(argc, argv, "def:pqrtv")) != EOF) switch(c) {
X	case 'd':	/* local delivery only */
X		usemailer = FALSE;
X		break;
X	case 'e':	/* check if any mail present */
X		exit(chk_mail());
X	case 'f':	/* use other mailbox */
X		setuid(getuid());	/* won't need to lock */
X		setgid(getgid());	/* won't need to lock */
X		usedrop = FALSE;
X		strncpy(mailbox, optarg, PATHLEN - 1);
X		break;
X	case 'p':	/* print all mail and exit */
X		printmode++;
X		break;
X	case 'q':	/* abort if SIGINT received */
X		quitmode++;
X		break;
X	case 'r':	/* REVERSE mode */
X		reversemode++;
X		break;
X	case 't':	/* include list of all addressees in "To:" */
X		sayall++;
X		break;
X	case 'v':	/* verbose mode */
X		verbose++;
X		break;
X	default:
X		usage();
X		exit(2);
X  }
X
X  if (optind < argc) {
X	if (deliver(argc - optind, argv + optind) < 0) {
X		deadletter();
X		exit(1);
X	} else exit(0);
X  }
X
X  if (usedrop) sprintf(mailbox, DROPNAME, whoami());
X
X  readbox();
X
X  if (printmode) printall();
X    else interact();
X
X  if (needupdate) updatebox();
X
X  exit(1);
X}
END_OF_SHAR
fi
echo "		End of archive"
rm -f /tmp/uudecode
exit 0
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+
| MINIX User Group (Holland)   UUCP: hp4nl!kyber!minixug!waltje	|
| c/o Fred van Kempen,		 or: minixug!waltje@kyber.uucp	|
| Hoefbladhof  27		 or: waltje@kyber.uucp		|
| 2215 DV  VOORHOUT						|
| The Netherlands	"A good programmer knows his Sources"	|
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-+