#ifndef lint
static char *RCSid = "$Header: /home/jsellens/src/users/biffer/RCS/biffer.c,v 1.26 1996/02/14 17:43:01 rbutterworth Exp $";
#endif

/* We accept connections, either from comsat, or from remote biffers.
   They send us a stream in the following form:
       <userid to biff on this host>\n		{ biffee }
       <original mail recipient userid>\n	{ recipient }
       <original mail recipient host>\n		{ machine }
       <number of hops to get here>\n
       MESSAGE FOLLOWS\n
       <various header lines>\n
       \n
       <lines of the message>\n
    The number of hops is to guard against loops in biff forwarding.
    If hops is negative, then we are tracing rlogins so we should
    ignore any forwarding instructions.
    All information is in string form, to avoid byte ordering problems
    in numbers.  If "machine" is a null string, it will default to
    the current host.
*/

#include "biffer.h"
#include "bits.h"
/* #include <mfcf/libc/signal.h> */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* #include <mfcf/libc/unix/utmp.h> */
#include <utmp.h>
#ifndef UTMP_FILE
#define UTMP_FILE _PATH_UTMP
#endif

/* #include <mfcf/libc/errno.h> */
#include <errno.h>
/* #include <mfcf/libc/termios.h> */
#include <termios.h>
#include <netdb.h>
#include <syslog.h>
#include <ctype.h>
#include <setjmp.h>
#if defined(TTYSX) || defined(USERPROG)
#	include <ttyent.h>
#endif

extern	char	*re_comp();
static int parsebits();

#define ALLOW           (S_IEXEC)       /* biff permission bit */
#define DEV		"/dev/"

#define MSGBUFSIZE	2044
#ifdef OPTS_NOW
#define MAXIGNORES	25
#endif

char	recipient[USERSIZE];	/* where mail got delivered */
char	machine[HOSTSIZE];	/* recipient's machine */
char	biffee[USERSIZE];	/* the name we're biffing */
char	home[MAXPATHLEN];	/* the home dir of biffee */
char	host[HOSTSIZE];		/* this host's name */
int	inheader = FALSE;	/* flag for printing */
#ifdef OPTS_NOW
int	lines = LINES;		/* how much of the message to biff */
int	showbiffee = FALSE;	/* display name of biffee too */
int	showtoline = BI_ALL;	/* show the To: line of message? */
int	showfromline = BI_ALL;	/* show the From: line of message? */
int	showsubjline = BI_ALL;	/* show the Subject: line of message? */
#endif
char	savetoline[BUFSIZ];	/* save the To: line */
char	savefromline[BUFSIZ];	/* save the From: line */
char	savesubjline[BUFSIZ];	/* save the Subject: line */
char	msgbuf[MSGBUFSIZE];	/* were we collect the message */

#ifdef USERPROG
#ifdef OPTS_NOW
char	uprognetwork[1024];	/* shell cmd to biff with on network */
char	uprognonet[1024];	/* shell cmd to biff with on non-network */
#endif
char	*uprog;			/* points to one of the above */
#endif

char	*progname;
int	debugFlag = 0;		/* want debug output?? */

static	int	hops;			/* how many it took to get here */
static	int	uid;
#ifdef OPTS_NOW
static	char	forwardto[USERHOSTSIZE] = "";	/* where BIFFRC says to fwd */
static	int	tracerlogins = FALSE;	/* go to original non-rlogin? */
static	int	heretoofwd = FALSE;	/* forward and biff here too?? */
static	int	heretoorem = FALSE;	/* tracerlogins and biff here too?? */
static	int	dospecial = FALSE;	/* special handling on some terminals */
static	int	x10special = FALSE;	/* do special for X10 */
static	int	x11special = FALSE;	/* do special for X11 */
#ifdef SUNGUESS
static	int	sunx = FALSE;		/* assume X if on Sun console */
#endif
static	char	*ignores[MAXIGNORES];	/* list of pointers to ignore lines */
static	int	ignorecount = 0;	/* count of entries in ignores */
#endif

biffopt		globopt;	/* global options */
biffopt		ttyopt;		/* tty specific options */
/* initialize with a routine, so we don't have to remember the exact
   order of the struct and never ever ever change it */
static void
initGlobalOpt()
{
    globopt.lines		= LINES;
    globopt.showbiffee		= FALSE;
    globopt.showtoline		= BI_ALL;
    globopt.showfromline	= BI_ALL;
    globopt.showsubjline	= BI_ALL;
#ifdef USERPROG
    *globopt.uprognetwork	= '\0';
    *globopt.uprognonet		= '\0';
#endif
    *globopt.forwardto		= '\0';
    globopt.tracerlogins	= FALSE;
    globopt.heretoofwd		= FALSE;
    globopt.heretoorem		= FALSE;
    globopt.dospecial		= FALSE;
    globopt.x10special		= FALSE;
    globopt.x11special		= FALSE;
    *globopt.display		= '\0';
    *globopt.hostname		= '\0';
#ifdef SUNGUESS
    globopt.sunx		= FALSE;
#endif
    globopt.ignorecount		= 0;
}

