[comp.sources.unix] v18i055: Remote biff/comsat

rsalz@uunet.uu.net (Rich Salz) (03/23/89)

Submitted-by: koreth@ssyx.UCSC.EDU (Steven Grimm)
Posting-number: Volume 18, Issue 55
Archive-name: biff-comsat

This is a replacement for the standard BSD "biff" and "comsat" programs,
which notify users when new mail arrives.  The old programs were very
limited; they would only notify a user if mail arrived in his mailbox on
the local host.  This version allows monitoring of other users' mailboxes,
even on remote hosts.  They run under inetd.

---
These are my opinions, which you can probably ignore if you want to.
Steven Grimm		Moderator, comp.{sources,binaries}.atari.st
koreth@ssyx.ucsc.edu	uunet!ucbvax!ucscc!ssyx!koreth

#!/bin/sh
# shar:	Shell Archiver  (v1.22)
#
#	Run the following text with /bin/sh to create:
#	  BUGS
#	  Makefile
#	  README
#	  biff.1
#	  biff.c
#	  comsat.8c
#	  comsat.c
#	  config.h
#
sed 's/^X//' << 'SHAR_EOF' > BUGS &&
XBUGS REMAINING/ADDED:
X---------------------
Xusernames are limited to 64 characters.
X
Xif a user logs off then back on in between two remote rollcalls, he will
Xstill get new mail notices requested during his previous login.  this may
Xbe a feature.
X
Xcomsat should be split into multiple source files.
X
Xon extremely active systems, roll may never be called.
X
Xgack() should time out.
X
Xgetservbyname() appears to be really slow, at least on Suns.  Yellow Pages
Xshould be outlawed.  YP is also responsible for the fact that biff and rbiff
Xneed to have different names.
X
Xin biff.c, addusers() and delusers() should be combined.
X
X
XBUGS FIXED FROM OLD VERSION:
X----------------------------
X2-line message bodies are handled correctly; the old comsat would print
X"...more..." even if there wasn't anything more.
X
Xblank lines in the message body aren't shown.
X
Xsince biff is now setuid-root, Sunview users can turn biff on and off in
Xspecific windows.
SHAR_EOF
chmod 0600 BUGS || echo "restore of BUGS fails"
sed 's/^X//' << 'SHAR_EOF' > Makefile &&
XCFLAGS = -O
XBINDIR = /usr/ucb		# this is where biff gets installed
XETCDIR = /usr/etc		# this is comsat's directory.
XMANDIR = /usr/man
X
Xall: comsat biff
X
Xcomsat: comsat.c config.h
X	$(CC) $(CFLAGS) comsat.c -o $@
X
X# Biff has to be setuid root, because it has to grab a secure internet port.
X# There are no calls to gets() or insecure strcpy() or bcopy() calls.
Xbiff: biff.c config.h
X	$(CC) $(CFLAGS) biff.c -o $@
X
Xinstallprogs: all
X	install -m 4755 -o root biff $(BINDIR)
X	install -m 755 -o root comsat $(ETCDIR)
X
Xinstallman:
X	install -m 644 -o root biff.1 $(MANDIR)/man1
X	install -m 644 -o root comsat.8c $(MANDIR)/man8
X
Xinstall: installprogs installman
X
Xclean:
X	/bin/rm -f biff comsat core
SHAR_EOF
chmod 0600 Makefile || echo "restore of Makefile fails"
sed 's/^X//' << 'SHAR_EOF' > README &&
XThis is a replacement for the standard BSD "biff" and "comsat"
Xprograms, which notify users when new mail arrives.  The old programs
Xwere very limited; they would only notify a user if mail arrived in his
Xmailbox on the local host.  This version allows monitoring of other
Xusers' mailboxes, even on remote hosts.
X
XTo install the programs, become root and do a make install.  That
Xwill install the manual pages, /usr/ucb/biff, and /usr/etc/comsat.
XYou will need to edit /etc/services and /etc/servers (or /etc/inetd.conf,
Xon some systems).  Add a TCP biff service to /etc/services:
X
Xrbiff		522/tcp
X
Xand in servers or inetd.conf, comment out the line that causes
Xinetd to run comsat.  In /etc/servers it looks like
X
Xcomsat  udp     /usr/etc/in.comsat
X
Xand in inetd.conf,
X
Xcomsat	dgram	udp	wait	root	/usr/etc/in.comsat	in.comsat
X
XAdd an entry to /etc/rc.local so that comsat starts at boot time:
X
Xif [ -f /usr/etc/comsat ]; then
X	/usr/etc/comsat;	(echo -n ' comsat')	>/dev/console
Xfi
X
XYou can remove /usr/etc/in.comsat if desired; it will no longer be
Xcalled.  Restart inetd (on some systems, sending it a HUP signal will
Xcause it to reload its database) and run /usr/etc/comsat.  You're
Xall set!
X
XIf you're installing this on your system, drop me a line.  I'm interested
Xto know how widely this catches on.  I'm not opposed to replacing biff
Xwith this for the next version of BSD (or SunOS), but I'd like to be
Xtold about it first.  Send me any suggestions or bug reports, too.
XEnjoy!
X
X---
XThese are my opinions, which you can probably ignore if you want to.
XSteven Grimm		Moderator, comp.{sources,binaries}.atari.st
Xkoreth@ssyx.ucsc.edu	uunet!ucbvax!ucscc!ssyx!koreth
SHAR_EOF
chmod 0600 README || echo "restore of README fails"
sed 's/^X//' << 'SHAR_EOF' > biff.1 &&
X.TH BIFF 1 "28 December 1988"
X.SH NAME
Xbiff \- watch for incoming mail
X.SH SYNOPSIS
X.B biff
X[ y|n|a|d [ [ user ] [ @host ] ... ] ]
X.SH DESCRIPTION
X.B biff
Xcontrols the mail arrival notification system.  With no arguments,
X.B biff
Xwill display the notification status for the current terminal.  The status
Xis `y' if notification is allowed, or `n' if it is not.
X.SH OPTIONS
X.LP
XThe first parameter can set the current terminal's notification status.  If
Xit is `y' or `n', notification is enabled or disabled, respectively.
X.LP
XBy default, the owner of the tty will only be notified of new mail arriving
Xin his own mailbox.  Additional parameters control monitoring of other
Xmailboxes, optionally on remote machines.  If a username is specified without
Xa hostname, it is assumed to be a user on the local host; if a hostname is
Xspecified without a username, the tty owner's name is assumed.  To monitor
Xanother account's mailbox, a user must be on a host listed in the remote host's
X.B hosts.equiv
Xfile and have the same account name as the remote account, or be listed in
Xthe other account's
X.B \&.rhosts
Xfile.
X.LP
XIf the first parameter is `y' or `a', any additional parameters are added to
Xthe list of other accounts being monitored.  If it is `n' or `d', they are
Xremoved from the list.  The `a' and `d' options allow addition and deletion
Xof other account names without affecting the terminal's notification state.
X.SH "SEE ALSO"
X.BR mail (1),
X.BR comsat (8C)
X.SH BUGS
XThere should be a way of displaying notifications on regular terminals without
Xdisturbing the user's screen display.  On Sun consoles (and possibly others),
Xusers can select the windows in which they wish to enable notification.
X.SH AUTHOR
XSteven Grimm (koreth@ssyx.ucsc.edu)
SHAR_EOF
chmod 0600 biff.1 || echo "restore of biff.1 fails"
sed 's/^X//' << 'SHAR_EOF' > biff.c &&
X/*
X** BIFF
X**
X** Monitor remote mailboxes.
X*/
X
X#include <stdio.h>
X#include <netdb.h>
X#include <ctype.h>
X#include <strings.h>
X#include <sys/types.h>
X#include <netinet/in.h>
X#include <sys/socket.h>
X#include <arpa/inet.h>
X#include <sys/stat.h>
X#include "config.h"
X
Xint tcp_port;
X
Xmain(argc, argv)
Xchar **argv;
X{
X	char code, *tty, *ttyname();
X	int fd, port;
X	struct sockaddr_in in;
X	struct servent *sent;
X	struct stat st;
X
X	if (! isatty(1))
X	{
X		fprintf(stderr, "Can't biff to a pipe/socket\n");
X		exit(-1);
X	}
X
X	tty = ttyname(1);
X	if (stat(tty, &st))
X	{
X		perror("statting tty");
X		exit(-1);
X	}
X
X	if (argc == 1)
X	{
X		printf("is %c\n", st.st_mode & S_IEXEC ? 'y' : 'n');
X		exit(0);
X	}
X
X/* you can't biff when su-ed to another account, unless it's to root */
X	if (geteuid() && geteuid() != st.st_uid)
X	{
X		fprintf(stderr, "%s: Not owner\n", tty);
X		exit(-1);
X	}
X
X	switch(argv[1][0])
X	{
X		case 'y': case 'Y':
X			st.st_mode |= S_IEXEC;
X			chmod(tty, st.st_mode);
X		case 'a': case 'A':
X			if (argc > 2)
X				addusers(argc-2, argv+2);
X			break;
X		case 'n': case 'N':
X			st.st_mode &= ~S_IEXEC;
X			chmod(tty, st.st_mode);
X		case 'd': case 'D':
X			if (argc > 2)
X				delusers(argc-2, argv+2);
X			break;
X		default:
X			fprintf(stderr,
X				"Usage: %s [y|n|a|d [[user][@machine]...]]\n",
X					argv[0]);
X			exit(-1);
X	}
X}
X
X/*
X** Sort an argument list by machine name.  This makes the load on remote
X** comsats (not to mention the network) a bit lighter, since we can easily
X** service all the requests for each host individually.
X*/
Xsortargs(num, vec)
Xint num;
Xchar **vec;
X{
X	int mcomp();
X
X	qsort(vec, num, sizeof(char *), mcomp);
X}
X
X/*
X** Compare the hostnames of two user@host entries.
X*/
Xmcomp(a1, a2)
Xchar *a1, *a2;
X{
X	a1 = index(a1, '@');
X	a2 = index(a2, '@');
X
X	if (a1 == NULL)
X		if (a2 == NULL)
X			return 0;
X		else
X			return -1;
X	if (a2 == NULL)
X		return 1;
X
X	return(strcmp(a1, a2));
X}
X
Xinit_tcp()
X{
X	struct servent *sent;
X
X	if ((sent = getservbyname("rbiff", "tcp")) != NULL)
X		tcp_port = sent->s_port;
X	else
X		tcp_port = htons(TCP_PORT);
X}
X
X/*
X** Look up a host address, or translate numeric (a.b.c.d) notation to a
X** valid inet address.  Puts the host address in *buf.  Returns 0 if
X** the host wasn't found.
X*/
Xhaddr(host, buf)
Xchar *host;
Xunsigned long *buf;
X{
X	struct hostent *hent;
X
X	if (isdigit(host[0]))
X	{
X		*buf = inet_addr(host);
X		return 1;
X	}
X	hent = gethostbyname(host);
X	if (hent == NULL)
X		return 0;
X	bcopy(hent->h_addr, buf, hent->h_length);
X	return 1;
X}
X
Xint sockfd;
X
X/*
X** Initiate a connection with a host.  Returns 0 on failure.
X*/
Xgetcon(hostname)
Xchar *hostname;
X{
X	char	buf[10];
X	struct	sockaddr_in in;
X	int	port;
X
X	port = 1023;
X	sockfd = rresvport(&port);
X	if (sockfd < 0)
X	{
X		perror("rresvport");
X		return 0;
X	}
X
X	bzero(&in, sizeof(in));
X	if (! haddr(hostname, &in.sin_addr.s_addr))
X	{
X		fprintf("%s: Host unknown\n", hostname);
X		return 0;
X	}
X	in.sin_port = tcp_port;
X	in.sin_family = AF_INET;
X
X	if (connect(sockfd, &in, sizeof(in)))
X	{
X		perror(hostname);
X		return 0;
X	}
X
X	sprintf(buf, "P%d", tcp_port);
X	write(sockfd, buf, strlen(buf)+1);
X	read(sockfd, buf, 1);
X	return 1;
X}
X
X/*
X** Extract a hostname from a user@host string.  If there's no @host, use
X** localhost.
X*/
Xchar *hname(str)
Xchar *str;
X{
X	char *at;
X
X	at = index(str, '@');
X	if (at == NULL)
X		return("localhost");
X	else
X		return(at+1);
X}
X
Xchar *addcodes[] = {
X"",
X"Out of memory",
X"Already monitoring",
X"Permission denied"
X};
X
Xaddusers(num, vec)
Xint num;
Xchar **vec;
X{
X	int	i, npos;
X	char	*curhost, *tmp, buf[130], *getlogin(), code;
X
X	sortargs(num, vec);
X	curhost = vec[0];
X	i = 0;
X
X	init_tcp();
X
X	sprintf(buf, "W%s", getlogin());
X	npos = strlen(buf)+1;	/* position to place second name */
X	
X	while(i < num) {
X		if (! getcon(hname(vec[i])))
X		{
X			fprintf(stderr, "Couldn't register %s\n", vec[i]);
X			while (! mcomp(curhost, vec[i]))
X				if (++i == num)
X					break;
X			if (i < num)
X				curhost = vec[i];
X		}
X		else
X		{
X			while (! mcomp(curhost, vec[i]))
X			{
X				if (vec[i][0] == '@')
X					strcpy(buf+npos, getlogin());
X				else
X				{
X					strncpy(buf+npos, vec[i], 64);
X					buf[npos+64] = '\0';
X					if ((tmp=index(buf+npos,'@')) != NULL)
X						*tmp = '\0';
X				}
X				write(sockfd, buf, npos+strlen(buf+npos)+1);
X				read(sockfd, &code, 1);
X				if (code)
X					fprintf(stderr, "%s: %s\n", vec[i],
X						addcodes[code]);
X				if (++i == num)
X					break;
X			}
X			if (i < num)
X				curhost = vec[i];
X			close(sockfd);
X		}
X	}
X}
X
X/*
X** Delete users from watch lists.  This looks mostly identical to
X** addusers(), but is just different enough to warrant its own
X** routine.
X*/
Xdelusers(num, vec)
Xint num;
Xchar **vec;
X{
X	int	i, npos;
X	char	*curhost, *tmp, buf[130], *getlogin(), code;
X
X	sortargs(num, vec);
X	curhost = vec[0];
X	i = 0;
X
X	init_tcp();
X
X	sprintf(buf, "D%s", getlogin());
X	npos = strlen(buf)+1;	/* position to place second name */
X	
X	while(i < num) {
X		if (! getcon(hname(vec[i])))
X		{
X			fprintf(stderr, "Couldn't delete %s\n", vec[i]);
X			while (! mcomp(curhost, vec[i]))
X				if (++i == num)
X					break;
X			if (i < num)
X				curhost = vec[i];
X		}
X		else
X		{
X			while (! mcomp(curhost, vec[i]))
X			{
X				if (vec[i][0] == '@')
X					buf[npos] = '\0';
X				else
X				{
X					strncpy(buf+npos, vec[i], 64);
X					buf[npos+64] = '\0';
X					if ((tmp=index(buf+npos,'@')) != NULL)
X						*tmp = '\0';
X				}
X				write(sockfd, buf, npos+strlen(buf+npos)+1);
X				read(sockfd, &code, 1);
X				if (!code)
X					printf("error deleting %s\n", vec[i]);
X				if (++i == num)
X					break;
X			}
X			if (i < num)
X				curhost = vec[i];
X			close(sockfd);
X		}
X	}
X}
X
SHAR_EOF
chmod 0600 biff.c || echo "restore of biff.c fails"
sed 's/^X//' << 'SHAR_EOF' > comsat.8c &&
X.TH COMSAT 8C "28 December 1988"
X.SH NAME
Xcomsat \- mail notification daemon
X.SH SYNOPSIS
X/usr/etc/comsat
X.SH DESCRIPTION
X.B comsat
Xwatches for datagrams on the UDP "biff" service port (usually 512) for messages
Xof the form
X.IR user @ mailbox-offset
Xwhere
X.I user
Xis a valid username on the local host and
X.I mailbox-offset
Xis the byte offset into that user's mailbox of a new mail message.  It then
Xdisplays some of the header lines and up to two lines of the message body to
Xeveryone who is monitoring the mailbox in question (see below).
X.LP
XIf the owner of the mailbox is logged in and the owner-execute bit of his tty
Xis set, he is automatically notified.  The
X.BR biff (1)
Xcommand can be used to set that bit easily, as well as to register requests
Xto monitor other mailboxes.
X.LP
X.B comsat
Xalso listens for stream connections on the TCP "biff" service port (522 by
Xdefault).  Connections to that port must come from secure port numbers
X(numbered less than 1024) or they will be closed immediately.  Once the
Xconnection has been established, the remote program (usually a
X.B comsat
Xon another host, or
X.BR biff )
Xcan issue commands to
X.BR comsat :
Xadd a monitor, delete a monitor, verify that a user is logged on, or send
Xa mail excerpt to a user on the local host.
X.LP
XA user must meet the authentication requirements enforced by
X.BR ruserok (3)
Xin order to monitor a mailbox other than his own.
X.LP
XWhen
X.B comsat
Xreceives a
X.IR user @ offset
Xdatagram, it looks through its list of monitors.  If anyone is monitoring the
X.IR user 's
Xmailbox,
X.B comsat
Xeither notifies him directly (if he is monitoring from the local host, is
Xlogged on, and his tty has the owner-execute bit set) or initiates a
Xconnection to the
X.B comsat
Xrunning on his host, and sends the mail excerpt to that host, which
Xnotifies the monitor if he has his owner-execute bit set.
X.LP
X.B comsat
Xwakes up on a regular basis and verifies that all the users monitoring
Xmailboxes are still logged onto their respective hosts.  It deletes the
Xabsent monitors from its monitor list.
X.SH "SEE ALSO"
X.BR biff (1),
X.BR rcmd (3)
X.SH BUGS
XIf a user logs off then on in between two login checks, he will still
Xbe notified of mail arriving at mailboxes monitored during his first
Xlogin session.  This may be a feature.
X.LP
XOn extremely active systems, the login checks may never occur; a minimum
Xnumber of seconds (120 in the default distribution, though this is
Xconfigurable at each site) must pass since the last datagram or stream
Xconnection before the check occurs.
X.SH AUTHOR
XSteven Grimm (koreth@ssyx.ucsc.edu)
X
SHAR_EOF
chmod 0600 comsat.8c || echo "restore of comsat.8c fails"
sed 's/^X//' << 'SHAR_EOF' > comsat.c &&
X/*
X** COMSAT
X**
X** A network version of the BSD biff server.
X**
X** Written by Steven Grimm (koreth@ssyx.ucsc.edu), 12-27-88.
X*/
X#include <sys/types.h>
X#include <utmp.h>
X#include <stdio.h>
X#include <netdb.h>
X#include <ctype.h>
X#include <strings.h>
X#include <signal.h>
X#include <sys/file.h>
X#include <sys/stat.h>
X#include <sys/time.h>
X#include <sys/ioctl.h>
X#include <sys/socket.h>
X#include <netinet/in.h>
X#include <arpa/inet.h>
X#ifndef MAXPATHLEN
X#include <sys/param.h>
X#endif
X#include "config.h"
X
X#ifndef FD_SETSIZE	/* for 4.2BSD */
X#define FD_SETSIZE      (sizeof(fd_set) * 8)
X#define FD_SET(n, p)    (((fd_set *) (p))->fds_bits[0] |= (1 << ((n) % 32)))
X#define FD_CLR(n, p)    (((fd_set *) (p))->fds_bits[0] &= ~(1 << ((n) % 32)))
X#define FD_ISSET(n, p)  (((fd_set *) (p))->fds_bits[0] & (1 << ((n) % 32)))
X#define FD_ZERO(p)      bzero((char *)(p), sizeof(*(p)))
X#endif
X
Xextern	char *sys_errlist[];
Xextern	int errno;
X
Xint	udp_fd, tcp_fd;
Xstruct	sockaddr_in tcp_host[FD_SETSIZE];
Xfd_set	readset;
X
X/*
X** A linked list of these structures is used to keep track of remote users
X** who are watching mailboxes on the local host.
X*/
Xstruct ruser {
X	struct	sockaddr_in host;	/* watcher's host */
X	char	watcher[64];		/* watcher's name */
X	char	watchee[64];		/* watchee's name */
X	int	local;			/* set if host==localhost */
X	int	flag;			/* flag for temporary use */
X	struct	ruser *next;		/* link to next entry */
X};
X
Xstruct ruser *rlist = NULL;
X
X/*
X** Add a remote user to the watch list.  Returns 0 on success, 1 on
X** malloc failure, 2 if that user is repeating an existing request,
X** or 3 if the remote user authentication fails.
X*/
Xr_add(host, watcher, watchee)
Xstruct sockaddr_in *host;
Xchar *watcher, *watchee;
X{
X	struct	ruser *entry;
X	struct	hostent *hostent;
X	int	ruok;
X	char	hbuf[64];
X
X	for (entry = rlist; entry != NULL; entry = entry->next)
X		if ((!strcmp(watcher, entry->watcher)) &&
X				(!strcmp(watchee, entry->watchee)))
X			return 2;
X
X	if ((hostent = gethostbyaddr(&host->sin_addr, sizeof(host->sin_addr),
X				AF_INET)) == NULL)
X		strcpy(hbuf, inet_ntoa(host->sin_addr));
X	else
X		strncpy(hbuf, hostent->h_name, 63);
X	hbuf[63] = '\0';
X	if (! host->sin_addr.s_addr)
X		strcpy(hbuf, "localhost");
X
X	ruok = ruserok(hbuf, 0, watcher, watchee);
X	setreuid(0,0);		/* make up for some Sun bugs */
X	if (ruok)
X		return 3;
X
X	entry = (struct ruser *)malloc(sizeof(struct ruser));
X	if (entry == NULL)
X		return 1;
X
X	entry->next = rlist;
X	rlist = entry;
X	strncpy(entry->watcher, watcher, 64);
X	strncpy(entry->watchee, watchee, 64);
X	bcopy(host, &entry->host, sizeof(struct sockaddr_in));
X	entry->local = localhost(host);
X
X	return 0;
X}
X
X/*
X** Compare a host-username pair with the remote host and username in a
X** watch list entry.  Returns 0 if they're the same.  Watche[er] may be empty
X** in which case it's ignored; host is always significant.
X*/
Xr_cmp(host, watcher, watchee, entry)
Xstruct sockaddr_in *host;
Xchar *watcher, *watchee;
Xstruct ruser *entry;
X{
X	if (watcher[0] && strcmp(watcher, entry->watcher))
X		return 1;
X	if (watchee[0] && strcmp(watchee, entry->watchee))
X		return 1;
X/* we have to check for localhost, since watchees registered without */
X/* machine names will be watched by 'localhost', and checking for */
X/* user@realmachinename will fail unless we do the special check. */
X	if (entry->local)
X	{
X		if (!localhost(host))
X			return 1;
X	}
X	else if (bcmp(&host->sin_addr, &entry->host.sin_addr,
X			sizeof(host->sin_addr)))
X		return 1;
X	return 0;
X}
X
X/*
X** Delete a remote user from the watch list.
X*/
Xr_del(host, watcher, watchee)
Xstruct sockaddr_in *host;
Xchar *watcher, *watchee;
X{
X	struct ruser *cur, *tmp;
X	int delflg = 0;
X
X#ifdef DEBUG
X	printf("r_del(%s, %s, %s)\n", inet_ntoa(host->sin_addr.s_addr),
X					watcher, watchee);
X#endif
X
X	while (rlist != NULL)
X		if (! r_cmp(host, watcher, watchee, rlist))
X		{
X			tmp = rlist;
X			rlist = rlist->next;
X			free(tmp);
X			delflg = 1;
X		}
X		else
X			break;
X
X	if (rlist == NULL)
X		return delflg;
X
X	cur = rlist;
X
X	while (cur->next != NULL)
X		if (! r_cmp(host, watcher, watchee, rlist))
X		{
X			tmp = cur->next;
X			cur->next = cur->next->next;
X			free(tmp);
X			delflg = 1;
X		}
X		else
X			cur = cur->next;
X
X	return delflg;
X}
X
X/*
X** Open and bind the necessary sockets.  One socket listens for datagrams,
X** the other for stream connection requests.
X*/
Xvoid
Xgetsocks()
X{
X	struct servent *tcpsvc, *udpsvc;
X	struct sockaddr_in addr;
X
X	tcpsvc = getservbyname("rbiff", "tcp");
X
X	tcp_fd = socket(PF_INET, SOCK_STREAM, 0);
X	if (tcp_fd < 0)
X		panic("tcp socket()");
X
X	udp_fd = socket(PF_INET, SOCK_DGRAM, 0);
X	if (udp_fd < 0)
X		panic("udp socket()");
X
X	bzero(&addr, sizeof(addr));
X	addr.sin_family = AF_INET;
X	addr.sin_addr.s_addr = INADDR_ANY;
X
X	if (tcpsvc == NULL)
X		addr.sin_port = htons(TCP_PORT);
X	else
X		addr.sin_port = tcpsvc->s_port;
X	if (bind(tcp_fd, &addr, sizeof(addr)) < 0)
X		panic("tcp bind()");
X
X	udpsvc = getservbyname("biff", "udp");
X
X	if (udpsvc == NULL)
X		addr.sin_port = htons(UDP_PORT);
X	else
X		addr.sin_port = udpsvc->s_port;
X	if (bind(udp_fd, &addr, sizeof(addr)) < 0)
X		panic("udp bind()");
X
X	if (listen(tcp_fd, 5) < 0)
X		panic("listen (weird)");
X
X	if (fcntl(tcp_fd, F_SETFL, FIONBIO) == -1)
X		panic("fcntl tcp_fd");
X	if (fcntl(udp_fd, F_SETFL, FIONBIO) == -1)
X		panic("fcntl udp_fd");
X
X	FD_ZERO(&readset);
X	FD_SET(tcp_fd, &readset);
X	FD_SET(udp_fd, &readset);
X}
X
X/*
X** Send a message to a user, once to each of his ttys that has the owner
X** execute bit set.
X*/
Xvoid
Xbiff(user, msg)
Xchar *user, *msg;
X{
X	char filename[MAXPATHLEN];
X	struct utmp uent;
X	struct stat st;
X	FILE *fp;
X	int tty;
X
X	if ((fp = fopen(UTMP, "r")) == NULL)
X		return;
X
X	while (! feof(fp))
X	{
X		if (! fread(&uent, sizeof(uent), 1, fp))
X			break;
X		if (strcmp(uent.ut_name, user))
X			continue;
X		sprintf(filename, "%s/%s", DEVDIR, uent.ut_line);
X		if (stat(filename, &st) < 0)
X			continue;
X		if ((st.st_mode & S_IEXEC)
X#ifndef DEBUG
X			&& !fork()
X#endif
X			)
X		{
X			tty = open(filename, O_RDWR);
X			if (tty)
X				write(tty, msg, strlen(msg));
X#ifdef DEBUG
X			close(tty);
X#else
X			exit(0);
X#endif
X		}
X	}
X	fclose(fp);
X}
X
X/*
X** Is a header line valid?  This routine finds out.  Returns 0 if the
X** line shouldn't be included in the biff message.
X*/
Xhdrline(buf)
Xchar *buf;
X{
X	int	i;
X
X	for (i = 0; i < Nfields; i++)
X		if (!strncmp(buf, fields[i], strlen(fields[i])))
X			return 1;
X	return 0;
X}
X
X/*
X** Build a message to send out to everyone who's waiting for mail
X** to a specific user.  Pass the username, offset in his mailbox of
X** the new mail, and the address of a buffer at least 800 bytes long.
X*/
Xvoid
Xbuildmsg(name, offset, buf)
Xchar *name, *buf;
Xint offset;
X{
X	struct	ruser *cur;
X	struct	stat st;
X	struct	timeval tvp[2];
X	FILE	*fp;
X	int	bpos, body;
X	char	filename[MAXPATHLEN];
X
X	gethostname(filename, 79);
X	filename[79] = '\0';
X
X	sprintf(buf, "\n\r\007New mail for %.20s@%.30s\007 has arrived:\n\r----\n\r",
X		name, filename);
X	bpos = strlen(buf);
X
X	sprintf(filename, "%s/%s", MAILDIR, name);
X
X	stat(filename, &st);
X	if ((fp = fopen(filename, "r")) == NULL)
X	{
X		sprintf(buf+bpos, "(%s: %s)\n\r----\n\r", filename,
X			sys_errlist[errno]);
X		return;
X	}
X
X	body = 0;	/* we are looking at the message header. */
X
X	fseek(fp, offset, 0);
X
X	while (! feof(fp))
X	{
X		if (fgets(buf+bpos, 780-bpos, fp) == NULL)
X		{
X			strcpy(buf+bpos, "----\n\r");
X			break;
X		}
X		if (! body)
X		{
X			if (buf[bpos] == '\n')
X			{
X				bpos++;
X				body++;
X			}
X			else if (hdrline(buf+bpos))
X			{
X				bpos += strlen(buf+bpos);
X				buf[bpos++] = '\r';
X			}
X		}
X		else
X		{
X			if (buf[bpos] == '\n')
X				continue;
X			if (++body == 4)
X			{
X				strcpy(buf+bpos, "...more...\n\r");
X				break;
X			}
X			bpos += strlen(buf+bpos);
X			buf[bpos++] = '\r';
X		}
X	}
X	fclose(fp);
X
X/* login's "you have new mail" message won't work unless mtime > atime */
X	bzero(tvp, sizeof(tvp));
X	tvp[0].tv_sec = st.st_atime;
X	tvp[1].tv_sec = st.st_mtime;
X	utimes(filename, tvp);
X}
X
X/*
X** Determine whether or not a host address points to the local host.  Returns
X** 0 if the host isn't local.  There's probably a much better way of doing
X** this: first we see if the host is called "localhost", then see if its
X** name is the same one that gethostname() returns, then see if gethostname()
X** returns a machine name whose address is equal to the host address we're
X** checking.  Yucko.
X*/
Xlocalhost(addr)
Xstruct sockaddr_in *addr;
X{
X	struct	hostent *host;
X	char	hname[64];
X	int	i;
X
X	if (! addr->sin_addr.s_addr)
X		return 1;
X
X	gethostname(hname, 64);
X
X	if ((host = gethostbyaddr(&addr->sin_addr, sizeof(addr->sin_addr),
X			AF_INET)) != NULL)
X	{
X		if (! stricmp(host->h_name, "localhost"))
X			return 1;
X		if (! stricmp(host->h_name, hname))
X			return 1;
X	}
X
X	if ((host = gethostbyname(hname)) != NULL)
X#ifndef h_addr
X		if (!bcmp(host->h_addr,&addr->sin_addr,host->h_length))
X			return 1;
X#else /* if we have multiple host addresses, check all of 'em */
X		for (i = 0; host->h_addr_list[i] != NULL; i++)
X			if (! bcmp(host->h_addr_list[i], &addr->sin_addr,
X					sizeof(addr->sin_addr)))
X				return 1;
X#endif
X	return 0;
X}
X
X/*
X** See if a user is logged in.  Return 1 if he is.
X*/
Xlogged_in(user)
Xchar *user;
X{
X	struct	utmp uent;
X	int	sawhim = 0;
X	FILE	*fp;
X
X	fp = fopen(UTMP, "r");
X	if (fp == NULL)
X		return 0;
X
X	while (! feof(fp))
X	{
X		if (! fread(&uent, sizeof(uent), 1, fp))
X			break;
X		if (strcmp(uent.ut_name, user))
X			continue;
X		sawhim++;
X		break;
X	}
X	fclose(fp);
X
X	return(sawhim);
X}
X
X/*
X** Open up a dialogue with a remote comsat, and send it a biff for someone.
X*/
Xvoid
Xrbiff(cur, buf)
Xstruct ruser *cur;
Xchar *buf;
X{
X	char	biffcmd[90];
X	int	fd, port, len;
X
X	sprintf(biffcmd, "B%s%c%d", cur->watcher, '\0', strlen(buf) + 1);
X
X/* connect to the remote, as specified in the rlist entry.  make sure we */
X/* are coming from a secure port else the other guy will hang up on us.  */
X	port = 1023;
X	fd = rresvport(&port);
X	if (fd < 0)
X		return;
X	if (connect(fd, &cur->host, sizeof(cur->host)))
X	{
X		close(fd);
X		return;
X	}
X
X	len = strlen(biffcmd);
X	len += strlen(biffcmd+len+1)+2;
X	write(fd, biffcmd, len);
X
X	if (len = gack(fd))
X	{
X		shutdown(fd, 2);
X		close(fd);
X		return;
X	}
X
X	write(fd, buf, strlen(buf)+1);
X
X	gack(fd);
X
X	shutdown(fd, 2);
X	close(fd);
X}
X
X/*
X** Handle an incoming datagram.  This should be a message of the form
X** "user@offset", where user is the user who has new mail and offset
X** is the byte offset into his mailbox of the new message.  If SECUREBIFF
X** is defined in config.h, the datagram must come from a reserved (<1024)
X** port on the local host.
X*/
Xvoid
Xudp()
X{
X	struct	ruser *cur;
X	struct	sockaddr_in from;
X	char	buf[800], user[64], *cp;
X	int	offset;
X
X	offset = sizeof(from);
X	if ((offset = recvfrom(udp_fd, buf, 800, 0, &from, &offset)) < 3)
X		return;
X	buf[offset] = '\0';
X
X#ifdef SECUREBIFF
X	if (ntohs(from.sin_port) >= IPPORT_RESERVED)
X		return;
X	if (! localhost(&from))
X		return;
X#endif
X
X	if ((cp = index(buf, '@')) == NULL)
X		return;
X	*cp++ = '\0';
X
X	strncpy(user, buf, 63);
X	user[63] = '\0';
X
X	if (! isdigit(*cp))
X		return;
X	offset = atoi(cp);
X
X	buildmsg(user, offset, buf);
X	biff(user, buf);
X
X	for (cur = rlist; cur != NULL; cur = cur->next)
X		if (! strcmp(cur->watchee, user))
X/* if the watcher is on the local host, just biff him. */
X			if (cur->local)
X			{
X/* if the watcher is watching himself, don't biff him again. */
X				if (strcmp(user, cur->watcher))
X					biff(cur->watcher, buf);
X			}
X			else
X				rbiff(cur, buf);
X}
X
X/*
X** Handle a pending TCP connection.  If it's not coming from a secure port
X** on the remote host, close it immediately.
X*/
Xvoid
Xtcp()
X{
X	struct sockaddr_in host;
X	int len, tcp_fd_c;
X
X	len = sizeof(host);
X	tcp_fd_c = accept(tcp_fd, &host, &len);
X	if (tcp_fd_c < 0)
X		return;
X
X	if (ntohs(host.sin_port) >= IPPORT_RESERVED)
X	{
X		shutdown(tcp_fd_c, 2);
X		close(tcp_fd_c);
X		return;
X	}
X
X	len = 1;
X	setsockopt(tcp_fd_c, SOL_SOCKET, SO_KEEPALIVE, &len, sizeof(len));
X	fcntl(tcp_fd_c, F_SETFL, FIONBIO);
X
X	bcopy(&host, &tcp_host[tcp_fd_c], sizeof(host));
X	FD_SET(tcp_fd_c, &readset);
X}
X
X/*
X** Handle incoming data on a connected socket.  This should be in the form
X** of a single-character command byte followed by null-terminated parameters.
X** There probably isn't enough error checking here.
X*/
Xvoid
Xtcpc(fd)
Xint fd;
X{
X	char	buf[800], user[64];
X	int	nbytes;
X
X	ioctl(fd, FIONREAD, &nbytes);
X	if (! nbytes)		/* 0 bytes available means disconnect */
X	{
X		shutdown(fd, 2);
X		close(fd);
X		FD_CLR(fd, &readset);
X		return;
X	}
X
X	if (nbytes > sizeof(buf))
X		nbytes = sizeof(buf);
X
X	read(fd, buf, nbytes);
X	buf[nbytes] = '\0';
X
X	if (buf[0] == 'W')	/* Watch watcher\0watchee\0 */
X		ack(fd, r_add(&tcp_host[fd], buf+1, buf+strlen(buf) + 1));
X	else if (buf[0] == 'D')	/* Delete watcher\0[watchee]\0 */
X		ack(fd, r_del(&tcp_host[fd], buf+1, buf+strlen(buf) + 1));
X	else if (buf[0] == 'V') /* Verify watcher\0 */
X		ack(fd, logged_in(buf+1));
X	else if (buf[0] == 'P')	/* Port portnum\0 */
X	{
X		tcp_host[fd].sin_port = htons(atoi(buf+1));
X		ack(fd, 0);
X	}
X	else if (buf[0] == 'B')	/* Biff watcher\0length\0 */
X	{
X		char	user[64];
X		int	len, nread;
X
X		strcpy(user, buf+1);
X		len = atoi(buf+strlen(buf) + 1);
X		if (len > sizeof(buf))	/* don't wanna overflow our buffer */
X		{
X			ack(fd, 1);
X			return;
X		}
X
X		ack(fd, 0);	/* tell him to start sending */
X
X		nbytes = 0;
X		while (nbytes < len)
X		{
X			fd_set readbits;
X			struct timeval tout;
X
X			FD_ZERO(&readbits);
X			FD_SET(fd, &readbits);
X			timerclear(&tout);
X			tout.tv_sec = 20;	/* biff msg must arrive fast */
X
X			select(fd+1, &readbits, NULL, NULL, &tout);
X			nread = read(fd, buf+nbytes, len-nbytes);
X			if (nread < 1)	/* if timed out, we get EWOULDBLOCK */
X			{
X				close(fd);
X				FD_CLR(fd, &readset);
X				return;
X			}
X			nbytes += nread;
X		}
X		biff(user, buf);
X		ack(fd, 0);
X	}
X}
X
X/*
X** Send a byte out on a file descriptor.
X*/
Xack(fd, ch)
Xint fd, ch;
X{
X	char c;
X
X	c = (char) ch;
X	write(fd, &c, 1);
X}
X
X/*
X** Read a byte from a file descriptor.
X*/
Xgack(fd)
X{
X	char c;
X
X	read(fd, &c, 1);
X	return (int) c;
X}
X
X/*
X** Do a perror() and exit.
X*/
Xpanic(string)
Xchar *string;
X{
X	perror(string);
X	exit(-1);
X}
X
X/*
X** A custom bcopy whose behavior is known for overlapping regions
X*/
Xbcopy(src, des, len)
Xregister char *src, *des;
Xregister int len;
X{
X	while (len--)
X		*des++ = *src++;
X}
X
X/*
X** Case-insensitive compare.  This should use lookup tables for fast case
X** conversion.
X*/
Xstricmp(str1, str2)
Xregister char *str1, *str2;
X{
X	while (*str1 || *str2)
X	{
X		if ((islower(*str1) ? toupper(*str1) : *str1) !=
X		    (islower(*str2) ? toupper(*str2) : *str2))
X			return 1;
X		str1++;
X		str2++;
X	}
X	return 0;
X}
X
X/*
X** See which watchers are still online, and delete the entries of those
X** who have logged off.  This probably scans through the list a lot more
X** times than is necessary.  Oh well.
X*/
Xvoid
Xrollcall()
X{
X	struct	ruser *cur, *user, *place;
X	int	port, fd, uflag;
X	char	buf[66], code;
X
X/* if there are no watchers, this is pretty easy. */
X
X	if (rlist == NULL)
X		return;
X
X/* first, see who's logged on locally.  while we're going through the linked */
X/* list, set all the remote-host entries' flag fields to 0 and all the local */
X/* ones to 1, to simplify things later. */
X
X	for (cur = rlist; cur != NULL; cur = cur->next)
X		if (cur->local)
X			if (logged_in(cur->watcher))
X				cur->flag = 1;
X			else
X				cur->flag = -1;
X		else
X			cur->flag = 0;
X
X/* now go through and look at the remote hosts.  each time we find a remote */
X/* host, we will connect to it then scan forward in the linked list so that */
X/* we only have to connect to each host once. */
X
X	buf[0] = 'V';
X
X	for (place = rlist; place != NULL; place = place->next)
X	{
X		if (place->flag)
X			continue;
X
X		port = 1023;
X		fd = rresvport(&port);
X		if (fd < 0)
X			break;
X
X/* if we can't connect to a host, toast all entries from that host. */
X
X		if (connect(fd, &place->host, sizeof(place->host)))
X		{
X			for (cur = place; cur != NULL; cur = cur->next)
X				if (! cur->flag)
X					if (! r_cmp(&place->host, "", "", cur))
X						cur->flag = -1;
X		}
X
X/* ask about each watcher on the current host. */
X
X		else for (user = place; user != NULL; user = user->next)
X		{
X			if (user->flag)
X				continue;
X			if (r_cmp(&place->host, "", "", user))
X				continue;
X			strcpy(buf+1, user->watcher);
X			write(fd, buf, strlen(buf)+1);
X			read(fd, &code, 1);
X			uflag = code ? 1 : -1;
X
X/* toast or keep all the watcher's entries. */
X
X			for (cur = user; cur != NULL; cur = cur->next)
X				if (!(cur->flag || r_cmp(&user->host,
X						user->watcher, "", cur)))
X					cur->flag = uflag;
X		}
X		close(fd);
X	}
X
X/* now go through and delete all the entries that are marked as toast. */
X
X	while (rlist != NULL)
X		if (rlist->flag == -1)
X		{
X			cur = rlist;
X			rlist = rlist->next;
X			free(cur);
X		}
X		else
X			break;
X
X	if (rlist == NULL)
X		return;
X
X	cur = rlist;
X	while (cur->next != NULL)
X		if (cur->next->flag == -1)
X		{
X			place = cur->next;
X			cur->next = cur->next->next;
X			free(place);
X		}
X		else
X			cur = cur->next;
X}
X
X#ifdef DEBUG
X/*
X** Dump the ruser list to stdout.
X*/
Xdumplist()
X{
X	struct ruser *cur;
X
X	if (rlist == NULL)
X		printf("No users in rlist\n");
X	else
X		for (cur = rlist; cur != NULL; cur = cur->next)
X			printf("%s@%s watching %s\n", cur->watcher,
X				inet_ntoa(cur->host.sin_addr.s_addr),
X				cur->watchee);
X}
X#endif
X
Xreap()
X{
X	wait(0);
X}
X
Xmain()
X{
X	int	fd;
X	fd_set	myread, other;
X	struct	timeval timer;
X
X	getsocks();
X
X	setreuid(0, 0);
X#ifndef DEBUG
X	if (fork())
X		exit(0);
X	fd = open("/dev/tty", O_RDWR);
X	ioctl(fd, TIOCNOTTY, 0);
X	close(fd);
X	close(0);
X	close(1);
X	close(2);
X#else
X	signal(SIGQUIT, dumplist);
X#endif
X
X	signal(SIGCHLD, reap);
X
X	FD_ZERO(&other);
X
X	while (1)
X	{
X		timerclear(&timer);
X		timer.tv_sec = ROLLCALL;
X		bcopy(&readset, &myread, sizeof(fd_set));
X
X		if (! select(FD_SETSIZE, &myread, &other, &other, &timer))
X			rollcall();
X		else
X		{
X			if (FD_ISSET(udp_fd, &myread))
X			{
X				udp();
X				FD_CLR(udp_fd, &myread);
X			}
X			if (FD_ISSET(tcp_fd, &myread))
X			{
X				tcp();
X				FD_CLR(tcp_fd, &myread);
X			}
X/* we have to do this in a loop; ffs() won't work for FD_SETSIZE > 32 */
X			for (fd = 0; fd < FD_SETSIZE; fd++)
X				if (FD_ISSET(fd, &myread))
X					tcpc(fd);
X		}
X	}
X}
SHAR_EOF
chmod 0600 comsat.c || echo "restore of comsat.c fails"
sed 's/^X//' << 'SHAR_EOF' > config.h &&
X/*
X** Various pieces of configuration for comsat
X*/
X
X/*
X** If SECUREBIFF is defined, comsat will only accept datagrams from privileged
X** (<1024) port numbers.  This is to prevent users from sending "biff bombs"
X** to each other by sending messages to the server.  Note that /bin/mail may
X** have to be modified for this to work, as it does not normally use a secure
X** port to talk to comsat.
X*/
X/*#define SECUREBIFF*/
X
X/*
X** Set ROLLCALL to the number of seconds between "roll calls" -- a roll call
X** checks to see that all the remote users who are watching someone are still
X** logged in.
X*/
X#define ROLLCALL	120
X
X/*
X** These are the default port numbers to use, if we can't find the biff
X** entry in /etc/services.
X*/
X#define UDP_PORT	512
X#define TCP_PORT	522
X
X/*
X** Mail header fields we print in a mail notification.
X*/
Xchar *fields[] = {
X"From: ",
X"Subject: ",
X"To: ",
X"Date: "
X};
X
X/*
X** Directory where users' mailboxes are kept.
X*/
X#define MAILDIR		"/usr/spool/mail"
X
X/*
X** Location of utmp file.
X*/
X#define UTMP		"/etc/utmp"
X
X/*
X** Directory to search for tty devices.
X*/
X#define DEVDIR		"/dev"
X
X#define Nfields (sizeof(fields)/sizeof(fields[0]))
SHAR_EOF
chmod 0600 config.h || echo "restore of config.h fails"
exit 0

-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.