[alt.sources] SysV versions of finger and last, with lastlog.

woods@robohack.UUCP (Greg A. Woods) (01/01/90)

Sorry, I didn't have the finger or last manual pages, and I haven't
written one for lastlog.  The finger and last sources didn't have the
BSD copyright, but I assume they apply.  Lastlog is PD.

----------- Cut Here ----------
#! /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 <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  ReadMe.finger Makefile finger.c last.c lastlog.c
# Wrapped by woods@robohack on Sun Dec 31 17:36:05 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'ReadMe.finger' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'ReadMe.finger'\"
else
echo shar: Extracting \"'ReadMe.finger'\" \(3255 characters\)
sed "s/^X//" >'ReadMe.finger' <<'END_OF_FILE'
Xwoods@robohack -- Sun Dec 31 16:45:30 EST 1989
X
XI finally have managed to find time to get finger working nicely on my
XSysV machine (an AT&T 3B2/400 running SysVr3.1v2).  This holiday
Xseason has been quite relaxing!
X
XWhile I tried to make as few changes as possible, I ended up making
Xquite a few, and as such the complete source is provided, not just the
Xdiffs.  I have removed almost all of the special localizations and I
Xhave generalized the GECOS field format a bit.  (See the comment at
Xthe top of the source for the GECOS field format description.)
X
X	1. There is now a macro, ISMODEM(), which should be defined to
X	indicate which tty's are dialup.
X
X	2. The "office" field now has no special character flags, and
X	now the first 14 chars are simply printed as the office.
X
X	3. There is a general info field which is always read and
X	printed if it exists.
X
X	3. The TTY field in the -l and -w displays is now LMAX-1 chars
X	wide (sizeof utmp.ut_line - 1), and special recognition of tty
X	is gone.
X
X	4. The messages indicator in the -l and -w displays now has
X	its own field (M), but is still displayed in the same manner.
X
X	5. Various changes to the code which formats and prints the
X	reports.  The long form is considerably more consistent for
X	various forms of the GECOS field.
X
XI have written a companion programme, lastlog, to be called from
X/etc/profile, which updates the entry for the calling user in
X/usr/adm/lastlog.  The records in this file are copies of the
X/etc/utmp record for the current login session, and finger has been
Xmodified to understand the new format.  This programme is designed to
Xwork the way it does, such that it can't be fooled too easily.
XLastlog should be setgid to adm, and /usr/adm/lastlog should be
Xwriteable by the group adm for the best results!
X
XAlso included is a port of 'last'.  This is handy, but when running
XSysV Accounting, not all that great.
X
XThere were a few more fixes and changes to finger as well, including
Xone courtesy of Steve Blasingame, who also did an initial port to SysV.
X
XI doubt this is derived from the most recent version of finger, as it
Xhas no support for remote fingering.  For a large part of the current
XSysV community this won't be a big loss.  However, as networking
Xbecomes more widespread, this feature will be required everywhere.
X
XAfter spending some time in the guts of finger, I would suggest that
Xit not be worked on too much more, but rather it should be
Xre-written.  It is far better than some BSD utilities, but it has
Xquite a few hacks, most of which seem to have been done in order to
Xconform to some existing practice on the author's machine.  Perhaps a
Xbetter thought out standard would help.  In addition, the formatting
Xcode seems to have been hacked to make a pleasing output, not
Xdesigned.  There are some pretty silly things, such as printf's for
Xblanks, and the worst of them I've removed.  I almost ran the source
Xthrough cb or indent, and you'll notice slight variances in style
Xwhere my changes were thickest, but I did resist the temptation.
X
XAnyway, here it is.  It does what I want, and works well for me!
XLet me know if you find any bugs.  If there is a great demand for
Xfinger, perhaps I could re-post it to comp.sources.{misc|unix}.
X
X			HAPPY NEW YEAR!
END_OF_FILE
if test 3255 -ne `wc -c <'ReadMe.finger'`; then
    echo shar: \"'ReadMe.finger'\" unpacked with wrong size!