static	readinfo();
static	flushinput();
static	getuserinfo();
static	void	readBifferFile();
static	parse();
static	readmsg();
static	passiton();
static	int	senditto();
static	biffem();
static	int	ignorethismsg();


main( argc, argv )
char *argv[];
int argc;
{
    struct sockaddr_in from;
    int fromlen;

    initGlobalOpt();

    progname = argv[0];

    if (debugFlag)
	debug("just testing === %d ===", getpid());

#ifdef SOCKETCHECK
    /* make sure we have a client on 0, as if we were called from inetd */
    fromlen = sizeof( from );
    if ( getsockname( 0, (struct sockaddr *)&from, &fromlen ) < 0 ) {
	fprintf( stderr, "%s: ", progname );
	if ( errno == ENOTSOCK )
	    fprintf( stderr, "(not invoked by inetd?): " );
	perror( "getsockname" );
	exit( 1 );
    }
#endif

#ifdef LOG_DAEMON
    openlog( progname, LOG_PID, LOG_DAEMON );
#else
    openlog( progname, LOG_PID );
#endif

    if ( argc > 2 )
	eprintf( "Too many arguments - only -d is possible" );
    if ( argc == 2 ) {
	if ( strcmp( argv[1], "-d" ) == 0 )
	    debugFlag++;
	else
	    eprintf( "Strange argument '%s' - only -d is possible", argv[1] );
    }

    if ( gethostname( host, sizeof(host) ) == -1 )
	fatal( "couldn't gethostname(): %s", strerror(errno) );

    readinfo();		/* get header packet */
    getuserinfo();
    readmsg();
    if ( ! ignorethismsg() ) {
	if ( *globopt.forwardto != '\0' && hops>=0 ) {
	    passiton();
	    if ( globopt.heretoofwd ) {
		biffem();
	    }
	} else
	    biffem();
    }
    if (debugFlag)
	debug("exiting\n");
    exit(0);
}


static char *
mygets(char *s, int size)
{
    char *p;
    if ( fgets( s, size, stdin ) == NULL )
	return( NULL );
    if ( s && *s ) {
	/* remove any crlf or lf in the string */
	if ( (p = strrchr( s, '\n' )) != NULL )
	    *p = '\0';
	if ( (p = strrchr( s, '\r' )) != NULL )
	    *p = '\0';
    }
    return( s );
}


static
readinfo()
{
    char buf[BUFSIZ];
    (void) mygets( biffee, sizeof(biffee) );
    (void) mygets( recipient, sizeof(recipient) );
    (void) mygets( machine, sizeof(machine) );
    if ( *machine == '\0' ) strcpy( machine, host );
    (void) mygets( buf, sizeof(buf) );
    hops = atoi( buf );
    (void) mygets( buf, sizeof(buf) );

    if (ferror(stdin) || abs(hops)>MAX_HOPS || strcmp("MESSAGE FOLLOWS",buf)) {
#ifdef DEBUG
	fprintf(stderr,"readinfo: something went wrong ...\n");
#endif
	/* something went wrong, just die quietly */
	flushinput();
	die();
    }
}



/* we need this so that the sending program doesn't get stuck while
   writing?? */
static
flushinput()
{
    char buf[BUFSIZ];
#ifdef DEBUG
    fprintf(stderr,"flushing input!!!\n");
#endif
    while ( fgets ( buf, BUFSIZ, stdin ) )
	;
}



static
getuserinfo()
{
    char biffrc[MAXPATHLEN];
    struct passwd *pw = getpwnam( biffee );
#ifdef DUMBSHORT
    /* hmmm - maybe we don't really need this, since everyone will know
       to use the complete userid? */
    if ( pw == (struct passwd *)0 ) {
	/* loop through the whole passwd file looking for a
	   truncated match */
	struct utmp *ut;	/* just for sizeof() */
	setpwent();
	while ( (pw = getpwent()) != (struct passwd *) 0 ) {
	    if ( strncmp(biffee,pw->pw_name,sizeof(ut->ut_name)) == 0 ) {
		(void) strcpy( biffee, pw->pw_name );
		break;
	    }
	}
    }
#endif
    if ( pw == (struct passwd *)0 ) {
	flushinput();
	die();
    }
    (void) strcpy( home, pw->pw_dir );
    uid = pw->pw_uid;
    endpwent();

    /* now read biffee's BIFFRC file to set delivery desires */
    (void) strcpy( biffrc, home );
    (void) strcat( biffrc, BIFFRC );
    readBifferFile( biffrc, &globopt );
}


