From decwrl!sun-barr!apple!brutus.cs.uiuc.edu!tut.cis.ohio-state.edu!cs.utexas.edu!uunet!allbery Fri Aug 25 22:47:05 PDT 1989 Article 1054 of comp.sources.misc: Path: decwrl!sun-barr!apple!brutus.cs.uiuc.edu!tut.cis.ohio-state.edu!cs.utexas.edu!uunet!allbery From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) Newsgroups: comp.sources.misc Subject: v08i015: comsat and biff for System V with a FIFO Message-ID: <64928@uunet.UU.NET> Date: 25 Aug 89 01:48:20 GMT Sender: allbery@uunet.UU.NET Reply-To: edf@ROCKY2.ROCKEFELLER.EDU (David MacKenzie) Lines: 874 Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) Posting-number: Volume 8, Issue 15 Submitted-by: edf@ROCKY2.ROCKEFELLER.EDU (David MacKenzie) Archive-name: comsat.s5 This is a port of the free comsat program from the 4.3BSD-tahoe release to System V, with a FIFO (named pipe) replacing sockets. When a new mail message is delivered, the local mail delivery agent writes a message to a FIFO, which the comsat daemon reads. If the recipient of the mail is logged in and `biff y' (similar to mesg y but a different bit of the tty is used), a message is printed on the user's screen summarizing the new mail. #! /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 'README' <<'END_OF_FILE' XThis is a port of the free comsat program from the 4.3BSD-tahoe release Xto System V, with a FIFO (named pipe) replacing sockets. X XWhen a new mail message is delivered, the local mail delivery agent Xwrites a message (for the format, see the comment at the top of Xcomsat.c) to a FIFO, which the comsat daemon reads. If the recipient Xof the mail is logged in and `biff y' (similar to mesg y but a Xdifferent bit of the tty is used), a message is printed on the user's Xscreen summarizing the new mail. X XFiles included in this package: X XREADME This file. Xcomsat.c Daemon that prints the biff messages. Xbiff.c Allow/prevent receipt of biff messages. Xlmail.diffs Diffs to lmail from comp.sources.misc volume 4, a local X mail delivery program for smail 2.5, to make it write a X message to the comsat FIFO when new mail is delivered. Xmailwrap.c An alternative approach to using lmail; a program to X write a message to the comsat FIFO, then run your old, X renamed local mail delivery program. X Written by Jim Mattson . X XFollowing is a list of the main portability issues I encountered while Xporting comsat from 4.3BSD to System V. In addition to these, I added Xmore comments and gave some identifiers more descriptive names, and ran Xthe file through GNU indent. X Xsignals Xsockets vs. FIFO Xinvocation and exit Xerror logging Xindex vs. strchr Xsgtty vs. termio Xgethostname vs. uname Xfailure of interrupted system calls (fgets seems to handle this correctly) X XDavid MacKenzie X END_OF_FILE if test 1518 -ne `wc -c <'README'`; then echo shar: \"'README'\" unpacked with wrong size! fi # end of 'README' fi if test -f 'comsat.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'comsat.c'\" else echo shar: Extracting \"'comsat.c'\" \(11659 characters\) sed "s/^X//" >'comsat.c' <<'END_OF_FILE' X/* X * Copyright (c) 1980 Regents of the University of California. X * All rights reserved. X * X * Redistribution and use in source and binary forms are permitted X * provided that the above copyright notice and this paragraph are X * duplicated in all such forms and that any documentation, X * advertising materials, and other materials related to such X * distribution and use acknowledge that the software was developed X * by the University of California, Berkeley. The name of the X * University may not be used to endorse or promote products derived X * from this software without specific prior written permission. X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED X * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. X */ X X#ifndef lint Xchar copyright[] = X"@(#) Copyright (c) 1980 Regents of the University of California.\n\ X All rights reserved.\n"; X#endif /* not lint */ X X/* comsat - daemon to notify registered users of new mail X Usage: comsat 2> /tmp/comsat.errors X X Receives one line messages of the form X user@mailbox-offset\n X on a named pipe, and if the user is logged on and "biff y" (owner X execute bit of tty is turned on), prints a message summarizing the new X mail on the user's screen. X X Converted for System V with FIFO X by David MacKenzie X with additional changes X by Jim Mattson X X Latest revision: 08/23/89 */ X X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X X/* BSD-compatible constants for lseek. */ X#define L_SET 0 X#define L_INCR 1 X#define L_XTND 2 X X/* The directory where system mailboxes are located. */ X#define SYSMAILDIR "/usr/mail" X X/* Path of the named pipe used to send messages to this program. */ X#define FIFO "/etc/comsat.fifo" X X/* The number of seconds between checks of the utmp. */ X#define ALARM_INTERVAL 15 X X/* If this is defined, no blank line will be printed between the X header lines (e.g., "Subject:") and the head of the message text. X The blank line is present in the 4.3BSD comsat but absent in the X 4.3BSD-tahoe comsat from which this version is derived. */ X/* #define JOIN_HEADER_AND_TEXT */ X Xchar *malloc (); Xchar *realloc (); Xoff_t atol (); Xoff_t fseek (); Xoff_t lseek (); Xtime_t time (); X Xchar *xmalloc (); Xchar *xrealloc (); Xint read_utmp (); Xint note_alarm (); Xvoid mail_for (); Xvoid msg_perror_fatal (); Xvoid notify (); Xvoid summarize_new_mail (); X X/* This machine's host name, used in the notification message. */ Xchar hostname[10]; X X/* Contents of the utmp. */ Xstruct utmp *utmp; X X/* If nonzero, the alarm rang while sending a biff message. */ Xint alarm_rang; X X/* Number of entries in `utmp'. */ Xint nutmp; X X/* File descriptor for reading the utmp. */ Xint utfd; X X/* The name this program was run with, for error messages. */ Xchar *program_name; X X/* ARGSUSED */ Xint Xmain (argc, argv) X int argc; X char **argv; X{ X FILE *fifp; X char msgbuf[100]; X X program_name = argv[0]; X X signal (SIGINT, SIG_IGN); X signal (SIGHUP, SIG_IGN); X X /* Don't tie up a filesystem. */ X if (chdir ("/") == -1) X msg_perror_fatal ("Cannot chdir to /"); X X if (daemon_running (FIFO)) X { X fprintf (stderr, "%s: Daemon is already running\n", program_name); X exit (1); X } X switch (fork ()) X { X case -1: X msg_perror_fatal ("Cannot fork"); X case 0: /* Child. */ X break; X default: /* Parent. */ X _exit (0); X } X X fclose (stdin); /* Don't need these anymore. */ X fclose (stdout); X setpgrp (); /* Detach from parent's process group. */ X X fifp = fdopen (open_fifo (FIFO), "r"); X if (fifp == NULL) X msg_perror_fatal ("Cannot fdopen FIFO %s", FIFO); X X utfd = open (UTMP_FILE, O_RDONLY); X if (utfd == -1) X msg_perror_fatal ("Cannot read %s", UTMP_FILE); X X utmp = NULL; X nutmp = 0; X gethostname (hostname, sizeof (hostname)); X signal (SIGCLD, SIG_IGN); /* Prevent zombie process creation. */ X X read_utmp (); X X while (1) X { X while (fgets (msgbuf, sizeof msgbuf, fifp) == NULL) X sleep (1); X if (nutmp == 0) X continue; /* No one has logged in yet. */ X /* Don't let automatic utmp updating corrupt the in-core copy while X we're using it. */ X alarm_rang = 0; X signal (SIGALRM, note_alarm); X mail_for (msgbuf); X /* If we missed a utmp update while biffing the user, do the X update manually and set the alarm again. */ X if (alarm_rang) X read_utmp (); X else X signal (SIGALRM, read_utmp); X } X /* NOTREACHED */ X} X X/* SIGALRM handler for while mail_for is running. */ X Xint Xnote_alarm () X{ X alarm_rang = 1; X} X X/* Normal SIGALRM handler. Every ALARM_INTERVAL seconds, read a current X copy of the utmp into `utmp'. */ X Xint Xread_utmp () X{ X static unsigned utmp_size = 0;/* Bytes allocated for `utmp'. */ X static unsigned utmp_mtime = 0; /* Last modification time of utmp. */ X struct stat stats; X X if (fstat (utfd, &stats) == -1) X msg_perror_fatal ("Cannot fstat utmp"); X if (stats.st_mtime > utmp_mtime) X { X utmp_mtime = stats.st_mtime; X if (stats.st_size > utmp_size) X { X utmp_size = stats.st_size + 10 * sizeof (struct utmp); X utmp = (struct utmp *) xrealloc ((char *) utmp, utmp_size); X } X if (lseek (utfd, 0L, L_SET) < 0) X msg_perror_fatal ("Cannot seek to beginning of utmp"); X nutmp = read (utfd, utmp, (unsigned) stats.st_size); X if (nutmp == -1) X msg_perror_fatal ("Cannot read utmp"); X nutmp /= sizeof (struct utmp); X } X alarm ((unsigned) ALARM_INTERVAL); X signal (SIGALRM, read_utmp); X} X X/* `name' has the form "user@mailbox-offset\n". Check whether "user" is X logged on; if so, try to notify them of the new mail. */ X Xvoid Xmail_for (name) X char *name; X{ X struct utmp *utp; X char *cp; X off_t offset; X X cp = strchr (name, '@'); X if (cp == NULL) X { X fprintf (stderr, "%s: Invalid message: %s\n", program_name, name); X return; X } X *cp++ = '\0'; X offset = atol (cp); X utp = &utmp[nutmp]; X while (--utp >= utmp) X { X if (!strncmp (utp->ut_user, name, sizeof (utmp[0].ut_user))) X notify (utp, offset); X } X} X X/* The carriage return character needed for the terminal being notified; X it will be the null string if the terminal driver or the terminal X is supplying a carriage return automatically with each newline. */ Xstatic char *cr; X X/* If the user described in `utp' is logged on and "biff y", notify them X of the new mail in their system mailbox at offset `offset'. */ X Xvoid Xnotify (utp, offset) X struct utmp *utp; X off_t offset; X{ X static char tty[20] = "/dev/"; X struct termio termio; X FILE *tp; X char name[sizeof (utmp[0].ut_user) + 1]; X struct stat stats; X int i; X X strncpy (tty + 5, utp->ut_line, sizeof (utp->ut_line)); X while ((i = stat (tty, &stats)) == -1 && errno == EINTR) X /* Do nothing. */ ; X if (i == -1 || !(stats.st_mode & S_IEXEC)) X return; X switch (fork ()) X { X case -1: X msg_perror_fatal ("Cannot fork"); X case 0: /* Child. */ X break; X default: /* Parent. */ X return; X } X signal (SIGALRM, SIG_DFL); X alarm ((unsigned) 30); X tp = fopen (tty, "w"); X if (tp == NULL) X _exit (1); X ioctl (fileno (tp), TCGETA, &termio); X cr = (termio.c_oflag & OPOST) && (termio.c_oflag & ONLCR) X || (termio.c_oflag & ONLRET) ? "" : "\r"; X strncpy (name, utp->ut_user, sizeof (utp->ut_user)); X name[sizeof (name) - 1] = '\0'; X fprintf (tp, "%s\n\007New mail for %s@%.*s\007 has arrived:%s\n----%s\n", X cr, name, sizeof (hostname), hostname, cr, cr); X summarize_new_mail (tp, name, offset); X fclose (tp); X _exit (0); X} X X/* Print the first 7 lines or 560 characters (whichever comes first) of X the new mail message that starts at byte `offset' in the system X mailbox for user `name' to stream `tp'. Skip header lines other than X From, Subject, [To, and Date]. */ X Xvoid Xsummarize_new_mail (tp, name, offset) X FILE *tp; X char *name; X off_t offset; X{ X char *cp; X FILE *fi; X int linecnt; X int charcnt; X int inheader; X char line[BUFSIZ]; X X cp = xmalloc (sizeof (SYSMAILDIR) + strlen (name) + 2); X sprintf (cp, "%s/%s", SYSMAILDIR, name); X fi = fopen (cp, "r"); X free (cp); X if (fi == NULL) X return; X if (fseek (fi, offset, L_SET)) X return; X linecnt = 7; X charcnt = 560; X inheader = 1; X X while (fgets (line, sizeof (line), fi) != NULL) X { X if (inheader) X { X if (line[0] == '\n') X { X inheader = 0; X#ifdef JOIN_HEADER_AND_TEXT X continue; X#endif X } X else if (line[0] == ' ' || line[0] == '\t' X || strncmp (line, "From:", 5) X && strncmp (line, "Subject:", 8)) X /* Skip header continuation lines and non-essential header lines. */ X continue; X } X if (linecnt <= 0 || charcnt <= 0) X { X fprintf (tp, "...more...%s\n", cr); X return; X } X cp = strchr (line, '\n'); X if (cp) X *cp = '\0'; X fprintf (tp, "%s%s\n", line, cr); X charcnt -= strlen (line); X linecnt--; X } X fprintf (tp, "----%s\n", cr); X} X X/* Simulate the BSD gethostname(2) system call on System V. */ X Xint Xgethostname (name, length) X char *name; X int length; X{ X struct utsname uts; X X if (uname (&uts) < 0) X return -1; X strncpy (name, uts.nodename, length); X return 0; X} X Xstatic void Xmemory_out () X{ X fprintf (stderr, "%s: Virtual memory exhausted\n", program_name); X exit (1); X} X X/* Allocate `n' bytes of memory dynamically, with error checking. */ X Xchar * Xxmalloc (n) X unsigned n; X{ X char *p; X X p = malloc (n); X if (p == 0) X memory_out (); X return p; X} X X/* Change the size of an allocated block of memory `p' to `n' bytes, X with error checking. X If `p' is NULL, run xmalloc. X If `n' is 0, run free and return NULL. */ X Xchar * Xxrealloc (p, n) X char *p; X unsigned n; X{ X if (p == 0) X return xmalloc (n); X if (n == 0) X { X free (p); X return 0; X } X p = realloc (p, n); X if (p == 0) X memory_out (); X return p; X} X X/* ANSI C function. */ X Xchar * Xstrerror (n) X int n; X{ X extern char *sys_errlist[]; X extern int sys_nerr; X X return n >= 0 && n < sys_nerr ? sys_errlist[n] : "Unknown error"; X} X X/* Print "program_name: str_and_optional_args: perror_message" on stderr, X then exit with error status. */ X/* VARARGS */ Xvoid Xmsg_perror_fatal (str, va_alist) X char *str; X va_dcl X{ X va_list args; X extern int errno; X int save_errno; X X save_errno = errno; X fprintf (stderr, "%s: ", program_name); X va_start (args); X vfprintf (stderr, str, args); X va_end (args); X fprintf (stderr, ": %s\n", strerror (save_errno)); X exit (1); X} X X/* Open `path' for reading as a mode 0600 FIFO, creating it if necessary. X Return the file descriptor. */ X Xint Xopen_fifo (path) X char *path; X{ X int fifd; X X if (mknod (path, 010600, 0) == -1 && errno != EEXIST) X msg_perror_fatal ("Cannot create FIFO %s", path); X if (chmod (path, 0600)) X msg_perror_fatal ("Cannot change mode of FIFO %s", path); X while ((fifd = open (path, O_RDONLY | O_TRUNC)) == -1 && errno == EINTR) X /* Do nothing. */ ; X if (fifd == -1) X msg_perror_fatal ("Cannot open FIFO %s for reading", path); X return fifd; X} X X/* Return nonzero if there is another process already reading `fifo'. X If there isn't, open will fail with EPIPE (write on broken pipe). */ X Xint Xdaemon_running (fifo) X char *fifo; X{ X int fifd; X X fifd = open (fifo, O_WRONLY | O_NDELAY); X if (fifd == -1) X return 0; X else X { X close (fifd); X return 1; X } X} END_OF_FILE if test 11659 -ne `wc -c <'comsat.c'`; then echo shar: \"'comsat.c'\" unpacked with wrong size! fi # end of 'comsat.c' fi if test -f 'biff.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'biff.c'\" else echo shar: Extracting \"'biff.c'\" \(2171 characters\) sed "s/^X//" >'biff.c' <<'END_OF_FILE' X/* biff - accept or refuse new mail messages from comsat X X Usage: biff [yn] X X David MacKenzie X public domain X Latest revision: 08/19/89 */ X X#include X#include X#include X#include X Xchar *ttyname (); X Xchar *any_ttyname (); Xvoid msg_perror_fatal (); Xvoid usage (); X Xchar *program_name; X Xint Xmain (argc, argv) X int argc; X char **argv; X{ X struct stat stats; X char *tty; X X program_name = argv[0]; X tty = any_ttyname (); X if (tty == NULL) X { X fprintf (stderr, "%s: Not connected to a terminal\n", argv[0]); X exit (1); X } X if (stat (tty, &stats) == -1) X msg_perror_fatal ("Cannot stat %s", tty); X switch (argc) X { X case 1: X printf ("is %c\n", (stats.st_mode & S_IEXEC) ? 'y' : 'n'); X break; X case 2: X if (*argv[1] == '-') X ++argv[1]; /* Allow '-y' or 'y'. */ X switch (*argv[1]) X { X case 'y': X if (chmod (tty, stats.st_mode | S_IEXEC) == -1) X msg_perror_fatal ("Cannot change mode of %s", tty); X break; X case 'n': X if (chmod (tty, stats.st_mode & ~S_IEXEC) == -1) X msg_perror_fatal ("Cannot change mode of %s", tty); X break; X default: X usage (); X } X break; X default: X usage (); X } X exit (0); X /* NOTREACHED */ X} X Xchar * Xany_ttyname () X{ X char *tty; X X tty = ttyname (2); X if (tty) X return tty; X tty = ttyname (1); X if (tty) X return tty; X tty = ttyname (0); X if (tty) X return tty; X return NULL; X} X X/* ANSI C function. */ X Xchar * Xstrerror (n) X int n; X{ X extern char *sys_errlist[]; X extern int sys_nerr; X X return n >= 0 && n < sys_nerr ? sys_errlist[n] : "Unknown error"; X} X X/* Print "program_name: str_and_optional_args: perror_message" on stderr, X then exit with error status. */ X/* VARARGS */ Xvoid Xmsg_perror_fatal (str, va_alist) X char *str; X va_dcl X{ X va_list args; X extern int errno; X int save_errno; X X save_errno = errno; X fprintf (stderr, "%s: ", program_name); X va_start (args); X vfprintf (stderr, str, args); X va_end (args); X fprintf (stderr, ": %s\n", strerror (save_errno)); X exit (1); X} X Xvoid Xusage () X{ X fprintf (stderr, "Usage: %s [yn]\n", program_name); X exit (1); X} END_OF_FILE if test 2171 -ne `wc -c <'biff.c'`; then echo shar: \"'biff.c'\" unpacked with wrong size! fi # end of 'biff.c' fi if test -f 'lmail.diffs' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'lmail.diffs'\" else echo shar: Extracting \"'lmail.diffs'\" \(1782 characters\) sed "s/^X//" >'lmail.diffs' <<'END_OF_FILE' X*** lmail.c.dist Tue Aug 15 03:34:47 1989 X--- lmail.c Wed Aug 16 02:30:06 1989 X*************** X*** 527,532 **** X--- 527,533 ---- X return(8); X } X } else { X+ long where = -1, ftell (); X setgid(MAILGID); X setuid(pwd_mailman->pw_uid); /* give up root uid */ X sprintf(temp, "%s%s", MAIL_DIR, dest); X*************** X*** 535,542 **** X if ((outfile = fopen(temp, "a")) == NULL) { X (void) fprintf(stderr, "** Can't open user mail file %s\n", temp); X status = 5; X! } else X copy(outfile, in_fd, author); X chown(temp, (int)pwd->pw_uid, MAILGID); X if (fclose(outfile)) { X (void) fprintf(stderr, "** Could not close mail file %s\n", temp); X--- 536,545 ---- X if ((outfile = fopen(temp, "a")) == NULL) { X (void) fprintf(stderr, "** Can't open user mail file %s\n", temp); X status = 5; X! } else { X! where = ftell(outfile); X copy(outfile, in_fd, author); X+ } X chown(temp, (int)pwd->pw_uid, MAILGID); X if (fclose(outfile)) { X (void) fprintf(stderr, "** Could not close mail file %s\n", temp); X*************** X*** 543,548 **** X--- 546,553 ---- X status = 7; X } X unlock(temp); X+ if (where != -1) X+ biff(dest, where); X _exit(status); X } X } X*************** X*** 626,629 **** X--- 631,651 ---- X return; X } X X+ #include X+ X+ /* Do our best to biff the recipient. Errors don't bother us. */ X X+ biff (user, offset) X+ char *user; X+ long offset; X+ { X+ int fd; X+ char biffbuf[30]; X+ X+ fd = open ("/etc/comsat.fifo", O_WRONLY | O_NDELAY); X+ if (fd == -1) X+ return; X+ sprintf (biffbuf, "%s@%ld\n", user, offset); X+ write (fd, biffbuf, strlen (biffbuf)); X+ close (fd); X+ } END_OF_FILE if test 1782 -ne `wc -c <'lmail.diffs'`; then echo shar: \"'lmail.diffs'\" unpacked with wrong size! fi # end of 'lmail.diffs' fi if test -f 'mailwrap.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'mailwrap.c'\" else echo shar: Extracting \"'mailwrap.c'\" \(1624 characters\) sed "s/^X//" >'mailwrap.c' <<'END_OF_FILE' X/* Local Mail Interceptor to handle interface to comsat */ X/* Args for local mail are [ -r sender ] parties ... */ X/* Jim Mattson */ X X#include X#include X#include X#include X X#define FIFO "/usr/tmp/comsat.fifo" X#define MAILDIR "/usr/spool/mail" X#define MAILER "/usr/lib/mail/mail.orig" X Xmain(argc, argv) Xint argc; Xchar **argv; X{ X off_t *offsets; /* Mailbox offsets before delivery */ X struct stat stbuf; /* For stat'ing mailboxes */ X char **av = argv; /* Old argv */ X int fd; /* file descriptor for FIFO */ X FILE *fp; /* File pointer for FIFO */ X int rc; /* Return Code from local mailer */ X int i; X X /* Get rid of program name and [ -r sender ] */ X argc--; X argv++; X if(!strcmp(argv[0], "-r")) { X argc -= 2; X argv += 2; X } X if(argc == 0) X exit(0); X X if((offsets = (off_t *)malloc(sizeof(off_t) * argc)) == NULL) X bailout(av); X X /* Get mailbox offsets */ X if(chdir(MAILDIR) < 0) X bailout(av); X for(i = 0; i < argc; i++) { X if(stat(argv[i], &stbuf) < 0) X offsets[i] = 0; X else X offsets[i] = stbuf.st_size; X } X X /* Send mail and wait for exit code */ X switch(fork()) { X case -1: X case 0: X bailout(av); /* No return */ X default: X wait(&rc); X } X /* Send messages to comsat if it's listening */ X if((fd = open(FIFO, O_WRONLY | O_NDELAY)) >= 0) { X if((fp = fdopen(fd, "w")) != NULL) { X for(i = 0; i < argc; i++) X fprintf(fp, "%s@%d\n", argv[i], offsets[i]); X fclose(fp); X } X close(fd); X } X /* Exit with local mailer's return code */ X exit(rc >> 8); X} X Xbailout(argv) Xchar **argv; X{ X argv[0] = MAILER; X execv(MAILER, argv); X _exit(0); X} END_OF_FILE if test 1624 -ne `wc -c <'mailwrap.c'`; then echo shar: \"'mailwrap.c'\" unpacked with wrong size! fi # end of 'mailwrap.c' fi echo shar: End of shell archive. exit 0