fi
# end of 'ReadMe.finger'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(895 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X#
X#	Makefile - general makefile for normal one-file commands
X#
X# This file should not require editing when new commands are added.
X#
X# To install a new command:
X#
X#	'make CMD=<command_name> install'
X#
X# Created:
X#
X# woods@tslanpar: Tue May 19 22:33:14 EDT 1987
X#
X# BUGS:
X#
X# Unfortunately, this breaks the 'make cat cmp diff grep' idiom when
X# attempting to install.  To do that would require a 'supermake'(?) and
X# may require a makefile for each command, and/or changes to built-in
X# rules in make.
X#
X
XSRCDIR = /usr/src/local/cmd
XBINDIR = /usr/local/bin
XMANDIR = /usr/local/man/man1
XMANDEST = man1
X
XCFLAGS = -O -DSYSV -DSYSVR3 $(DEBUG) $(PROF)
XLDFLAGS =
X
Xinstall: $(BINDIR)/$(CMD) $(MANDIR)/$(MANDEST)/$(CMD).1
X
X$(BINDIR)/$(CMD): $(CMD)
X	install -c -m 755 $(CMD) $(BINDIR)
X
X$(MANDIR)/$(MANDEST)/$(CMD).1: $(CMD).1
X	install -c -m 644 $(CMD).1 $(MANDIR)/$(MANDEST)
X
Xclean:
X	rm -f a.out core *.o
END_OF_FILE
if test 895 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'finger.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'finger.c'\"
else
echo shar: Extracting \"'finger.c'\" \(32249 characters\)
sed "s/^X//" >'finger.c' <<'END_OF_FILE'
X#ifdef SYSVR3
X# ident "@(#)finger.c	4.1 (Berkeley) 10/1/80";
X# ident	"@(#)finger.c	1.1.1.1	89/12/31 17:30:03 [LOCAL] (Greg A. Woods)"
X#else
X# ifndef lint
Xstatic char *sccsid = "@(#)finger.c	4.1 (Berkeley) 10/1/80";
Xstatic cahr *sccsloc= "@(#)finger.c	1.1.1.1\t89/12/31 17:30:03 [LOCAL] (Greg A. Woods)";
X# endif
X#endif
X
X/*  This is a finger program.  It prints out useful information about users
X *  by digging it up from various system files.  It is not very portable
X *  because the most useful parts of the information (the full user name,
X *  office, and phone numbers) are all stored in the VAX-unused gecos field
X *  of /etc/passwd, which, unfortunately, other UNIXes use for other things.
X *
X *  There are three output formats, all of which give login name, teletype
X *  line number, and login time.  The short output format is reminiscent
X *  of finger on ITS, and gives one line of information per user containing
X *  in addition to the minimum basic requirements (MBR), the full name of
X *  the user, his idle time and office location and phone number.  The
X *  quick style output is UNIX who-like, giving only name, teletype and
X *  login time.  Finally, the long style output give the same information
X *  as the short (in more legible format), the home directory and shell
X *  of the user, and, if it exits, a copy of the file .plan in the users
X *  home directory.  Finger may be called with or without a list of people
X *  to finger -- if no list is given, all the people currently logged in
X *  are fingered.
X *
X *  The program is validly called by one of the following:
X *
X *	finger			{short form list of users}
X *	finger -l		{long form list of users}
X *	finger -b		{briefer long form list of users}
X *	finger -q		{quick list of users}
X *	finger -i		{quick list of users with idle times}
X *	finger namelist		{long format list of specified users}
X *	finger -s namelist	{short format list of specified users}
X *	finger -w namelist	{narrow short format list of specified users}
X *
X *  where 'namelist' is a list of users login names.
X *  The other options can all be given after one '-', or each can have its
X *  own '-'.  The -f option disables the printing of headers for short and
X *  quick outputs.  The -b option briefens long format outputs.  The -p
X *  option turns off plans for long format outputs.
X */
X
X/*
X * The GECOS field should have the following format:
X *
X *	:Real Name,location,office,home,extra:
X *
X * where:
X *	location is text describing office location (14 chars)
X *	office and home are phone numbers (7 or 10 digits only, no -'s)
X *	extra is text for misc. data (1 line's worth)
X *
X * If the first field is not a phone #, then it will be the office.  If there
X * is no office, it may be left blank (",,").  Any other, or all, field(s)
X * may be omitted.  If there is only one phone number, no distinction will be
X * made between home and office, unless there is an office, in which case the
X * phone number defaults to home, unless a blank home field is given.s
X */
X
X/*
X *  Fixes:
X * 	Steve Blasingame 09/01/86
X *	  fixed bug associated with reads from .plan and .project files
X *	  stored in a char. This prevented recognition of EOF.
X *
X *	Greg Woods 12/31/89
X *	  Various changes to update for generic use and final porting
X *	  to SysV.  Fixed arg2 in call to lseek, casting it to long.
X */
X
X#include	<sys/types.h>
X#include	<sys/stat.h>
X#include	<utmp.h>
X#include	<signal.h>
X#include	<pwd.h>
X#include	<stdio.h>
X#ifndef SYSV
X# include	<sgtty.h>
X# include	<sccs.h>
X# include	<lastlog.h>
X#else
X# include	<string.h>
X# include	<memory.h>
X# include	<unistd.h>
X#endif
X#include	<time.h>
X
X#ifndef SEEK_SET	/* from unistd.h */
X/* Symbolic constants for the "lseek" routine: */
X# define SEEK_SET	0	/* Set file pointer to "offset" */
X# define SEEK_CUR	1	/* Set file pointer to current plus "offset" */
X# define SEEK_END	2	/* Set file pointer to EOF plus "offset" */
X#endif
X
X#define		ASTERISK	'*'	/* ignore this in real name */
X#define		BLANK		' '	/* blank character (i.e. space) */
X#define		CAPITALIZE	0137&	/* capitalize character macro */
X#define		COMMA		','	/* separator in pw_gecos field */
X#define		COMMAND		'-'	/* command line flag char */
X#define		LINEBREAK	012	/* line feed */
X#define		NULLSTR		""	/* the null string, opposed to NULL */
X#define		SAMENAME	'&'	/* repeat login name in real name */
X#define		TALKABLE	0222	/* tty is writeable if 222 mode */
X#ifndef LASTLOG_FILE
X# define	LASTLOG_FILE	"/usr/adm/lastlog"
X#endif
X#ifndef ISMODEM
X# define	ISMODEM(tty)	(strncmp(tty, "tty13", 5) == 0)
X#endif
X
Xstruct	utmp	utmp;	/* for sizeof */
X#define NMAX sizeof(utmp.ut_name)
X#define LMAX sizeof(utmp.ut_line)
X
Xstruct  person  {			/* one for each person fingered */
X	char		name[NMAX+1];	/* login name */
X	char		tty[LMAX+1];	/* NULL terminated tty line */
X	long		loginat;	/* time of login (possibly last) */
X	long		idletime;	/* how long idle (if logged in) */
X	short int	loggedin;	/* flag for being logged in */
X	short int	writeable;	/* flag for tty being writeable */
X	char		*realname;	/* pointer to full name */
X	char		*office;	/* pointer to office name */
X	char		*officephone;	/* pointer to office phone no. */
X	char		*homephone;	/* pointer to home phone no. */
X	char		*random;	/* for any random stuff in pw_gecos */
X	struct  passwd	*pwd;		/* structure of /etc/passwd stuff */
X	struct  person	*link;		/* link to next person */
X};
X
Xstruct  passwd			*NILPWD = 0;
Xstruct  person			*NILPERS = 0;
X
Xint		persize		= sizeof( struct person );
Xint		pwdsize		= sizeof( struct passwd );
X
Xchar		LASTLOG[]	= LASTLOG_FILE;		/* last login info */
X#ifndef SYSV
Xchar		USERLOG[]	= "/etc/utmp";		/* who is logged in */
X#else
Xchar		USERLOG[]	= UTMP_FILE;		/* who is logged in */
X#endif
Xchar		outbuf[BUFSIZ];				/* output buffer */
Xchar		*ctime();
X
Xint		unbrief		= 1;		/* -b option default */
Xint		header		= 1;		/* -f option default */
Xint		hack		= 1;		/* -h option default */
Xint		idle		= 0;		/* -i option default */
Xint		large		= 0;		/* -l option default */
Xint		match		= 1;		/* -m option default */
Xint		plan		= 1;		/* -p option default */
Xint		unquick		= 1;		/* -q option default */
Xint		small		= 0;		/* -s option default */
Xint		wide		= 1;		/* -w option default */
X
Xint		lf;
Xint		llopenerr;
X
Xlong		tloc;				/* current time */
X
X
X
Xmain( argc, argv )
X
Xint	argc;
Xchar	*argv[];
X
X{
X	FILE			*fp,  *fopen();		/* for plans */
X	struct  passwd		*getpwent();		/* read /etc/passwd */
X	struct  person		*person1,  *p,  *pend;	/* people */
X	struct  passwd		*pw;			/* temporary */
X	struct  utmp		user;			/*   ditto   */
X	char			*malloc();
X	char			*s,  *pn,  *ln;
X	int			c;			/* storage for getc */
X	char			*PLAN = "/.plan";	/* what plan file is */
X	char			*PROJ = "/.project";	/* what project file */
X	int			PLANLEN = strlen( PLAN );
X	int			PROJLEN = strlen( PROJ );
X	int			numnames = 0;
X	int			orgnumnames;
X	int			uf;
X	int			usize = sizeof user;
X	int			unshort;
X	int			i, j;
X	int			fngrlogin;
X
X	setbuf( stdout, outbuf );			/* buffer output */
X
X	/*  parse command line for (optional) arguments */
X
X	i = 1;
X	if(  strcmp( *argv, "sh" )  )  {
X		fngrlogin = 0;
X		while( i++ < argc  &&  (*++argv)[0] == COMMAND )  {
X			for( s = argv[0] + 1; *s != NULL; s++ )  {
X				switch  (*s)  {
X
X				case 'b':
X					unbrief = 0;
X					break;
X
X				case 'f':
X					header = 0;
X					break;
X
X				case 'h':
X					hack = 0;
X					break;
X
X				case 'i':
X					idle = 1;
X					unquick = 0;
X					break;
X
X				case 'l':
X					large = 1;
X					break;
X
X				case 'm':
X					match = 0;
X					break;
X
X				case 'p':
X					plan = 0;
X					break;
X
X				case 'q':
X					unquick = 0;
X					break;
X
X				case 's':
X					small = 1;
X					break;
X
X				case 'w':
X					wide = 0;
X					break;
X
X				default:
X					fprintf( stderr, "finger: Usage -- 'finger [-bfhilmpqsw] [login1 [login2 ...] ]'\n" );
X					exit( 1 );
X				}
X			}
X		}
X	}
X	else  {
X		fngrlogin = 1;
X	}
X	if( unquick )  {
X		time( &tloc );
X	}
X	else  {
X		if( idle )  {
X			time( &tloc );
X		}
X	}
X
X	/*  i > argc means no login names given so get them by reading USERLOG */
X
X	if(  (i > argc)  ||  fngrlogin  )  {
X		unshort = large;
X		if(  ( uf = open(USERLOG, 0) ) >= 0  )  {
X#ifndef SYSV
X			user.ut_name[0] = NULL;
X			while( user.ut_name[0] == NULL )  {
X				if( read( uf, (char *) &user, usize ) != usize ) {
X					printf( "\nNo one logged on\n" );
X					exit( 0 );
X				}
X			}
X#else
X                        /* SYSV has lots of entry types */
X			user.ut_type = EMPTY;
X			while( user.ut_type != USER_PROCESS ) {
X				if( read( uf, (char *) &user, usize ) != usize ) {  
X					printf( "\nNo one logged on\n" );
X					exit( 0 );
X				}
X			}
X#endif
X			person1 = (struct person  *) malloc( persize );
X			for( j = 0; j < NMAX; j++ )  {
X				person1->tty[j] = user.ut_line[j];
X				person1->name[j] = user.ut_name[j];
X			}
X			person1->name[NMAX] = NULL;
X			person1->tty[NMAX] = NULL;
X			person1->loginat = user.ut_time;
X			person1->pwd = NILPWD;
X			person1->loggedin = 1;
X			numnames++;
X			p = person1;
X			while( read( uf, (char *) &user, usize ) == usize )  {
X#ifndef	SYSV
X				if( user.ut_name[0] == NULL )  continue;
X#else
X				if( user.ut_type != USER_PROCESS )  continue;
X#endif
X				p->link = (struct person  *) malloc( persize );
X				p = p->link;
X				for( j = 0; j < NMAX; j++ )  {
X					p->tty[j] = user.ut_line[j];
X					p->name[j] = user.ut_name[j];
X				}
X				p->name[NMAX] = NULL;
X				p->tty[NMAX] = NULL;
X				p->loginat = user.ut_time;
X				p->pwd = NILPWD;
X				p->loggedin = 1;
X				numnames++;
X			}
X			p->link = NILPERS;
X			close( uf );
X		}
X		else  {
X			fprintf( stderr, "finger: error opening %s\n", USERLOG );
X			exit( 2 );
X		}
X
X		/*  if we are doing it, read /etc/passwd for the useful info */
X
X		if( unquick )  {
X			setpwent();
X			fwopen();
X			i = numnames;
X			while(  ( (pw = getpwent()) != NILPWD )  &&  ( i > 0 )  )  {
X				p = person1;
X				do  {
X					if( p->pwd == NILPWD )  {
X						if(  strcmp( p->name, pw->pw_name ) == 0  )  {
X							p->pwd = (struct passwd  *) malloc( pwdsize );
X							pwdcopy( p->pwd, pw );
X							decode( p );
X							i--;
X						}
X					}
X					p = p->link;
X				}  
X				while( p != NILPERS );
X			}
X			fwclose();
X			endpwent();
X		}
X	}
X
X	/* get names from command line and check to see if they're  logged in */
X
X	else  {
X		unshort = ( small == 1 ? 0 : 1 );
X		i++;
X		person1 = (struct person  *) malloc( persize );
X		strcpy(  person1->name, (argv++)[ 0 ]  );
X		person1->loggedin = 0;
X		person1->pwd = NILPWD;
X		numnames++;
X		p = person1;
X		while( i++ <= argc )  {
X			p->link = (struct person  *) malloc( persize );
X			p = p->link;
X			strcpy(  p->name, (argv++)[ 0 ]  );
X			p->loggedin = 0;
X			p->pwd = NILPWD;
X			numnames++;
X		}
X		p->link = NILPERS;
X		pend = p;
X
X		/*  if we are doing it, read /etc/passwd for the useful info */
X
X		orgnumnames = numnames;
X		if( unquick )  {
X			setpwent();
X			while(  ( pw = getpwent() ) != NILPWD  )  {
X				p = person1;
X				i = 0;
X				do  {
X					if( strcmp( p->name, pw->pw_name ) == 0    ||
X					    matchcmp( pw->pw_gecos, pw->pw_name, p->name ) )  {
X						if( p->pwd == NILPWD )  {
X							p->pwd = (struct passwd  *) malloc( pwdsize );
X							pwdcopy( p->pwd, pw );
X						}
X						else  {	/* handle multiple logins -- append new
X																													   "duplicate" entry to end of list */
X							pend->link = (struct person  *) malloc(persize);
X							pend = pend->link;
X							pend->link = NILPERS;
X							strcpy( pend->name, p->name );
X							pend->pwd = (struct passwd  *) malloc(pwdsize);
X							pwdcopy( pend->pwd, pw );
X							numnames++;
X						}
X					}
X					p = p->link;
X				}  
X				while( ++i < orgnumnames );
X			}
X			endpwent();
X		}
X
X		/*  Now get login information */
X
X		if(  ( uf = open(USERLOG, 0) ) >= 0  )  {
X			while( read( uf, (char *) &user, usize ) == usize )  {
X#ifndef	SYSV
X				if( user.ut_name[0] == NULL )  continue;
X#else
X				if( user.ut_type != USER_PROCESS )  continue;
X#endif
X				p = person1;
X				do  {
X					pw = p->pwd;
X					if( pw == NILPWD )  {
X						i = ( strcmp( p->name, user.ut_name ) ? 0 : NMAX );
X					}
X					else  {
X						i = 0;
X						while(  (i < NMAX)  &&
X						    ( pw->pw_name[i] == user.ut_name[i])  )  {
X							if( pw->pw_name[i] == NULL )  {
X								i = NMAX;
X								break;
X							}
X							i++;
X						}
X					}
X					if( i == NMAX )  {
X						if( p->loggedin == 1 )  {
X							pend->link = (struct person  *) malloc(persize);
X							pend = pend->link;
X							pend->link = NILPERS;
X							strcpy( pend->name, p->name );
X							for( j = 0; j < NMAX; j++ )  {
X								pend->tty[j] = user.ut_line[j];
X							}
X							pend->tty[ NMAX ] = NULL;
X							pend->loginat = user.ut_time;
X							pend->loggedin = 2;
X							if(  pw == NILPWD  )  {
X								pend ->pwd = NILPWD;
X							}
X							else  {
X								pend->pwd = (struct passwd  *) malloc(pwdsize);
X								pwdcopy( pend->pwd, pw );
X							}
X							numnames++;
X						}
X						else  {
X							if( p->loggedin != 2 )  {
X								for( j = 0; j < NMAX; j++ )  {
X									p->tty[j] = user.ut_line[j];
X								}
X								p->tty[ NMAX ] = NULL;
X								p->loginat = user.ut_time;
X								p->loggedin = 1;
X							}
X						}
X					}
X					p = p->link;
X				}  
X				while( p != NILPERS );
X			}
X			fwopen();
X			p = person1;
X			while( p != NILPERS )  {
X				if( p->loggedin == 2 )  {
X					p->loggedin = 1;
X				}
X				decode( p );
X				p = p->link;
X			}
X			fwclose();
X			close( uf );
X		}
X		else  {
X			fprintf( stderr, "finger: error opening %s\n", USERLOG );
X			exit( 2 );
X		}
X	}
X
X	/* print out what we got */
X
X	if( header )  {
X		if( unquick )  {
X			if( !unshort )  {
X				if( wide )  {
X					printf(
X					"Login       Name              M%*.*s Idle    When            Office\n", LMAX-1, LMAX-1, "TTY" );
X				}
X				else  {
X					printf(
X					"Login    M%*.*s Idle    When            Office\n", LMAX-1, LMAX-1, "TTY" );
X				}
X			}
X		}
X		else  {
X			printf( "Login      TTY            When" );
X			if( idle )  {
X				printf( "             Idle" );
X			}
X			printf( "\n" );
X		}
X	}
X	p = person1;
X	do  {
X		if( unquick )  {
X			if( unshort )  {
X				personprint( p );
X				if( p->pwd != NILPWD )  {
X					if( hack )  {
X						s = malloc(strlen((p->pwd)->pw_dir) + PROJLEN + 1 );
X						strcpy(  s, (p->pwd)->pw_dir  );
X						strcat( s, PROJ );
X						if(  ( fp = fopen( s, "r") )  != NULL  )  {
X							printf( "Project: " );
X							while(  ( c = getc(fp) )  !=  EOF  )  {
X								if( c == LINEBREAK )  {
X									break;
X								}
X								putc( (char) c, stdout );
X							}
X							fclose( fp );
X							printf( "\n" );
X						}
X					}
X					if( plan )  {
X						s = malloc( strlen( (p->pwd)->pw_dir ) + PLANLEN + 1 );
X						strcpy(  s, (p->pwd)->pw_dir  );
X						strcat( s, PLAN );
X						if(  ( fp = fopen( s, "r") )  == NULL  )  {
X							printf( "No Plan.\n" );
X						}
X						else  {
X							printf( "Plan:\n" );
X							while(  ( c = getc(fp) )  !=  EOF  )  {
X								putc((char) c, stdout );
X							}
X							fclose( fp );
X						}
X					}
X				}
X				if( p->link != NILPERS )  {
X					printf( "\n" );
X				}
X			}
X			else  {
X				shortprint( p );
X			}
X		}
X		else  {
X			quickprint( p );
X		}
X		p = p->link;
X	}  
X	while( p != NILPERS );
X	exit(0);
X}
X
X
X/*  given a pointer to a pwd (pfrom) copy it to another one, allocating
X *  space for all the stuff in it.  Note: Only the useful (what the
X *  program currently uses) things are copied.
X */
X
Xpwdcopy( pto, pfrom )		/* copy relevant fields only */
X
Xstruct  passwd		*pto,  *pfrom;
X{
X	pto->pw_name = malloc(  strlen( pfrom->pw_name ) + 1  );
X	strcpy( pto->pw_name, pfrom->pw_name );
X	pto->pw_uid = pfrom->pw_uid;
X	pto->pw_gecos = malloc(  strlen( pfrom->pw_gecos ) + 1  );
X	strcpy( pto->pw_gecos, pfrom->pw_gecos );
X	pto->pw_dir = malloc(  strlen( pfrom->pw_dir ) + 1  );
X	strcpy( pto->pw_dir, pfrom->pw_dir );
X	pto->pw_shell = malloc(  strlen( pfrom->pw_shell ) + 1  );
X	strcpy( pto->pw_shell, pfrom->pw_shell );
X}
X
X
X/*  print out information on quick format giving just name, tty, login time
X *  and idle time if idle is set.
X */
X
Xquickprint( pers )
X
Xstruct  person		*pers;
X{
X	int			idleprinted;
X
X	printf( "%-*.*s", NMAX, NMAX, pers->name );
X	printf( "  " );
X	if( pers->loggedin )  {
X		if( idle )  {
X			findidle( pers );
X			if( pers->writeable )  {
X				printf(  " %-*.*s %-16.16s", LMAX, LMAX, 
X				pers->tty, ctime( &pers->loginat )  );
X			}
X			else  {
X				printf(  "*%-*.*s %-16.16s", LMAX, LMAX, 
X				pers->tty, ctime( &pers->loginat )  );
X			}
X			printf( "   " );
X			idleprinted = ltimeprint( &pers->idletime );
X		}
X		else  {
X			printf(  " %-*.*s %-16.16s", LMAX, LMAX, 
X			pers->tty, ctime( &pers->loginat )  );
X		}
X	}
X	else  {
X		printf( "          Not Logged In" );
X	}
X	printf( "\n" );
X}
X
X
X/*  print out information in short format, giving login name, full name,
X *  tty, idle time, login time, office location and phone.
X */
X
Xshortprint( pers )
X
Xstruct  person	*pers;
X
X{
X	struct  passwd		*pwdt = pers->pwd;
X	char			buf[ 26 ];
X	int			i,  len,  offset,  dialup;
X
X	if( pwdt == NILPWD )  {
X		printf( "%-*.*s", NMAX, NMAX,  pers->name );
X		printf( "       ???\n" );
X		return;
X	}
X	printf( "%-*.*s", NMAX, NMAX,  pwdt->pw_name );
X	dialup = 0;
X	if( wide )  {
X		if(  strlen( pers->realname ) > 0  )  {
X			printf( " %-20.20s", pers->realname );
X		}
X		else  {
X			printf( "        ???          " );
X		}
X	}
X	if( pers->loggedin )  {
X		if( pers->writeable )  {
X			printf( "  " );
X		}
X		else  {
X			printf( " *" );
X		}
X	}
X	else  {
X		printf( "  " );
X	}
X	if(  strlen( pers->tty ) > 0  )  {
X		if(  ISMODEM(pers->tty)  &&  pers->loggedin  )  {
X			dialup = 1;
X		}
X		printf( "%*.*s ", LMAX-1, LMAX-1, pers->tty );
X	}
X	else  {
X		printf( "%*.*s ", LMAX-1, LMAX-1, " " );
X	}
X	strcpy( buf, ctime( &pers->loginat ) );
X	if( pers->loggedin )  {
X		stimeprint( &pers->idletime );
X		offset = 7;
X		for( i = 4; i < 19; i++ )  {
X			buf[i] = buf[i + offset];
X		}
X		printf( " %-9.9s ", buf );
X	}
X	else  {
X		printf( " " );
X		offset = 4;
X		for( i = 0; i <22; i++ )  {
X			buf[i] = buf[i + offset];
X		}
X		printf( "<%-12.12s>", buf );
X	}
X	len = strlen( pers->homephone );
X	if(  dialup  &&  (len > 0)  )  {
X		if( len == 8 )  {
X			printf( "             " );
X		}
X		else  {
X			if( len == 12 )  {
X				printf( "         " );
X			}
X			else {
X				for( i = 1; i <= 21 - len; i++ )  {
X					printf( " " );
X				}
X			}
X		}
X		printf( "%s", pers->homephone );
X	}
X	else  {
X		if(  strlen( pers->office ) > 0  )  {
X			printf( " %-11.11s", pers->office );
X			if(  strlen( pers->officephone ) > 0  )  {
X				printf( " %8.8s", pers->officephone );
X			}
X			else  {
X				if( len == 8 )  {
X					printf( " %8.8s", pers->homephone );
X				}
X			}
X		}
X		else  {
X			if(  strlen( pers->officephone ) > 0  )  {
X				printf( "             %8.8s", pers->officephone );
X			}
X			else  {
X				if( len == 8 )  {
X					printf( "             %8.8s", pers->homephone );
X				}
X				else  {
X					if( len == 12 )  {
X						printf( "         %12.12s", pers->homephone );
X					}
X				}
X			}
X		}
X	}
X	printf( "\n" );
X}
X
X
X/*  print out a person in long format giving all possible information.
X *  directory and shell are inhibited if unbrief is clear.
X */
X
Xpersonprint( pers )
X
Xstruct  person	*pers;
X{
X	struct  passwd		*pwdt = pers->pwd;
X	int			idleprinted;
X
X	if( pwdt == NILPWD )  {
X		printf( "Login name: %-10s\t\t\tIn real life: ???\n", pers->name );
X		return;
X	}
X	printf( "Login name: %-10s", pwdt->pw_name );
X	if( pers->loggedin && !pers->writeable )  {
X		printf( " (messages off)\t" );
X	}
X	else  {
X		printf( "\t\t\t" );
X	}
X	if(  strlen( pers->realname ) > 0  )  {
X		printf( "In real life: %s", pers->realname );
X	}
X	if(  strlen( pers->office ) > 0  )  {
X		char	pbuf[40];
X
X		printf("\nOffice: ");
X		if(  strlen( pers->officephone ) > 0  )  {
X			sprintf(pbuf,  "%-.14s, %-.12s", pers->office, pers->officephone );
X			printf( "%-26.26s", pbuf );
X			if(  strlen( pers->homephone ) > 0  )  {
X				printf( "\tHome phone: %-.12s", pers->homephone );
X			}
X		}
X		else  {
X			printf("%-14.14s", pers->office);
X			if(  strlen( pers->homephone ) > 0  )  {
X				printf("\t\t\tHome phone: %-.12s", pers->homephone);
X			}
X		}
X	}
X	else  {
X		if(  strlen( pers->officephone ) > 0  )  {
X			printf( "\nOffice Phone: %-12.12s", pers->officephone );
X			if(  strlen( pers->homephone ) > 0  )  {
X				printf( "\t\tHome Phone: %-.12s", pers->homephone );
X			}
X		}
X		else  {
X			if(  strlen( pers->homephone ) > 0  )  {
X				printf( "\nPhone: %-.12s", pers->homephone );
X			}
X		}
X	}
X	if(  strlen( pers->random ) > 0  )  {
X		printf( "\n%s", pers->random );
X	}
X	if( unbrief )  {
X		printf( "\nDirectory: %-25s", pwdt->pw_dir );
X		if(  strlen( pwdt->pw_shell ) > 0  )  {
X			printf( "\tShell: %-s", pwdt->pw_shell );
X		}
X	}
X	if( pers->loggedin )  {
X		register char *ep = ctime( &pers->loginat );
X		printf("\nOn since %15.15s on %-*.*s\t", &ep[4], LMAX, LMAX, pers->tty );
X		idleprinted = ltimeprint( &pers->idletime );
X		if( idleprinted )  {
X			printf( " Idle Time" );
X		}
X	}
X	else  {
X		register char *ep = ctime( &pers->loginat );
X		printf("\nLast login %16.16s on %.*s", ep, LMAX, pers->tty );
X	}
X	printf( "\n" );
X}
X
X
X/*
X *  very hacky section of code to format phone numbers.  filled with
X *  magic constants like 4, 7 and 10.
X */
X
Xchar  *phone( s, len )
X
Xchar		*s;
Xint			len;
X{
X	char		*strsave();
X	char		fonebuf[ 15 ];
X	int		i;
X
X	switch(  len  )  {
X
X	case  7:
X		for( i = 0; i <= 2; i++ )  {
X			fonebuf[ i ] = *s++;
X		}
X		fonebuf[ 3 ] = '-';
X		for( i = 0; i <= 3; i++ )  {
X			fonebuf[ 4 + i ] = *s++;
X		}
X		fonebuf[ 8 ] = NULL;
X		return( strsave( &fonebuf[0] ) );
X		break;
X
X	case 10:
X		for( i = 0; i <= 2; i++ )  {
X			fonebuf[ i ] = *s++;
X		}
X		fonebuf[ 3 ] = '-';
X		for( i = 0; i <= 2; i++ )  {
X			fonebuf[ 4 + i ] = *s++;
X		}
X		fonebuf[ 7 ] = '-';
X		for( i = 0; i <= 3; i++ )  {
X			fonebuf[ 8 + i ] = *s++;
X		}
X		fonebuf[ 12 ] = NULL;
X		return( strsave( &fonebuf[0] ) );
X		break;
X
X	default:
X		fprintf( stderr, "finger: error in phone numbering\n" );
X		return( strsave(s) );
X		break;
X	}
X}
X
X
X/*  decode the information in the gecos field of /etc/passwd
X *  another hacky section of code, but given the format the stuff is in...
X */
X
Xdecode( pers )
X
Xstruct  person	*pers;
X
X{
X	struct  passwd		*pwdt = pers->pwd;
X	char			buffer[ 40 ],  *bp,  *gp,  *lp;
X	char			*phone();
X	int			alldigits;
X	int			len;
X	int			i;
X
X	pers->realname = NULLSTR;
X	pers->office = NULLSTR;
X	pers->officephone = NULLSTR;
X	pers->homephone = NULLSTR;
X	pers->random = NULLSTR;
X	if(  pwdt != NILPWD )  {
X		gp = pwdt->pw_gecos;
X		bp = &buffer[ 0 ];
X		if( *gp == ASTERISK )
X			gp++;
X		while(  (*gp != NULL)  &&  (*gp != COMMA)  )  {	/* name */
X			if( *gp == SAMENAME )  {
X				lp = pwdt->pw_name;
X				*bp++ = CAPITALIZE(*lp++);
X				while( *lp != NULL )
X					*bp++ = *lp++;
X			}  else
X				*bp++ = *gp;
X			gp++;
X		}
X		*bp = NULL;
X		len = strlen( &buffer[0] );
X		if ( len ) {
X			pers->realname = malloc( len + 1 );
X			strcpy( pers->realname, &buffer[0] );
X		}
X		if( *gp++ == COMMA )  {		/* office, supposedly */
X			alldigits = 1;
X			bp = &buffer[ 0 ];
X			while(  (*gp != NULL)  &&  (*gp != COMMA)  )  {
X				*bp = *gp++;
X				alldigits = alldigits && ('0' <= *bp) && (*bp <= '9');
X				bp++;
X			}
X			*bp = NULL;
X			len = strlen( &buffer[0] );
X			if( alldigits )  {	/* not the office, a phone number? */
X				if( (len == 7) || (len == 10) ) {
X					pers->homephone = phone( &buffer[0], len );
X				} else if ( len )  {	/* must be random stuff */
X					pers->random = malloc( len + 1 );
X					strcpy( pers->random, &buffer[0] );
X				}
X			}  else if ( len )  {	/* yes!, the office! */
X				pers->office = malloc( len + 1 );
X				strcpy( pers->office, &buffer[0] );
X			}
X			if( *gp++ == COMMA )  {	/* office phone, if not already found */
X				bp = &buffer[ 0 ];
X				alldigits = 1;
X				while(  (*gp != NULL)  &&  (*gp != COMMA)  )  {
X					*bp = *gp++;
X					alldigits = alldigits && ('0' <= *bp) && (*bp <= '9');
X					bp++;
X				}
X				*bp = NULL;
X				len = strlen( &buffer[0] );
X				if( alldigits && ( (len == 7) || (len == 10) )  )  {
X					if( *pers->homephone != NULL )
X						pers->officephone = pers->homephone;
X					pers->homephone = phone( &buffer[0], len );
X				}  else if ( len )  {	/* nope, random stuff */
X					pers->random = malloc( len + 1 );
X					strcpy( pers->random, &buffer[0] );
X				}
X				if( *gp++ == COMMA )  {		/* home phone?? */
X					bp = &buffer[ 0 ];
X					alldigits = 1;
X					while(  (*gp != NULL)  &&  (*gp != COMMA)  )  {
X						*bp = *gp++;
X						alldigits = alldigits && ('0' <= *bp) && (*bp <= '9');
X						bp++;
X					}
X					*bp = NULL;
X					len = strlen( &buffer[0] );
X					if( alldigits  &&  ( (len == 7) || (len == 10) )  )  {
X						/* lose first if 3 numbers */
X						if( *pers->homephone != NULL )
X							pers->officephone = pers->homephone;
X						pers->homephone = phone( &buffer[0], len );
X					}  else if ( len )  {	/* random stuff */
X						pers->random = malloc( strlen( &buffer[0] ) + 1 );
X						strcpy( pers->random, &buffer[0] );
X					}  else  {
X						if( *pers->homephone != NULL ) {
X							pers->officephone = pers->homephone;
X							pers->homephone = NULLSTR;
X						}
X					}
X					if( *gp++ == COMMA )  {	/* real random stuff?? */
X						bp = &buffer[ 0 ];
X						while(  *gp != NULL  )  {
X							*bp = *gp++;
X							bp++;
X						}
X						*bp = NULL;
X						len = strlen( &buffer[0] );
X						if ( len ) {
X							pers->random = malloc( len + 1 );
X							strcpy( pers->random, &buffer[0] );
X						}
X					}
X				}
X			}
X		}
X		if( pers->loggedin == 0 )  {
X			findwhen( pers );
X		}
X		else  {
X			findidle( pers );
X		}
X	}
X}
X
X
X/*  find the last log in of a user by checking the LASTLOG file.
X *  the entry is indexed by the uid, so this can only be done if
X *  the uid is known (which it isn't in quick mode)
X */
X
Xfwopen()
X{
X	if(  ( lf = open(LASTLOG, 0) ) >= 0  )  {
X		llopenerr = 0;
X	}
X	else  {
X		fprintf( stderr, "finger: lastlog open error\n" );
X		llopenerr = 1;
X	}
X}
X
X
Xfindwhen( pers )
X
Xstruct  person	*pers;
X{
X	struct  passwd		*pwdt = pers->pwd;
X#ifndef SYSV
X	struct  lastlog		ll;
X#else
X	struct	utmp		ll;
X#endif
X	int			llsize = sizeof ll;
X	int			i;
X
X	if( !llopenerr )  {
X		lseek( lf, (long) (pwdt->pw_uid*llsize), SEEK_SET );
X		if( read( lf, (char *) &ll, llsize ) == llsize )  {
X#ifndef SYSV
X			for( i = 0; i < LMAX; i++ )  {
X				pers->tty[ i ] = ll.ll_line[ i ];
X			}
X			pers->tty[ LMAX ] = NULL;
X			pers->loginat = ll.ll_time;
X#else
X			for( i = 0; i < LMAX; i++ )  {
X				pers->tty[ i ] = ll.ut_line[ i ];
X			}
X			pers->tty[ LMAX ] = NULL;
X			pers->loginat = ll.ut_time;
X#endif
X		}
X		else  {
X			fprintf( stderr, "finger: lastlog read error\n" );
X			pers->tty[ 0 ] = NULL;
X			pers->loginat = 0L;
X		}
X	}
X	else  {
X		pers->tty[ 0 ] = NULL;
X		pers->loginat = 0L;
X	}
X}
X
X
Xfwclose()
X{
X	if( !llopenerr )  {
X		close( lf );
X	}
X}
X
X
X/*  find the idle time of a user by doing a stat on /dev/histty,
X *  where histty has been gotten from USERLOG, supposedly.
X */
X
Xfindidle( pers )
X
Xstruct  person	*pers;
X{
X	struct  stat		ttystatus;
X	struct  passwd		*pwdt = pers->pwd;
X	char			buffer[ 20 ];
X	char			*TTY = "/dev/";
X	int			TTYLEN = strlen( TTY );
X	int			i;
X
X	strcpy( &buffer[0], TTY );
X	i = 0;
X	do  {
X		buffer[ TTYLEN + i ] = pers->tty[ i ];
X	}  while( ++i <= LMAX );
X	if(  stat( &buffer[0], &ttystatus ) >= 0  )  {
X		time( &tloc );
X		if( tloc < ttystatus.st_atime )  {
X			pers->idletime = 0L;
X		}
X		else  {
X			pers->idletime = tloc - ttystatus.st_atime;
X		}
X		if(  (ttystatus.st_mode & TALKABLE) == TALKABLE  )  {
X			pers->writeable = 1;
X		}
X		else  {
X			pers->writeable = 0;
X		}
X	}
X	else  {
X		fprintf( stderr, "finger: error STATing %s\n", &buffer[0] );
X		exit( 4 );
X	}
X}
X
X
X/*  print idle time in short format; this program always prints 4 characters;
X *  if the idle time is zero, it prints 4 blanks.
X */
X
Xstimeprint( dt )
X
Xlong	*dt;
X{
X	struct  tm		*gmtime();
X	struct  tm		*delta;
X
X	delta = gmtime( dt );
X	if( delta->tm_yday == 0 )  {
X		if( delta->tm_hour == 0 )  {
X			if( delta->tm_min >= 10 )  {
X				printf( " %2.2d ", delta->tm_min );
X			}
X			else  {
X				if( delta->tm_min == 0 )  {
X					printf( "    " );
X				}
X				else  {
X					printf( "  %1.1d ", delta->tm_min );
X				}
X			}
X		}
X		else  {
X			if( delta->tm_hour >= 10 )  {
X				printf( "%3.3d:", delta->tm_hour );
X			}
X			else  {
X				printf( "%1.1d:%02.2d", delta->tm_hour, delta->tm_min );
X			}
X		}
X	}
X	else  {
X		printf( "%3dd", delta->tm_yday );
X	}
X}
X
X
X/*  print idle time in long format with care being taken not to pluralize
X *  1 minutes or 1 hours or 1 days.
X */
X
Xltimeprint( dt )
X
Xlong	*dt;
X{
X	struct  tm		*gmtime();
X	struct  tm		*delta;
X	int			printed = 1;
X
X	delta = gmtime( dt );
X	if( delta->tm_yday == 0 )  {
X		if( delta->tm_hour == 0 )  {
X			if( delta->tm_min >= 10 )  {
X				printf( "%2d minutes", delta->tm_min );
X			}
X			else  {
X				if( delta->tm_min == 0 )  {
X					if( delta->tm_sec > 10 )  {
X						printf( "%2d seconds", delta->tm_sec );
X					}
X					else  {
X						printed = 0;
X					}
X				}
X				else  {
X					if( delta->tm_min == 1 )  {
X						if( delta->tm_sec == 1 )  {
X							printf( "%1d minute %1d second",
X							delta->tm_min, delta->tm_sec );
X						}
X						else  {
X							printf( "%1d minute %d seconds",
X							delta->tm_min, delta->tm_sec );
X						}
X					}
X					else  {
X						if( delta->tm_sec == 1 )  {
X							printf( "%1d minutes %1d second",
X							delta->tm_min, delta->tm_sec );
X						}
X						else  {
X							printf( "%1d minutes %d seconds",
X							delta->tm_min, delta->tm_sec );
X						}
X					}
X				}
X			}
X		}
X		else  {
X			if( delta->tm_hour >= 10 )  {
X				printf( "%2d hours", delta->tm_hour );
X			}
X			else  {
X				if( delta->tm_hour == 1 )  {
X					if( delta->tm_min == 1 )  {
X						printf( "%1d hour %1d minute",
X						delta->tm_hour, delta->tm_min );
X					}
X					else  {
X						printf( "%1d hour %2d minutes",
X						delta->tm_hour, delta->tm_min );
X					}
X				}
X				else  {
X					if( delta->tm_min == 1 )  {
X						printf( "%1d hours %1d minute",
X						delta->tm_hour, delta->tm_min );
X					}
X					else  {
X						printf( "%1d hours %2d minutes",
X						delta->tm_hour, delta->tm_min );
X					}
X				}
X			}
X		}
X	}
X	else  {
X		if( delta->tm_yday >= 10 )  {
X			printf( "%2d days", delta->tm_yday );
X		}
X		else  {
X			if( delta->tm_yday == 1 )  {
X				if( delta->tm_hour == 1 )  {
X					printf( "%1d day %1d hour",
X					delta->tm_yday, delta->tm_hour );
X				}
X				else  {
X					printf( "%1d day %2d hours",
X					delta->tm_yday, delta->tm_hour );
X				}
X			}
X			else  {
X				if( delta->tm_hour == 1 )  {
X					printf( "%1d days %1d hour",
X					delta->tm_yday, delta->tm_hour );
X				}
X				else  {
X					printf( "%1d days %2d hours",
X					delta->tm_yday, delta->tm_hour );
X				}
X			}
X		}
X	}
X	return( printed );
X}
X
X
Xmatchcmp( gname, login, given )
X
Xchar		*gname;
Xchar		*login;
Xchar		*given;
X{
X	char		buffer[ 20 ];
X	char		c;
X	int		flag,  i,  unfound;
X
X	if( !match )  {
X		return( 0 );
X	}
X	else  {
X		if(  namecmp( login, given )  )  {
X			return( 1 );
X		}
X		else  {
X			if( *gname == ASTERISK )  {
X				gname++;
X			}
X			flag = 1;
X			i = 0;
X			unfound = 1;
X			while(  unfound  )  {
X				if( flag )  {
X					c = *gname++;
X					if( c == SAMENAME )  {
X						flag = 0;
X						c = *login++;
X					}
X					else  {
X						unfound = (*gname != COMMA)  &&  (*gname != NULL);
X					}
X				}
X				else {
X					c = *login++;
X					if( c == NULL )  {
X						if(  (*gname == COMMA)  ||  (*gname == NULL)  )  {
X							break;
X						}
X						else  {
X							flag = 1;
X							continue;
X						}
X					}
X				}
X				if( c == BLANK )  {
X					buffer[i++] = NULL;
X					if(  namecmp( buffer, given )  )  {
X						return( 1 );
X					}
X					i = 0;
X					flag = 1;
X				}
X				else  {
X					buffer[ i++ ] = c;
X				}
X			}
X			buffer[i++] = NULL;
X			if(  namecmp( buffer, given )  )  {
X				return( 1 );
X			}
X			else  {
X				return( 0 );
X			}
X		}
X	}
X}
X
X
Xnamecmp( name1, name2 )
X
Xchar		*name1;
Xchar		*name2;
X{
X	char		c1,  c2;
X
X	c1 = *name1;
X	if( (('A' <= c1) && (c1 <= 'Z')) || (('a' <= c1) && (c1 <= 'z')) )  {
X		c1 = CAPITALIZE( c1 );
X	}
X	c2 = *name2;
X	if( (('A' <= c2) && (c2 <= 'Z')) || (('a' <= c2) && (c2 <= 'z')) )  {
X		c2 = CAPITALIZE( c2 );
X	}
X	while( c1 == c2 )  {
X		if( c1 == NULL )  {
X			return( 1 );
X		}
X		c1 = *++name1;
X		if( (('A'<=c1) && (c1<='Z')) || (('a'<=c1) && (c1<='z')) )  {
X			c1 = CAPITALIZE( c1 );
X		}
X		c2 = *++name2;
X		if( (('A'<=c2) && (c2<='Z')) || (('a'<=c2) && (c2<='z')) )  {
X			c2 = CAPITALIZE( c2 );
X		}
X	}
X	if( *name1 == NULL )  {
X		while(  ('0' <= *name2)  &&  (*name2 <= '9')  )  {
X			name2++;
X		}
X		if( *name2 == NULL )  {
X			return( 1 );
X		}
X	}
X	else  {
X		if( *name2 == NULL )  {
X			while(  ('0' <= *name1)  &&  (*name1 <= '9')  )  {
X				name1++;
X			}
X			if( *name1 == NULL )  {
X				return( 1 );
X			}
X		}
X	}
X	return( 0 );
X}
X
X
Xchar  *strsave( s )
X
Xchar		*s;
X{
X	char		*malloc();
X	char		*p;
X
X	p = malloc( strlen( s ) + 1 );
X	strcpy( p, s );
X	return( p );
X}
END_OF_FILE
if test 32249 -ne `wc -c <'finger.c'`; then
    echo shar: \"'finger.c'\" unpacked with wrong size!
