[comp.sources.misc] v05i034: finger distribution for System V

ag@elgar.UUCP (Keith Gabryelski) (11/09/88)

Posting-number: Volume 5, Issue 34
Submitted-by: "Keith Gabryelski" <ag@elgar.UUCP>
Archive-name: s5finger/Part1

[*Process table*?!  Maybe he's got "finger" confused with "w"?  ++bsa]

The next three shar files are part of my finger distribution.

The following programs come with the distribution:

	finger  - Who is on the system and what are they doing.
	whois   - Display information about users on the system.
	ttyloc  - Change location information of terminal.
	bwiz    - The birthday wizard.
	inquire - Non-interactive whois database updater.
	watson  - Interactive whois database updater.
				   
Finger has been tested on a SCO Xenix 386 system and a AT&T Unix PC.
Untested code has been written for SCO Xenix 286 and some others.
This distribution shouldn't be too difficult to port to any System V
system.

Porting to BSD may be painful.

If nothing else, the code can be used as a reference to the internal
structure of the process array and user areas.

Note:	Watson is a bourne shell script used as a front end to
	inquire.  Watson should be very user friendly.  The current
	doc files aren't.

	This distribution is complete (working and tested), but not
	refined.  If you make changes, please send them back to me.

Pax, Keith
--
ag@elgar.CTS.COM         Keith Gabryelski          ...!{ucsd, jack}!elgar!ag
----------------------------------------------------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	Makefile
#	finger.c
#	rots.c
#	dbase.h
#	bwiz.c
# This archive created: Sun Oct 30 14:55:24 1988
export PATH; PATH=/bin:$PATH
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \SHAR_EOF > 'README'
This is the finger distribution.
Written by Keith Gabryelski
Released into public domain September 1, 1988.

	1. Edit the makefile and change anything that looks wrong.
	   Specifically BINDIR, MANDIR, LIBDIR, DBASEDIR, NOVICEDIR.

	2. Edit defs.h and change anything that look suspicious.

	3. You may want to look at finger.h to make sure everyhting
	   looks correct.

	4. You may want to look in ttyloc.h and change TTYLOCS and/or
	   LTMP.

	5. Edit watson (a Bourne shell script) and change
	   novice_directory to whatever you changed NOVICEDIR in the
	   makefile.

	6. make

	7. su
	   Note:
		finger must be setuid root so it can access /dev/mem,
		/dev/kmem, /dev/swap and handle other random stuff.

	8. make -n install
	   Note:
		Some systems already have a program called finger.
		You might want to move it if it is in danger of
		being over written by the make install.  On SCO
		Xenix, there is a /usr/bin/finger.  I moved it
		to /usr/bin/Finger and installed this software
		in /u/local/bin.  My path is set somewhat like:
		"PATH=/bin:/usr/bin:/u/local/bin".  This is
		my personal preference.

	9. If 8 looks ok, then do a "make install"
SHAR_EOF
fi # end of overwriting check
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#
# Makefile for the finger distribution.
# Written by Keith Gabryelski
# Released into public domain September 1, 1988.
# Please keep this header.
#

SHELL=/bin/sh
BINDIR=/u/local/bin
MANDIR=/usr/man/man.LOCAL
LIBDIR=/usr/lib/finger
DBASEDIR=/usr/lib/finger/dbase
NOVICEDIR=/usr/lib/finger/watson
CFLAGS= -g

all: finger ttyloc inquire bwiz

#
# finger - (linked to `whois').  Show users on system and some stats.
#
finger:	finger.o ttyrots.o rots.o inqrots.o
	cc $(CFLAGS) finger.o ttyrots.o rots.o inqrots.o -o finger

#
# ttyloc - Get or set a tty location.
#
ttyloc:	ttyloc.o ttyrots.o rots.o ttyloc.h defs.h
	cc $(CFLAGS) ttyloc.o ttyrots.o rots.o -o ttyloc

#
# inquire - get or set user information.
#
inquire:	inquire.o inqrots.o rots.o
	cc $(CFLAGS) inquire.o inqrots.o rots.o -o inquire

#
# bwiz - The birthday wizard.  Run once a night and wait for
#        the birthday card.
bwiz:	bwiz.o inqrots.o rots.o
	cc $(CFLAGS) bwiz.o inqrots.o rots.o -o bwiz

shar:
	shar README Makefile finger.c rots.c dbase.h bwiz.c  >finger.shar.1
	shar finger.h ttyloc.c ttyrots.c ttyloc.h doc novice >finger.shar.2
	shar watson birthday_card defs.h inquire.c inqrots.c >finger.shar.3

install:
	-mkdir $(LIBDIR)
	-mkdir $(DBASEDIR)
	chmod 777 $(DBASEDIR)
	cp finger ttyloc bwiz inquire $(BINDIR)
	cp birthday_card $(LIBDIR)
	chown root $(BINDIR)/finger
	chown bin $(BINDIR)/ttyloc
	chmod 4711 $(BINDIR)/finger $(BINDIR)/ttyloc
	-ln $(BINDIR)/finger $(BINDIR)/whois
	-mkdir $(NOVICEDIR)
	cp novice/* $(NOVICEDIR)
	chmod 755 $(NOVICEDIR)
	chmod 644 $(NOVICEDIR)/*

clean:
	rm -f *.o finger ttyloc bwiz inquire

finger.o:	finger.c
ttyloc.o:	ttyloc.c
ttyrots.o:	ttyrots.c
rots.o:	rots.c
inquire.o:	inquire.c
watrots.o:	watrots.c
bwiz.o:	bwiz.c
finger.c:	finger.h defs.h
ttyloc.c:	ttyloc.h defs.h
ttyrots.c:	ttyloc.h defs.h
bwiz.c:	defs.h finger.h
finger.h:	ttyloc.h dbase.h
SHAR_EOF
fi # end of overwriting check
if test -f 'finger.c'
then
	echo shar: will not over-write existing file "'finger.c'"
else
cat << \SHAR_EOF > 'finger.c'
/*
** finger - In memory of MIT-OZ.
**
** So, I dial in, . . . Wow! bell 2400 baud, this is nice - oh--oh!
** What's this?  No "@"?  No "Oz had another head crash last week,
** we are restoring as much as we can." message?  All of KANSAS:
** wiped clean!  Nothing but rice-chex.
**
**	Written by Keith Gabryelski (ag@elgar.UUCP)
**   	Release into public domain sometime soon.
**	Please Keep this header.
*/

#include <stdio.h>
#include "defs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>

#ifdef SCO_XENIX_386
#include <sys/page.h>
#include <sys/seg.h>
#endif /* SCO_XENIX_386 */

#include <sys/dir.h>
#include <sys/signal.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/var.h>

#ifdef NEED_NLIST.H
#include <nlist.h>
#endif /* NEED_NLIST.H */

#include <fcntl.h>
#include <utmp.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include "finger.h"

extern void setutent(), utmpname();

extern char
    *strrchr(), *ttyname(), *strcpy(), *strncpy(), *strcat(),
    *strtok(), *ctime(), *last_login();

extern int errno, sys_nerr;

extern long time();

extern struct passwd *getpwnam();
extern struct utmp *getutent();
extern struct group *getgrgid();
extern FILE *fopen();

void
    mem_copy(), fill_n_add(), add_to_list(), display_finger(),
    display_dbase(), display_mailbox(), display_plan();

char *group_name(), *itot(), *get_what();

long find_recent();

char
    *progname,				/* Our name. */
    *kernel_file = KERNEL_FILE,		/* The kernal file. */
    *kmem_file = KMEM_FILE,		/* Kernals memory file. */
    *mem_file = MEM_FILE,		/* Physical memory file. */
    *swap_file = SWAP_FILE,		/* Swappin' file. */
    *nlist_file = NLIST_FILE,		/* Our nlist file. */
    *whois_dbase_dir = WHOIS_DBASE_DIR;	/* Whois data base stuff. */

int
    whatarewe = FINGER,			/* list of what we should be doing. */
    idle_time = -1,		/* Maximum idle time; -1 == not defined. */
    uname_length = sizeof("-Name-") - 1,   /* Current max username length. */
    gname_length = sizeof("-Group-") - 1,  /* Current max group name length. */
    flname_length = sizeof("--Full Name--") - 1, /* Current max flname len. */
    what_length = sizeof("-What-") - 1,	   /* Current max What length. */
    idle_length = sizeof("Idle") - 1,	   /* Current max idle time length. */
    ttyname_length = sizeof("-TTY-") - 1;  /* Current max TTY length. */

struct finger *finger_list;	/* Linked list of possible canidates */

main(argc, argv)
int argc;
char **argv;
{
    char *word;				/* Word for arg lookup. */

    int
	i,				/* Interations counter. */
	fd,				/* All purpose file descriptor. */
	options,			/* Are we processing options? */
	status,				/* all purpose status holder. */
	kmem,				/* File dscrptr for kernel memory. */
	mem,				/* File dscrptr for physical memory. */
	swap,				/* File dscrptr for swapped memory. */
	NPROC,				/* Number of processes. */
	specified = FALSE,		/* User specified people to finger. */
	noswap = TRUE;			/* Is swap a bogus file descriptor?*/

    struct stat stbuf[2];		/* Couple of stat buffs to work in. */
    struct proc *proc;			/* The process table. */
    struct user *u;			/* Used to get args. */
    struct var v;			/* The var struct holds NPROC. */
    struct utmp *ut;			/* For looking up logged in users. */
    struct finger *f;			/* Pointer to list. */

    if ((progname = strrchr(*argv, '/')) == NULL)
	progname = *argv++;
    else
    {
	++progname;
	++argv;
    }
    --argc;

    /*
    ** Make a head for the finger_list list.
    */

    finger_list = (struct finger *)mymalloc(sizeof(struct finger));
    finger_list->uname[0] = '\0';	/* Just for good measure */
    finger_list->next = (struct finger *)NULL;

    if (!strcmp(progname, "whois"))
	whatarewe |= WHOIS;

    options = TRUE;
    while (argc--)
    {
	if (**argv != '-')
	    options = FALSE;
	else
	    if (options)
	    {
		if (!strcmp(*argv, "--"))
		    options = FALSE;
		else
		{
		    /*
		    ** Do options thing
		    */
		    word = *argv+1;

		    while (*word != '\0')
			switch(i = *word++)
			{
#ifdef SCO_XENIX
/*
** SCO XENIX systems have a DIALOUT "user" in utmp.
*/
			case 'D':	/* Show DIALOUTs */
			    whatarewe |= DIALOUT;
			    break;
#endif /* SCO_XENIX */
			case 'I':	/* Sort by idle time. */
			    whatarewe &= ~(SORT_MASK|HIDABLE);
			    whatarewe |= SORT_BY_IDLE;
			    break;

			case 'M':	/* Force no printing of mbox info. */
			    whatarewe &= ~MAILBOX;
			    break;

			case 'P':	/* Force no printing of .plan. */
			    whatarewe &= ~PLAN;
			    break;

			case 'S':	/* Don't hide repeated Unames. */
			    whatarewe &= ~HIDABLE;
			    break;

			case 'T':	/* Sort by ttyname. */
			    whatarewe &= ~(SORT_MASK|HIDABLE);
			    whatarewe |= SORT_BY_TTY;
			    break;

			case 'i':	/* Idle time */
			                /* Should be in date format (hh:mm) */
			    if (*word == '\0')
				idle_time = DEFAULT_IDLE_TIME;
			    else
			    {
				char *ts;
				register unsigned horm; /* hours or minutes */

				idle_time = 0;
				horm = atoi(strtok(word, ":"));
				ts = strtok(NULL, ":");
				if (ts != NULL)
				{
				    if ((idle_time = atoi(ts)) >= 60)
				    {
					fprintf(stderr,
						"%s: invalid idle time.\n",
						progname);
					usage();
				    }
				    else
					idle_time *= 60;
				    horm *= 60;		/* make it hours */
				}

				idle_time += horm * 60;
			    }
#ifdef DEBUG
			    printf("idle time given = %d seconds.\n",
				   idle_time);
#endif /* DEBUG */
			    word = "";
			    break;

			case 'l':	/* Long format */
			    whatarewe |= LONG;
			    break;

			case 'm':	/* Show mailbox */
			    whatarewe |= MAILBOX;
			    break;

			case 'p':	/* Show plan */
			    whatarewe |= PLAN;
			    break;

			case 'w':	/* Whois */
			    whatarewe |= WHOIS;
			    break;

			default:
			    fprintf(stderr, "%s: invalid switch '%c'.\n",
				    progname, i);
			    usage();
			}
		}

		*argv++;
		continue;
	    }

	/*
	** It's a username (or tty name).
	*/

	specified = TRUE;

	if (!strcmp(*argv, "."))	/* Current user. */
	{
	    ++argv;
	    add_user(getlogin(), TRUE);
	} else if (!strcmp(*argv, "*"))	/* All users in the data base. */
	{
	    struct direct dir;

	    ++argv;

	    if ((fd = open(whois_dbase_dir, O_RDONLY)) < 0)
		fprintf(stderr,
			"%s: Couldn't find database directory %s (%s).\n",
			progname, whois_dbase_dir, puterr(errno));
	    else
		while (read(fd, &dir, sizeof(struct direct)) ==
		       sizeof(struct direct))
		    if (strcmp(dir.d_name, ".") && strcmp(dir.d_name, ".."))
			add_user(dir.d_name, TRUE);
	} else				/* Specified by name. */
	    add_user(*argv++, TRUE);
    }

/*
** If no specific users were specified on the command line.  Do all
** logged in users.
*/    
    if (!specified)
    {
	setutent();

	while ((ut = getutent()) != (struct utmp *)NULL)
	    if (ut->ut_type == USER_PROCESS)
		fill_n_add(ut);
    }

/*
** We now have a list of possible canidates.  Look for people to
** remove from the list.
**
** This is where |not| switches (like -i) should be taken care of.
*/

/*
** Remove idle users (if applicable) and compute string idle time,
** also compute maximum idle string length.
*/

    f = finger_list;
    while (f->next != (struct finger *)NULL)
	if (idle_time != -1 && idle_time < f->next->idletime)
	    f->next = f->next->next;
	else
	{
	    register int l;

	    strcpy(f->next->idle, itot(f->next->idletime));
	    if ((l = strlen(f->next->idle)) > idle_length)
		idle_length = l;

	    f = f->next;
	}

#ifdef DEBUG
    printf("whatarewe = %x, idle = %x\n", whatarewe, idle_time);

    f = finger_list;

    printf("list of people:\n");
    while (f->next != (struct finger *)NULL)
    {
	printf("---->%s:%ld:%d.\n", f->next->uname, f->next->idletime,
	       f->next->pid);
	f = f->next;
    }
    printf(">>>>>NULL.\n");
#endif /* DEBUG */

/*
** At this point, we have a list of people we want to finger.
** We now need to fill in the What element of finger structure
** and print the pup.
*/

/*
** Check to see if our nlist is up to date (stat its time against /xenix).
** If not, nlist /xenix and update it for future use.
*/

    if (stat(kernel_file, &stbuf[0]) < 0)
    {
	fprintf(stderr, "%s: Couldn't stat kernel file %s (%s).\n", progname,
		kernel_file, puterr(errno));
	exit(-1);
    }

    if ((status = stat(nlist_file, &stbuf[1])) < 0)
    {
	switch(errno)
	{
	case ENOENT: /* Brian ENOent */
#ifdef DEBUG
	    printf("No nlist file.\n");
#endif /* DEBUG */
	    break;

	default:
	    fprintf(stderr, "%s: Couldn't stat nlist file %s (%s).\n",
		    progname, nlist_file, puterr(errno));
	    exit(-1);
	}
    }

    if (stbuf[0].st_mtime > stbuf[1].st_mtime || status < 0)
	nlist_kernel();	/* Kernel has changed or nlist file does not exist. */
    else
    {
	/*
	** Open up nlist file and get the symbols that are needed.
	*/

	if ((fd = open(nlist_file, O_RDONLY)) < 0)
	{
	    fprintf(stderr, "%s: Couldn't open nlist save file %s (%s).\n",
		   progname, nlist_file, puterr(errno));

	    nlist_kernel(); /* Ok, so try to nlist the kernel. */
	}
	else
	{
	    if ((status = read(fd, &v_addr, sizeof(v_addr))) < sizeof(v_addr))
	    {
		if (status >= 0)
		    errno = sys_nerr;	/* Invalid error */

		fprintf(stderr, "%s: Couldn't read nlist save file %s (%s).\n",
			progname, nlist_file, puterr(errno));

		unlink(nlist_file);

		nlist_kernel(); /* Ok, so try to nlist the kernel. */
	    }

	    if ((status = read(fd, &proc_addr, sizeof(proc_addr))) <
		sizeof(proc_addr))
	    {
		if (status >= 0)
		    errno = sys_nerr;	/* Invalid error */

		fprintf(stderr, "%s: Couldn't read nlist save file %s (%s).\n",
			progname, nlist_file, puterr(errno));

		unlink(nlist_file);

		nlist_kernel(); /* Ok, so try to nlist the kernel. */
	    }
	}
    }

    close(fd);

#ifdef DEBUG
    printf("_v = %x.\n", v_addr);
    printf("_proc = %x.\n", proc_addr);
#endif /* DEBUG */

/*
** Copy the var struct from kernel memory.
*/

    if ((kmem=open(kmem_file, O_RDONLY)) < 0 )
    {
	fprintf(stderr, "%s: Can't open %s (%s).\n", progname, kmem_file,
		puterr(errno));
	exit(1);
    }

    mem_copy(kmem, kmem_file, (char *)&v, v_addr, sizeof(struct var));

/*
** Now we get NPROC by looking it up in the var struct.
*/

    NPROC = v.v_proc;

#ifdef DEBUG
    printf("NPROC = %x.\n", NPROC);
#endif /* DEBUG */

    /*
    ** Read in entire proc array.
    */

    proc = (struct proc *)mymalloc(sizeof(struct proc) * NPROC);

    mem_copy(kmem, kmem_file, (char *)proc, (long)((struct proc *)proc_addr),
	     sizeof(struct proc) * NPROC);

    /*
    ** Read in all user arrays.
    */

    if ((mem=open(mem_file, O_RDONLY)) < 0 )
    {
	fprintf(stderr, "%s: Can't open %s (%s).\n", progname, mem_file,
		puterr(errno));
	exit(1);
    }

    if ((swap=open(swap_file, O_RDONLY)) < 0 )
	fprintf(stderr, "%s: Can't open %s (%s).\n", progname, swap_file,
		puterr(errno));
    else
	noswap = FALSE;

    u = (struct user *)mymalloc(sizeof(struct user) * NPROC);

    for (i=0; i < NPROC; ++i)
	if (proc[i].p_stat)
	    if (proc[i].p_flag&SLOAD)	/* Is the process in memory? */
		mem_copy(mem, mem_file, (char *)&u[i],
			 IN_CORE_USER_STRUCT_ADDRESS, sizeof(struct user));
	    else
	    {
		if (!noswap)	/* If we can look at swapped memory, do. */
		    mem_copy(swap, swap_file, (char *)&u[i],
			     SWAPPED_USER_STRUCT_ADDRESS, sizeof(struct user));
		else		/* Otherwise ignore this process entry. */
		    proc[i].p_stat = 0;
	    }

    close(kmem);
    close(mem);
    close(swap);

    /*
    ** Get `what' field.
    */

    f = finger_list;

    while (f->next != NULL && f->next->pid != -1)
    {
	strncpy(f->next->what, get_what(proc, NPROC, u, f->next->pid),
		sizeof(f->next->what));

	f = f->next;
    }

/*
** Ok, so we have a big table of people.  Print this pup with info
** from the data base.
*/

    /*
    ** Get info from data base and update the lengths if needed.
    */

    f = finger_list;

    while (f->next != (struct finger *)NULL)
    {
	register int l;

	if (whatarewe&LONG)
	    get_dbase_info(f->next);
	else
	    f->next->valid_whois = FALSE;	/* Not needed. */

	if ((l = strnlen(f->next->uname, sizeof(f->next->uname))) >
	    uname_length)
	    uname_length = l;

	if ((l = strnlen(f->next->gname, sizeof(f->next->gname))) >
	    gname_length)
	    gname_length = l;

	if ((l = strnlen(f->next->flname, sizeof(f->next->flname))) >
	    flname_length)
	    flname_length = l;

	if ((l = strnlen(f->next->what, sizeof(f->next->what))) > what_length)
	    what_length = l;

	if ((l = strnlen(f->next->ttyname, sizeof(f->next->ttyname))) >
	    ttyname_length)
	    ttyname_length = l;

	f = f->next;
    }

    /*
    ** Now, print.
    */

    display_finger(finger_list);

    exit(0);
}

/*
** Some switch error.  Display usage an exit.
*/

usage()
{
    fprintf(stderr, "%s: usage %s [-lmpw%sIMPST*.] [-i[hh:]mm] [usernames]\n",
	    progname, progname,
#ifdef SCO_XENIX
	    "D"
#else
	    ""
#endif /* SCO_XENIX */
	    );
    exit(-1);
}

/*
** Copy bytes from kernel address space to this process.
*/

void
mem_copy(fd, name, caddr, kaddr, nbytes)
char *caddr, *name;
int fd;
long kaddr;
int nbytes;
{
    if ( lseek(fd, kaddr, 0) < 0L ||
	read(fd, caddr, (unsigned)nbytes) != nbytes )
    {
	fprintf(stderr, "%s: Can't read %s (%s).\n", progname, name,
		puterr(errno));
	exit(1);
    }
}

/*
** add_user - Add a user to the linked of fingered users.  name is
** a pointer to the user name (or tty) of the user that should be
** added. always_add, if TRUE, will add a user (or tty) even if
** they aren't logged in (or no one is using the device).
*/

add_user(name, always_add)
char *name;
int always_add;				/* Add to list if not logged in? */
{
    int l, found = FALSE;
    struct finger f;			/* A struct of handy information. */
    struct utmp *ut;			/* For looking up logged in users. */
    struct passwd *pt;			/* For looking up logged out users. */
    struct stat stbuf;			/* To make errors look informative. */

    static char device[] = "/dev/            \0";

    if (name == NULL)
	return;		/* Handle the awkward case. */

    setutent();	/* re-position utmp file pointer. */

    while ((ut = getutent()) != (struct utmp *)NULL)
	if (ut->ut_type == USER_PROCESS &&
	    (!strncmp(ut->ut_user, name, sizeof(ut->ut_user)) ||
	     !strncmp(ut->ut_line, name, sizeof(ut->ut_user))))
	{
	    found = TRUE;
	    fill_n_add(ut);
	}
	
    if (always_add && !found)
    {
	/*
	** Make sure this person exists.
	*/

	if ((pt = getpwnam(name)) == (struct passwd *)NULL)
	{
	    int status;

	    /*
	    ** Not a username!
	    ** Is this a ttyname or something else ... ?
	    */

	    strncpy(device + sizeof("/dev/") - 1, name,
		    sizeof(device) - sizeof("/dev/"));

	    if ((status = stat(device, &stbuf)) < 0 && errno == ENOENT)
		fprintf(stderr, "%s: no user %s.\n", progname, name);
	    else
	    {
		/*
		** Aha!  A device with no one logged into it.
		*/

		if (status != -1 && (stbuf.st_mode&S_IFMT) == S_IFCHR)
		{
		    strncpy(f.uname, "UNUSED", sizeof(f.uname));
		    strcpy(f.gname, "");
		    strcpy(f.flname, "EMPTY PORT");
		    strcpy(f.what, "");
		    strncpy(f.ttyname, name, sizeof(f.ttyname));
		    strncpy(f.ttyloc, get_ttyloc(f.ttyname), TTYLOC_LENGTH);
		    f.idletime = -1;
		    f.pid = -1;
		    f.messy = TRUE;

		    add_to_list(f);
		}
		else
		    fprintf(stderr, "%s: %s not a terminal device.\n",
			    progname, device);
	    }
	}
	else
	{
	    /*
	    ** This is a user that is not logged in.
	    */

	    strncpy(f.uname, name, sizeof(f.uname));

	    strncpy(f.gname, group_name(pt->pw_gid), sizeof(f.gname));
	    strncpy(f.flname, pt->pw_gecos, sizeof(f.flname));
	    f.home_dir = mymalloc(l = strlen(pt->pw_dir) + 1);
	    strcpy(f.home_dir, pt->pw_dir);
	    strcpy(f.what, "");
	    f.login_shell = mymalloc(l = strlen(pt->pw_shell) + 1);
	    strcpy(f.login_shell, pt->pw_shell);
	    f.uid = pt->pw_uid;
	    f.gid = pt->pw_gid;

	    strcpy(f.ttyname, "");
	    strcpy(f.ttyloc, last_login(f.uname));

	    f.idletime = -1;
	    f.pid = -1;
	    f.messy = TRUE;

	    add_to_list(f);
	}
    }
}

/*
** fill_n_add() - Ok, so we have a utmp struct for the current user.
** Move some valid info into a finger structure and pass it on to
** add_to_list.  This also puts the idle time info in the finger struct
** but does not test idle time ala the -i switch.
*/

void
fill_n_add(ut)
struct utmp *ut;
{
    int l;
    struct stat stbuf;			/* For idle time lookup. */
    struct finger f;			/* Filling in user stats. */
    struct passwd *pt;			/* For fullname and group name. */
    static char device[] = "/dev/            \0";

    strncpy(f.uname, ut->ut_user, sizeof(f.uname));

    /*
    ** If we can't find the user in /etc/passwd, then something
    ** is really wrong.  Fake a fullname and hope he exists in
    ** the database.
    **
    ** Actually, I think this could happen if a user was removed
    ** from /etc/passwd while he was logged in.
    **
    ** Upon running this code, it happened.  On a SCO XENIX system
    ** DIALOUTs are marked in utmp.  So, we should make the full
    ** name generic and the sysadmin should make a passwd entry 
    ** if he wants a real full name.
    **
    ** We now ignore DIALOUT marked lines specificly.  Yes, this is
    ** a crock.  -D ignores our ignoring.
    */
    if ((pt = getpwnam(f.uname)) != (struct passwd *)NULL)
    {
	strncpy(f.gname, group_name(pt->pw_gid), sizeof(f.gname));
	strncpy(f.flname, pt->pw_gecos, sizeof(f.flname));
	f.home_dir = mymalloc(l = strlen(pt->pw_dir) + 1);
	strcpy(f.home_dir, pt->pw_dir);
	f.login_shell = mymalloc(l = strlen(pt->pw_shell) + 1);
	strcpy(f.login_shell, pt->pw_shell);
	f.uid = pt->pw_uid;
	f.gid = pt->pw_gid;
    }
    else
    {
#ifdef SCO_XENIX
	if (!(whatarewe&DIALOUT))
	    if (!strncmp(f.uname, "DIALOUT"))
		return;
#endif /* SCO_XENIX */

	strcpy(f.gname, "");
	strncpy(f.flname, "Not a user.", sizeof(f.flname));
    }

    strncpy(f.ttyname, ut->ut_line, sizeof(f.ttyname));

    strncpy(device + sizeof("/dev/") - 1, f.ttyname,
	    sizeof(device) - sizeof("/dev/"));

    if (stat(device, &stbuf) < 0)
    {
	f.idletime = -1;
	f.messy = TRUE; /* We're confused, so fake like we know something. */
    }
    else
    {
	f.idletime = time((long *)0) - (long)stbuf.st_atime;

	if (stbuf.st_mode&STAT_OWRITE)
	    f.messy = TRUE;
	else
	    f.messy = FALSE;
    }

    f.pid = ut->ut_pid;
    strcpy(f.ttyloc, get_ttyloc(f.ttyname));
    add_to_list(f);
}

/*
** add_to_list - Add (struct finger)new to a list and keep
** the list alphabetical by username.  If usernames match then
** alphabetize by ttyname.
*/

void
add_to_list(new)
struct finger new;
{
    char *fp, *tp;
    int i;
    long l;
    struct finger *f, *keep;

    /*
    ** First go through list and make sure this user is not already on.
    */

    f = finger_list;
    while (f->next != NULL)
	if (!strncmp(f->next->ttyname, new.ttyname))
	    return;
	else
	    f = f->next;

    f = finger_list;
    switch (whatarewe&SORT_MASK)
    {

/*
** Ya'know, I hate using these defines, but I couldn't stand looking
** at the lines when they were expanded wrapping around on a 80 column
** screen.  NO, there was no decent way to break them up either.
*/

#define F_N_TTY		f->next->ttyname
#define S_F_N_TTY	sizeof(f->next->ttyname)
#define F_N_UNA		f->next->uname
#define S_F_N_UNA	sizeof(f->next->uname)

    case SORT_BY_IDLE:
	while (f->next != NULL)
	    if ((l = f->next->idletime - new.idletime) < 0 ||
		(!l && ((i = strncmp(F_N_UNA, new.uname, S_F_N_UNA)) < 0 ||
			(!i && strncmp(F_N_TTY, new.ttyname, S_F_N_TTY) < 0))))
		f = f->next;
	    else
		break;
	break;

    case SORT_BY_TTY:
	while (f->next != NULL)
	    if (strncmp(F_N_TTY, new.ttyname, S_F_N_TTY) < 0)
		f = f->next;
	else
	    break;
	break;

    default:
	while (f->next != NULL)
	    if ((i = strncmp(F_N_UNA, new.uname, S_F_N_UNA)) < 0 ||
		(!i && strncmp(F_N_TTY, new.ttyname, S_F_N_TTY) < 0))
		f = f->next;
	    else
		break;
	break;

/*
** Make those `Local' defines.
*/
#undef F_N_TTY
#undef S_F_N_TTY
#undef F_N_UNA
#undef S_F_N_UNA
    }

    keep = (struct finger *)mymalloc(sizeof(struct finger));

    fp = (char *)&new;
    tp = (char *)keep;
    for (i=0; i < sizeof(struct finger); ++i)
	*tp++ = *fp++;

    keep->next = f->next;
    f->next = keep;
}

/*
** nlist kernel for _v and write it out to nlist_file for future reference.
*/

nlist_kernel()
{
    int
	fd,
	status;

#ifdef DEBUG
    printf("Gotta nlist the kernel.\n");
#endif /* DEBUG */

    if (nlist(kernel_file, nl) < 0)
    {
	fprintf(stderr, "%s: nlist failed on %s (%s).\n", progname,
		kernel_file, puterr(errno));
	exit(-1);
    }

    if (v_addr == 0 || proc_addr == 0)
    {
	fprintf(stderr,
	       "%s: couldn't nlist needed symbols: _v = 0x%x, _proc = 0x%x.\n",
		progname, v_addr, proc_addr);
	exit(-1);
    }
#ifdef DO_INDIRECTION_ON_PROC

    /*
    ** On some systems proc is a pointer to an array of struct proc
    ** instead of just an array of struct proc.  So, we do indirection
    ** on this pointer to make the it compatible with the rest of
    ** the program.
    */
    if ((fd=open(kmem_file, O_RDWR)) < 0 )
    {
	fprintf(stderr, "%s: Can't open %s (%s).\n", progname, kmem_file,
		puterr(errno));
	exit(1);
    }

    mem_copy(fd, kmem_file, (char *)&proc_addr, proc_addr, sizeof(proc_addr));

    close(fd);
#endif /* DO_INDIRECTION_ON_PROC */

    /*
    ** Write symbol(s) to nlist file for quick reference next time.
    */

    if ((fd = open(nlist_file, O_CREAT|O_WRONLY, 0666)) < 0)
	fprintf(stderr, "%s: Couldn't open nlist save file %s (%s).\n",
		progname, nlist_file, puterr(errno));
    else
    {
	if ((status = write(fd, &v_addr, sizeof(v_addr))) < sizeof(v_addr))
	{
	    if (status >= 0)
		errno = -1;

	    fprintf(stderr, "%s: Couldn't write to nlist save file %s (%s).\n",
		    progname, nlist_file, puterr(errno));

	    unlink(nlist_file);
	}

	if ((status = write(fd, &proc_addr, sizeof(proc_addr))) <
	    sizeof(proc_addr))
	{
	    if (status >= 0)
		errno = -1;

	    fprintf(stderr, "%s: Couldn't write to nlist save file %s (%s).\n",
		    progname, nlist_file, puterr(errno));

	    unlink(nlist_file);
	}
    }
}

/*
** get_what() - returns the name of program PID is currently running.
*/

char *
get_what(proc, NPROC, u, pid)
int NPROC, pid;
struct proc *proc;
struct user *u;
{
    register int i;
    long entry;

    for (i=0; i < NPROC; ++i)
	if (proc[i].p_stat && proc[i].p_pid == pid)
	    break;

    if (proc[i].p_stat == SSLEEP && proc[i].p_wchan ==
	(caddr_t)(proc_addr + (sizeof(struct proc) * i)))
	entry = find_recent(pid, proc, NPROC, u);
    else
	entry = i;

    if (entry == -1)
	return ".lost";
    else
	return u[entry].u_comm;
}

/*
** find_recent() - Find the most recently execed child process of PPID.
** This is done by looking at u.u_start of child process of PPID that
** is not is waiting (proc.p_start == SSLEEP) on a child process
** (proc.p_wchan != &proc);
*/

long
find_recent(ppid, proc, NPROC, u)
int NPROC;
long ppid;
struct proc *proc;
struct user *u;
{
    register int i;
    long ours = -1, tmp;
    time_t ourtime = 0;

    for (i=0; i < NPROC; ++i)
	if (proc[i].p_stat && proc[i].p_ppid == ppid)
	    if (proc[i].p_stat == SSLEEP && proc[i].p_wchan ==
		(caddr_t)(proc_addr + (sizeof(struct proc) * i)))
	    {
		tmp = find_recent((long)proc[i].p_pid, proc, NPROC, u);
		if (u[tmp].u_start > ourtime)
		{
		    ourtime = u[tmp].u_start;
		    ours = tmp;
		}
	    }
	    else
		if (u[i].u_start > ourtime)
		{
		    ourtime = u[i].u_start;
		    ours = i;
		}
    
    return ours;
}

/*
** group_name() - return a the name of the group whose group id is GID
** or a nul string if the group does not exist.
*/

char *
group_name(gid)
int gid;
{
    static char gname[8];
    struct group *gp;

    if ((gp = getgrgid(gid)) != (struct group *)NULL)
	strncpy(gname, gp->gr_name, sizeof(gname));
    else
	gname[0] = '\0';

    return gname;
}

/*
** itot() - Convert a long to a time string of the form:
**		time < minute			- [Blank]
**		time < hour			- MM
**		hour < time < day		- HH:MM
**		time > day && HH == 0		= D day[s]
**		1 day < time < 10 days		- Dd[s] HHh[s]
**		time >= 10 days     		- DDD days
*/

char *
itot(seconds)
long seconds;
{
    char *tp;
    long t, tm, minutes = seconds / 60;
    static char tbuf[9];

    tp = tbuf;

    if (!minutes)		/* Not idle? */
	tbuf[0] = '\0';
    else if (minutes < 60)	/* less than an hour? */
	sprintf(tbuf, "%ld", minutes);
    else if (minutes < 60*24)	/* Less than a day? */
	sprintf(tbuf, "%ld:%02ld", minutes/60, minutes%60);
    else if (minutes < 60*24*10) /* Less than 10 days? */
    {
	t = minutes/(60*24);	/* how many days */
	tm = minutes%(60*24)/60;/* how many hours left in the day */

	if (tm)
	    sprintf(tbuf, "%ldd%s %ldh%s", t, t == 1 ? "" : "s", tm,
		    tm == 1 ? "" : "s");
	else
	    sprintf(tbuf, "%ld day%s", t, t == 1 ? "" : "s");

    } else if (minutes < 60*24*1000) /* Less than 999 days */
	sprintf(tbuf, "%ld days", minutes/(60*24));
    else				/* Just obnoxious! */
	strcpy(tbuf, "forever");

    return tbuf;
}

/*
** last_login() - return a string explaining last time user logged in, if
** possible.
*/

char *
last_login(name)
char *name;
{
    time_t last_time = -1;
    struct utmp *ut;
    struct stat stbuf;
    static char last_log[sizeof("No log entry since ") + 26];

    utmpname(WTMP_FILE);

    while((ut = getutent()) != (struct utmp *)NULL)
	if (ut->ut_type == USER_PROCESS &&
	    !strncmp(ut->ut_user, name, sizeof(ut->ut_user)))
	    last_time = ut->ut_time;

    if (last_time != -1)
    {
	strcpy(last_log, "Last logged on ");
	strcat(last_log, ctime(&last_time));
    }
    else
	if (stat(WTMP_FILE, &stbuf) < 0)
	    strcpy(last_log, "Not logged in.");
	else
	{
	    strcpy(last_log, "No log entry since ");
	    strcat(last_log, ctime(&stbuf.st_atime));
	}

    utmpname(UTMP_FILE);

    return last_log;
}

/*
** The actual display routine.
*/

void
display_finger(f)
struct finger *f;
{
    char *last;				/* Last uname we printed. */
    int	hidename;			/* Print multiple name entries? */

    if (f->next == (struct finger *)NULL)
    {
	fprintf(stderr, "%s: no users.\n", progname);
	exit(-1);
    }
    else
	printf("%-*s %-*s %-*s %-*s %-*s %-*s %s\n",
	       uname_length, "-Name-",
	       gname_length, "-Group-",
	       flname_length, "--Full Name--",
	       what_length, "-What-",
	       idle_length, "Idle",
	       ttyname_length, "-TTY-",
	       "-Console Location-");

    last = "";
    while (f->next != (struct finger *)NULL)
    {
	if (whatarewe&HIDABLE && 
	    (!strncmp(last, f->next->uname, sizeof(f->next->uname))))
	    hidename = TRUE;
	else
	    hidename = FALSE;

	printf("%-*.*s %-*.*s %-*.*s %-*.*s %-*.*s%c%-*.*s %-.*s\n",
	       uname_length, sizeof(f->next->uname),
	       hidename ? "" : f->next->uname,
	       gname_length, sizeof(f->next->gname),
	       hidename ? "" : f->next->gname,
	       flname_length, sizeof(f->next->flname),
	       hidename ? "" : f->next->flname,
	       what_length, sizeof(f->next->what), f->next->what,
	       idle_length, sizeof(f->next->idle), f->next->idle,
	       f->next->messy ? ' ' : '*',
	       ttyname_length, sizeof(f->next->ttyname), f->next->ttyname,
	       strnlen(f->next->ttyloc, sizeof(f->next->ttyloc)),
	       f->next->ttyloc);

	if (f->next->next == (struct finger *)NULL ||
	    strncmp(f->next->next->uname, f->next->uname,
		    sizeof(f->next->uname)))
	{
	    if (whatarewe&LONG)
		display_dbase(f);

	    if (whatarewe&MAILBOX)
		display_mailbox(f->next->home_dir, f->next->uname);

	    if (whatarewe&LONG &&
		f->next->valid_whois && f->next->remark != '\0')
		fputs(f->next->remark, stdout);

	    if (whatarewe&PLAN)
		display_plan(f->next->home_dir);

	    if (whatarewe&WHOIS && f->next->next != (struct finger *)NULL)
		putchar('\n');
	}

	last = f->next->uname;
	f = f->next;
    }
}

void
display_dbase(f)
struct finger *f;
{
    int	terpri;

    if (f->next->valid_whois)
    {
	terpri = FALSE;
	if (f->next->nickname[0] != '\0')	/* Non blank field. */
	{
	    printf("(%s) ", f->next->nickname);
	    terpri = TRUE;
	}

	if (f->next->home_dir[0] != '\0')	/* Non blank field. */
	{
	    printf("<%s> ", f->next->home_dir);
	    terpri = TRUE;
	}

	if (f->next->project[0] != '\0' ||
	    f->next->supervisor[0] != '\0')	/* Non blank field. */
	{
	    printf("Hacking %s", f->next->project);

	    if (f->next->supervisor[0] != '\0') /* Non blank field. */
		printf(" for %s", f->next->supervisor);

	    terpri = TRUE;
	}

	if (terpri)
	{
	    putchar('\n');
	    terpri = FALSE;
	}

	printf("[%d, %d] [%s]", f->next->uid, f->next->gid,
	       f->next->login_shell[0] == '\0' ?
	       "no shell" : f->next->login_shell);

	if (f->next->birthday[0] != '\0')	/* Non blank field. */
	    printf(" Birthday %s\n", f->next->birthday);
	else
	    putchar('\n');

	if (f->next->home_addr[0] != '\0' ||
	    f->next->home_phone[0] != '\0')	/* Non blank field. */
	{
	    fputs("Home ", stdout);

	    if (f->next->home_addr[0] != '\0')
		printf("%s; ", f->next->home_addr);

	    if (f->next->home_phone[0] != '\0')
		printf("Phone %s", f->next->home_phone);

	    putchar('\n');
	}

	if (f->next->work_addr[0] != '\0' ||
	    f->next->work_phone[0] != '\0')	/* Non blank field. */
	{
	    fputs("Work ", stdout);

	    if (f->next->work_addr[0] != '\0')
		printf("%s; ", f->next->work_addr);

	    if (f->next->work_phone[0] != '\0')
		printf("Phone %s", f->next->work_phone);

	    putchar('\n');
	}
    }
    else
    {
	if (f->next->home_dir[0] != '\0')	/* Non blank field. */
	    printf("<%s> ", f->next->home_dir);

	printf("[%d, %d] [%s]", f->next->uid, f->next->gid,
	       f->next->login_shell[0] == '\0' ?
	       "no shell" : f->next->login_shell);

	puts("\n-- No data base entry --");
    }
}

/*
** Show info about mailbox (if any).
*/

void
display_mailbox(home_dir, name)
char *home_dir, *name;
{
    char *filename, *bp, buf[BUFSIZ], hold[BUFSIZ];
    struct stat stbuf;
    FILE *fp;

    /*
    ** Check for .forward file.
    */

    filename = mymalloc(strlen(home_dir) + sizeof('/') + sizeof(FORWARD_FILE));

    strcpy(filename, home_dir);
    strcat(filename, "/");
    strcat(filename, FORWARD_FILE);

    if ((fp = fopen(filename, "r")) != (FILE *)NULL)
    {
	buf[0] = '\0';
	
	fgets(buf, sizeof(buf), fp);
	if (buf[0] != '\0')
	{
	    bp = buf;
	    
	    while(*bp != '\n' && *bp != '\0')
		++bp;
	    
	    *bp = '\0';
	    
	    printf("[mail is forwarded to %s]\n", buf);
	    return;
	}
    }

    filename = myrealloc(filename,
			 sizeof(MAIL_SPOOL_DIR) + sizeof('/') + strlen(name));

    strcpy(filename, MAIL_SPOOL_DIR);
    strcat(filename, "/");
    strcat(filename, name);

    if ((fp = fopen(filename, "r")) == (FILE *)NULL)
	puts("[no mailbox]");
    else
    {
	hold[0] = '\0';

	while (fgets(buf, sizeof(buf), fp) != NULL)
	    if (!strncmp(buf, "From ", 5))
		strcpy(hold, buf);

	fclose(fp);

	if (hold[0] == '\0')
	{
	    /*
	    ** No mail since.
	    */
	    if (stat(filename, &stbuf) < 0)
		printf("[%s has no mail]\n", name);
	    else
	    {
		char *timestr = ctime(&stbuf.st_mtime);

		timestr[24] = '\0';		/* ditch the '\n'. */
		printf("[%s has had no mail since %s]\n", name, timestr);
	    }
	}
	else
	{
	    hold[strlen(hold)-1] = '\0';	/* ditch the '\n'. */
	    printf("[%s has mail f%s]\n", name, hold+1);
	}
    }

    free(filename);

}

/*
** Show ~/.plan if exist.
*/

void
display_plan(home_dir)
char *home_dir;
{
    char *plan_file, buf[BUFSIZ];
    FILE *fp;

    plan_file = mymalloc(strlen(home_dir) + sizeof('/') + sizeof(PLAN_FILE));

    strcpy(plan_file, home_dir);
    strcat(plan_file, "/");
    strcat(plan_file, PLAN_FILE);

    if ((fp = fopen(plan_file, "r")) != (FILE *)NULL)
    {
	puts("Plan:");

	while (fgets(buf, sizeof(buf), fp) != NULL)
	    fputs(buf, stdout);

	fclose(fp);
    }
    else if (!(whatarewe&LONG))
	puts("No plan.");

    free(plan_file);
}
SHAR_EOF
fi # end of overwriting check
if test -f 'rots.c'
then
	echo shar: will not over-write existing file "'rots.c'"
else
cat << \SHAR_EOF > 'rots.c'
/*
** rots.h - For the finger distribution.
**
** Written by Keith Gabryelski
** Released into public domain September 1, 1988.
** Please keep this header.
**
** Some basic routines for most programs in the distribution.
*/

#include <stdio.h>
#include <errno.h>

extern int errno, sys_nerr;
extern char *sys_errlist[];
extern char *malloc(), *realloc();

extern *progname;

char *puterr();

char *
mymalloc(size)
unsigned size;
{
    char *p;

    if ((p = malloc(size)) == NULL)
    {
	(void) fprintf(stderr, "%s: Out of memory.\n", progname);
	exit(-1);
    }

    return p;
}

char *
myrealloc(p, size)
char *p;
unsigned size;
{
    if (p == NULL)
    {
	if ((p = malloc(size)) == NULL)
	{
	    (void) fprintf(stderr, "%s: Out of memory.\n", progname);
	    exit(-1);
	}
    }
    else if ((p = realloc(p, size)) == NULL)
    {
	(void) fprintf(stderr, "%s: Out of memory.\n", progname);
	exit(-1);
    }

    return p;
}

strnlen(string, length)
char *string;
int length;
{
    register i;

    for (i=0; i < length; ++i)
	if (*string++ == '\0')
	    break;

    return i;
}

char *
puterr(error)
int error;
{
    static char qwerty[42];

    (void) sprintf(qwerty, "Unknown error %d", error);

    return ((unsigned)error >= sys_nerr) ? qwerty : sys_errlist[error];
}
SHAR_EOF
fi # end of overwriting check
if test -f 'dbase.h'
then
	echo shar: will not over-write existing file "'dbase.h'"
else
cat << \SHAR_EOF > 'dbase.h'
/*
** dbase.h - For the finger distribution.
**
** Written by Keith Gabryelski
** Released into public domain September 1, 1988.
** Please keep this header.
*/

/*
** The finger structure.
*/

struct finger
{
    int valid_whois;	/* Is this whois entry valid? */
    int pid;		/* Process id. */
    char uname[8];	/* User name. */
    ushort uid;		/* User ID of user. */
    char gname[12];	/* Group name. */
    ushort gid;		/* Group ID of user. */
    char flname[30];	/* Full name. */
    int messy;		/* Messages on?. */
    char ttyname[12];	/* Name of controlling tty device. */
    long idletime;	/* Idle time of tty device. */
    char idle[9];	/* string for the idle time. */
    char what[14];	/* Currently running program. */
    char ttyloc[TTYLOC_LENGTH];	/* Informative tty location. */
    char *nickname;	/* Nickname of user. */
    char *home_dir;	/* Home directory. */
    char *login_shell;	/* Login Shell. */
    char *work_addr;	/* Work address. */
    char *work_phone;	/* Work phone. */
    char *home_addr;	/* Home address. */
    char *home_phone;	/* Home phone. */
    char *birthday;	/* Birthday. */
    char *project;	/* Current Project. */
    char *supervisor;	/* Supervisor. */
    char *remark;	/* Ramblings. */
    struct finger *next;/* Next struct in the linked list. */
};
SHAR_EOF
fi # end of overwriting check
if test -f 'bwiz.c'
then
	echo shar: will not over-write existing file "'bwiz.c'"
else
cat << \SHAR_EOF > 'bwiz.c'
/*
** bwiz.c - This is the Birthday Wizard.
**	  - This is part of the finger distribution.
**
** Run once every morning to brighten a users
** birthday up.
**
** On SCO Xenix, I created a user, "bwiz" that crons this program at
** 00:05 in the morning.
**
** Written by Keith Gabryelski
** Released into public domain September 1, 1988.
** Please keep this header.
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <fcntl.h>
#include <a.out.h>
#include "defs.h"
#include "finger.h"

extern int errno;
extern char *ctime(), *strtok();

char
    *progname,
    *whois_dbase_dir = WHOIS_DBASE_DIR;

main(argc, argv)
int argc;
char **argv;
{
    char *timestr;
    int fd;
    long tloc;
    struct direct dir;

    progname = *argv++; --argc;

    
    tloc = time((long *)0);
    timestr = ctime(&tloc);
    timestr[sizeof("DAY MON DD")-1] = '\0';
    timestr += (sizeof("DAY ") - 1);		/* - 1 for '\0'. */

#ifdef DEBUG
    printf("timestr = `%s'.\n", timestr);
#endif						/* DEBUG */

    if ((fd = open(whois_dbase_dir, O_RDONLY)) < 0)
	fprintf(stderr,	"%s: Couldn't find database directory %s (%s).\n",
		progname, whois_dbase_dir, puterr(errno));
    else
	while (read(fd, &dir, sizeof(struct direct)) == sizeof(struct direct))
	    if (strcmp(dir.d_name, ".") && strcmp(dir.d_name, "..") &&
		is_birthday(dir.d_name, timestr))
		send_birthday_card(dir.d_name);

}

