[alt.sources] a .forward-triggered biff

pkern@csri.toronto.edu (pkern) (04/20/89)

Here's code for a biff which was inspired by vacation(1).
It's only been tested on SUNs. Hope someone finds it useful.
pk.

----------
#! /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
# If this archive is complete, you will see the following message at the end:
#		"End of shell archive."
#
# Contents:
#   README biff.c biff.l
#
# Wrapped by pkern@csri.toronto.edu on Wed Apr 19 19:23:10 1989
#
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(3245 characters\)
sed "s/^X//" >README <<'END_OF_README'
XA biff for diskless systems.
X----------------------------
XThis is a version of biff designed to work for users on diskless workstations.
XIts main feature is that it is triggered by the user's .forward file in much
Xthe same way as vacation(1).  Since it doesn't need the comsat/biff service,
Xthis version could also be used on systems where mail is handled locally
X
XOn diskless systems, a file called .bwhere in the user's home directory is used
Xto keep track of biff commands. On ordinary systems, no such file is needed.
X(This is decided at compile time. See the note about -DNFS in the Makefile.)
X
XThe biff for diskless systems has 3 modes:
Xmode 1: user-invoked (the familiar usage)
X	"biff y" - sets the user-exec bit on the user's tty
X		 - makes sure the user's .forward includes "| biff".
X		 - adds the ttyname, the hostname, and the time to
X		   the user's .bwhere file, creating it if necessary.
X
X	"biff n" - resets the user-exec bit on the user's tty
X		 - makes sure the user's .forward includes "| biff".
X		 - if it can find a utmp entry in the user's .bwhere file
X		   which matches the current environment, the entry is deleted.
X		 
X	"biff"	- checks tty status
X		- if user's tty is executable, biff reports "is y"
X		- if the tty is *not* executable, biff reports "is n".
X		- updates the user's .bwhere file accordingly.
X	
Xmode 2: mail-invoked (from the pipe-address in $HOME/.forward)
X    When new mail arrives, it auto-magically sends the message
Xon stdin to the "| biff" address. In this mode, biff grabs the 4
Xstandard header lines and up to 4 lines of actual text to make up
Xthe familiar biff-message. The biff-message is then sent, via pipe
Xand rsh, to the stdin of biffs started on each host listed in the
Xuser's .bwhere.  Eg. something like ...
X	for rhost in $.bwhere
X	do
X		 cat biff-message | rsh $rhost biff
X	done
X
Xmode 3: self-invoked (via rsh from the mail server)
X    Called via rsh, the user's ttys are located by reading /etc/utmp
X(not .bwhere since /etc/utmp should be more accurate) and the text
Xfound on stdin is sent to all the user's executable tty's (this is
Xpossible even with "mesg n" since the biff process should be owned
Xby the user). If the user can't be found (eg. the user logged out without
Xusing "biff n") then all the .bwhere entries for that host are deleted.
X
XOn ordinary systems (ie. systems which ahndle their onw mail), the .bwhere
Xfile is not needed so modes 2 and 3 are combined into one mode and .bwhere
Xis not used.
X
XThe use of a .bwhere on diskless systems isn't a very elegant solution since
Xit means having to maintain one more file, but maintaining a list seems to be
Xmuch easier (system load-wise and program code-wise) than having to hunt down
Xusers across a possibly crowded network.
XThe utmp format was chosen for .bwhere info so that who(1) could be used
Xto read the file contents. Also, since utmp data is somewhat binary, it might
Xdiscourage naive users from trying to making changes just by simply using any
Xtext editor and possibly messing up the data (yeah, sure :-).
X----
X
XIf there are any problems or if something hasn't
Xbeen covered, please don't hesitate to contact me.
XPaul Kern	pkern@utcsri.uucp	pkern@csri.toronto.edu
X..!uunet!utai!utcsri!pkern	..!attcan!utzoo!utcsri!pkern
END_OF_README
if test 3245 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f biff.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"biff.c\"
else
echo shar: Extracting \"biff.c\" \(11292 characters\)
sed "s/^X//" >biff.c <<'END_OF_biff.c'
X/*
X * biff via the .forward file (eg. vacation(1)).
X *	usage:  biff [yn]
X *
X * 3 modes:
X *	user-invoked	- chmod tty
X *			- check or alter ~/.forward and ~/.bwhere
X *	mail-invoked (ie. from piped-address in ~/.forward)
X *			- compose message from mail-text on stdin
X *			- pipe it to biff on hosts listed in ~/.bwhere
X *	self-invoked (ie. via rsh call from mail-invoked mode)
X *			- find user by reading /etc/utmp
X *			- send message to tty if tty is executable
X * files used:
X *	~/.forward	- list of forwarding addresses
X *	~/.bwhere	- utmp data, a log for "biff y" requests
X *
X * Copyright (c) 1989 University of Toronto. All rights reserved.
X * Anyone may use or copy this software, except that it may not be
X * sold for profit, that this copyright notice remain intact, and that
X * credit is given where it is due. The University of Toronto and the
X * author make no warranty and accept no liability for this software.
X *
X * $Header: biff.c,v 1.18 89/03/01 22:33:59 pkern Exp $
X */
X#include <stdio.h>
X#include <sys/file.h>
X#include <utmp.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <pwd.h>
X#include <signal.h>
X
X#define NSIZE	32
X#define LSIZE	256
X#define BSIZE	4096
X
X#define UTMP	"/etc/utmp"
X
X#ifndef BIFF
X#define BIFF	"/usr/local/biff"
X#endif
X
Xchar *program;
Xstruct utmp ut;
Xstruct stat st;
Xint uL, uN, uH, utsz;
Xchar user[NSIZE], target[LSIZE];
X
Xlong now;
X#define DAY	(long)(24*60*60)
Xstruct utmp *utp;
Xchar bwr[LSIZE];
X
Xint ynflag=0;	/* becomes 1 if "biff y" or -1 if "biff n" */
X
X	/* if flag then ... */
Xint fflag=0;	/* ... called from .forward */
Xint rflag=0;	/* ... called via rsh for delivery */
Xint t0flag=0;	/* ... stdin is from a tty */
Xint t1flag=0;	/* ... stdout is to a tty */
Xint t2flag=0;	/* ... stderr is to a tty */
Xint errflag=0;
X
Xextern int errno;
Xextern char *sys_errlist[];
X#ifdef debug
XFILE *dfp;
X#endif
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	int fd, i, n, uid;
X	FILE *pp0, *pp1;
X	struct passwd *pw;
X	char *lp, *op, *p, fc=0;
X	char *index(), *rindex();
X	char lbuf[LSIZE], cbuf[LSIZE];
X	char obuf[BSIZE], ttbuf[NSIZE];
X#ifdef debug
X	int pid, ppid, euid;
X#endif
X
X	program = BIFF;
X
X	t0flag = isatty(0);
X	t1flag = isatty(1);
X	t2flag = isatty(2);
X
X	/* let's not assume utmp sizes */
X	uN = sizeof(ut.ut_name);
X	uL = sizeof(ut.ut_line);
X	uH = sizeof(ut.ut_host);
X	utsz = sizeof(ut);
X
X	/*
X	 * if there are args then assume user-invoked
X	 * else test the first char from stdin
X	 *	if first char is 'F' assume .forward-invoked
X	 *	else if char is ^G assume biff-invoked
X	 */
X	if (argc > 1)
X		switch(argv[1][0]) {
X		case 'y': ynflag = 1; break;	/* "biff y" */
X		case 'n': ynflag = -1; break;	/* "biff n" */
X		default: errflag++; break;
X		}
X	else if (!t0flag && !t1flag && !t2flag && read(0, &fc, 1) > 0)
X		switch (fc) {	/* test first char from stdin */
X		case 'F': fflag++; break;	/* From ... */
X		case 007: rflag++; break;	/* ^G^M^JNew mail ... */
X		default: errflag++; break;
X		}
X
X	p = (p = rindex(program, '/')) ? p+1 : program;
X	if (errflag) {
X		fprintf(stderr, "usage: %s [y] [n]\n", p);
X		exit(1);
X	}
X
X	uid = getuid();
X	if ((pw = getpwuid(uid)) == NULL) exit(errno);
X	strncpy(user, pw->pw_name, NSIZE);
X	sprintf(bwr, "%s/.bwhere", pw->pw_dir);
X	now = (long)time(0);
X	if (gethostname(target, LSIZE) < 0) {
X		perror(p);
X		exit(1);
X	}
X#ifdef debug
Xpid = getpid();
Xppid = getppid();
Xeuid = geteuid();
Xdfp = fopen(DBTMP, "a");
Xfprintf(dfp, "%s (%d, %d) (%d, %d)\n", target, uid, euid, pid, ppid);
Xfprintf(dfp, "%d: fc:%o yn:%d r:%d f:%d\n", pid, fc, ynflag, rflag, fflag);
Xfclose(dfp);
X#endif
X
X	if (ynflag || !(ynflag || rflag || fflag)) {	/* normal */
X		char fwd[LSIZE];
X
X		if (!t2flag) {
X			fprintf(stderr, "Where are you?\n");
X			exit(1);
X		}
X
X	/*
X	 * normal biff activity:
X	 *	check/change owner's exec bit of user's tty
X	 */
X		/* check mode for user's tty*/
X		strcpy(ttbuf, ttyname(1));
X		stat(ttbuf, &st);
X		if (ynflag) {
X			if (ynflag > 0) /* y : set exec bit */
X				st.st_mode |= S_IEXEC;
X			else 		/* n : reset exec bit */
X				st.st_mode &= ~S_IEXEC;
X			if (chmod(ttbuf, st.st_mode) < 0) {
X				perror(ttbuf);
X				exit(1);
X			}
X		}
X		else		/* print current status */
X			printf("is %c\n", (st.st_mode & S_IEXEC) ? 'y' : 'n');
X
X		/* rest can be done in background */
X		if (fork() > 0) exit(0);
X
X		signal(SIGHUP, SIG_IGN);
X
X	/*
X	 * check out .forward :
X	 * if .forward exists
X	 *	if it doesn't contain "| biff" {
X	 *		mv .forward .forward.orig
X	 *		cat .forward.orig  `, "| biff"' > .forward
X	 *	}
X	 * else
X	 *	make a new .forward with `login, "| biff"'
X	 */
X
X		sprintf(fwd, "%s/.forward", pw->pw_dir);
X		/* set possible new .forward contents ... */
X		sprintf(cbuf, "%s, \"| %s\"\n", user, program);
X		n = 0;
X
X		if (access(fwd, F_OK) == 0) { /* .forward exists */
X			/* does it already have "| biff" ... ? */
X
X			/* load .forward contents */
X			if ((fd = open(fwd, O_RDONLY)) < 0
X			    || (n = read(fd, lbuf, LSIZE)) < 0) {
X				perror(fwd);
X				exit(1);
X			}
X			close(fd);
X
X			/* hunt for "| biff" */
X			lp = lbuf; n = strlen(program);
X			while (p = index(lp, '|')) {
X				do p++; while(*p == ' ' || *p == '\t');
X				if (strncmp(program, p, n) == 0)
X					break;
X				lp = p;
X			}
X	
X			n = (p != NULL);
X
X			if (!n) {	/* couldn't find "| biff" */
X				/* mv .forward .forward.orig */
X				sprintf(cbuf, "%s.orig", fwd);
X				if (rename(fwd, cbuf) < 0) {
X					perror(cbuf);
X					exit(1);
X				}
X
X				/* set new .forward contents 
X				 * to include old contents */
X				sprintf(cbuf,
X					"%.*s, \"| %s\"\n",
X					strlen(lbuf)-1, lbuf, program);
X			}
X		}
X
X		if (!n) {	/* no .forward with "| biff" */
X			/* make a new .forward */
X			if ((fd=open(fwd,O_WRONLY|O_CREAT,0644)) < 0) {
X				perror(fwd);
X				exit(1);
X			}
X			write(fd, cbuf, strlen(cbuf));
X			close(fd);
X		}
X
X#ifdef NFS
X	/*
X	 * check out .bwhere :
X	 * load .bwhere's utmp contents into buffer
X	 * if tty is -x then delete any matching utmp entry
X	 * else if tty is +x and there's no entry for this tty
X	 *	then make one.
X	 * write buffer back to .bwhere
X	 */
X		lp = rindex(ttbuf, '/') + 1;  /* short ttyname */
X
X		b_load();	/* get .bwhere contents */
X		if (st.st_mode & S_IEXEC)	/* wants biff on */
X			b_add(target, lp);	/* add new location */
X		else		/* wants biff off */
X			b_del(target, lp);	/* delete location */
X		b_save();
X#endif
X		exit(0);
X	}
X
Xbiffit:
X	if (rflag) {
X		/* - read biff mesg on stdin from "parent."
X		 * - blast it to all user's executable ttys. */
X		int ufd;
X
X		op = obuf;
X		if (rflag < 0)	/* message already in buffer */
X			n = strlen(op);
X		else {		/* get the message */
X			*op++ = fc;
X			if ((n = read(0, op, BSIZE)) < 0)
X				exit(errno);
X			n++;
X			if (fork())
X				exit(0);
X		}
X
X		if ((ufd = open(UTMP, O_RDONLY)) < 0)
X			exit(errno);
X
X		lp = lbuf; *lp = '\0';
X		while (read(ufd, &ut, utsz) > 0) {
X			if (strncmp(ut.ut_name, user, uN) == 0) {
X				sprintf(lp, "/dev/%.*s", uL, ut.ut_line);
X				if (stat(lp, &st) < 0
X				    || st.st_uid != uid
X				    || !(st.st_mode & S_IEXEC)
X				    || (fd = open(lp, O_WRONLY)) < 0) {
X					*lp = '\0';
X					continue;
X				}
X#ifdef debug
Xdfp = fopen(DBTMP, "a");
Xfprintf(dfp, "%d: blasting %s,%s.\n", pid, target, lp);
Xfclose(dfp);
X#endif
X				write(fd, obuf, n); /* fire away */
X				close(fd);
X			}
X		}
X#ifdef NFS
X		if (lbuf[0] == '\0') { /* couldn't find user in utmp */
X			/* user probably logged out without a 'biff n'
X			 * delete any 'target' entries from .bwhere */
X			b_load();
X			b_del(target, "");
X			b_save();
X		}
X#endif
X		exit(0);
X	}
X
X	/* if (fflag) ... */
X	/* assemble the familiar biff message */
X
X#ifdef NFS
X	if (getdomainname(target, LSIZE) < 0)
X		exit(errno);
X#else
X	if (gethostname(target, LSIZE) < 0)
X		exit(errno);
X#endif
X
X	sprintf(obuf,
X		"\007\r\nNew mail for %s@%s has arrived:\r\n----\r\n",
X		user, target);
X	op = obuf + strlen(obuf);
X
X	lp = lbuf;
X	*lp++ = fc; fc = 0;	/* don't forget the first char */
X
X	do {			/* round up the usual headers */
X		if ((lp = fgets(lp, LSIZE, stdin)) == NULL)
X			break;
X		if ((n = strlen(lp))==1
X		    || strncmp(lp, "Date: ", 6)==0
X		    || strncmp(lp, "From: ", 6)==0
X		    || strncmp(lp, "Subject: ", 9)==0
X		    || strncmp(lp, "To: ", 4)==0
X		    || fc == ',' ) {
X			sprintf(op, "%s\r", lp);
X			op += n+1;
X			/* fc flags "To: " continuations */
X			fc = (*lp=='T' || fc==',') * lp[n-2];
X		}
X		lp = lbuf;
X	} while(n>1);
X
X	for(n=3; n; n--) {	/* include first 4 lines of message */
X		if ((lp = fgets(lbuf, LSIZE, stdin)) == NULL)
X			break;
X		sprintf(op, "%s\r", lp);
X		op += strlen(lp)+1;
X	}
X	if ( !(n || (lp = fgets(lbuf, LSIZE, stdin)) == NULL) )
X		strcpy(op, "...more...\r\n");
X	op += 12;
X	
X	/* don't keep mail waiting so fork off and die
X	 * ... the rest can be done in background. */
X	if (fork() > 0) exit(0);
X
X	/* make local deliveries (if any) */
X
X#ifdef NFS
X	b_load();
X	
X	while(b_next(&ut) > 0) {
X#ifdef debug
Xdfp = fopen(DBTMP, "a");
Xfprintf(dfp, "%d: next %s,%s\n", pid, ut.ut_host, ut.ut_line);
Xfclose(dfp);
X#endif
X		strncpy(lp = ttbuf, ut.ut_host, uH);
X		ttbuf[uH] = '\0';
X		/* compare hostname with those already called.
X		 * if hostname found then don't call again,
X		 * else save hostname.  */
X		p = lbuf;
X		while ((p - lbuf) < LSIZE && *p && strncmp(p, lp, uH))
X			p += uH;
X		if (*p)
X			continue;
X		strcpy(p, lp);
X			
X		sprintf(cbuf, "exec rsh %s exec %s", lp, program);
X		if ((pp1 = popen(cbuf, "w")) == NULL) {
X			continue;
X		}
X#ifdef debug
Xdfp = fopen(DBTMP, "a");
Xfprintf(dfp, "%d: %s\n", pid, cbuf);
X		if (fputs(obuf, pp1) == EOF) {
Xfprintf(dfp, "%d: %s\n", pid, sys_errlist[errno]);
X		}
Xfclose(dfp);
X#else
X		fputs(obuf, pp1);
X#endif
X		pclose(pp1);
X	}
X#else /* NFS */
X	rflag = -1;
X	goto biffit;
X#endif /* NFS */
X}
X
X#ifdef NFS
X
X/* Jeez! This is getting compilcated! */
X
Xchar b_buf[BSIZE];
Xint b_nx = 0, b_size = 0;
X
X/*
X * load contents from .bwhere into buffer
X */
Xb_load()
X{
X	int fd; 
X	struct stat st;
X
X	if ((fd = open(bwr, O_RDONLY)) > 0) {
X		fstat(fd, &st);
X		if (st.st_size == 0L)
X			return(0);
X		if ((b_size = read(fd, b_buf, st.st_size)) < 0) {
X			perror(bwr);
X			exit(1);
X		}
X		close(fd);
X	}
X	b_nx = -utsz;
X	return(b_size);
X}
X
X/*
X * get next .bwhere utmp entry
X */
Xb_next(up)
Xstruct utmp *up;
X{
X	b_nx += utsz;
X	if (b_nx < b_size) {
X		bcopy(&b_buf[b_nx], (char *)up, utsz);
X		return(1);
X	}
X	return(0);
X}
X
X/*
X * add a utmp entry to .bwhere
X */
Xb_add(host, tty)
Xchar *host, *tty;
X{
X	struct utmp *up;
X
X	b_del(host, tty);	/* first clean out any duplicates */
X
X	up = (struct utmp *) &b_buf[b_size];
X	strncpy(up->ut_line, tty, uL);
X	strncpy(up->ut_host, host, uH);
X	strncpy(up->ut_name, user, uN);
X	up->ut_time = now;
X
X	b_size += utsz;
X	return(b_size);
X}
X
X/*
X * delete any matching entries from .bwhere
X * tty == "" means delete all entries for 'target' host.
X */
Xb_del(host, tty)
Xchar *host, *tty;
X{
X	int i, j, n = 0;
X	struct utmp *np;
X
X	if (!b_size)
X		return(0);
X	np = (struct utmp *)b_buf;
X	for (i = j = 0; i < b_size/utsz; i++) {
X		if (strncmp(np[i].ut_host, host, uH) == 0
X		    && (!*tty || strncmp(np[i].ut_line, tty, uL) == 0))
X			continue;
X		if (i != j)
X			bcopy((char *)&np[i], (char *)&np[j], utsz);
X		j++;
X	}
X	b_size = j * utsz;
X	return(b_size);
X}
X
X/*
X * save buffered utmp data to .bwhere
X */
Xb_save()
X{
X	int fd;
X
X	chmod(bwr, 0644);
X	fd = open(bwr, O_WRONLY|O_CREAT|O_TRUNC, 0644);
X	if (fd < 0 || (b_size && write(fd, b_buf, b_size) < 0)) {
X		perror(bwr);
X		exit(1);
X	}
X	close(fd);
X	chmod(bwr, 0444);	/* try to keep others from writing */
X}
X#endif /* NFS */
END_OF_biff.c
if test 11292 -ne `wc -c <biff.c`; then
    echo shar: \"biff.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f biff.l -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"biff.l\"
