[comp.os.minix] A version of binmail

housel@en.ecn.purdue.edu (Peter S. Housel) (07/03/89)

Here is a version of /bin/mail. If you've ever actually used /bin/mail
under Unix, you know it is archaic and has a poor user interface. This
version duplicates these characteristics quite faithfully.

However, it is the standard delivery agent on most Unix systems, even
those with better user interfaces. (The UCB "Mail" program is freely
redistributable, if anyone wants to port it, but it is big and depends
on reliable signals and file locking. One of these days I intend to write
a simple MH-clone in sh-scripts.)

If you have a "network transport agent" available, such as smail or
sendmail (!), then you can uncomment the line defining a MAILER. Without
such an agent, the program is still useful for reading and sending local
mail between users on your local Minix system.

The program is suid-root, so there could potentially be a sercurity hole
or two. I was careful, but if you find any problems please tell me.
Code implementing V7 functionality that I was too lazy to include would
also be appreciated. (Nothing beyond this, however: fancy mail-reading
features belong in a new user agent.)

-Peter S. Housel-	housel@ecn.purdue.edu		...!pur-ee!housel

------------------cut-here-----------------------
#!/bin/sh
echo 'x - mail.doc'
sed 's/^X//' <<'**-mail.doc-EOF-**' >mail.doc
X
XMAIL(1)             MINIX Programmer's Manual             MAIL(1)
X
XNAME     
X     mail - send or read electronic mail
X
XSYNOPSIS     
X     mail [ -v ] user [ ... ]
X     mail [  -epqr ] [ -f file ] [ -t arg ]
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
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 the
X     postmark)  is  added,  and  delivery   errors   will   cause
X     diagnostics on standard error. 
X
X     Otherwise,  if a MAILER is available, it is used to send the
X     messages. Examples of suitable MAILERs are  the  smail  UUCP
X     mail  router,  and  the  sendmail  internetwork mail router.
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
XAUTHOR     
X     Peter S. Housel
X
XFILES     
X     /usr/spool/mail/user   maildrop file
X     /usr/spool/mail/user.lock      lock for maildrop files
X     /tmp/mail*      temporary file
X     mbox           default save file
X
XSEE ALSO    
X     smail(8), sendmail(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     A few interactive commands are not implemented. "-t" and "-e"
X     don't do anything because I don't know what they are for.
**-mail.doc-EOF-**
echo 'x - mail.c'
sed 's/^X//' <<'**-mail.c-EOF-**' >mail.c
X/* file: mail.c
X** author: Peter S. Housel 08/16/88
X*/
X
X#include <stdio.h>
X#include <signal.h>
X#include <pwd.h>
X#include <errno.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <time.h>
X#include <setjmp.h>
X#include <string.h>
X
X#ifdef DEBUG
X#define D(Q) (Q)
X#else
X#define D(Q)
X#endif
X
X#define SHELL		"/bin/sh"
X
X#define DROPNAME 	"/usr/spool/mail/%s"
X#define LOCKNAME	"/usr/spool/mail/%s.lock"
X#define LOCKWAIT	5		/* seconds to wait after collision */
X#define LOCKTRIES	4		/* maximum number of collisions */
X
X#define MBOX		"mbox"
X
X#define HELPFILE	"/usr/lib/mail.help"
X#define PROMPT		"? "
X#define PATHLEN		80
X#define MAXRCPT		100		/* maximum number of recipients */
X#define LINELEN		512
X
X/* #define MAILER		"/usr/bin/smail"	/* smart mailer */
X/* #define MAILERARGS				/* (unused) */
X
X#define UNREAD		1		/* 'not read yet' status */
X#define DELETED		2		/* 'deleted' status */
X#define READ		3		/* 'has been read' status */
X
Xextern int optind;			/* getopt() globals */
Xextern char *optarg;
Xextern int errno;
X
Xstruct letter
X       {
X	struct letter *prev, *next;	/* linked letter list */
X	int status;			/* letter status */
X	off_t location;			/* location within mailbox file */
X       };
X
Xstruct letter *firstlet, *lastlet;
X
Xint usemailer = 1;			/* use MAILER to deliver (if any) */
Xint printmode = 0;			/* print-and-exit mode */
Xint quitmode = 0;			/* take interrupts */
Xint reversemode = 0;			/* print mailbox in reverse order */
Xint usedrop = 1;			/* read the maildrop (no -f given) */
Xint verbose = 0;			/* pass "-v" flag on to mailer */
Xint needupdate = 0;			/* need to update mailbox */
Xchar mailbox[PATHLEN];			/* user's mailbox/maildrop */
Xchar tempname[PATHLEN] = "/tmp/mailXXXXXX";	/* temporary file */
XFILE *boxfp = NULL;			/* mailbox file */
Xjmp_buf printjump;			/* for quitting out of letters */
Xunsigned oldmask;			/* saved umask() */
X
Xint deliver();
XFILE *makerewindable();
Xint copy();
Xvoid readbox(), printall(), interact(), savelet(), updatebox();
Xvoid printlet(), doshell(), usage();
Xint onint();
Xchar *basename(), *whoami();
Xextern FILE *fopen(), *freopen(), *fdopen();
Xextern struct passwd *getpwnam(), *getpwuid();
Xextern char *getenv();
X
Xmain(argc, argv)
Xint argc; char *argv[];
X{
X int c;
X
X if('l' == (basename(argv[0]))[0])	/* 'lmail' link? */
X    usemailer = 0;			/* yes, let's deliver it */
X
X (void) mktemp(tempname);		/* name the temp file */
X
X oldmask = umask(022);			/* change umask for security */
X
X while(EOF != (c = getopt(argc, argv, "epqrf:t:dv")))
X       switch(c)
X	     {
X	      case 'e':
X	      case 't':
X			fprintf(stderr, "option not implemented yet\n");
X			/* because I don't know what they do */
X			exit(1);
X
X	      case 'p':
X			++printmode;
X			break;
X
X	      case 'q':
X			++quitmode;
X			break;
X
X	      case 'r':
X			++reversemode;
X			break;
X
X	      case 'f':
X			setuid(getuid());	/* won't need to lock */
X			usedrop = 0;
X			strncpy(mailbox, optarg, PATHLEN - 1);
X			break;
X
X	      case 'd':
X			usemailer = 0;
X			break;
X
X	      case 'v':
X			++verbose;
X			break;
X
X	      default:
X			usage();
X			exit(1);
X	     }
X
X if(optind < argc)
X   {
X    if(deliver(argc - optind, argv + optind) < 0)
X       exit(1);
X    else
X       exit(0);
X   }
X
X if(usedrop)
X    sprintf(mailbox, DROPNAME, whoami());
X
X D(printf("mailbox=%s\n", mailbox));
X
X readbox();
X
X if(printmode)
X    printall();
X else
X    interact();
X
X if(needupdate)
X    updatebox();
X
X exit(0);
X}
X
Xint deliver(count, vec)
Xint count; char *vec[];
X{
X int i;
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 FILE *mailfp;				/* fp for mail */
X struct stat stb;			/* for checking drop modes, owners */
X int (*sigint)(), (*sighup)(), (*sigquit)();	/* saving signal state */
X time_t now;				/* for datestamping the postmark */
X char sender[32];			/* sender's login name */
X char lockname[PATHLEN];		/* maildrop lock */
X int locktries;				/* tries when box is locked */
X struct passwd *pw;			/* sender and recipent */
X
X if(count > MAXRCPT)
X   {
X    fprintf(stderr, "mail: too many recipients\n");
X    return -1;
X   }
X
X#ifdef MAILER
X if(usemailer)
X   {
X    char *argvec[MAXRCPT + 3];
X    char **argp;
X
X    setuid(getuid());
X
X    argp = argvec;
X    *argp++ = "send-mail";
X    if(verbose)
X       *argp++ = "-v";
X
X    for(i = 0; i < count; ++i)
X	*argp++ = vec[i];
X
X    *argp = (char *)NULL;
X    execv(MAILER, argvec);
X    fprintf(stderr, "mail: couldn't exec %s\n", MAILER);
X    return -1;    
X   }
X#endif MAILER
X
X if(NULL == (pw = getpwuid(getuid())))
X   {
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, 0) < 0L))
X   {
X    mailfp = makerewindable();
X   }
X else
X    mailfp = stdin;
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 for(i = 0; i < count; ++i)
X    {
X     if(count > 1)
X        rewind(mailfp);
X
X     D(printf("deliver to %s\n", vec[i]));
X
X     if(NULL == (pw = getpwnam(vec[i])))
X       {
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     D(printf("maildrop='%s', lock='%s'\n", mailbox, lockname));
X
X     /* Lock the maildrop while we're messing with it. Races are possible
X      * (though not very likely) when we have to create the maildrop, but
X      * not otherwise. 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       {
X	if(ENOENT == errno)		/* user doesn't have a drop yet */
X	  {
X	   if((dropfd = creat(mailbox, 0600)) < 0)
X	     {
X	      fprintf(stderr, "mail: couln't create a maildrop for user %s\n",
X		      vec[i]);
X	      ++errs;
X	      continue;
X             }
X	   ++created;
X	   goto trylock;
X          }
X	else	/* somebody else has it locked, it seems - wait */
X	  {
X	   if(++locktries >= LOCKTRIES)
X	     {
X	      fprintf(stderr, "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       {
X	(void) chown(mailbox, pw->pw_uid, pw->pw_gid);
X	boxfp = fdopen(dropfd, "a");
X       }
X     else
X	boxfp = fopen(mailbox, "a");
X
X     if(NULL == boxfp || stat(mailbox, &stb) < 0)
X       {
X	fprintf(stderr, "mail: serious maildrop problems for %s\n", 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       {
X	fprintf(stderr, "mail: mailbox for user %s is illegal\n", 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
X     if((copy(mailfp, boxfp) < 0) | (fclose(boxfp) != 0))
X       {
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 (0 == errs) ? 0 : -1;
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 '~/dead.letter'
X * for interactive mails, this might be the place to do it (though the
X * case where a MAILER is being used would also need to be handled).
X */
XFILE *makerewindable()
X{
X FILE *tempfp;			/* temp file used for copy */
X int c;				/* character being copied */
X int state;			/* ".\n" detection state */
X
X if(NULL == (tempfp = fopen(tempname, "w")))
X   {
X    fprintf(stderr, "mail: can't create temporary file");
X    return NULL;
X   }
X
X /* Here we copy until we reach the end of the letter (end of file or a line
X  * containing only a '.'), painstakingly avoiding setting a line length
X  * limit.
X  */
X state = '\n';
X while(EOF != (c = getc(stdin)))
X       switch(state)
X	     {
X	      case '\n':
X			if('.' == c)
X			   state = '.';
X			else
X			  {
X			   if('\n' != c)
X			      state = '\0';
X			   putc(c, tempfp);
X			  }
X			break;
X	       case '.':
X			if('\n' == c)
X				goto done;
X			state = '\0';
X			putc('.', tempfp);
X			putc(c, tempfp);
X			break;
X	       default:
X			state = ('\n' == c) ? '\n' : '\0';
X			putc(c, tempfp);
X	      }
Xdone:
X if(ferror(tempfp) || fclose(tempfp))
X   {
X    fprintf(stderr, "mail: couldn't copy letter to temporary file\n");
X    return NULL;
X   }
X tempfp = freopen(tempname, "r", stdin);
X unlink(tempname);	/* unlink name; file lingers on in limbo */
X return tempfp;
X}
X
Xcopy(fromfp, tofp)
XFILE *fromfp, *tofp;
X{
X int c;				/* character being copied */
X int state;			/* ".\n" and postmark detection state */
X int blankline = 0;		/* was most recent line completely blank? */
X static char postmark[] = "From ";
X char *p, *q;
X
X /* Here we copy until we reach the end of the letter (end of file or a line
X  * containing only a '.'). Postmarks (lines beginning with "From ")
X  * are copied with a ">" prepended. Here we also complicate things
X  * by not setting a line limit.
X  */
X state = '\n';
X p = postmark;
X while(EOF != (c = getc(fromfp)))
X      {
X       switch(state)
X	     {
X	      case '\n':
X			if('.' == c)		/* '.' at BOL */
X			   state = '.';
X			else if(*p == c)	/* start of postmark */
X			  {
X			   ++p;
X			   state = 'P';
X			  }
X			else			/* anything else */
X			  {
X			   if('\n' == c)
X			      blankline = 1;
X			   else
X			     {
X			      state = '\0';
X			      blankline = 0;
X			     }
X			   putc(c, tofp);
X			  }
X			break;
X	       case '.':
X			if('\n' == c)
X				goto done;
X			state = '\0';
X			putc('.', tofp);
X			putc(c, tofp);
X			break;
X	       case 'P':
X			if(*p == c)
X			  {
X			   if(*++p == '\0')	/* successfully reached end */
X			     {
X			      p = postmark;
X			      putc('>', tofp);
X			      fputs(postmark, tofp);
X			      state = '\0';
X			      break;
X			     }
X			   break;		/* not there yet */
X			  }
X			state = ('\n' == c) ? '\n' : '\0';
X			for(q = postmark; q < p; ++q)
X			    putc(*q, tofp);
X			putc(c, tofp);
X			blankline = 0;
X			p = postmark;
X			break;
X	       default:
X			state = ('\n' == c) ? '\n' : '\0';
X			putc(c, tofp);
X	      }
X      }
X if('\n' != state)
X    putc('\n', tofp);
Xdone:
X if(!blankline)
X    putc('\n', tofp);
X if(ferror(tofp))
X    return -1;
X return 0;
X}
X
Xvoid readbox()
X{
X char linebuf[512];
X struct letter *let;
X off_t current;
X 
X firstlet = lastlet = NULL;
X
X if(access(mailbox, 4) < 0 || NULL == (boxfp = fopen(mailbox, "r")))
X   {
X    if(usedrop && ENOENT == errno)
X       return;
X    fprintf(stderr, "can't access mailbox ");
X    perror(mailbox);
X    exit(1);
X   }
X
X current = 0L;
X while(1)
X      {
X       if(NULL == fgets(linebuf, sizeof linebuf, boxfp))
X          break;
X       
X       if(!strncmp(linebuf, "From ", 5))
X         {
X          if(NULL == (let = (struct letter *)malloc(sizeof(struct letter))))
X            {
X	     fprintf(stderr, "Out of memory.\n");
X	     exit(1);
X	    }
X          if(NULL == lastlet)
X	    {
X	     firstlet = let;
X	     let->prev = NULL;
X	    }
X	  else
X	    {
X	     let->prev = lastlet;
X	     lastlet->next = let;
X	    }
X	  lastlet = let;
X	  let->next = NULL;
X
X	  let->status = UNREAD;
X	  let->location = current;
X	  D(printf("letter at %ld\n", current));
X	 }
X       current += strlen(linebuf);
X      }
X}
X
Xvoid printall()
X{
X struct letter *let;
X off_t current, limit;
X int c;
X
X let = reversemode ? firstlet : lastlet;
X
X if(NULL == let)
X   {
X    printf("No mail.\n");
X    return;
X   }
X
X while(NULL != let)
X      {
X       printlet(let, stdout);
X       let = reversemode ? let->next : let->prev;
X      }
X}
X
Xvoid interact()
X{
X char linebuf[512];			/* user input line */
X char *p;
X struct letter *let, *next;		/* current and next letter */
X int interrupted = 0;			/* SIGINT hit during letter print */
X int needprint = 1;			/* need to print this letter */
X char *savefile;			/* filename to save into */
X
X if(NULL == firstlet)
X   {
X    printf("No mail.\n");
X    return;
X   }
X
X let = reversemode ? firstlet : lastlet;
X
X while(1)
X      {
X       next = reversemode ? let->next : let->prev;
X       if(NULL == next)
X	  next = let;
X
X       if(!quitmode)
X	 {
X	  interrupted = setjmp(printjump);
X	  signal(SIGINT, onint);
X	 }
X
X       if(!interrupted && needprint)
X	 {
X          if(DELETED != let->status)
X	     let->status = READ;
X	  printlet(let, stdout);
X	 }
X
X       if(interrupted)
X	  putchar('\n');
X       needprint = 0;
X       fputs(PROMPT, stdout);
X       fflush(stdout);
X
X       if(fgets(linebuf, sizeof linebuf, stdin) == NULL)
X	  break;
X
X       if(!quitmode)
X          signal(SIGINT, SIG_IGN);
X
X       switch(linebuf[0])
X	     {
X	      case '\n':
X			let = next;
X			needprint = 1;
X			continue;
X	      case 'd':
X			let->status = DELETED;
X			if(next != let)		/* look into this */
X			   needprint = 1;
X			needupdate = 1;
X			let = next;
X			continue;
X	      case 'p':
X			needprint = 1;
X			continue;
X	      case '-':
X			next = reversemode ? let->prev : let->next;
X			if(NULL == next)
X			  next = let;
X			let = next;
X			needprint = 1;
X			continue;
X	      case 's':
X			for(savefile = strtok(linebuf + 1, " \t\n");
X				savefile != NULL;
X				savefile = strtok((char *) NULL, " \t\n"))
X			   {
X			    savelet(let, savefile);
X			   }
X			continue;
X	      case '!':
X			doshell(linebuf + 1);
X			continue;
X#ifdef NOTDEF
X	      case '?':
X			dohelp();
X			continue;
X#endif NOTDEF
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
Xint onint()
X{
X longjmp(printjump, 1);
X}
X
Xvoid savelet(let, savefile)
Xstruct letter *let; char *savefile;
X{
X int waitstat, pid;
X FILE *savefp;
X
X if((pid = fork()) < 0)
X   {
X    perror("mail: couldn't fork");
X    return;
X   }
X else if(pid != 0)	/* parent */
X   {
X    wait(&waitstat);
X    return;
X   }
X			/* child */
X setgid(getgid());
X setuid(getuid());
X if((savefp = fopen(savefile, "a")) == NULL)
X   {
X    perror(savefile);
X    exit(0);
X   }
X printlet(let, savefp);
X if((ferror(savefp) != 0) | (fclose(savefp) != 0))
X   {
X    fprintf(stderr, "savefile write error:");
X    perror(savefile);
X   }
X exit(0);
X}
X
Xvoid updatebox()
X{
X FILE *tempfp;				/* fp for tempfile */
X char lockname[PATHLEN];		/* maildrop lock */
X int locktries = 0;			/* tries when box is locked */
X struct letter *let;			/* current letter */
X int c;
X
X if(NULL == (tempfp = fopen(tempname, "w")))
X   {
X    perror("mail: can't create temporary file");
X    return;
X   }
X
X for(let = firstlet; let != NULL; let = let->next)
X    {
X     if(let->status != DELETED)
X       {
X        printlet(let, tempfp);
X	D(printf("printed letter at %ld\n", let->location));
X       }
X    }
X
X if(ferror(tempfp) || NULL == (tempfp = freopen(tempname, "r", tempfp)))
X   {
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)
X    while(link(mailbox, lockname) != 0)
X         {
X	  if(++locktries >= LOCKTRIES)
X	    {
X	     fprintf(stderr, "mail: couldn't lock maildrop for update\n");
X	     return;
X	    }
X	  sleep(LOCKWAIT);
X	 }
X
X if(NULL == (boxfp = freopen(mailbox, "w", boxfp)))
X   {
X    perror("mail: couldn't reopen maildrop\n");
X    fprintf(stderr, "mail may have been lost; look in %s\n", tempname);
X    unlink(lockname);
X    return;
X   }
X
X unlink(tempname);
X
X while((c = getc(tempfp)) != EOF)
X       putc(c, boxfp);
X
X fclose(boxfp);
X
X if(usedrop)
X    unlink(lockname);
X}
X
Xvoid printlet(let, tofp)
Xstruct letter *let; FILE *tofp;
X{
X off_t current, limit;
X int c;
X
X fseek(boxfp, (current = let->location), 0);
X limit = (NULL != let->next) ? let->next->location : -1;
X
X while(current != limit && (c = getc(boxfp)) != EOF)
X      {
X       putc(c, tofp);
X       ++current;
X      }
X}
X
Xvoid doshell(command)
Xchar *command;
X{
X int waitstat, pid;
X char *shell;
X
X if(NULL == (shell = getenv("SHELL")))
X    shell = SHELL;
X
X if((pid = fork()) < 0)
X   {
X    perror("mail: couldn't fork");
X    return;
X   }
X else if(pid != 0)	/* parent */
X   {
X    wait(&waitstat);
X    return;
X   }
X			/* child */
X setgid(getgid());
X setuid(getuid());
X umask(oldmask);
X
X execl(shell, shell, "-c", command, (char *) NULL);
X fprintf(stderr, "can't exec shell");
X exit(127);
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
Xchar *basename(name)
Xchar *name;
X{
X char *p;
X extern char *rindex();
X
X if(NULL == (p = rindex(name, '/')))
X    return name;
X else
X    return p + 1;
X}
X
Xchar *whoami()
X{
X struct passwd *pw;
X
X if(NULL != (pw = getpwuid(getuid())))
X    return pw->pw_name;
X else
X    return "nobody";
X}
**-mail.c-EOF-**
echo 'x - Makefile'
sed 's/^X//' <<'**-Makefile-EOF-**' >Makefile
X#
X# makefile for mail
X#
XCFLAGS=
XLDFLAGS=-i
X
Xall:	mail
X
Xmail:	mail.c
X	cc $(CFLAGS) $(LDFLAGS) -o mail mail.c
X	chmem =6144 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
**-Makefile-EOF-**
exit 0