static void
setttyopts( line, uid )
char *line;
int uid;
{
    struct stat sbuf;
    char fname[TTYBIFFRCSIZE];

    /* copy the global options into the tty struct */
    ttyopt = globopt;

    {
	auto int slashes;
	auto char *p;

	for (slashes=0,p=TTYBIFFRC; *p; ++p)
	    if (*p == '/')
		++slashes;
	sprintf(fname, TTYBIFFRC, line, uid);
	for (p=fname; *p; ++p) {
	    if (*p == '/')
		if (slashes)
		    --slashes;
		else
		    *p = '_';
	}
    }
    if (debugFlag)
	debug("biffrc=\"%s\"", fname);

    if ( stat( fname, &sbuf ) == 0 ) {
	/* file exists, check ownership */
	if ( sbuf.st_uid == uid ) {
	    /* right name, right owner, ok to read it */
	    readBifferFile( fname, &ttyopt );
	}
    }
}


static void
readBifferFile( fname, opt )
char *fname;
biffopt *opt;
{
    char buf[BUFSIZ];
    char *keyword, *rest;
    FILE *fp;

    if (debugFlag)
	debug("reading biffer file \"%s\"", fname);

    if ( (fp = fopen( fname, "r" )) != FPNULL ) {
	while( fgets( buf, BUFSIZ, fp ) != NULL ) {
	    parse( buf, &keyword, &rest );
	    if (debugFlag)
		debug("keyword=\"%s\" rest=\"%s\"", keyword, rest);

	    if ( strcmp( keyword, "ignore" ) == 0 ) {
		if ( *rest!='\0' && opt->ignorecount<MAXIGNORES ) {
		    opt->ignores[opt->ignorecount] = strdup( rest );
		    if ( opt->ignores[opt->ignorecount] != CPNULL )
			opt->ignorecount++;
		}
		continue;
	    } else if ( strcmp( keyword, "remotehost" ) == 0 ) {
		opt->tracerlogins = TRUE;
		continue;
	    } else if ( strcmp( keyword, "heretooremote" ) == 0 ) {
		opt->heretoorem = TRUE;
		continue;
	    } else if ( strcmp( keyword, "heretooforward" ) == 0 ) {
		opt->heretoofwd = TRUE;
		continue;
	    } else if ( strcmp( keyword, "heretoo" ) == 0 ) {
		opt->heretoofwd = TRUE;
		opt->heretoorem = TRUE;
		continue;
	    } else if ( strcmp( keyword, "noshowtoline" ) == 0 ) {
		opt->showtoline = BI_OFF;
		continue;
	    } else if ( strcmp( keyword, "showtoline" ) == 0 ) {
		opt->showtoline = parsebits(rest);
		continue;
	    } else if ( strcmp( keyword, "showsubjline" ) == 0 ) {
		opt->showsubjline = parsebits(rest);
		continue;
	    } else if ( strcmp( keyword, "showfromline" ) == 0 ) {
		opt->showfromline = parsebits(rest);
		continue;
#ifdef USERPROG
	    } else if ( strcmp( keyword, "program-network" ) == 0 ) {
		(void) strncpy( opt->uprognetwork, rest,
		    sizeof(opt->uprognetwork)-1 );
		continue;
	    } else if ( strcmp( keyword, "program-nonet" ) == 0 ) {
		(void) strncpy( opt->uprognonet, rest,
		    sizeof(opt->uprognonet)-1 );
		continue;
#endif
	    } else if ( strcmp( keyword, "showbiffee" ) == 0 ) {
		opt->showbiffee = TRUE;
		continue;
	    } else if ( strcasecmp( keyword, "display" ) == 0 ) {
		strncpy( opt->display, rest, sizeof(opt->display)-1 );
		opt->display[ sizeof(opt->display)-1 ] = '\0'; /* terminate */
	    } else if ( strcasecmp( keyword, "hostname" ) == 0 ) {
		strncpy( opt->hostname, rest, sizeof(opt->hostname)-1 );
		opt->hostname[ sizeof(opt->hostname)-1 ] = '\0'; /* terminate */
	    }
	    /* ok, so it's not one of those, split up the rest of the line */
	    trimword( rest );
	    lowercase( rest );
	    if ( strcmp( keyword, "forward" ) == 0 ) {
		(void) strcpy( opt->forwardto, rest );
	    } else if ( strcmp( keyword, "lines" ) == 0 ) {
		opt->lines = atoi( rest );
	    } else if ( strcmp( keyword, "special" ) == 0 ) {
		if ( strcmp( rest, "x" ) == 0
		    || strcmp( rest, "x10" )==0 ) {
		    opt->dospecial = opt->x10special = TRUE;
		} else if ( strcmp( rest, "x11" ) == 0 ) {
		    opt->dospecial = opt->x11special = TRUE;
		}
#ifdef SUNGUESS
		else if ( strcmp( rest, "sunx" )==0
		    || strcmp( rest, "xsun" )==0 ) {
		    opt->dospecial = opt->sunx = TRUE;
		}
#endif
	    }
	}
	(void) fclose(fp );
    }
    if (debugFlag)
	debug("opt->hostname=\"%s\"", opt->hostname);
}