/*
** Return TRUE if it is NAME's birthday is on THE_DATE.
*/

is_birthday(name, the_date)
char *name, *the_date;
{
    char *ptr, our_bday[sizeof("MMM DD")];
    int is_bday = FALSE;
    struct finger f;

    strncpy(f.uname, name, sizeof(f.uname));
    get_dbase_info(&f);

    if (f.valid_whois)
    {
	if ((ptr = strtok(f.birthday, " ")) != NULL)
	{
	    sprintf(our_bday, "%.3s %2d", ptr, atoi(strtok(NULL, " ")));
#ifdef DEBUG
	    printf("name = %s, our_bday = %s.\n", name, our_bday);
#endif /* DEBUG */
	    if (!strcmp(our_bday, the_date))
		is_bday = TRUE;
	}
    }

    return is_bday;
}

/*
** Send a birthday card to NAME.
*/
send_birthday_card(name)
char *name;
{
    FILE *fp;

    if ((fp = freopen(BIRTHDAY_CARD, "r", stdin)) == (FILE *)NULL)
    {
	fprintf(stderr, "%s: could reopen stdin (%s).\n", progname,
		puterr(errno));
	exit(-1);
    }

    execl(MAILER, MAILER, name, NULL);

    fprintf(stderr, "%s: Couldn't exec %s (%s).\n", progname, MAILER,
	    puterr(errno));
    exit(-1);
}
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0
-- 
ag@elgar.CTS.COM         Keith Gabryelski          ...!{ucsd, jack}!elgar!ag