fi
# end of 'finger.c'
fi
if test -f 'last.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'last.c'\"
else
echo shar: Extracting \"'last.c'\" \(4274 characters\)
sed "s/^X//" >'last.c' <<'END_OF_FILE'
X#ifdef SYSVR3
X# ident "@(#)last.c	4.3 (Berkeley) 2/28/81";
X# ident	"@(#)last.c	1.1.1.1	89/12/31 17:34:32 [LOCAL] (Greg A. Woods)"
X#else
X# ifndef lint
Xstatic	char *sccsid = "@(#)last.c	4.3 (Berkeley) 2/28/81";
Xstatic cahr *sccsloc= "@(#)last.c	1.1.1.1\t89/12/31 17:34:32 [LOCAL] (Greg A. Woods)";
X# endif
X#endif
X/*
X * last
X */
X#include <sys/types.h>
X#include <stdio.h>
X#include <signal.h>
X#ifdef SYSV
X# include <sys/stat.h>
X#else
X# include <stat.h>
X#endif
X#include <utmp.h>
X
X#define NMAX	sizeof(buf[0].ut_name)
X#define LMAX	sizeof(buf[0].ut_line)
X#define	SECDAY	(24*60*60)
X
X#define	lineq(a,b)	(!strncmp(a,b,LMAX))
X#define	nameq(a,b)	(!strncmp(a,b,NMAX))
X
X#define MAXTTYS 256
X
X#ifndef WTMP_FILE
X# define WTMP_FILE	"/usr/adm/wtmp"
X#endif
X
Xchar	**argv;
Xint	argc;
X
Xstruct	utmp buf[128];
Xchar	ttnames[MAXTTYS][LMAX+1];
Xlong	logouts[MAXTTYS];
X
Xchar	*ctime(), *strspl();
Xint	onintr();
X
Xmain(ac, av)
X	char **av;
X{
X	register int i, k;
X	int bl, wtmp;
X	char *ct;
X	register struct utmp *bp;
X	long otime;
X	struct stat stb;
X	int print;
X	char * crmsg = (char *)0;
X	long crtime;
X 
X	time(&buf[0].ut_time);
X	ac--, av++;
X	argc = ac;
X	argv = av;
X	for (i = 0; i < argc; i++) {
X		if (strlen(argv[i])>2)
X			continue;
X		if (!strcmp(argv[i], "~"))
X			continue;
X		if (getpwnam(argv[i]))
X			continue;
X		argv[i] = strspl("tty", argv[i]);
X	}
X	wtmp = open(WTMP_FILE, 0);
X	if (wtmp < 0) {
X		perror(WTMP_FILE);
X		exit(1);
X	}
X	fstat(wtmp, &stb);
X	bl = (stb.st_size + sizeof (buf)-1) / sizeof (buf);
X	if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
X		signal(SIGINT, onintr);
X		signal(SIGQUIT, onintr);
X	}
X	for (bl--; bl >= 0; bl--) {
X		lseek(wtmp, bl * sizeof (buf), 0);
X		bp = &buf[read(wtmp, buf, sizeof (buf)) / sizeof(buf[0]) - 1];
X		for ( ; bp >= buf; bp--) {
X			print = want(bp);
X			if (print) {
X				ct = ctime(&bp->ut_time);
X				printf("%-*.*s  %-*.*s  %10.10s %5.5s ",
X				    NMAX, NMAX, bp->ut_name,
X				    LMAX, LMAX, bp->ut_line, ct, 11+ct);
X			}
X			for (i = 0; i < MAXTTYS; i++) {
X				if (ttnames[i][0] == 0) {
X					strncpy(ttnames[i], bp->ut_line,
X					    sizeof(bp->ut_line));
X					otime = logouts[i];
X					logouts[i] = bp->ut_time;
X					break;
X				}
X				if (lineq(ttnames[i], bp->ut_line)) {
X					otime = logouts[i];
X					logouts[i] = bp->ut_time;
X					break;
X				}
X			}
X			if (print) {
X				if (otime == 0)
X					printf("  still logged in\n");
X				else {
X					long delta;
X					if (otime < 0) {
X						otime = -otime;
X						printf("- %s", crmsg);
X					} else
X						printf("- %5.5s",
X						    ctime(&otime)+11);
X					delta = otime - bp->ut_time;
X					if (delta < SECDAY)
X					    printf("  (%5.5s)\n",
X						asctime(gmtime(&delta))+11);
X					else
X					    printf(" (%ld+%5.5s)\n",
X						delta / SECDAY,
X						asctime(gmtime(&delta))+11);
X				}
X				fflush(stdout);
X			}
X			if (lineq(bp->ut_line, "~")) {
X				for (i = 0; i < MAXTTYS; i++)
X					logouts[i] = -bp->ut_time;
X				if (nameq(bp->ut_name, "shutdown"))
X					crmsg = "down ";
X				else
X					crmsg = "crash";
X			}
X		}
X	}
X	ct = ctime(&buf[0].ut_time);
X	printf("\nwtmp begins %10.10s %5.5s \n", ct, ct + 11);
X	exit(0);
X}
X
Xonintr(signo)
X	int signo;
X{
X	char *ct;
X
X	if (signo == SIGQUIT)
X		signal(SIGQUIT, onintr);
X	ct = ctime(&buf[0].ut_time);
X	printf("\ninterrupted %10.10s %5.5s \n", ct, ct + 11);
X	if (signo == SIGINT)
X		exit(1);
X}
X
Xwant(bp)
X	struct utmp *bp;
X{
X	register char **av;
X	register int ac;
X
X#ifdef SYSV
X	switch (bp->ut_type) {
X	case BOOT_TIME:
X		/* sizeof(BOOT_MSG) > sizeof ut_name */
X		strcpy(bp->ut_name, "reboot");
X		break;
X	case EMPTY:
X	case RUN_LVL:
X	case OLD_TIME:
X	case NEW_TIME:
X	case INIT_PROCESS:
X	case LOGIN_PROCESS:
X	case DEAD_PROCESS:
X	case ACCOUNTING:
X		bp->ut_name[0] = '\0';	/* ignore these ones */
X		break;
X	case USER_PROCESS:
X		break;			/* normal */
X	default:
X		strcpy(bp->ut_name, "unknown");		/* weird? */
X		break;
X	}
X#else
X	if (bp->ut_line[0] == '~' && bp->ut_name[0] == '\0')
X		strcpy(bp->ut_name, "reboot");		/* bandaid */
X#endif
X	if (bp->ut_name[0] == 0)
X		return (0);
X	if (argc == 0)
X		return (1);
X	av = argv;
X	for (ac = 0; ac < argc; ac++) {
X		if (nameq(*av, bp->ut_name) || lineq(*av, bp->ut_line))
X			return (1);
X		av++;
X	}
X	return (0);
X}
X
Xchar *
Xstrspl(left, right)
X	char *left, *right;
X{
X	char *res = (char *)malloc(strlen(left)+strlen(right)+1);
X
X	strcpy(res, left);
X	strcat(res, right);
X	return (res);
X}
END_OF_FILE
if test 4274 -ne `wc -c <'last.c'`; then
    echo shar: \"'last.c'\" unpacked with wrong size!