/* look for keywords to set bits in show*line variables */
    static int
parsebits( line )
    char *line;
{
    char *keyword;
    char *rest;
    int mask = 0;

    do {
	parse( line, &keyword, &rest );
	if( strcmp(keyword,"tty") == 0 )
	    mask |= BI_TTY;
	else if( strcmp(keyword,"net") == 0 )
	    mask |= BI_NET;
	else if( strcmp(keyword,"nonet") == 0 )
	    mask |= BI_NON;
	line = rest;
    } while( line[0] != '\0' );

    return( mask ? mask : BI_ALL );
}

/* split the BIFFRC line up into its component parts */
static
parse( buf, keyword, arg1 )
char *buf;
char **keyword, **arg1;
{
    register char *p;
    p = buf;
    while ( *p!='\0' && isspace(*p) )
	p++;
    *keyword = p;
    while ( *p!='\0' && !isspace(*p) ) {
	if ( isupper(*p) ) *p = tolower(*p);
	p++;
    }
    if ( *p != '\0' )
	*p++ = '\0';
    while ( *p!='\0' && isspace(*p) )
	p++;
    *arg1 = p;
    /* now trim excess spaces off the end of the line */
    while ( *p!='\0' )
	p++;
    while ( --p > *arg1 && isascii(*p) && isspace(*p) )
	;
    if ( ++p > *arg1 )
	*p = '\0';
#ifdef unused
/* This stuff is left over from when we used to parse out two arguments
(only) here and lowercase them.  I hate to throw such wonderful code
away :-) */
    while ( *p!='\0' && !isspace(*p) ) {
	if ( isupper(*p) ) *p = tolower(*p);
	p++;
    }
    if ( *p != '\0' )
	*p++ = '\0';
    while ( *p!='\0' && isspace(*p) )
	p++;
    *arg2 = p;
    while ( *p!='\0' && !isspace(*p) ) {
	if ( isupper(*p) ) *p = tolower(*p);
	p++;
    }
    *p = '\0';
#endif /* unused */
}


static
readmsg()
{
    register int i = 0, c, col = 0, linecount = 0;
    while ( (c=getc(stdin))!=EOF && i<MSGBUFSIZE-40 ) {
	if ( c == '\n' ) {
	    if ( linecount++ == 0 )
		msgbuf[i++] = c;
	    col = 0;
	} else {
	    if ( linecount > 0 ) {
		if ( linecount > 1 )
		    msgbuf[i++] = '\n';	/* put out only 1 of them */
		linecount = 0;
	    }
	    switch ( c ) {
		case '\t':
		    do {
			msgbuf[i++] = ' ';
		    } while ( ++col % 8 );
		    break;
		default:
		    msgbuf[i++] = c;
		    col++;
		    break;
	    }
	}
    }
    if ( c != EOF ) {
	char *p = "...more...\n";
	while ( *p != '\0' )
	    msgbuf[i++] = *p++;
    }
    /* absolutely guarantee we end msgbuf with \n\0 */
    if ( i==0 ) {
	msgbuf[0] = '\n';
	msgbuf[1] = '\0';
    } else {
	if ( msgbuf[i-1] != '\n' )
	    msgbuf[i++] = '\n';
	msgbuf[i] = '\0';
    }
}


/* try ignores list on header of message and return TRUE if this message
   should be ignored */
static
ignorethismsg()
{
#define MAXHEAD		20
    char *headends[MAXHEAD+1], *p;
    int i, j, headcount = 0;
    if ( globopt.ignorecount < 1 )
	return( FALSE );	/* no ignore lines, so don't ignore it */
    /* find the ends of the first MAXHEAD header lines */
    p = msgbuf;
    headends[0] = p - 1;
    /* The \n we test for here is the one separating header and body */
    while ( *p && *p!='\n' && headcount<MAXHEAD ) {
	while ( *p != '\n' )
	    p++;
	headends[++headcount] = p;
	*p++ = '\0';
    }
    /* now we can look at each header line individually */
    for ( i=0; i<globopt.ignorecount; i++ ) {
	if ( re_comp( globopt.ignores[i] ) != 0 ) {
	    /* this ignores line is invalid - ignore it */
	    continue;
	}
	for ( j=0; j<headcount; j++ ) {
	    if ( re_exec( headends[j]+1 ) )
		return( TRUE );	/* header is mangled but we don't care */
	}
    }
    /* restore the header \n characters */
    for ( j=1; j<=headcount; j++ )
	*headends[j] = '\n';
    return( FALSE );
}


