/*
** 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);
}