fi
# end of 'last.c'
fi
if test -f 'lastlog.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lastlog.c'\"
else
echo shar: Extracting \"'lastlog.c'\" \(3399 characters\)
sed "s/^X//" >'lastlog.c' <<'END_OF_FILE'
X#ifdef SYSVR3
X# ident "@(#)lastlog.c	1.1	89/12/30 17:49:02 [LOCAL] (Greg A. Woods)"
X#endif
X
X#ifndef SYSVR3
X# ifndef lint
X	static char	*SCCS_ID = "@(#)lastlog.c	1.1\t89/12/30 17:49:02 [LOCAL] (Greg A. Woods)";
X# endif
X#endif
X
X/*
X *	lastlog - update the lastlog file
X */
X
X#include <sys/types.h>
X#include <stdio.h>
X#include <unistd.h>
X#include <string.h>
X#include <time.h>
X#include <fcntl.h>
X#include <utmp.h>
X
X#define LASTLOG	"/usr/adm/lastlog"
X
Xchar	*progname = NULL;
X
Xint
Xmain(argc, argv, envp)
X	int	argc;
X	char	*argv[];
X	char	*envp[];
X{
X	unsigned short	uid;
X	int		lfd;
X	int		ufd;
X	char		*ltm;
X	struct utmp	ut;
X	int		utent;
X	int		rec_sz = sizeof(ut);
X
X	extern unsigned short	getuid();
X
X	if (!(progname = strrchr(argv[0], '/')))
X		progname = argv[0];
X	else
X		progname++;
X	if (argc > 1) {
X		fprintf(stderr, "Usage: %s\n", progname);
X		exit(2);
X	}
X	if ((lfd = open(LASTLOG, O_RDWR)) < 0 ) {
X		fprintf(stderr, "%s: open(%s, O_RDWR): ", progname, LASTLOG);
X		perror("");
X		exit(1);
X	}
X	uid = getuid();
X	if (uid > 0) {
X		if (lseek(lfd, (off_t) (uid * rec_sz), SEEK_SET) == EOF) {
X			fprintf(stderr, "%s: lseek(%d): ", progname, uid * rec_sz);
X			perror(LASTLOG);
X			exit(1);
X		}
X	}
X	if (read(lfd, (char *) &ut, rec_sz) == rec_sz) {
X		ltm = ctime(&ut.ut_time);
X		ltm[24]	= '\0';
X		printf("Last login at %s on %s.\n", ltm, ut.ut_line);
X	} else
X		printf("No last login info found.\n");
X	if ((ufd = open(UTMP_FILE, O_RDONLY)) < 0 ) {
X		fprintf(stderr, "%s: open(%s, O_RDONLY): ", progname, UTMP_FILE);
X		perror("");
X		exit(1);
X	}
X	utent = ttyslot();
X	if (utent > 0) {
X		if (lseek(ufd, (off_t) (utent * rec_sz), SEEK_SET) == EOF) {
X			fprintf(stderr, "%s: lseek(%d): ", progname, uid * rec_sz);
X			perror(UTMP_FILE);
X			exit(1);
X		}
X	} else {
X		fprintf(stderr, "%s: I can't find your tty slot!\n", progname);
X		exit(1);
X	}
X	if (read(ufd, (char *) &ut, rec_sz) < rec_sz) {
X		fprintf(stderr, "%s: read(): ", progname);
X		perror(UTMP_FILE);
X		exit(1);
X	}
X	if (close(ufd) == EOF) {
X		fprintf(stderr, "%s: close(): ", progname);
X		perror(UTMP_FILE);
X		exit(1);
X	}
X#ifdef DEBUG
X	printf("%d:%s:%s:%s:%d:", utent, ut.ut_user, ut.ut_id, ut.ut_line, ut.ut_pid);
X	switch (ut.ut_type) {
X	case BOOT_TIME:
X		printf("BOOT_TIME");
X		break;
X	case EMPTY:
X		printf("EMPTY");
X		break;
X	case RUN_LVL:
X		printf("RUN_LVL");
X		break;
X	case OLD_TIME:
X		printf("OLD_TIME");
X		break;
X	case NEW_TIME:
X		printf("NEW_TIME");
X		break;
X	case INIT_PROCESS:
X		printf("INIT_PROCESS");
X		break;
X	case LOGIN_PROCESS:
X		printf("LOGIN_PROCESS");
X		break;
X	case DEAD_PROCESS:
X		printf("DEAD_PROCESS");
X		break;
X	case ACCOUNTING:
X		printf("ACCOUNTING");
X		break;
X	case USER_PROCESS:
X		printf("USER_PROCESS");
X		break;
X	default:
X		printf("unknown");		/* weird? */
X		break;
X	}
X	if (ut.ut_type == DEAD_PROCESS)
X		printf(":%d:%d", ut.ut_exit.e_termination, ut.ut_exit.e_exit);
X	printf(":%s", ctime(&ut.ut_time));
X#endif
X	if (ut.ut_type != USER_PROCESS) {
X		fprintf(stderr, "%s: your utmp entry isn't right.\n", progname);
X		exit(1);
X	}
X	if (lseek(lfd, (off_t) (uid * rec_sz), SEEK_SET) == EOF) {
X		fprintf(stderr, "%s: lseek(%d): ", progname, uid * rec_sz);
X		perror(LASTLOG);
X		exit(1);
X	}
X	if (write(lfd, (char *) &ut, rec_sz) < rec_sz) {
X		fprintf(stderr, "%s: write(): ", progname);
X		perror(LASTLOG);
X		exit(1);
X	}
X	if (close(lfd) == EOF) {
X		fprintf(stderr, "%s: close(): ", progname);
X		perror(LASTLOG);
X		exit(1);
X	}
X	exit(0);
X	/* NOTREACHED */
X}
END_OF_FILE
if test 3399 -ne `wc -c <'lastlog.c'`; then
    echo shar: \"'lastlog.c'\" unpacked with wrong size!
fi
# end of 'lastlog.c'
fi
echo shar: End of shell archive.
exit 0
-- 
						Greg A. Woods

woods@{robohack,gate,tmsoft,ontmoh,utgpu,gpu.utcs.Toronto.EDU,utorgpu.BITNET}
+1 416 443-1734 [h]   +1 416 595-5425 [w]   VE3-TCP   Toronto, Ontario; CANADA