/* parse forwardto and senditto() */
static int
passiton()
{
    char *p, *ruser=globopt.forwardto, *rhost = host;
    if ( (p=strchr(globopt.forwardto,'@')) != (char *)0 ) {
	*p++ = '\0';
	rhost = p;
    } else if ( (p=strchr(globopt.forwardto,'!')) != (char *)0 ) {
	rhost = globopt.forwardto;
	*p++ = '\0';
	ruser = p;
    }
    if (!*ruser)
	ruser = biffee;
    if (debugFlag)
	debug("ruser=\"%s\" rhost=\"%s\"", ruser, rhost);
    if ( *rhost != '\0' ) {
	int doithere = FALSE;
	/* this test allows you to forward to the same machine as a no-op */
	if ( strcmp(ruser,biffee)!=0 || strcmp(rhost,host)!=0 ) {
	    if ( ! senditto( ruser, rhost, FALSE ) ) {
		/* couldn't send it where they wanted, so do it here */
		doithere = TRUE;
		if (debugFlag)
		    debug("senditto failed");
	    }
	} else if ( ! globopt.heretoofwd ) {
	    doithere = TRUE;
	}
	if ( doithere )
	    biffem();	/* so they get it somewhere */
    }
}

/* forward the message on to another machine */
static int
senditto( ruser, rhost, tracing )
char *ruser, *rhost;
int tracing;	/* TRUE if following rlogins back to source so ignore
		   any forwarding the user asks for ... */
{
    int	sock;
    struct sockaddr_in	sin;
    static struct servent *sp = NULL;
    struct hostent *hp;
    FILE *fp;

    if (debugFlag)
	debug("senditto(%s,%s,%d)", ruser, rhost, tracing);

    if ( hops < 0 )
	tracing = TRUE;
    if (sp == NULL)
	    sp = getservbyname( SERVICE, "tcp" );
    if (sp == NULL)
	fatal( "%s/tcp not defined in services file", SERVICE );
    hp = gethostbyname( rhost );
    if (hp == NULL)	/* no such host */
	return( FALSE );
    memset((char *)&sin, '\0', sizeof(sin) );
    memcpy( (char *)&sin.sin_addr, hp->h_addr, hp->h_length );
    sin.sin_family	= hp->h_addrtype;
    sin.sin_port	= sp->s_port;
    sock = socket( hp->h_addrtype, SOCK_STREAM, 0 );
    if (sock < 0)
	fatal( "couldn't create socket: %s", strerror(errno) );
    if (connect( sock, (struct sockaddr*)&sin, sizeof(sin) ) < 0) {
	eprintf( "couldn't connect to '%s': %s", rhost, strerror(errno) );
	(void) close( sock );
	return( FALSE );
    }
    if ( (fp = fdopen(sock,"w")) == FPNULL ) {
	eprintf( "couldn't fdopen the socket" );
	(void) close( sock );
	return( FALSE );
    }
    /* now write the stream to the remote biffer */
    (void) fprintf( fp, "%s\n", ruser );
    (void) fprintf( fp, "%s\n", recipient );
    (void) fprintf( fp, "%s\n", machine );
    (void) fprintf( fp, "%d\n", (abs(hops)+1)*( tracing ? -1 : 1 ) );
    (void) fprintf( fp, "%s\n", "MESSAGE FOLLOWS" );
    (void) fputs( msgbuf, fp );
    (void) fclose( fp );
    return( TRUE );
}


static jmp_buf timeout;

static SignalType
wakeup(signum)
{
    longjmp( timeout, 1 );
}


#if defined(NO_TERMIOS)
#	include <sgtty.h>
	static char *
need_cr(stream)
	FILE *stream;
{
	auto struct sgttyb gttybuf;
	ioctl(fileno(stream), TIOCGETP, &gttybuf);
	return (gttybuf.sg_flags&CRMOD) && !(gttybuf.sg_flags&RAW) ? "" : "\r";
}
#else   /* termios */
	static char *
need_cr(stream)
	FILE *stream;
{
	auto struct termios termios;
	tcgetattr(fileno(stream), &termios);
	return ((termios.c_oflag&ONLCR) && (termios.c_lflag&ICANON)) ? "" : "\r";
}
#endif  /* termios */