else
echo shar: Extracting \"biff.l\" \(2408 characters\)
sed "s/^X//" >biff.l <<'END_OF_biff.l'
X.TH BIFF L "Apr 1989" local
X.UC
X.SH NAME
Xbiff \- be notified if mail arrives and who it is from
X.SH SYNOPSIS
X.B biff
X[
X.B yn
X]
X.SH DESCRIPTION
X.I Biff
Xinforms the system whether or not
Xyou want to be notified when mail arrives
Xduring the current terminal session.
XThe command
X.IP
X.B "biff y"
X.LP
Xenables notification; the command
X.IP
X.B "biff n"
X.LP
Xdisables it.
XWhen mail notification is enabled, the header and first few lines of
Xthe message will be printed on your screen whenever mail arrives.
XA ``biff y'' command is often included in the file
X.I \&.login
Xor
X.I \&.profile
Xto be executed at each login.
XA ``biff n'' command should be included in the file
X.I \&.logout
Xor, for sh(1) users, the command "trap 'biff n' 0"
Xshould be included in the file
X.I \&.profile
Xto be executed at each logout.
X.PP
X.I Biff
Xoperates asynchronously.
XFor synchronous notification use the MAIL variable of
X.IR sh (1)
Xor the
X.I mail
Xvariable of
X.IR csh (1).
X.PP
XThis version of
X.I biff 
Xworks by including a pipe to itself as a forwarding address
Xin the user's
X.I \&.forward
Xfile.
XIf .forward already has the pipe-to-biff address then it's left as is.
XIf .forward does not exist, a new one is created containing
Xthe pipe and the user's login as forwarding addresses.
XThe user's .forward file will then look something like
X.IP
Xname, "| biff"
X.LP
XIf .forward exists but doesn't contain the pipe address,
Xthe pipe will be added after the original contents are moved to
X.I \&.forward.orig.
XThe pipe address causes
X.I biff
Xto be executed each time new mail arrives (see
X.I vacation(1)
Xfor a vaguely similar usage of the .forward file).
X.PP
XOn distributed systems (ie. servers with diskless clients),
X.I biff
Xcreates a
X.I utmp(5)
Xformat file called
X.I .bwhere
Xin the user's home directory.
XThis file is used to record when and where the user might
Xhave used the "biff y" command.
XWhen new mail arrives,
X.I biff
Xreads the .bwhere file
Xto discover on which hosts the user might be found
X(users can view the file by typing "who $HOME/.bwhere").
X.I Biff
Xthen calls itself via
X.I rsh(1)
Xon each of the recorded hosts to deliver the "New mail" message.
X.SH FILES
X $HOME/.forward
X $HOME/.forward.orig
X $HOME/.bwhere
X /etc/utmp
X.SH SEE ALSO
Xcsh(1),
Xrsh(1),
Xsh(1),
Xmail(1),
Xvacation(1),
Xwho(1),
Xutmp(5)
X.SH BUGS
XWell, no bugs yet but it's got lots of messy code and a few GOTOs :-).
X.SH CONTACT
Xpkern@utcsri.uucp or pkern@csri.toronto.edu
END_OF_biff.l
if test 2408 -ne `wc -c <biff.l`; then
    echo shar: \"biff.l\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0