/* biff the user on this machine */
static
biffem()
{
    register char *p;
    struct utmp ut;
    FILE *fp, *term;
    char terminal[40];	/* should be enough ... */
    struct stat sbuf;
#if defined(TTYSX) || defined(USERPROG)
    struct ttyent *ty;
#endif
    char line[sizeof(ut.ut_line)+5], *cr;
#if defined(UTMP_NO_UT_HOST)
    char *rhost = "";
#else
    char rhost[HOSTSIZE];
#endif

    if (!biffee)
	if (debugFlag)
	    debug("no biffee");
    if (debugFlag)
	debug("biffem biffee=\"%s\"", biffee);
    if ( (fp=fopen( UTMP_FILE, "r" )) == FPNULL )
	fatal( "Couldn't open utmp file '%s': %s", UTMP_FILE, strerror(errno) );
    /* keep on until end of file in case s/he's signed on more than once */
    while ( fread( (char *)&ut, sizeof(struct utmp), 1, fp ) != 0 ) {
	if (!ut.ut_name[0])
	    continue;
	if (strncmp( biffee, ut.ut_name, sizeof(ut.ut_name) ) != 0)
	    continue;
	if (debugFlag)
	    debug("found biffee");
#ifdef USER_PROCESS
	if ( ut.ut_type != USER_PROCESS )
	    continue;
#endif

	/* Make null-terminated copies of utmp data */
	(void) strncpy( line, ut.ut_line, sizeof(ut.ut_line) );
	line[sizeof(ut.ut_line)] = '\0';
#if !defined(UTMP_NO_UT_HOST)
	(void) strncpy( rhost, ut.ut_host, sizeof(ut.ut_host)-1 );
	rhost[sizeof(ut.ut_host)-1] = '\0';
#endif
	if (debugFlag)
	    debug("on line '%s'",line);

	/* stat it so we can check some stuff */
	(void) sprintf( terminal, "%s%s", DEV, line );
	if ( stat(terminal,&sbuf) == -1 )
	    /* couldn't stat it, ignore ... */
	    continue;
	
	/* make sure it's not a leftover entry - compare utmp entry to tty */
	if ( sbuf.st_uid != uid )
	    /* not the right person, ignore */
	    continue;

	/* read the tty specific options file and set the ttyopt struct */
	setttyopts( line, uid );

#if defined(UTMP_NO_UT_HOST)
	rhost = *ttyopt.hostname ? ttyopt.hostname : "";
	if (debugFlag)
	    debug("no_ut_host rhost=\"%s\"", rhost?rhost:"");
#else
	if ( *ttyopt.hostname ) {
	    (void) strncpy( rhost, ttyopt.hostname, sizeof(rhost)-1 );
	    rhost[sizeof(rhost)] = '\0';	/* terminate */
	    if (debugFlag)
		debug("ut_host 1 rhost=\"%s\"", rhost?rhost:"");
	}
	else {
	    if (debugFlag)
		debug("ut_host 2 rhost=\"%s\"", rhost?rhost:"");
	}
#endif

	/* is it an rlogin session? a : will be assumed to mean an X
	   DISPLAY and an xterm invoked with +ut */
	if ( ttyopt.tracerlogins && *rhost!='\0'
	    && strchr(rhost,':')==CPNULL ) {
	    int ok = TRUE;
	    /* only send it on if it's not this host */
	    if ( strcmp( host, rhost ) ) {
		/* send it on to the original host */
		ok = senditto( biffee, rhost, TRUE );
	    }
	    /* i.e. if we tried and failed to send it on (e.g. remote
	       machine is not running biffer (like a terminal server
	       perhaps?)) then fall back to doing it here */
	    if ( ok && ! ttyopt.heretoorem )
		continue;
	}

	/* see if we're allowed to write */
	if ( (sbuf.st_mode&ALLOW) == 0 )
	    /* biff bit off */
	    continue;

#ifdef USERPROG
	if (debugFlag)
	    debug("doing USERPROG");
	/*
	 * See if user has a private biff program in his/her .biffer file.
	 * I'm paranoid about root problems, so I don't let root do this.
	 * We pass stripped From, To, and Subject lines via environment
	 * variables BIFFER_FROM, BIFFER_TO, BIFFER_SUBJECT.
	 * We also can select which of these lines to add to the message body.
	 * The body of the stored message is sent to the user's
	 * program as standard input.  We time out after 5 minutes.
	 * (Should be a way to set the timeout somehow.)
	 * -IAN!
	 */
	if (debugFlag)
	    debug("uprognetwork=\"%s\" uprognonet=\"%s\" uid=%d",
		ttyopt.uprognetwork, ttyopt.uprognonet, uid);
	if( (ttyopt.uprognetwork[0] != '\0' || ttyopt.uprognonet[0] != '\0')
	    && uid > 0 ){
	    int isnetwork;

	    if( (ty=getttynam(line)) == NULL )
		goto dodefault;

	    if (debugFlag)
		debug("ttynam=\"%s\"", ty);

	    isnetwork = (strncmp(ty->ty_type,"network",8) == 0);

	    if( ttyopt.uprognetwork[0] != '\0' && isnetwork )
		uprog = ttyopt.uprognetwork;
	    else if( ttyopt.uprognonet[0] != '\0' && !isnetwork )
		    uprog = ttyopt.uprognonet;
	    else
		goto dodefault;

	    /*
	     * If you want the default biffer action for some type of
	     * terminal, use this silly string as the program name.
	     * This is for future expansion.  -IAN!
	     */
#define DEFAULT "$*$DEFAULT"
	    if( strcmp(uprog,DEFAULT) == 0 )
		goto dodefault;
		
#ifndef DOPROG
	    if (debugFlag)
		debug("calling doprog(*, %d, \"%s\", \"%s\", \"%s\", \"%s\", %d)", isnetwork, ty->ty_type, terminal, rhost, uprog, uid);

	    if ( doprog(&ttyopt,isnetwork,ty->ty_type,terminal,rhost,uprog,uid) == -1 )
		/* it failed, so do the other thing */
		goto dodefault;
	    continue;
#else /* DOPROG */
	    /*
	     * Set up our execution environment for the coming popen()
	     */
	    if (debugFlag)
		debug("DOPROG forking");
	    switch( fork() ){
	    case -1:
		goto dodefault;
	    case 0:
		break;			/* child goes on to exec program */
	    default:
		continue;		/* parent goes back for more */
	    }
	    setpgrp(0,getpid());	/* so kill(0,) works below */
	    /*
	     * I'm about to exec a program written by "uid", so I
	     * want to run this process as that user, not as me.
	     * But unless biffer was called from root, I can't setuid().
	     * So this biff will fail for non-root uses of biffer.
	     * Maybe biffer should run setuid root?  -IAN!
	     */
	    if( setuid(uid) == -1 )
		exit(1);
	    if( chdir(home) == -1 )
		exit(1);

	    if( ty->ty_type[0] != '\0' )
		setenv("BIFFER_TERM",ty->ty_type,1);
	    if( terminal[0] != '\0' )
		setenv("BIFFER_TTY",terminal,1);
	    if( rhost[0] != '\0' )
		setenv("BIFFER_REMOTEHOST",rhost,1);

	    /*
	     * Parse the header of the message and set environment
	     * variables with just the text portion.
	     */
#define EQ(str,env,save) \
	    if( strncmp(p,str,sizeof(str)-1) == 0 ){\
		register char *endp = strchr(p,'\n');\
		if( endp != NULL )\
			*endp = '\0';\
		strcpy(save,p+sizeof(str)-1);\
		setenv(env,save,1);\
		if( endp != NULL ){\
			*endp = '\n';\
			p = endp + 1;\
		} else\
			p = "";\
		continue;\
	    }

	    for ( p=msgbuf; *p && *p != '\n'; ) {
		EQ("From: ",    "BIFFER_FROM",    savefromline);
		EQ("To: ",      "BIFFER_TO",      savetoline);
		EQ("Subject: ", "BIFFER_SUBJECT", savesubjline);
		while( *p && *p++ != '\n' )
		    ;
	    }

	    /*
	     * Strip leading blank lines from the text.
	     */
	    while( *p && *p == '\n' )
		++p;

	    /*
	     * Write the rest of the text to the user's process.
	     * Time out after 5 minutes and SIGTERM us all.
	     */
	    if ( setjmp( timeout ) == 0 ) {
		(void) signal( SIGALRM, wakeup );
		(void) alarm( 5 * 60 );	/* 5 minute timeout */
		if ( (term=popen(uprog,"w")) == NULL )
		    exit(1);
		/*
		 * Print the header lines the user wants into the program.
		 * Then write out the text body into the program.
		 */
#define DO(mask,nam,str) \
		if( ((isnetwork && SHOWNET(mask)) || \
		   (!isnetwork && SHOWNON(mask))) && str[0] != '\0' ) \
			fprintf(term, "%s%s\n", nam, str);
		DO(ttyopt.showtoline,   "To: ",      savetoline);
		DO(ttyopt.showfromline, "From: ",    savefromline);
		DO(ttyopt.showsubjline, "Subject: ", savesubjline);
		fwrite(p,1,strlen(p),term);
		pclose(term);	/* this waits for child */
	    } else
		kill(0,SIGTERM);	/* signal this process group */
	    exit(0);
#endif /* DOPROG */
	}
dodefault:
#else  /*!USERPROG*/
	if (debugFlag)
	    debug("not USERPROG");
#endif /*!USERPROG*/

	/* now since we know user wants to be biffed, see if it's a
	   special terminal that we can surprise him on (or something) */
#ifdef TTYSX
	if ( *rhost=='\0'	/* not an rlogin or xterm +ut session */
	     && ttyopt.dospecial ) {
	    if ( (ty = getttynam(line)) != NULL ) {
		if ( ttyopt.x10special
		    && strncmp( ty->ty_type, "xterm", 5 ) == 0 ) {
		    X10biffer( terminal, ttyopt.lines );
		    continue;
		}
	    }
	}
#endif

#ifdef SUNGUESS
	if ( ttyopt.sunx && strcmp( line, "console" ) == 0 ) {
#ifdef X11
	    if ( ttyopt.x11special ) {
		if (debugFlag)
		    debug("x11special");
		X11biffer( &ttyopt, ttyopt.display, uid );
	    } else
#endif
#ifdef X10
	    if ( ttyopt.x10special ) {
		/* X10biffer's argument is used to generate the DISPLAY
		   variable, so put a 0 at the end of it so it can guess
		   right.  It used to seem to work when it was (I think)
		   trying to open "hostname:e" (the e from "console").
		   But now - make it work better - I hope. */
		X10biffer( "0", ttyopt.lines );
	    } else
#endif
#ifdef SUNGUESSDEF10
	    {
		/* default to X10 if not specified */
		X10biffer( "0", ttyopt.lines );
	    }
#else  /* !sunguessdef10 */
	    {
		/* default to X11 if not specified */
		if (debugFlag)
		    debug("x11 default");
		X11biffer( &ttyopt, ttyopt.display, uid );
	    }
#endif /* !sunguessdef10 */
	    continue;
	}
#endif /* SUNGUESS */

#ifdef X11
	/* use the particular host they are using now, if known */
	if ( *rhost!='\0' && strchr(rhost,':')!=CPNULL ) {
	    if (debugFlag)
		debug("x11 rhost=\"%s\"", rhost);
	    if ( X11biffer( &ttyopt, rhost, uid ) == 0 )
		continue;	/* success */
	    if (debugFlag)
		debug("failed");
	}
	/* otherwise, do we have a default display to use */
	if ( *ttyopt.display ) {
	    if (debugFlag)
		debug("x11 display");
	    if ( X11biffer( &ttyopt, ttyopt.display, uid ) == 0 )
		continue;	/* success */
	}
#endif

	if ( (term=fopen(terminal,"w")) == FPNULL )
	    /* couldn't write */
	    continue;
        cr = need_cr(term);
	/* write to the terminal */
	if ( setjmp( timeout ) == 0 ) {
	    SignalType (*osig)() = signal( SIGALRM, wakeup );
	    int linecount = -1;	/* so the blank line after headers isn't
				   included in the linecount */
	    (void) alarm( TIMEOUT );
	    if ( ttyopt.showbiffee )
		fprintf(term,
		    "%s\n%s@%s: \007New mail for %s@%s\007 has arrived:%s\n",
		    cr, biffee, host, recipient, machine, cr);
	    else
		fprintf(term,
		    "%s\n\007New mail for %s@%s\007 has arrived:%s\n",
		    cr, recipient, machine, cr);
	    fprintf(term,"----%s\n", cr);
	    inheader = TRUE;
	    for ( p=msgbuf; *p; ) {
		if ( inheader && skipHeader( p ) ) {
		    while ( *p && *p++ != '\n' )
			;
		} else {
		    while ( *p && *p != '\n' )
			putc( *p++, term );
		    if ( *p == '\n' ) fprintf( term, " %s%c", cr, *p++ );
		    if ( !inheader && ++linecount >= ttyopt.lines )
			break;	/* seen all they want to see */
		}
	    }
	    if ( *p )
		fprintf( term, "...more...\n" );
	    fprintf(term,"----%s\n", cr);
	    /* we need this fflush() or else it would hang in the
	       fclose() after we've dealt with the alarm() */
	    (void) fflush( term );
	    (void) alarm( 0 );
	    (void) signal(SIGALRM, osig);
	}
	(void) fclose( term );
    }
    if (debugFlag)
	debug("closing utmp");
    (void) fclose( fp );
}

/* return FALSE if we shouldn't skip; return TRUE if we should skip */
int
skipHeader( p )
char *p;
{
    if ( ( SHOWTTY(ttyopt.showfromline) && strncmp( p, "From: ",    6 ) == 0 )
      || ( SHOWTTY(ttyopt.showtoline)   && strncmp( p, "To: ",      4 ) == 0 )
      || ( SHOWTTY(ttyopt.showsubjline) && strncmp( p, "Subject: ", 9 ) == 0 )
	)
	return( FALSE );	/* don't skip it, print it */
    if ( inheader && *p == '\n' ) {
	inheader = FALSE;
	return( FALSE );
    }
    return( TRUE );		/* skip this header line */
}
