/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: getut.c 1.20 90/01/25 $@(#)libc-port:gen/getut.c	1.10"

/*	Routines to read and write the /etc/utmp file.			*/

/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*************************************************************************
 * This code has been greatly modified to support a hashed utmp file.
 * Lookup and modify operations are much faster and support for networking
 * by associating a host machine name with a utmp entry has been added.
 * In addition login limit accounting is supported by refusing to add
 * utmp entries of type "USER_PROCESS" past some maximum number of users.
 *
 * Several defines are used to control the actions of this code.
 *
 *
 * HASH_UTMP   - Causes the utmp file to be hashed and enables entry points
 *               which support login accounting.
 *
 * ERRDEBUG    - Generates messages on a log file showing actions of these
 *               routines.
 *
 * DEBUG       - Cause files to be in local directory.  For testing.
 *
 * DEBUG_HASH  - Build a stand-alone program to allow testing the routines.
 *
 * LOG_FILE    - Log all error messages to a file "/etc/dbg.getut".
 * 
 */
  
#include	<stdio.h>
#include	<sys/param.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<utmp.h>
#include	<errno.h>
#include	<fcntl.h>
#include	<memory.h>

#define CONSOLE	"/dev/console"	/* define for error logging to console */
#define TERMINAL "/dev/tty"	/* define for error logging to terminal */
#define UT_ERR_IGNORE_C	"/etc/.ut_no_err_msgs"	/* no console error msgs */

/* #define ERRDEBUG	/* generates log messages in "dbg.getut" */
/* #define DEBUG	/* causes files to be in local dir, not /etc */
/* #define LOG_FILE	/* send ut_err output to log file */

#define HASH_UTMP	/* hash the utmp file and enable accounting */
/* #define DEBUG_HASH	/* create stand alone test programs  */

#ifdef	DEBUG
#undef	UTMP_FILE
#define	UTMP_FILE "utmp"
#include <ctype.h>
#endif


int _utmp_slot_num;	/* EXTERNAL - which slot did a lookup find, -1 if none */

static int fd = -1;	  /* File descriptor for the utmp file. */
static int utmp_canwrite;  /* 1 if we can write the utmp file */
static char utmpfile[DYNIX_MAXNAMLEN+1] = UTMP_FILE;	/* utmp file name */
static long loc_utmp;     /* Where in "utmp" the current "ubuf" was found */
static struct utmp ubuf;  /* Copy of last entry read in. */

extern int errno;

#ifdef HASH_UTMP          /* Data needed for hashed utmp support */

#define EQ(a,b)		(strcmp((a),(b)) == 0)

#include <string.h>
#include <sys/utsname.h>
#include <signal.h>
#include "ndbm.h"

#ifdef DEBUG_HASH
#include <time.h>
#endif

#define KEY_ID		0	/* data base key types */
#define KEY_LINE	1
#define KEY_HOST	2
#define KEY_PID		3
#define KEY_SPECIAL	4

#define BUILD_DB_NEW	0	/* build new data base - no existing data base */
#define BUILD_DB_BADF	1	/* build new data base - existing bad size */
#define BUILD_DB_EXIST	2	/* rebuild data base - existing out of date */


#ifndef DBM_FILE
#define DBM_FILE	"/etc/utmp_dbm"
#endif

#ifndef DBM_FILE_DIR
#define DBM_FILE_DIR	UTMP_DBM_DIR_FILE
#endif

#ifndef DBM_FILE_PAG
#define DBM_FILE_PAG	UTMP_DBM_PAG_FILE
#endif

#ifdef  DEBUG
#undef  DBM_FILE
#define DBM_FILE	"utmp_dbm"
#undef  DBM_FILE_DIR
#define DBM_FILE_DIR	"utmp_dbm.dir"
#undef  DBM_FILE_PAG
#define DBM_FILE_PAG	"utmp_dbm.pag"
#undef	UT_ERR_IGNORE_C
#define UT_ERR_IGNORE_C	"ut_no_err_msgs_c"
int debug_maxusers=8;
#endif	/* DEBUG */

static int not_utmp_file;	/* 1 if not using /etc/utmp */
static int utmp_write_locked;	/* 1 if utmp is write locked */
static int utmp_read_locked;	/* 1 if utmp is read locked */
static int signals_blocked;	/* 1 if we have blocked signals */
static int sig_mask_built;	/* 1 once signal mask created */
static sigset_t mask_set;	/* set of all signals */
static sigset_t old_set;	/* users saved set of signals */
static DBM *db;			/* data base information structure */

static char   *gen_key();		/* create key for general entry */
static char   *host_user_key();		/* create key for host/user */
static void   add_slot_key();		/* given key add slot number */
static int    findslot();		/* locate utmp slot */
static int    get_rec();		/* read utmp entry */
static int    add_rec();		/* write utmp entry at end of file */
static int    replace_rec();		/* write utmp entry at given index */
static int    open_ut_file();		/* open utmp file */
static int    check_utmp_hash();	/* check that utmp file is ready */
static int    check_dbm_size();		/* check that size of dbm is vaild */
static struct utmp *ut_update();	/* add or replace a utmp entry */
static int    local_line();		/* mark lines as local windows */
static void   set_num_users();		/* set the number of users */
static void   inc_numusers();		/* increment number of users */
static void   dec_numusers();		/* decrement number of users */
static int    utmp_write_lock();	/* write lock utmp file */
static void   utmp_write_unlock();	/* remove utmp write lock */
static int    utmp_read_lock();		/* read lock utmp file */
static void   utmp_read_unlock();	/* remove utmp read lock */
static void   block_signals();		/* block all signals */
static void   unblock_signals();	/* reset to users signal mask */
static int    check_utmp_hash();	/* insure data base ready */
static void   inc_host_user();		/* increment user/host count */
static void   dec_host_user();		/* decrement user/host count */
static void   delete_host();		/* delete host/line information */
static void   set_host();		/* add host/line information */
static int    hostcount();		/* how many logins for host/user */
static char   *findhost();		/* given a line return the assocated host */
static int    build_db();		/* build or re-build data base */

#endif /* HASH_UTMP */

static void   ut_err();			/* print error message */

#define L_C	0x1			/* print messages on console */
#define L_U	0x2			/* print messages on users terminal */
#define L_A	(L_C | L_U)		/* print on both */

#define FIND_LINE	1		/* see if line is local window */
#define SET_LINE	2		/* set line to local window */
#define DELETE_LINE	3		/* delete local window status */

/*
 *----------------------------------------------------------------------
 *  getutent
 *
 *  Return the next entry in the utmp file.  Return NULL if unable
 *  to open file or if at the end of file.
 *
 */

struct utmp *
getutent()
{
	extern long loc_utmp, lseek();

	_utmp_slot_num = -1;

	/* If utmp file is not open, attempt to open it. */

	if  (fd < 0) {
#ifdef ERRDEBUG
		gdebug("getutent: called with no utmp file open.\n");
#endif
		if (open_ut_file())
			return((struct utmp *) NULL);
	}


	/* Try to read in the next entry from the utmp file.  */

	if (read(fd,&ubuf,sizeof(ubuf)) != sizeof(ubuf)) {

		/* Make sure ubuf is zeroed. */
		(void) memset((char *) &ubuf, '\0', sizeof(ubuf));
		loc_utmp = 0;
		return((struct utmp *) NULL);
	}

	/* Save the location in the file where this entry was found. */

	loc_utmp = lseek(fd,0L,1) - (long)(sizeof(struct utmp));
	_utmp_slot_num = loc_utmp / sizeof(struct utmp);
	return(&ubuf);
}

/*
 *----------------------------------------------------------------------
 *  getutid
 *
 *  Find the specified entry in the utmp file.  Search by ut_id field.
 *  Return NULL if unable to locate entry.
 *
 */

struct utmp *
getutid(entry)
	register struct utmp *entry;
{
	struct utmp *getutent();
	register short type;

#ifdef HASH_UTMP
	int slot;
	char *key;

#ifdef ERRDEBUG
	gdebug("getutid: my pid= %d\n", getpid());
#endif

	if (not_utmp_file) {
#ifdef ERRDEBUG
		gdebug("getutpid: not_utmp_file = %d\n", not_utmp_file);
#endif
		goto nohash;
	}

	_utmp_slot_num = -1;

	/* Bail out if empty record passed in. */

	if (entry == NULL)
		return((struct utmp *) NULL);

	if (entry->ut_type == EMPTY || entry->ut_type == ACCOUNTING)
		return((struct utmp *) NULL);

	/* Insure all needed files are open. */

	if (check_utmp_hash()) {
#ifdef ERRDEBUG
		gdebug("getutpid: check_utmp_hash() returned non-zero\n");
#endif
		goto nohash;
	}

	switch (entry->ut_type) {
	case RUN_LVL:
	case BOOT_TIME:
	case OLD_TIME:
	case NEW_TIME:
		key = gen_key(entry, KEY_SPECIAL);
		slot = findslot(key);
#ifdef ERRDEBUG
		gdebug("getutid: type= %d, key='%s', slot= %d\n",
			entry->ut_type, key, slot);
#endif
		if (slot < 0)
			return((struct utmp *) NULL);
		if (get_rec(slot))
			return((struct utmp *) NULL);
		_utmp_slot_num = slot;
		return(&ubuf);
	case INIT_PROCESS:
	case LOGIN_PROCESS:
	case USER_PROCESS:
	case DEAD_PROCESS:	/* these four are all use id as key */
		key = gen_key(entry, KEY_ID);
		slot = findslot(key);
#ifdef ERRDEBUG
		gdebug("getutid: type= %d, key='%s', slot= %d\n",
			entry->ut_type, key, slot);
#endif
		if (slot < 0)
			return((struct utmp *) NULL);
		if (get_rec(slot))
			return((struct utmp *) NULL);
		_utmp_slot_num = slot;
		return(&ubuf);
	default:		/* return NULL for unknown type */
		return((struct utmp *) NULL);
	}

nohash:

#endif /* HASH_UTMP */

	/*
	 * Start looking for entry.  Look in our current buffer before
	 * reading in new entries.
	 */

	do {

	/* If there is no entry in "ubuf", skip to the read. */

		if (ubuf.ut_type != EMPTY) {
			switch(entry->ut_type) {

	/* Do not look for an entry if the user sent us an EMPTY entry. */

			case EMPTY:
				_utmp_slot_num = -1;
				return((struct utmp *) NULL);

	/*
	 * For RUN_LVL, BOOT_TIME, OLD_TIME, and NEW_TIME entries, only
	 * the types have to match.  If they do, return the address of
	 * internal buffer.
	 */
			case RUN_LVL:
			case BOOT_TIME:
			case OLD_TIME:
			case NEW_TIME:
				if (entry->ut_type == ubuf.ut_type) return(&ubuf);
				break;

	/*
	 * For INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, and DEAD_PROCESS
	 * the type of the entry in "ubuf", must be one of the above and
	 * id's must match.
	 */
			case INIT_PROCESS:
			case LOGIN_PROCESS:
			case USER_PROCESS:
			case DEAD_PROCESS:
				if (((type = ubuf.ut_type) == INIT_PROCESS
					|| type == LOGIN_PROCESS
					|| type == USER_PROCESS
					|| type == DEAD_PROCESS)
				    && ubuf.ut_id[0] == entry->ut_id[0]
				    && ubuf.ut_id[1] == entry->ut_id[1]
				    && ubuf.ut_id[2] == entry->ut_id[2]
				    && ubuf.ut_id[3] == entry->ut_id[3])
					return(&ubuf);
				break;

	/* Do not search for illegal types of entry. */
			default:
				_utmp_slot_num = -1;
				return((struct utmp *) NULL);
			}
		}
	} while (getutent() != NULL);

	/* Return NULL since the proper entry wasn't found. */
	_utmp_slot_num = -1;
	return((struct utmp *) NULL);
}

/*
 *----------------------------------------------------------------- 
 *  getutline
 *
 *  Search utmp file for a LOGIN_PROCESS or USER_PROCESS with a
 *  ut_line field that match the specified entry.
 *
 */

struct utmp *
getutline(entry)
	register struct utmp *entry;
{
	extern struct utmp *getutent();
	register struct utmp *cur;

#ifdef HASH_UTMP
	int slot;
	char *key;

#ifdef ERRDEBUG
	gdebug("getutline: my pid= %d\n", getpid());
#endif

	if (not_utmp_file) {
#ifdef ERRDEBUG
		gdebug("getutline: not_utmp_file = %d\n", not_utmp_file);
#endif
		goto nohash;
	}
	
	_utmp_slot_num = -1;

	if (entry == NULL)
		return((struct utmp *) NULL);
	
	/* Insure all needed files are open. */

	if (check_utmp_hash())
		goto nohash;

	key = gen_key(entry, KEY_LINE);
	slot = findslot(key);
#ifdef ERRDEBUG
		gdebug("getutline: type= %d, key='%s', slot= %d\n",
			entry->ut_type, key, slot);
#endif
	if (slot < 0)
		return((struct utmp *) NULL);
	if (get_rec(slot))
		return((struct utmp *) NULL);
	_utmp_slot_num = slot;
	return(&ubuf);

nohash:

#endif /* HASH_UTMP */

	/*
	 * Start by using the entry currently incore.  This prevents
	 * doing reads that aren't necessary.
	 */
	cur = &ubuf;
	do {
	/*
	 * If the current entry is the one we are interested in, return
	 * a pointer to it.
	 */
		if (cur->ut_type != EMPTY && (cur->ut_type == LOGIN_PROCESS
		    || cur->ut_type == USER_PROCESS) && strncmp(&entry->ut_line[0],
		    &cur->ut_line[0],sizeof(cur->ut_line)) == 0) return(cur);
	} while ((cur = getutent()) != NULL);

	/* Since entry wasn't found, return NULL. */
	_utmp_slot_num = -1;
	return((struct utmp *) NULL);
}

/*
 *----------------------------------------------------------------- 
 *  pututline
 *
 *  Writes the structure sent into the utmp file. If there is already
 *  an entry with the same id, then it is overwritten, otherwise a
 *  new entry is made at the end of the utmp file.
 *
 */

struct utmp *
pututline(entry)
	struct utmp *entry;
{
	int fc;
	static struct utmp *answer;
	extern long time();
	extern long loc_utmp, lseek();
	extern struct utmp *getutid();
	static struct utmp tmpbuf;

#ifdef HASH_UTMP
#ifdef ERRDEBUG
	char buf[80];
#endif

	_utmp_slot_num = -1;

#ifdef ERRDEBUG
	if (entry != NULL) {
		gdebug("pututline, my pid= %d, type= %d, id= [%d][%d][%d][%d]\n",
			getpid(), entry->ut_type, entry->ut_id[0],
			entry->ut_id[1], entry->ut_id[2], entry->ut_id[3]);
	} else {
		gdebug("pututline: my pid= %d, entry == NULL\n", getpid());
	}
#endif

	if (not_utmp_file) {
#ifdef ERRDEBUG
		gdebug("pututline: not_utmp_file= %d\n", not_utmp_file);
#endif
		goto nohash;
	}

	if (check_utmp_hash())
		goto nohash;

	if (utmp_canwrite == 0)
		return((struct utmp *) NULL);

	if (entry == NULL)
		return((struct utmp *) NULL);
	
	/* Make a copy of the user suplied entry. */

	tmpbuf = *entry;

	if (utmp_write_lock()) {				/* LOCKED */
#ifdef ERRDEBUG
		gdebug("pututline: write lock fails\n");
#endif
		return((struct utmp *) NULL);
	}
#ifdef ERRDEBUG
	gdebug("pututline: type= %d", tmpbuf.ut_type);
	(void) memset(buf, '\0', sizeof(buf));
	(void) strncpy(buf, tmpbuf.ut_user, sizeof(tmpbuf.ut_user));
	gdebug(" user= '%s',", buf);
	(void) memset(buf, '\0', sizeof(buf));
	(void) strncpy(buf, tmpbuf.ut_id, sizeof(tmpbuf.ut_id));
	gdebug(" id= '%s', ", buf);
	(void) memset(buf, '\0', sizeof(buf));
	(void) strncpy(buf, tmpbuf.ut_line, sizeof(tmpbuf.ut_line));
	gdebug(" line= '%s'\n", buf);
#endif

	answer = ut_update(&tmpbuf, NULL);
	
	utmp_write_unlock();					/* UNLOCKED */
	return(answer);

nohash:

#endif /* HASH_UTMP */

	/*
	 * Copy the user supplied entry into our temporary buffer to
	 * avoid the possibility that the user is actually passing us
	 * the address of "ubuf".
	 */
	tmpbuf = *entry;
	(void) getutent();
	if (fd < 0) {
#ifdef	ERRDEBUG
		gdebug("pututline: Unable to create utmp file.\n");
#endif
		return((struct utmp *)NULL);
	}
	/* Make sure file is writable */
	if ((fc=fcntl(fd, F_GETFL, NULL)) == -1
	    || (fc & O_RDWR) != O_RDWR) {
		return((struct utmp *)NULL);
	}

	/*
	 * Find the proper entry in the utmp file.  Start at the current
	 * location.  If it isn't found from here to the end of the
	 * file, then reset to the beginning of the file and try again.
	 * If it still isn't found, then write a new entry at the end of
	 * the file.  (Making sure the location is an integral number of
	 * utmp structures into the file incase the file is scribbled.)
	 */

	if (getutid(&tmpbuf) == NULL) {
#ifdef	ERRDEBUG
		gdebug("First getutid() failed.  fd: %d\n",fd);
#endif
		setutent();
		if (getutid(&tmpbuf) == NULL) {
#ifdef	ERRDEBUG
			loc_utmp = lseek(fd, 0L, 1);
			gdebug("Second getutid() failed.  fd: %d loc_utmp: %ld\n",fd,loc_utmp);
#endif
			(void) fcntl(fd, F_SETFL, fc | O_APPEND);
		} else {
			(void) lseek(fd, -(long)sizeof(struct utmp), 1);
		}
	} else {
		(void) lseek(fd, -(long)sizeof(struct utmp), 1);
	}

	/*
	 * Write out the user supplied structure.  If the write fails,
	 * then the user probably doesn't have permission to write the
	 * utmp file.
	 */

	if (write(fd,&tmpbuf,sizeof(tmpbuf)) != sizeof(tmpbuf)) {
#ifdef	ERRDEBUG
		gdebug("pututline failed: write-%d\n",errno);
#endif
		answer = (struct utmp *)NULL;
	} else {
	/*
	 * Copy the user structure into ubuf so that it will be up to
	 * date in the future.
	 */
		ubuf = tmpbuf;
		answer = &ubuf;

#ifdef	ERRDEBUG
		gdebug("id: %c%c%c%c loc: %x\n",ubuf.ut_id[0],ubuf.ut_id[1],
		    ubuf.ut_id[2],ubuf.ut_id[3],loc_utmp);
#endif
	}
	(void) fcntl(fd, F_SETFL, fc);
	return(answer);
}

/*
 *-----------------------------------------------------------------
 *  getutpid
 *
 *  This does a search based on the ut_pid field.  Like the getutline
 *  routine.  Returns a NULL if the entry is not found.
 *
 */

struct utmp *
getutpid(entry)
	register struct utmp *entry;
{
	extern struct utmp *getutent();
	register struct utmp *cur, *answer;
	int slot;

#ifdef HASH_UTMP

	char *key;

#ifdef ERRDEBUG
	gdebug("getutpid: my pid= %d\n", getpid());
#endif

	if (not_utmp_file) {
#ifdef ERRDEBUG
		gdebug("getpid: not_utmp_file = %d\n", not_utmp_file);
#endif
		goto nohash;
	}

	_utmp_slot_num = -1;

	if (entry == NULL)
		return((struct utmp *) NULL);
	
	/* Insure all needed files are open. */

	if (check_utmp_hash())
		return((struct utmp *) NULL);

	key = gen_key(entry, KEY_PID);
	if (utmp_read_lock())					/* LOCKED */
		return((struct utmp *) NULL);
	slot = findslot(key);
#ifdef ERRDEBUG
	gdebug("getutpid: pid= %d, key= '%s', slot= %d\n",
	    entry->ut_pid, key, slot);
#endif
	if (slot < 0)
		answer = (struct utmp *) NULL;
	else if (get_rec(slot))
		answer = (struct utmp *) NULL;
	else
		answer = &ubuf;
	utmp_read_unlock();					/* UNLOCKED */
	_utmp_slot_num = slot;
	return(answer);

nohash:

#endif /* HASH_UTMP */

	/*
	 * Start by using the entry currently incore.  This prevents
	 * doing reads that aren't necessary.
	 */
	cur = &ubuf;
	do {
	/*
	 * If the current entry is the one we are interested in, return
	 * a pointer to it.
	 */
		if (cur->ut_type != EMPTY && (cur->ut_type == LOGIN_PROCESS
		    || cur->ut_type == USER_PROCESS) && 
		    cur->ut_pid == entry->ut_pid) return(cur);
	} while ((cur = getutent()) != NULL);

	/* Since entry wasn't found, return NULL. */
	_utmp_slot_num = -1;
	return((struct utmp *) NULL);
}


/*
 *-----------------------------------------------------------------
 *  setutent
 *
 *  Reset the utmp file back to beginning.
 *
 */

setutent()
{
	extern int fd;
	extern long loc_utmp;

	if (fd != -1) (void) lseek(fd,0L,0);

	/*
	 * Zero the stored copy of the last entry read, since we are
	 * resetting to the beginning of the file.
	 */

	(void) memset((char *) &ubuf, '\0', sizeof(ubuf));
	loc_utmp = 0L;
	_utmp_slot_num = -1;
}

/*
 *----------------------------------------------------------------------
 *  endutent
 *
 *  Close the utmp file.
 *
 */

endutent()
{
	extern int fd;
	extern long loc_utmp;

	if (fd != -1) (void) close(fd);
	fd = -1;
	loc_utmp = 0;
	_utmp_slot_num = -1;
	(void) memset((char *) &ubuf, '\0', sizeof(ubuf));

#ifdef ERRDEBUG
	gdebug("endutent: my pid= %d\n", getpid());
#endif

#ifdef HASH_UTMP

	if (db != (DBM *) 0) {
		dbm_close(db);
		db = (DBM *) 0;
	}

#endif /* HASH_UTMP */

}

/*
 *-----------------------------------------------------------------
 *  utmpname
 *
 *  Allow user to read a file other than the normal "utmp" file.
 *
 */

utmpname(newfile)
	char *newfile;
{
	extern char utmpfile[];

	/*
	 * Determine if the new filename will fit.  If not, return 0.
	 * Otherwise copy in the new file name.
	 */

#ifdef ERRDEBUG
	gdebug("utmpname: my pid= %d, newfile= '%s'\n", getpid(), newfile);
#endif

	if (strlen(newfile) > DYNIX_MAXNAMLEN)
		return (0);
	else
		(void) strcpy(&utmpfile[0],newfile);

	/* Make sure everything is reset to the beginning state. */

#ifdef HASH_UTMP

	if (strcmp(utmpfile, UTMP_FILE) == 0)
		not_utmp_file = 0;
	else
		not_utmp_file = 1;

#ifdef ERRDEBUG
	gdebug("utmpname: newfile= '%s', not_utmp_file= %d\n",
		newfile, not_utmp_file);
#endif

#endif /* HASH_UTMP */

	endutent();
	return(1);
}

/*
 *-----------------------------------------------------------------
 * ut_get_entry
 *
 * Return the next utmp entry and associated host if any.
 * This will pick up the entry and host as an atomic operation.
 */

int
ut_get_entry(ut_ent, ut_host)
	struct utmp **ut_ent;
	char **ut_host;
{
	static char no_host[] = "";

	_utmp_slot_num = -1;

	if (not_utmp_file) {
#ifdef ERRDEBUG
		gdebug("ut_get_entry: not_utmp_file = %d\n", not_utmp_file);
#endif
		return(-1);
	}

	/* Insure all needed files are open. */

	if (check_utmp_hash()) {
		return(-1);
	}

	if (utmp_read_lock())				/* LOCKED */
		return(-1);

	if (read(fd, &ubuf, sizeof(ubuf)) != sizeof(ubuf)) {
		/* make sure ubuf is zeroed. */
		(void) memset((char *) &ubuf, '\0', sizeof(ubuf));
		utmp_read_unlock();			/* UNLOCKED */
		return(-1);
	}

	loc_utmp = lseek(fd, 0L, 1) - (long) (sizeof(struct utmp));
	_utmp_slot_num = loc_utmp / sizeof(struct utmp);
	
	if (ubuf.ut_line[0] == '\0')
		*ut_host = no_host;
	else {
		 *ut_host = findhost(ubuf.ut_line);
		 if (*ut_host == (char *) NULL)
		 	*ut_host = no_host;
	}
	
	*ut_ent = &ubuf;
	utmp_read_unlock();				/* UNLOCKED */
	return(0);
}


/*
 *----------------------------------------------------------------------
 * gdebug
 *
 * Print a log message for debugging.
 *
 */

#ifdef	ERRDEBUG

#ifdef DEBUG
#define UT_LOG_FILE	"dbg.getut"
#else
#define UT_LOG_FILE	"/etc/dbg.getut"
#endif

/*VARARGS*/
gdebug(format,arg1,arg2,arg3,arg4,arg5,arg6)
char *format;
int arg1,arg2,arg3,arg4,arg5,arg6;
{
	register FILE *fp;

	if ((fp = fopen(UT_LOG_FILE,"a+")) == NULL) {
		/* Can't print a message because might not have an */
		/* file open for io.  (init will not)		   */
		return;
	}
	(void) fprintf(fp,format,arg1,arg2,arg3,arg4,arg5,arg6);
	(void) fclose(fp);
	sync();
}
#endif	/* ERRDEBUG */


/*
 *----------------------------------------------------------------------
 *  open_ut_file
 *
 *  If the utmp file is not open this will attempt to open it.
 *  It will be opend read/write/create if possible.  Otherwise opened
 *  only for reading -- which would cause any later write to fail.
 *
 *  Returns:  0 -- Already open or open ok.
 *            1 -- Open failed.
 */

static int
open_ut_file()
{
	struct stat stbuf;

	/* Return if already open. */

	if (fd >= 0)
		return(0);

	_utmp_slot_num = -1;

	/*
	 * Try and stat the file.  If it exists make sure that it is a
	 * multiple of 'utmp' entries long.  If we can't open for read
	 * write open for read only.  Ability to read/write is recorded
	 * for later use.
	 */

	if (stat(utmpfile,&stbuf) == 0) {
		if((stbuf.st_size % sizeof(struct utmp)) != 0) {
			ut_err(L_A, "getut.c: utmp file not even # records\n");
			errno = EINVAL;
			return(1);
		}
	}

	if ((fd = open(utmpfile, O_RDWR|O_CREAT, 0644)) >= 0) {
#ifdef ERRDEBUG
	gdebug("open_ut_file: utmp file opened for read/write, my pid= %d\n",
		getpid());
#endif
		utmp_canwrite = 1;
		return(0);
	}
#ifdef ERRDEBUG
	gdebug("open_ut_file: utmp file open for read/write fails\n");
	gdebug(" file= '%s', errno= %d\n", utmpfile, errno);
#endif

	if ((fd = open(utmpfile, O_RDONLY)) >= 0) {
#ifdef ERRDEBUG
	gdebug("open_ut_file: utmp file opened for read only\n");
#endif
		utmp_canwrite = 0;
		return(0);
	}
#ifdef ERRDEBUG
	gdebug("open_ut_file: utmp file open for read fails\n");
	gdebug(" file= '%s', errno= %d\n", utmpfile, errno);
#endif

	return(1);	/* Open of utmp failed. */
}

/*
 *-----------------------------------------------------------------
 *  ut_add_user
 * 
 *  This will add an entry to the utmp file or update a present entry
 *  based on the line.  The data base file will be updated.  Returns a
 *  pointer to the utmp record or a NULL utmp pointer if unable to
 *  update utmp file (this happens if user limit is reached).
 *
 */

struct utmp *
ut_add_user(user, line, pid, host)
	char *user;
	char *line;
	int pid;
	char *host;
{

#ifdef HASH_UTMP

	static struct utmp tmpbuf;
	static struct utmp *answer;
	int slot;
	int i, j, k, c;
	char *key;

	if (not_utmp_file)
		return((struct utmp *) NULL);

	if (check_utmp_hash())
		return((struct utmp *) NULL);
	
	if (utmp_canwrite == 0)
		return((struct utmp *) NULL);
	
	if ((user == 0) || (*user == 0)) {
		ut_err(L_U, "getut.c: ut_add_user: ERROR: user name is null\n");
		return((struct utmp *) NULL);
	}

	if ((line == 0) || (*line == 0)) {
		ut_err(L_U, "getut.c: ut_add_line: ERROR: line name is null\n");
		return((struct utmp *) NULL);
	}


#ifdef ERRDEBUG
	gdebug("ut_add_user: user= '%s', line= '%s', pid= %d, ",
		user, line, pid);
	if ( (host != NULL) && (*host != '\0') )
		gdebug("host= '%s'\n", host);
	else
		gdebug("host= NULL\n");
#endif

	(void) memset(&tmpbuf, '\0', sizeof tmpbuf);
	(void) strncpy(tmpbuf.ut_user, user, sizeof(tmpbuf.ut_user));
	(void) strncpy(tmpbuf.ut_line, line, sizeof(tmpbuf.ut_line));
	c = sizeof(tmpbuf.ut_id);
	if (c > sizeof(tmpbuf.ut_line))
		c = sizeof(tmpbuf.ut_line);
	/* copy up to last 4 chars of line to id field */
	i = strlen(line);	/* number of chars in line name */
	if ( (i > 0) && (i <= c) ) {
		for (j=0; j < i; j++)
			tmpbuf.ut_id[j] = tmpbuf.ut_line[j];
	} else if (i > c) {
		k = i - c;		/* back up to 4 chars before ending null */
		for (j=0; j < c; j++, k++)
			 tmpbuf.ut_id[j] = tmpbuf.ut_line[k];
	}  else
		tmpbuf.ut_id[0] = '?';	/* shouldn't happen */
	tmpbuf.ut_pid = pid;
	tmpbuf.ut_type = USER_PROCESS;
	(void) time(&tmpbuf.ut_time);
	tmpbuf.ut_exit.e_termination = 0;
	tmpbuf.ut_exit.e_exit = 0;
	key = gen_key(&tmpbuf, KEY_LINE);
	if (utmp_write_lock())					/* LOCKED */
		return((struct utmp *) NULL);
	slot = findslot(key);
#ifdef ERRDEBUG
	gdebug("ut_add_user: key= '%s', slot= %d\n", key, slot);
#endif
	if (slot >= 0)
		if (get_rec(slot))
			(void) memcpy(tmpbuf.ut_id, ubuf.ut_id, sizeof(ubuf.ut_id));
	answer = ut_update(&tmpbuf, host);
	utmp_write_unlock();					/* UNLOCKED */
	return(answer);

#else

	return((struct utmp *) NULL);

#endif /* HASH_UTMP */
}

/*
 *-----------------------------------------------------------------
 *  utmp_delete_user
 *
 *  This will remove a user entry from data base and update the
 *  utmp file.  
 *
 */

struct utmp *
ut_delete_user(line, pid, e_term, e_status)
	char *line;
	int pid;
	int e_term;
	int e_status;
{
#ifdef HASH_UTMP
	int slot;
	char *key;
	struct utmp u;
	static struct utmp tmpbuf;
	static struct utmp *answer;

	if (check_utmp_hash())
		return((struct utmp *) NULL);
	
	if (utmp_canwrite == 0)
		return((struct utmp *) NULL);

#ifdef ERRDEBUG
	if ((line != NULL) && (*line != NULL))
	    gdebug("ut_delete_user: line= '%s', ", line);
	else
	    gdebug("ut_delete_user: line= NULL, ");
	gdebug("pid= %d, e_term= %d, e_status= %d\n", pid, e_term, e_status);
#endif

	(void) memset(&u, '\0', sizeof u);
	(void) memset(&tmpbuf, '\0', sizeof tmpbuf);

	if ((line != NULL) && (*line != NULL)) {
		(void) strncpy(u.ut_line, line, sizeof (u.ut_line));
		key = gen_key(&u, KEY_LINE);
	} else {
		if (pid == 0)
			return((struct utmp *) NULL);
		u.ut_pid = pid;
		key = gen_key(&u, KEY_PID);
	}

	slot = findslot(key);
#ifdef ERRDEBUG
	gdebug("key= '%s', slot= %d\n", key, slot);
#endif
	if (slot < 0) {
		return((struct utmp *) NULL);
	}
	if (utmp_write_lock())					/* LOCKED */
		return((struct utmp *) NULL);

	if (get_rec(slot)) {
		answer = (struct utmp *) NULL;
	} else {
		tmpbuf = ubuf;
		(void) time(&tmpbuf.ut_time);
		tmpbuf.ut_type = DEAD_PROCESS;
		tmpbuf.ut_exit.e_termination = e_term;
		tmpbuf.ut_exit.e_exit = e_status;
		answer = ut_update(&tmpbuf, (char *) NULL);
	}
	utmp_write_unlock();					/* UNLOCKED */
	return(answer);
	
#else

	return((struct utmp *) NULL);

#endif /* HASH_UTMP */

}

#ifdef HASH_UTMP

/*
 *-----------------------------------------------------------------
 *
 *  ut_update
 *
 *  This updates an entry in the utmp file as well as updating
 *  the data base.
 *
 *  Returns:
 *    o   a pointer to the utmp record
 *    o   NULL if unable to update utmp file
 *    o   NULL if user limit would be exceeded (message printed also).
 *
 *  NOTE:  Accounting is done only for types "USER_PROCESS".
 *         Must be called with data base locked, valid entry, host
 *         may be null.  Insure that utmp struct has been copied to
 *         static buffer before calling to insure that it is not a
 *         pointer to the buffer returned by other library routines.
 *	   If host is the same as this machine node name we assume
 *	   a local login using a pseudo terminal (ie. some windowing
 *	   package) and don't count the line in the user count.  But
 *	   it is still entered in the data base as a host for that line.
 *	   Other way to register the line as a pseudo terminal is to
 *	   enter the "hard" tty line as the host name.  This user
 *	   must be presently logged in on that line.
 *
 */

static struct utmp *
ut_update(entry, host)
	struct utmp *entry;
	char *host;
{
	int slot, m, u;
	char *key;
	char *h;
	int must_erase = 0;
	struct utmp tmpbuf, kbuf;
	datum k;
	struct utsname unm;
	int have_host = 0;		/* non NULL host name supplied */
	int host_is_us = 0;		/* host name is our node name */
	int host_is_hard_line = 0;	/* host is our hard wired line */
	int line_was_pseudo = 0;	/* this line was used for window */
	int allow_root = 0;		/* root && syscon ability to login */

    if (utmp_write_locked == 0) {
	ut_err(L_U, "getut.c: ut_update: INTERNAL ERROR, not write locked\n");
	return((struct utmp *) NULL);
    }

    tmpbuf = *entry;

    /*
     * If we have been given a host name we check to see if it matches
     * the machine we are running on.  If so, this will be considered
     * a local login -- some type of windowing terminal -- and we do
     * not increment the login count.  We may want to check our system
     * name later so the uname call is always done.
     */

    if (uname(&unm) == -1) {
	ut_err(L_U, "getut.c: ut_update: uname call fails, errno = %d\n", errno);
	strncpy(unm.nodename, "??node??", sizeof(unm.nodename));
    }


    if ((host != NULL) && (*host != NULL)) {
	have_host = 1;
	if (EQ(unm.nodename, host))
		host_is_us = 1;
    }

    switch (entry->ut_type) {
    case EMPTY:
	return((struct utmp *) NULL);
    case RUN_LVL:
    case BOOT_TIME:
    case OLD_TIME:
    case NEW_TIME:
	key = gen_key(&tmpbuf, KEY_SPECIAL);
	slot = findslot(key);
#ifdef ERRDEBUG
	gdebug("ut_update: type= %d, key= '%s', slot= %d\n",
		tmpbuf.ut_type, key, slot);
#endif
	if (slot >= 0) {
		replace_rec(&tmpbuf, slot);
	} else {
		add_rec(&tmpbuf, &slot);
		add_slot_key(key, slot);
	}
	return(&ubuf);
    case INIT_PROCESS:
    case LOGIN_PROCESS:
    case DEAD_PROCESS:
	key = gen_key(&tmpbuf, KEY_ID);
	slot = findslot(key);
#ifdef ERRDEBUG
	gdebug("ut_update: type= %d, key= '%s', slot= %d\n",
		tmpbuf.ut_type, key, slot);
#endif
	if (slot >= 0) {
		/* May need to delete user.  See if type was USER_PROCESS. */
		/* Might want to verify that lines match. */
		if ((get_rec(slot) == 0) && (ubuf.ut_type == USER_PROCESS)) {
		    if (ubuf.ut_type == USER_PROCESS) {
			h = findhost(ubuf.ut_line);   /* return host */
			line_was_pseudo = local_line(ubuf.ut_line, FIND_LINE);
			if (h == NULL) {	/* no associated host */
				dec_numusers();	/* so must be hard wired line */
			} else {
				if ((!EQ(h, unm.nodename)) && (!line_was_pseudo))
					dec_host_user(h, ubuf.ut_user);
				delete_host(ubuf.ut_line);
			}
			if (line_was_pseudo)
				(void) local_line(ubuf.ut_line, DELETE_LINE);
		    }
		}
	        replace_rec(&tmpbuf, slot);
	} else {
		add_rec(&tmpbuf, &slot);
	}
	key = gen_key(&tmpbuf, KEY_ID);
	add_slot_key(key, slot);		/* ut_id */
	key = gen_key(&tmpbuf, KEY_PID);
	add_slot_key(key, slot);		/* ut_pid */
	key = gen_key(&tmpbuf, KEY_LINE);
	add_slot_key(key, slot);		/* ut_line */
	return(&ubuf);
    case USER_PROCESS:
	/*
	 * We would hope that the entry would be "cleaned up" if it is
	 * being re-used so we would not see the prevous entry being type
	 * user process.  But if we do, we have to delete that entry
	 * and so lower the user count.  A minor problem is that the requested
	 * login could still fail.  In this case we want to update the
	 * utmp record so we don't decrement user count next time.
	 * 
	 * Another task is to see if this line is a being used for a
	 * terminal window (ie. pseudo tty).  This is indicated by the
	 * host name matching a hard wired line for this user.
	 *
	 * A special check is done for user "root" and line "syscon" to
	 * allow root to login on the console even when the user limit
	 * is reached.  Note that this does still increment the user count.
	 */
	
	if ( (strncmp(entry->ut_user, "root",   4) == 0) &&
	     (strncmp(entry->ut_line, "syscon", 6) == 0)     )
		allow_root = 1;

	if (have_host) {
	    memset(&kbuf, '\0', sizeof kbuf);
	    strncpy(kbuf.ut_line, host, sizeof(tmpbuf.ut_line));
	    kbuf.ut_type = USER_PROCESS;
	    key = gen_key(&kbuf, KEY_LINE);
	    slot = findslot(key);
	    if (slot >= 0) {
		if ((get_rec(slot) == 0) &&
			(ubuf.ut_type == USER_PROCESS) &&
		        (strncmp(ubuf.ut_user, tmpbuf.ut_user,
				    sizeof(ubuf.ut_user)) == 0)   ) {
			    host_is_hard_line = 1;
#ifdef ERRDEBUG
			    gdebug("ut_update: host_is_hard_line set true\n");
#endif
		}
	    }
	}

	key = gen_key(&tmpbuf, KEY_ID);
	slot = findslot(key);
#ifdef ERRDEBUG
	gdebug("ut_update: type= %d, key= '%s', slot= %d\n",
		tmpbuf.ut_type, key, slot);
#endif
	if (slot >= 0) {
		/* May need to delete user.  See if type was USER_PROCESS. */
		/* Might want to verify that lines match. */
		if ((get_rec(slot) == 0) && (ubuf.ut_type == USER_PROCESS)) {
			line_was_pseudo = local_line(ubuf.ut_line, FIND_LINE);
			h = findhost(ubuf.ut_line);   /* return host */
			if (h == NULL) {
			    dec_numusers();
			} else {		      /* ignore local logins */
			    if ((!EQ(h, unm.nodename)) && (!line_was_pseudo))
				dec_host_user(h, ubuf.ut_user);
			    delete_host(ubuf.ut_line);
			}
			if (line_was_pseudo)
				(void) local_line(ubuf.ut_line, DELETE_LINE);
			key = gen_key(&tmpbuf, KEY_PID);
			k.dptr = key;
			k.dsize = strlen(key);
			(void) dbm_delete(db, k);
			must_erase = 1;
		}
	}

#ifdef DEBUG
	m = debug_maxusers;
#else
	m = getmaxusers();	/* max allowed users */
#endif
	u = ut_get_numusers();	/* current number of users */

	/* If root on the console we are going to allow the login. */

	if (allow_root == 1)
		u = 0;

#ifdef ERRDEBUG
	gdebug("ut_update: maxusers= %d, numusers= %d\n", m, u);
#endif

	/*
	 * Limit hit if: current > max
	 * or          : current == max and no host, so direct line
	 */

	if ( (u > m) || ( (u == m) && (!have_host) ) ) {
		if (must_erase) {
			ubuf.ut_type = DEAD_PROCESS;
			(void) time(&ubuf.ut_time);
			(void) replace_rec(&ubuf, slot);
		}
		(void) fprintf(stderr, "Login limit reached.\nTry again later.\n");
		return((struct utmp *) NULL);
	}

	/*
	 * If sitting at the limit we will still allow the same user and
	 * host pair.  So here see if this is an existing user/host pair.
	 */

	if ((u == m) && (! host_is_us) && (!host_is_hard_line)) {
	    if (hostcount(host, entry->ut_user) == 0) {
		if (must_erase) {
			ubuf.ut_type = DEAD_PROCESS;
			(void) time(&ubuf.ut_time);
			(void) replace_rec(&ubuf, slot);
		}
		(void) fprintf(stderr, "Login limit reached.\nTry again later.\n");
		return((struct utmp *) NULL);
	    }
	}

	/* Ok, now we are going to allow the login, and previous entry
	 * if any has been cleaned up.
	 */

	if (slot >= 0)
		replace_rec(&tmpbuf, slot);
	else
		add_rec(&tmpbuf, &slot);
	key = gen_key(&tmpbuf, KEY_ID);
	add_slot_key(key, slot);		/* ut_id */
	key = gen_key(&tmpbuf, KEY_PID);
	add_slot_key(key, slot);		/* ut_pid */
	key = gen_key(&tmpbuf, KEY_LINE);
	add_slot_key(key, slot);		/* ut_line */
	if (have_host) {			/* ignore local logins */
		if ((! host_is_us) && (!host_is_hard_line))
			inc_host_user(host, tmpbuf.ut_user);
		set_host(host, tmpbuf.ut_line);
	} else {
		inc_numusers();
	}
	if (host_is_hard_line)
		(void) local_line(tmpbuf.ut_line, SET_LINE);
    	return(&ubuf);
    case ACCOUNTING:
    default:
	return((struct utmp *) NULL);
    }
}

/*
 *-----------------------------------------------------------------
 * local_line()
 *
 * Some lines will be marked as "local", meaning that they are
 * "windows", and are not added to the user count.  This is indicated
 * by a host for some line matching a line name which is a type
 * USER_PROCESS for the same user.
 * Return 1 for a match success, 0 for fail.
 */

static char ll_d[] = "LL_MARK";		/* 7 chars, mark local line */

static int
local_line(name, cmd)
	char *name;
	int cmd;

{
	char buf[80];
	datum k, r;

	if ((name == 0) || (*name == 0))
		return(0);

	strcpy(buf, "ut_ln_key:");
	strcat(buf, name);

	k.dptr = buf;
	k.dsize = strlen(buf);

	switch (cmd) {
	case FIND_LINE:
		r = dbm_fetch(db, k);
		if (r.dptr == 0)
			return(0);
		else
			return(1);
	case SET_LINE:
		r.dptr = ll_d;
		r.dsize = 7;
		(void) dbm_store(db, k, r, DBM_REPLACE);
		break;
	case DELETE_LINE:
		(void) dbm_delete(db, k);
		break;
	}
	return(0);
}

/*
 *-----------------------------------------------------------------
 * gen_key()
 *
 * Generate a key for a lookup.  This returns a poiner to a static
 * character buffer.  If another key is required before using the
 * first one it must be copied to preserve it.
 *
 */

static char key_buf[80];

static char *
gen_key(entry, key_type)
	register struct utmp *entry;
	int key_type;
{
	char key_pid[40];
	char *p;
	int i, j, k, c;

	(void) memset(key_buf, '\0', sizeof(key_buf));
	switch (key_type) {
	case KEY_ID:
		(void) strcpy(key_buf, "ut_id");
		if (entry->ut_id[0] == '\0') {
			if (entry->ut_line[0] == '\0')
				break;
			c = sizeof(ubuf.ut_id);
			if (c > sizeof(ubuf.ut_line))
				c = sizeof(ubuf.ut_line);

			for (i=0; i < sizeof(ubuf.ut_line); i++)
				if (entry->ut_line[i] == '\0')
					break;
			p = &key_buf[strlen(key_buf)];
			if ( (i > 0) && (i <= c) ) {
				for (j=0; j < c; j++)
					*p++ = entry->ut_line[j];
				*p = '\0';
			} else if (i > c) {
				k = i - c; /* back up to 4 chars before ending 0 */
				for (j=0; j < c; j++, k++)
			 		*p++ = entry->ut_line[k];
				*p = '\0';
			}  else
				(void) strcpy(key_buf, "???");
		} else {
			(void) strncat(key_buf, entry->ut_id, sizeof(ubuf.ut_id));
		}
#ifdef ERRDEBUG
		gdebug("gen_key: KEY_ID = '%s'\n", key_buf);
#endif
		return(key_buf);
	case KEY_LINE:
		(void) strcpy(key_buf, "ut_line");
		(void) strncat(key_buf, entry->ut_line, sizeof(ubuf.ut_line));
#ifdef ERRDEBUG
		gdebug("gen_key: KEY_LINE = '%s'\n", key_buf);
#endif
		return(key_buf);
	case KEY_HOST:
		(void) strcpy(key_buf, "ut_host");
		(void) strncat(key_buf, entry->ut_line, sizeof(ubuf.ut_line));
#ifdef ERRDEBUG
		gdebug("gen_key: KEY_HOST = '%s'\n", key_buf);
#endif
		return(key_buf);
	case KEY_PID:
		(void) strcpy(key_buf, "ut_pid");
		(void) sprintf(key_pid, "%d", entry->ut_pid);
		(void) strcat(key_buf, key_pid);
#ifdef ERRDEBUG
		gdebug("gen_key: KEY_PID = '%s'\n", key_buf);
#endif
		return(key_buf);
	case KEY_SPECIAL:
		switch (entry->ut_type) {
		case RUN_LVL:
#ifdef ERRDEBUG
			gdebug("gen_key: KEY_SPECIAL = 'RUN_LVL_key'\n");
#endif
			return("RUN_LVL_key");
		case BOOT_TIME:
#ifdef ERRDEBUG
			gdebug("gen_key: KEY_SPECIAL = 'BOOT_TIME_key'\n");
#endif
			return("BOOT_TIME_key");
		case OLD_TIME:
#ifdef ERRDEBUG
			gdebug("gen_key: KEY_SPECIAL = 'OLD_TIME_key'\n");
#endif
			return("OLD_TIME_key");
		case NEW_TIME:
#ifdef ERRDEBUG
			gdebug("gen_key: KEY_SPECIAL = 'NEW_TIME_key'\n");
#endif
			return("NEW_TIME_key");
		default:
			ut_err(L_U, "getut.c: gen_key: internal error %d\n",
				key_type);
			return("???");
		}
	default:
		ut_err(L_U, "getut.c: gen_key: internal error %d\n", key_type);
		return("???");
	}
	/*NOTREACHED*/
}

/*
 *-----------------------------------------------------------------
 * get_rec
 *
 * Read into the "ubuf" area the record at the given slot index.
 *
 */

static int
get_rec(slot)
	int slot;
{
	long offset;
	int did_lock = 0;
	int i;

	if ( (utmp_read_lock == 0) && (utmp_write_lock == 0) ) {
		if (utmp_read_lock()) {				/* LOCKED */
#ifdef ERRDEBUG
			gdebug("get_rec(slot= %d): cannot lock utmp file\n", slot);
#endif
			return(-1);
		}
		did_lock = 1;
	}

	offset = slot * sizeof(ubuf);
	loc_utmp = lseek(fd, offset, 0);
	if (loc_utmp < 0) {
#ifdef ERRDEBUG
		gdebug("get_rec(slot= %d): lseek fails\n", slot);
#endif
		loc_utmp = 0L;
		if (did_lock)
			utmp_read_unlock();			/* UNLOCKED */
		return(-1);
	}
#ifdef ERRDEBUG
	gdebug("get_rec: did seek to offset %ld\n", offset);
#endif
	errno = 0;
	if ((i = read(fd, &ubuf, sizeof(ubuf))) != sizeof(ubuf)) {
		loc_utmp = 0L;
		if (did_lock)
			utmp_read_unlock();			/* UNLOCKED */
#ifdef ERRDEBUG
		gdebug("get_rec(slot= %d): read fails, errno= %d\n",
			slot, errno);
		gdebug("get_rec: read %d, expected %d\n", i, sizeof(ubuf));
#endif
		return(-1);
	}
	if (did_lock)
		utmp_read_unlock();				/* UNLOCKED */
#ifdef ERRDEBUG
	gdebug("get_rec(slot= %d): read record into ubuf\n", slot);
#endif
	return(0);
}

/*
 *-----------------------------------------------------------------
 * add_slot_key
 *
 * Add to data base a slot and key.
 *
 */

static void
add_slot_key(key, slot)
	char *key;
	int slot;
{
	datum k, c;
	int s;
	int i;

#ifdef ERRDEBUG
	gdebug("add_slot_key: key= '%s', slot= %d\n", key, slot);
#endif

	k.dptr = key;
	k.dsize = strlen(key);

	s = slot;
	c.dptr = (char *) &s;
	c.dsize = sizeof(s);

	i = dbm_store(db, k, c, DBM_REPLACE);

#ifdef ERRDEBUG
	if (i)
		gdebug("add_slot_key: dbm_store fails, returned %d\n", i);
#endif
}

/*
 *-----------------------------------------------------------------
 * findslot
 *
 * Given a key, find the corresponding slot.
 *
 */

static int
findslot(key)
	char *key;
{
	int did_lock = 0;
	datum k, d;
	union {
		int  s_s;
		char s_c[4];
	} s;

	if ( (utmp_read_lock == 0) && (utmp_write_lock == 0) ) {
		if (utmp_read_lock()) {				/* LOCKED */
#ifdef ERRDEBUG
			gdebug("findslot: cannot lock utmp file\n");
#endif
			return(-1);
		}
		did_lock = 1;
	}

	k.dptr = key;
	k.dsize = strlen(key);

	d = dbm_fetch(db, k);
	if (d.dptr == 0) {
		if (did_lock)
			utmp_read_unlock();			/* UNLOCKED */
#ifdef ERRDEBUG
	gdebug("findslot: dbm_fetch returns 0 d.dptr with key '%s'\n", key);
#endif
		return(-1);
	}
	
	s.s_c[0] = d.dptr[0];
	s.s_c[1] = d.dptr[1];
	s.s_c[2] = d.dptr[2];
	s.s_c[3] = d.dptr[3];
	if (did_lock)
		utmp_read_unlock();				/* UNLOCKED */
#ifdef ERRDEBUG
	gdebug("findslot: dbm_fetch returns %d for key '%s'\n", s.s_s, key);
#endif
	return(s.s_s);
}

/*
 *-----------------------------------------------------------------
 * ut_get_numusers
 *
 * Get the number of users.
 *
 */

int
ut_get_numusers()
{
	char *key = "ut_numusers";
	int did_lock = 0;
	datum k, d;
	union {
		int  s_s;
		char s_c[4];
	} s;

	if (check_utmp_hash())
		return(-1);

	if ( (utmp_read_lock == 0) && (utmp_write_lock == 0) ) {
		if (utmp_read_lock()) {				/* LOCKED */
#ifdef ERRDEBUG
			gdebug("ut_get_numusers: utmp lock failed\n");
#endif
			return(-1);
		}
		did_lock = 1;
	}

	k.dptr = key;
	k.dsize = strlen(key);

	d = dbm_fetch(db, k);
	if (d.dptr == 0) {
		if (did_lock)
			utmp_read_unlock();			/* UNLOCKED */
#ifdef ERRDEBUG
		gdebug("ut_get_numusers: key= '%s', no entry\n", key);
#endif
		return (0);
	}
		
	s.s_c[0] = d.dptr[0];
	s.s_c[1] = d.dptr[1];
	s.s_c[2] = d.dptr[2];
	s.s_c[3] = d.dptr[3];
	if (did_lock)
		utmp_read_unlock();				/* UNLOCKED */
#ifdef ERRDEBUG
	gdebug("ut_get_numusers: key= '%s', count= %d\n", key, s.s_s);
#endif
	return(s.s_s);
}

/*
 *-----------------------------------------------------------------
 * set_num_users
 *
 * Set the number of users.
 *
 */

static void
set_num_users(count)
	int count;
{
	char *key = "ut_numusers";
	datum k, d;
	int s;
	int i;

	if (utmp_write_lock == 0) {
		ut_err(L_U, "getut.c: set_num_users: INTERNAL ERROR, not write locked\n");
		return;
	}

	s = count;
	d.dptr = (char *) &s;
	d.dsize = sizeof(s);

	k.dptr = key;
	k.dsize = strlen(key);

	i = dbm_store(db, k, d, DBM_REPLACE);
#ifdef ERRDEBUG
	if (i != 0)
		gdebug("set_num_users(count= %d), key= '%s', failed\n", count, key);
	else
		gdebug("set_num_users(count= %d), key= '%s'\n", count, key);
#endif
}

/*
 *-----------------------------------------------------------------
 * dec_numusers
 *
 * Decrement user count.
 *
 */

static void
dec_numusers()
{
	int count;

	count = ut_get_numusers();
#ifdef ERRDEBUG
	gdebug("dec_numusers called, previous count= %d\n", count);
#endif
	if (count > 0) {
		count --;
		set_num_users(count);
	}
}



/*
 *-----------------------------------------------------------------
 * inc_numusers
 *
 * Increment user count.
 *
 */

static void
inc_numusers()
{
	int count;

	count = ut_get_numusers();
#ifdef ERRDEBUG
	gdebug("inc_numusers called, previous count= %d\n", count);
#endif
	count++;
	set_num_users(count);
}

/*
 *-----------------------------------------------------------------
 * set_host
 *
 * Enter a host line combination.
 *
 */

static void
set_host(host, line)
	char *host;
	char *line;
{
	datum k, d;
	char *key;
	struct utmp u;
	int i;

	if (utmp_write_lock == 0) {
		ut_err(L_U, "getut.c: set_host: INTERNAL ERROR, not write locked\n");
		return;
	}

	(void) memset((char *) &u, '\0', sizeof(u));
	(void) memcpy(u.ut_line, line, sizeof(u.ut_line));
	key = gen_key(&u, KEY_HOST);

	k.dptr = key;
	k.dsize = strlen(key);

	d.dptr = host;
	d.dsize = strlen(host) + 1;	/* include null byte */

	i = dbm_store(db, k, d, DBM_REPLACE);
#ifdef ERRDEBUG
	if (i) {
		gdebug("set_host: key= '%s', host= '%s', ERROR\n", key, host);
	} else {
		gdebug("set_host: key= '%s', host= '%s'\n", key, host);
	}
#endif

}


/*
 *-----------------------------------------------------------------
 * delete_host
 *
 * Given a line, delete the host information for the line.
 *
 */

static void
delete_host(line)
	char *line;
{
	datum k;
	char *key;
	struct utmp u;

	if (utmp_write_lock == 0) {
		ut_err("L_U, getut.c: delete_host: INTERNAL ERROR, not write locked\n");
		return;
	}

	(void) memset((char *) &u, '\0', sizeof(u));
	(void) memcpy(u.ut_line, line, sizeof(u.ut_line));
	key = gen_key(&u, KEY_HOST);

	k.dptr = key;
	k.dsize = strlen(key);

	(void) dbm_delete(db, k);
#ifdef ERRDEBUG
	gdebug("delete_host: key= '%s', line= '%s'\n", key, line);
#endif
}


/*
 *-----------------------------------------------------------------
 * findhost
 *
 * Given a line, find the host associated with it.
 *
 */

static char *
findhost(line)
	char *line;
{
	datum k;
	datum d;
	char *key;
	struct utmp u;
	static char buf[512];

	if (utmp_write_lock == 0 && utmp_read_lock == 0) {
		ut_err(L_U, "getut.c: findhost: INTERNAL ERROR, not read locked\n");
		return("???");
	}

	(void) memset((char *) &u, '\0', sizeof(u));
	(void) memcpy(u.ut_line, line, sizeof(u.ut_line));
	key = gen_key(&u, KEY_HOST);

	k.dptr = key;
	k.dsize = strlen(key);

	d = dbm_fetch(db, k);
	if (d.dptr == 0) {
#ifdef ERRDEBUG
		gdebug("findhost: line= '%s', key= '%s', d.dptr= NULL\n",
			line, key);
#endif
		return((char *) NULL);
	}
	(void) strncpy(buf, d.dptr, sizeof(buf));
#ifdef ERRDEBUG
	gdebug("findhost: line= '%s', key= '%s', found d.dptr= '%s'\n",
		line, key, buf);
#endif
	return(buf);
}


/*
 *-----------------------------------------------------------------
 * ut_find_host
 *
 * Exported interface to allow finding the host name registered
 * for a tty line.
 *
 */

char *
ut_find_host(line)
	char *line;
{
	char *h;

	if ((line == NULL) || (*line == NULL))
		return((char *) NULL);

	if (check_utmp_hash())
		return((char *) NULL);

	if (utmp_read_lock())
		return((char *) NULL);

	h = findhost(line);
	utmp_read_unlock();
	return(h);
}


/*
 *-----------------------------------------------------------------
 * inc_host_user
 *
 * Increment host/user count.  If new combination, create it and
 * increment the overall number of users.
 *
 */

static void
inc_host_user(host, user)
	char *host;
	char *user;
{
	datum k, d, r;
	char *key;
	union {
		int  s_s;
		char s_c[4];
	} s;
	int i;

	if (utmp_write_lock == 0) {
		ut_err(L_U, "getut.c: inc_host_user: INTERNAL ERROR, not write locked\n");
		return;
	}

	key = host_user_key(host, user);
	k.dptr = key;
	k.dsize = strlen(key);

#ifdef ERRDEBUG
	gdebug("inc_host_user: host= '%s', user= '%s', key= '%s'\n",
		host, user, key);
#endif

	r = dbm_fetch(db, k);
	if (r.dptr == 0) {		/* new entry */
		s.s_s = 1;
		d.dptr = (char *) &s.s_s;
		d.dsize = sizeof(s.s_s);
		i =  dbm_store(db, k, d, DBM_REPLACE);
		if (i)
			ut_err(L_U, "getut.c: inc_host_user: ERROR: dmb_store fails\n");
#ifdef ERRDEBUG
		gdebug("inc_host_user: not found, entered & incrementing user cnt\n");
#endif
		inc_numusers();
	} else {
		s.s_c[0] = r.dptr[0];	/* existing entry */
		s.s_c[1] = r.dptr[1];
		s.s_c[2] = r.dptr[2];
		s.s_c[3] = r.dptr[3];
#ifdef ERRDEBUG
		gdebug("inc_host_user: found, old count was %d\n", s.s_s);
#endif
		s.s_s++;
		d.dptr = (char *) &s.s_s;
		d.dsize = sizeof(s.s_s);
		i = dbm_store(db, k, d, DBM_REPLACE);
		if (i)
			ut_err(L_U, "getut.c: inc_host_user: ERROR: dbm_store fails\n");
	}
}

/*
 *-----------------------------------------------------------------
 * dec_host_user
 *
 * Decrement host/user count.  If count for this host/user drops to
 * zero then decrement the overall number of users.
 *
 */

static void
dec_host_user(host, user)
	char *host;
	char *user;
{
	datum k, d, r;
	char *key;
	int i;

	union {
		int  s_s;
		char s_c[4];
	} s;

	if (utmp_write_lock == 0) {
		ut_err(L_U, "getut.c: dec_host_user: INTERNAL ERROR, not write locked\n");
		return;
	}

	key = host_user_key(host, user);
	k.dptr = key;
	k.dsize = strlen(key);

#ifdef ERRDEBUG
	gdebug("dec_host_user: host= '%s', user= '%s', key= '%s'\n",
		host, user, key);
#endif

	r = dbm_fetch(db, k);
	if (r.dptr != 0) {		/* entry exists */
		s.s_c[0] = r.dptr[0];
		s.s_c[1] = r.dptr[1];
		s.s_c[2] = r.dptr[2];
		s.s_c[3] = r.dptr[3];
#ifdef ERRDEBUG
	gdebug("dec_host_user: Found entry, old count was %d\n", s.s_s);
#endif
		s.s_s--;
		if (s.s_s >= 1) {
			d.dptr = (char *) &s.s_s;
			d.dsize = sizeof(s.s_s);
			i = dbm_store(db, k, d, DBM_REPLACE);
			if (i)
				ut_err(L_U, "getut.c: dec_host_user: ERROR: dbm_store fails\n");
#ifdef ERRDEBUG
	gdebug("dec_host_user: Decrementing and updating entry.\n");
#endif
		} else {
			(void) dbm_delete(db, k);
#ifdef ERRDEBUG
	gdebug("dec_host_user: Entry now zero, deleteing & decrementing user count\n");
#endif
			dec_numusers();
		}
	} else {
		/* Should have found it!  Something went wrong. */
#ifdef ERRDEBUG
	gdebug("dec_host_user: Did not find entry.\n");
#endif
		dec_numusers();
	}
}


/*
 *-----------------------------------------------------------------
 * hostcount
 *
 * Return the count of login entries for a host/user combination.
 *
 */

static int
hostcount(host, user)
	char *host;
	char *user;
{
	datum k, r;
	char *key;

	union {
		int  s_s;
		char s_c[4];
	} s;

	if (utmp_write_lock == 0 && utmp_read_lock == 0) {
		ut_err(L_U, "getut.c: hostcount: INTERNAL ERROR, not read locked\n");
		return(0);
	}

	key = host_user_key(host, user);
	k.dptr = key;
	k.dsize = strlen(key);

	r = dbm_fetch(db, k);
	if (r.dptr == 0) {		/* new entry */
#ifdef ERRDEBUG
		gdebug("hostcount: key= '%s', not found\n", key);
#endif
		return(0);
	} else {
		s.s_c[0] = r.dptr[0];	/* existing entry */
		s.s_c[1] = r.dptr[1];
		s.s_c[2] = r.dptr[2];
		s.s_c[3] = r.dptr[3];
#ifdef ERRDEBUG
		gdebug("hostcount: key= '%s', found, count = %d\n", key, s.s_s);
#endif
		return(s.s_s);
	}
}

/*
 *-----------------------------------------------------------------
 * host_user_key
 *
 * Given a host and user, generate a dbm key.
 *
 */

static char *
host_user_key(host, user)
	char *host;
	char *user;
{
	static char buf[256];

	(void) strcpy(buf, "hu_key:");
	(void) strncat(buf, host, 80);
	(void) strcat(buf, ":");
	(void) strncat(buf, user, 80);
	return(buf);
}


/*
 *-----------------------------------------------------------------
 * utmp_write_lock()
 *
 * Write lock the utmp file.  Data base will only be altered when utmp
 * file is locked.
 *
 */

static int
utmp_write_lock()
{
	struct flock flk;

	flk.l_type = F_WRLCK;
	flk.l_whence = 0;
	flk.l_start = 0;
	flk.l_len = 0;
	flk.l_pid = 0;

	if (utmp_write_locked != 0) {
		ut_err(L_U, "getut.c: utmp_write_lock: INTERNAL ERROR: utmp dup lock\n");
		return(-1);
	}
	if (db == (DBM *) 0) {
		ut_err(L_U, "getut.c: utmp_write_lock: INTERNAL ERROR: db not open\n");
		return(-1);
	}

	block_signals();
	if (fcntl(fd, F_SETLKW, &flk) == -1) {
		unblock_signals();
		ut_err(L_U, "ERROR: getut.c: fcntl of utmp file fails, errno = %d\n",
		    errno);
		return(-1);
	} else {
		utmp_write_locked = 1;
		dbm_blkflush(db);
		if (check_dbm_size() != 0) {
			utmp_write_unlock();
			return(-1);
		}
		return(0);
	}
}


/*
 *-----------------------------------------------------------------
 * utmp_write_unlock()
 *
 * Release utmp write lock.
 * It is possible that we will have updated the utmp file but not
 * written anything to the data base files.  So we will update the
 * time on one of the data bases to indicate that information
 * is current.
 */

static void
utmp_write_unlock()
{
	struct flock flk;

	flk.l_type = F_UNLCK;
	flk.l_whence = 0;
	flk.l_start = 0;
	flk.l_len = 0;
	flk.l_pid = 0;

	if (utmp_write_locked == 0)
		ut_err(L_U, "getut.c: utmp_write_unlock: INTERNAL ERROR: utmp missing lock\n");
	/* Let it continue. */

	(void) utime(DBM_FILE_DIR, NULL);	/* update modification time */
	(void) fcntl(fd, F_SETLKW, &flk);
	utmp_write_locked = 0;
	unblock_signals();
}


/*
 *-----------------------------------------------------------------
 * utmp_read_lock()
 *
 * Read lock the utmp file.  Data base may be read, but NOT altered.
 *
 */

static int
utmp_read_lock()
{
	struct flock flk;

	flk.l_type = F_RDLCK;
	flk.l_whence = 0;
	flk.l_start = 0;
	flk.l_len = 0;
	flk.l_pid = 0;

	if (utmp_read_locked != 0) {
		ut_err(L_U, "getut.c: utmp_read_lock: INTERNAL ERROR: utmp dup lock\n");
		return(-1);
	}

	if (db == (DBM *) 0) {
		ut_err(L_U, "getut.c: utmp_read_lock: INTERNAL ERROR: db not open\n");
		return(-1);
	}

	block_signals();
	if (fcntl(fd, F_SETLKW, &flk) == -1) {
		unblock_signals();
		ut_err(L_U, "ERROR: getut.c: fcntl of utmp file fails, errno = %d\n",
		    errno);
		return(-1);
	} else {
		utmp_read_locked = 1;
		dbm_blkflush(db);
		if (check_dbm_size() != 0) {
			utmp_read_unlock();
			return(-1);
		}
		return(0);
	}
}


/*
 *-----------------------------------------------------------------
 * utmp_read_unlock()
 *
 * Release the read lock on the utmp file.
 *
 */

static void
utmp_read_unlock()
{
	struct flock flk;

	flk.l_type = F_UNLCK;
	flk.l_whence = 0;
	flk.l_start = 0;
	flk.l_len = 0;
	flk.l_pid = 0;

	if (utmp_read_locked == 0)
		ut_err(L_U, "getut.c: utmp_read_unlock: INTERNAL ERROR: utmp missing lock\n");

	/* fall through */

	(void) fcntl(fd, F_SETLKW, &flk);
	utmp_read_locked = 0;
	unblock_signals();
}


/*
 *-----------------------------------------------------------------
 * block_signals()
 *
 * This will block all signals, saving the prior signal mask.
 *
 */

static void
block_signals()
{
	if (signals_blocked != 0)
		return;
	
	if (sig_mask_built == 0) {
		if (sigfillset(&mask_set) != 0)
			return;
		if (sigemptyset(&old_set) != 0)
			return;
		sig_mask_built = 1;
	}

	if (sigprocmask(SIG_SETMASK, &mask_set, &old_set) == 0)
		signals_blocked = 1;
}



/*
 *-----------------------------------------------------------------
 * unblock_signals()
 *
 * This will restore the signal mask to that found when block_signals
 * was called.
 *
 */

static void
unblock_signals()
{
	if (signals_blocked == 0)
		return;

	if (sigprocmask(SIG_SETMASK, &old_set, (sigset_t *) NULL) == 0)
		signals_blocked = 0;
}


/*
 *-----------------------------------------------------------------
 * add_rec()
 *
 * Add this record to the end of the utmp file and fill in the index
 * in the file.  Return 0 for success.  File must be write locked.
 *
 */

static int
add_rec(entry, slot)
	struct utmp *entry;
	int *slot;
{
	int fc;
	struct utmp tmpbuf;
	int i;

	/* Make sure writable and set APPEND flag. */

	if (( (fc = fcntl(fd, F_GETFL, NULL)) == -1) ||
	      (fc & O_RDWR) != O_RDWR       )            {
#ifdef ERRDEBUG
		gdebug("add_rec: fcntl, F_GETFL fails\n");
#endif
		return(-1);
	}
	if (fcntl(fd, F_SETFL, fc | O_APPEND) == -1) {
#ifdef ERRDEBUG
		gdebug("add_rec: fcntl, F_SETFL fails\n");
#endif
		return(-1);
	}
	
	tmpbuf = *entry;
	if ((i = write(fd, &tmpbuf, sizeof(tmpbuf))) != sizeof(tmpbuf)) {
#ifdef ERRDEBUG
		gdebug("add_rec: write fails, errno= %d\n", errno);
		gdebug("add_rec: wrote %d, expected %d\n", i, sizeof(tmpbuf));
#endif
		return(-1);
	}
	ubuf = tmpbuf;		/* update utmp so now up-to-date */

	(void) fcntl(fd, F_SETFL, fc);
	(void) lseek(fd, 0L, 2);
	loc_utmp = lseek(fd, -sizeof(struct utmp), 2);
	if (loc_utmp % sizeof(struct utmp) != 0) {
		ut_err(L_A, "getut.c: add_rec: utmp file record size error\n");
		return(-1);
	}
	*slot = loc_utmp / sizeof(struct utmp);
#ifdef ERRDEBUG
	gdebug("add_rec: returning slot= %d, loc_utmp= %d\n", *slot, loc_utmp);
#endif
	return(0);
}


/*
 *-----------------------------------------------------------------
 * replace_rec()
 *
 * Given a utmp index, replace the record.
 *
 */

static int
replace_rec(entry, slot)
	struct utmp *entry;
	int slot;
{
	long offset;
	struct utmp tmpbuf;
	int i;

	offset = slot * sizeof(ubuf);
#ifdef ERRDEBUG
		gdebug("replace_rec: slot= %d, loc= %ld\n", slot, offset);
#endif
	loc_utmp = lseek(fd, offset, 0);
	if (loc_utmp < 0) {
		loc_utmp = 0L;
#ifdef ERRDEBUG
		gdebug("replace_rec: seek fails\n");
#endif
		return(-1);
	}
	tmpbuf = *entry;
	if ((i = write(fd, &tmpbuf, sizeof(tmpbuf))) != sizeof(tmpbuf)) {
#ifdef ERRDEBUG
		gdebug("replace_rec: write fails, errno= %d\n", errno);
		gdebug("replace_rec: wrote %d, expected %d\n",
			i, sizeof(tmpbuf));
#endif
		return(-1);
	}
	ubuf = *entry;	/* update ubuf so it will be up-to-date */
	return(0);
}

/*
 *-----------------------------------------------------------------
 * check_utmp_hash
 *
 * Make sure utmp files are open.  Create if necessary.
 *
 */

static int
check_utmp_hash()
{
	struct stat sb_pag, sb_dir, sb_utmp;
	struct flock flk;
	int r_pag, r_dir, r_ut;
	int i, l;

	/* If utmp file not open we need to open it. */

	if (fd < 0)
		if (open_ut_file()) {
#ifdef ERRDEBUG
			gdebug("check_utmp_hash: open_ut_file returned non-zero\n");
#endif
			return(-1);
		}

	/* Typical case is that dbm files already open. */

	if (db != (DBM *) 0)
		return(0);

	/* Make sure user hasn't opened alternate file. */

	if (not_utmp_file) {
#ifdef ERRDEBUG
		gdebug("check_utmp_hash: not_utmp_file= %d\n", not_utmp_file);
#endif
		return(-1);
	}

#ifdef ERRDEBUG
	gdebug("check_utmp_hash: my pid= %d, opening dbm, utmp_canwrite= %d\n",
		getpid(), utmp_canwrite);
#endif


	/*
	 * If we cannot write the file then we are just going to open
	 * and and we will let later checks find that it is not correct
	 * size or out of date.  No use worring about that now since we
	 * can't write it.
	 */

	if (! utmp_canwrite) {
	    db = dbm_open(DBM_FILE, O_RDONLY, 0644);
	    if (db == (DBM *) 0) {
		ut_err(L_U, "ERROR: getut.c: dbm open fails\n");
		return(-1);
	    } else
		return(0);
	}



	r_ut  = stat(UTMP_FILE, &sb_utmp);
	if (r_ut != 0) {		/* should not happen */
		ut_err(L_U, "ERROR: getut.c: stat of utmp file fails, errno= %d\n",
		    errno);
		return(-1);
	}

	/*
	 * Need to prevent two tries at rebuilding utmp if it
	 * should be out of date.  Only will do this if can write
	 * the utmp file.  Don't use the utmp_write_lock() routine
	 * since we don't want the checks for size and time.
	 */

	flk.l_type = F_WRLCK;
	flk.l_whence = 0;
	flk.l_start = 0;
	flk.l_len = 0;
	flk.l_pid = 0;
	block_signals();
	if (fcntl(fd, F_SETLKW, &flk) == -1) {
	    unblock_signals();
	    ut_err(L_U, "ERROR: getut.c: fcntl of utmp file fails, errno = %d\n",
		errno);
	    return(-1);
	} else {
		utmp_write_locked = 1;			/* LOCKED */
	}
	
	/*
	 * See if dbm files exist.  Create new data base if not.
	 * Stat of utmp cannot fail, but other two files might not exist.
	 */

	r_ut  = stat(UTMP_FILE, &sb_utmp);
	if (r_ut != 0) {		/* should not happen */
	    ut_err(L_U, "ERROR: getut.c: stat of utmp file fails, errno= %d\n",
		   errno);
	    return(-1);
	}
	r_pag = stat(DBM_FILE_PAG, &sb_pag);
	r_dir = stat(DBM_FILE_DIR, &sb_dir);

#ifdef ERRDEBUG
	gdebug("check_utmp_hash: r_pag= %d, r_dir= %d, r_ut= %d\n",
		r_pag, r_dir, r_ut);
	if (r_pag == 0)
		gdebug("check_utmp_hash: pag size= %d\n", sb_pag.st_size);
	if (r_dir == 0)
		gdebug("check_utmp_hash: dir size= %d\n", sb_dir.st_size);
	if (r_pag == 0)
		gdebug("check_utmp_hash: utmp size= %d\n", sb_utmp.st_size);
#endif

	/*
	 * If both data base files are non-existant we will have to
	 * open the data base.  Everything is ok if the utmp file
	 * is empty.  But if there is data in the utmp file we have
	 * an error and we must build a new data base.
	 */

	if ((r_pag != 0) && (r_dir != 0)) {
		/*
		 * Neither file exist.  Fine if utmp file is empty.
		 * But do need to write in utmp directory.
		 */
		db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
		if (db == (DBM *) 0) {
			utmp_write_unlock();			/* UNLOCKED */
			ut_err(L_A, "getut.c: open of utmp data base fails\n");
			return(-1);
		}
		/* If utmp empty just return. */
		if (sb_utmp.st_size == 0) {
#ifdef ERRDEBUG
			gdebug("check_utmp_hash: opened new dbm, utmp size is zero\n");
#endif
			utmp_write_unlock();			/* UNLOCKED */
			return(0);
		} else {
			ut_err(L_C, "WARNING: getut.c: utmp data base missing - rebuilding\n");
			i = build_db(BUILD_DB_NEW);
			utmp_write_unlock();			/* UNLOCKED */
			return(i);
		}
	}

	/* 
	 * If one of the data base files is mising then we have to
	 * remove the other file and rebuild the data base.
	 */
	
	if ((r_pag != 0) || (r_dir != 0)) {
	    if (r_pag != 0) {
		ut_err(L_C, "WARNING: getut.c: utmp data base file '%s' missing\n",
		     DBM_FILE_PAG);
		if (unlink(DBM_FILE_DIR) != 0) {
			utmp_write_unlock();			/* UNLOCKED */
			ut_err(L_U, "getut.c: cannot unlink utmp dbm file '%s'\n",
			    DBM_FILE_DIR);
			return(-1);
		}
	    } else {
		ut_err(L_C, "WARNING: getut.c: utmp data base file '%s' missing\n",
		    DBM_FILE_PAG);
		if (unlink(DBM_FILE_PAG) != 0) {
			utmp_write_unlock();			/* UNLOCKED */
			ut_err(L_U, "getut.c: cannot unlink utmp dbm file '%s'\n",
			    DBM_FILE_PAG);
			return(-1);
		}
	    }
	    db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
	    if (db == (DBM *) 0) {
		utmp_write_unlock();				/* UNLOCKED */
		return(-1);
	    }
	    /* If utmp empty just return.  Shouldn't happen. */
	    if (sb_utmp.st_size == 0) {
		utmp_write_unlock();				/* UNLOCKED */
		return(0);
	    } else {
		i = build_db(BUILD_DB_NEW);
		utmp_write_unlock();				/* UNLOCKED */
		return(i);
	    }
	}

	/*
	 * Both data base files exist.  Make sure that at least one of
	 * them is newer than the utmp file.
	 */

	if ((sb_utmp.st_mtime <= sb_pag.st_mtime) || 
	    (sb_utmp.st_mtime <= sb_dir.st_mtime)   ) {
	    db = dbm_open(DBM_FILE, O_RDWR, 0644);
	    if (db == (DBM *) 0) {
		ut_err(L_U, "getut.c: utmp data base open fails\n");
	    	utmp_write_unlock();				/* UNLOCKED */
		return(-1);
	    }
	    i = check_dbm_size();
	    utmp_write_unlock();				/* UNLOCKED */
	    return(i);
	}

	/*
	 * Bad news -- both data base files are older than the utmp file.
	 * This means that the data base is out of date.  So we are
	 * going to have to build a new data base.  Of course if utmp file
	 * is empty we know there is no data in the utmp file and can
	 * just create a new data base file.
	 */
	
	if (sb_utmp.st_size == 0) {
		if ((l= creat(DBM_FILE_DIR, 0644)) < 0) {
			utmp_write_unlock();			/* UNLOCKED */
			ut_err(L_U, "getut.c:  cannot creat utmp dbm dir file for rebuild\n");
			return(-1);
		}
		(void) close(l);
		if ((l= creat(DBM_FILE_PAG, 0644)) < 0) {
			utmp_write_unlock();			/* UNLOCKED */
			ut_err(L_U, "getut.c: cannot creat utmp dbm pag file for rebuild\n");
			return(-1);
		}
		(void) close(l);
	    	db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
		utmp_write_unlock();				/* UNLOCKED */
	    	if (db == (DBM *) 0) {
			ut_err(L_U, "getut.c: dbm_open routine failed\n");
			return(-1);
		} else {
			return(0);
		}
	}

	/*
	 * Ok, so now open the file and work on rebuilding the file.
	 */
	 
	db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
	if (db == (DBM *) 0) {
		utmp_write_unlock();				/* UNLOCKED */
		ut_err(L_U, "getut.c: dbm_open routine failed\n");
		return(-1);
	}

	/*
	 * Check the sizes of the data base files as a sanity check for
	 * good files.
	 */
	
	if ((sb_pag.st_size % PBLKSIZ) != 0) {
		i = build_db(BUILD_DB_BADF);
		utmp_write_unlock();				/* UNLOCKED */
		return(i);
	}

	if ((sb_dir.st_size % DBLKSIZ) != 0) {
		i = build_db(BUILD_DB_BADF);
		utmp_write_unlock();				/* UNLOCKED */
		return(i);
	}

	ut_err(L_C, "WARNING: getut.c: utmp data base out of date - rebuilding\n");
	i = build_db(BUILD_DB_EXIST);
	utmp_write_unlock();					/* UNLOCKED */
	return(i);
}

/*
 *-----------------------------------------------------------------
 * build_db
 *
 * This is called when we have an existing utmp file (not empty).
 *
 * For case BUILD_DB_NEW, we have no data base and so just put the
 * information from utmp in the data base.
 *
 * For case BUILD_DB_BADF, we believe the data base file to be
 * corrupt due to unexpected size.  We delete files and rebuild.
 *
 * For case BUILD_DB_EXIST, we have a data base but it is out of
 * date.  In that event, we use the old utmp file and try and recover
 * whatever information we can from the data base.
 *
 * In any event, this is called with the data base opened and utmp
 * write locked.
 *
 */

static int
build_db(how)
	int how;
{
	struct utmp *u;
	char *key, *h, buf[80 + 2];
	int slot, users, i, l;
	FILE *f;
	struct utsname unm;

	if (!utmp_write_locked) {
		ut_err(L_A, "getut.c: internal error in build_db\n");
		return(-1);
	}

	(void) uname(&unm);

	if (how == BUILD_DB_BADF) {
		ut_err(L_C, "getut.c: utmp data base files bad size - rebuilding\n");
		dbm_close(db);
		db = (DBM *) 0;
		if ((l = creat(DBM_FILE_DIR, 0644)) < 0) {
		    ut_err(L_U, "getut.c: cannot creat '%s' to rebuild utmp data base\n",
		        DBM_FILE_DIR);
		    return(-1);
		}
		(void) close(l);
		if ((l = creat(DBM_FILE_PAG, 0644)) < 0) {
		    ut_err(L_U, "getut.c: cannot creat '%s' to rebuild utmp data base\n",
		        DBM_FILE_PAG);
		    return(-1);
		}
		(void) close(l);
		db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
		if (db == (DBM *) 0)
			return(-1);
		/* Now fall into code to build as if a new file. */
	}

	if ((how == BUILD_DB_NEW) || (how == BUILD_DB_BADF)) {
	    slot = 0;
	    users = 0;
	    setutent();
	    while ((u = getutent()) != NULL) {
		switch (u->ut_type) {
		case RUN_LVL:
		case BOOT_TIME:
		case OLD_TIME:
		case NEW_TIME:
			key = gen_key(u, KEY_SPECIAL);
			add_slot_key(key, slot);
			break;
		case USER_PROCESS:
			users++;
			/* fall through */
		case INIT_PROCESS:
		case LOGIN_PROCESS:
		case DEAD_PROCESS:
			key = gen_key(u, KEY_ID);
			add_slot_key(key, slot);
			key = gen_key(u, KEY_PID);
			add_slot_key(key, slot);
			key = gen_key(u, KEY_LINE);
			add_slot_key(key, slot);
			break;
	/*	case ACCOUNTING: */
	/*	case EMPTY: */
	/*	default: */
		}
		slot++;
	    }
	    set_num_users(users);
	    if (dbm_error(db)) {
		ut_err(L_U, "getut.c: create new utmp data base fails.\n");
		return(-1);
	    }
	    return(0);
	}

	if (how == BUILD_DB_EXIST) {
	    slot = 0;
	    setutent();
	    sprintf(buf, "/tmp/ut_db_%d", getpid());
	    if ((f = fopen(buf, "w+")) == NULL) {
		ut_err(L_U, "getut: cannot open temporary file '%s' for utmp data base rebuild\n",
		    buf);
		return(-1);
	    }
	    (void) unlink(buf);
	    while ((u = getutent()) != NULL) {
		if (u->ut_type == USER_PROCESS) {
			h = findhost(u->ut_line);
			if (h == NULL) {
				(void) fprintf(f, "*");
			} else {
				(void) fprintf(f, h);
			}
		} else {
			(void) fprintf(f, "*");
		}
	    }
	    rewind(f);
	    setutent();
	    slot = 0;
	    users = 0;
	    if (dbm_error(db)) {
	    	(void) dbm_close(db);
		db = (DBM *) 0;
		(void) fclose(f);
		ut_err(L_U, "getut: dbm error on scan of old utmp data base\n");
		if ((l = creat(DBM_FILE_DIR, 0644)) < 0)
			return(-1);
		close(l);
		if ((l = creat(DBM_FILE_PAG, 0644)) < 0)
			return(-1);
		(void) close(l);
		db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
		if (db == (DBM *) 0)
			return(-1);
		else
			return(build_db(BUILD_DB_NEW));
	    }
	    dbm_close(db);
	    db = (DBM *) 0;
	    if ((unlink(DBM_FILE_DIR) != 0) || (unlink(DBM_FILE_PAG) != 0)) {
		ut_err(L_U, "getut: cannot unlink old dbm file for utmp data base rebuild\n");
		(void) fclose(f);
		return(-1);
	    }
	    db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
	    if (db == (DBM *) 0) {
		ut_err(L_U, "getut: cannot create new utmp data base\n");
		(void) fclose(f);
		return(-1);
	    }
	    while ((u = getutent()) != NULL) {
		if (fgets(buf, sizeof(buf), f) == (char *) NULL) {
			(void) fclose(f);
			(void) dbm_close(db);
			db = (DBM *) 0;
			ut_err(L_U, "getut: cannot create new utmp data base\n");
			return(-1);
		}
		switch (u->ut_type) {
		case RUN_LVL:
		case BOOT_TIME:
		case OLD_TIME:
		case NEW_TIME:
			key = gen_key(u, KEY_SPECIAL);
			add_slot_key(key, slot);
			break;
		case USER_PROCESS:
			if (buf[0] == '*') {
				users++;
			} else {
				buf[strlen(buf)] = '\0';
				if (EQ(unm.nodename, buf)) /* ignore local logins */
					inc_host_user(buf, u->ut_user);
				set_host(buf,u->ut_line);
			}
			/* fall through */
		case INIT_PROCESS:
		case LOGIN_PROCESS:
		case DEAD_PROCESS:
			key = gen_key(u, KEY_ID);
			add_slot_key(key, slot);
			key = gen_key(u, KEY_PID);
			add_slot_key(key, slot);
			key = gen_key(u, KEY_LINE);
			add_slot_key(key, slot);
			break;
	/*	case ACCOUNTING:	*/
	/*	case EMPTY:	*/
	/*	default:	*/
		}
		slot++;
	    }
	    i = ut_get_numusers();
	    set_num_users(i + users);
	    if (dbm_error(db)) {
	    	dbm_close(db);
		db = (DBM *) 0;
		ut_err(L_U, "getut: dbm error on scan of old utmp data base\n");
		if ((l = creat(DBM_FILE_DIR, 0644)) < 0)
			return(-1);
		(void) close(l);
		if ((l = creat(DBM_FILE_PAG, 0644)) < 0)
			return(-1);
		(void) close(l);
		db = dbm_open(DBM_FILE, O_RDWR|O_CREAT, 0644);
		if (db == (DBM *) 0)
			return(-1);
		else
			return(build_db(BUILD_DB_NEW));
	    } else {
		return(0);
	    }
	}

	ut_err(L_U, "getut.c: build_db: internal error, cannot build utmp data base\n");
	return(-1);	/* shouldn't be able to hit */
}

/*
 *-----------------------------------------------------------------
 * check_dbm_size
 *
 * Try and insure that data base is valid by seeing that it is the
 * correct size.  The files should always be some modulo of block
 * sizes as defined in the data base header file.  So stat the files
 * and verify this.  Intent is to protect against some other file
 * clobbering the data base.  Rebuild data base if incorrect file size
 * is found.  Returns 0 if everything ok (even if rebuilt as long as
 * that worked.
 *
 * Note: This also checks that the modification times to insure that
 * one of the data base files is newer than the utmp file.
 */

static int
check_dbm_size()
{
	struct stat sb_pag, sb_dir, sb_utmp;

	if ((utmp_write_locked == 0) && (utmp_read_locked == 0))
		ut_err(L_U, "ERROR: getut.c: dbm not locked in check_dbm_size\n");

	if (fstat(db->dbm_dirf, &sb_dir) != 0) {
		ut_err(L_U, "ERROR: getut.c: fstat on dirf (%d)  errno = %d\n",
		  db->dbm_dirf, errno);
		return(build_db(BUILD_DB_BADF));
	}
	if ((sb_dir.st_size % DBLKSIZ) != 0)
		return(build_db(BUILD_DB_BADF));

	if (fstat(db->dbm_pagf, &sb_pag) != 0) {
		ut_err(L_U, "ERROR: getut.c: fstat on pagf (%d)  errno = %d\n",
		  db->dbm_pagf, errno);
		return(build_db(BUILD_DB_BADF));
	}
	if ((sb_pag.st_size % PBLKSIZ) != 0)
		return(build_db(BUILD_DB_BADF));

	if (fstat(fd, &sb_utmp) != 0) {
		ut_err(L_U, "ERROR: getut.c: fstat on utmp file fails, errno = %d\n",
		  errno);
		return(1);
	}

	/* The sizes are ok, now check the modification times.  */

	if ((sb_utmp.st_mtime <= sb_pag.st_mtime) ||
	    (sb_utmp.st_mtime <= sb_dir.st_mtime)    )
		return(0);
	
	return(build_db(BUILD_DB_EXIST));
}

#endif /* HASH_UTMP */

/*
 *-----------------------------------------------------------------
 * ut_err
 *
 * Log an error on console if possible and also on users terminal.
 * Arg "where" is:
 *    LU -- log on users terminal
 *    LC -- log on console
 *    LA -- log on both users terminal and console
 */

/*VARARGS*/
static void
ut_err(where, format, arg1, arg2, arg3, arg4, arg5, arg6)
	int where;
	char *format;
	int arg1, arg2, arg3, arg4, arg5, arg6;
{
	register FILE *fp;
	struct stat sb;

#ifdef LOG_FILE
	if ((fp = fopen(UT_LOG_FILE,"a+")) != NULL) {
		if (where == L_A)
			(void) fprintf(fp, "** ut_err(L_A) **\n");
		if (where == L_C)
			(void) fprintf(fp, "** ut_err(L_C) **\n");
		if (where == L_U)
			(void) fprintf(fp, "** ut_err(L_U) **\n");
		(void) fprintf(fp, format, arg1, arg2, arg3, arg4, arg5, arg6);
		(void) fclose(fp);
		sync();
	}
#else
#ifdef CONSOLE
	if ( (where & L_C) && (stat(UT_ERR_IGNORE_C, &sb) != 0) ) {
	    if ((fp = fopen(CONSOLE, "w")) != NULL) {
		(void) fprintf(fp, "\n");
		(void) fprintf(fp, format, arg1, arg2, arg3, arg4, arg5, arg6);
		(void) fclose(fp);
	    }
	}

#endif /* CONSOLE */
#ifdef TERMINAL
	if (where & L_U) {
	    if ((fp = fopen(TERMINAL, "w")) != NULL) {
		(void) fprintf(fp, "\n");
		(void) fprintf(fp, format, arg1, arg2, arg3, arg4, arg5, arg6);
		(void) fclose(fp);
	    }
	}
#endif /* TERMINAL */
#endif /* LOG_FILE */
}

#ifdef DEBUG_HASH

main()
{
	int i;

#ifdef ERRDEBUG
	gdebug("\n\n******************************************\n\n");
#endif

	for (;;) {
		i = show_menu();
		do_task(i);
	}
}

show_menu()
{
	char buf[80];
	int i;

loop:
	(void) printf("\n");
	(void) printf("1: utmpname         2: setutent         3: endutent\n");
	(void) printf("4: getutent         5: getutid          6: getuline\n");
	(void) printf("7: getutpid         8: pututline        9: ut_add_user\n");
	(void) printf("10: ut_delete_user  11: get_numusers    12: dump utmp file\n");
	(void) printf("13: dump db keys    14. add entries     15. delete entries\n");
	(void) printf("16: lookup host     17. scan host names\n");
	(void) printf("\nSelect  (0 to exit):  ");

	(void) gets(buf);
	if (buf[0] == '\0')
		goto loop;
	if (buf[0] < '0' || buf[0] > '9')
		goto loop;
	i = atoi(buf);
#ifdef ERRDEBUG
	gdebug("\nshow_menu: selection was %d, ", i);
#endif
	return(i);
}

do_task(n)
	int n;
{
#ifdef HASH_UTMP
	datum k;
#endif
	char buf[80];

	switch (n) {
	case 0:			/* exit */
#ifdef ERRDEBUG
		gdebug("exit\n");
#endif
		exit(0);
	case 1:			/* utmpname */
#ifdef ERRDEBUG
		gdebug("utmpname\n");
#endif
		ut_tst_utmpname();
		return;
	case 2:			/* setutent */
#ifdef ERRDEBUG
		gdebug("setutent\n");
#endif
		ut_tst_setutent();
		return;
	case 3:			/* endutent */
#ifdef ERRDEBUG
		gdebug("endutent\n");
#endif
		ut_tst_endutent();
		return;
	case 4:			/* getutent */
#ifdef ERRDEBUG
		gdebug("getutent\n");
#endif
		ut_tst_getutent();
		return;
	case 5:			/* getutid */
#ifdef ERRDEBUG
		gdebug("getutid\n");
#endif
		ut_tst_getutid();
		return;
	case 6:			/* getutline */
#ifdef ERRDEBUG
		gdebug("getutline\n");
#endif
		ut_tst_getutline();
		return;
	case 7:			/* getutpid */
#ifdef ERRDEBUG
		gdebug("getutpid\n");
#endif
		ut_tst_getutpid();
		return;
	case 8:			/* pututline */
#ifdef ERRDEBUG
		gdebug("pututline\n");
#endif
		ut_tst_pututline();
		return;
#ifdef HASH_UTMP
	case 9:			/* ut_add_user */
#ifdef ERRDEBUG
		gdebug("ut_add_user\n");
#endif
		ut_tst_ut_add_user();
		return;
	case 10:		/* ut_delete_user */
#ifdef ERRDEBUG
		gdebug("ut_delete_user\n");
#endif
		ut_tst_ut_delete_user();
		return;
	case 11:		/* get_numusers */
#ifdef ERRDEBUG
		gdebug("get_numusers\n");
#endif
		ut_tst_getnumusers();
		return;
#endif /* HASH_UTMP */
	case 12:		/* dump utmp file */
#ifdef ERRDEBUG
		gdebug("dump utmp file\n");
#endif
		ut_dump_utmp();
		return;
#ifdef HASH_UTMP
	case 13:		/* dump db keys */
#ifdef ERRDEBUG
		gdebug("dump db keys\n");
#endif
		if (check_utmp_hash()) {
			printf("data base open fails\n");
#ifdef ERRDEBUG
			gdebug("data base open fails\n");
#endif
			return;
		}
		for (k = dbm_firstkey(db); k.dptr != NULL; k = dbm_nextkey(db)) {
			memcpy(buf, k.dptr, k.dsize);
			buf[k.dsize] = '\0';
			(void) printf("key = '%s'\n", buf);
#ifdef ERRDEBUG
			gdebug("key = '%s'\n", buf);
#endif
		}
		return;
#endif /* HASH_UTMP */
	case 14:		/* add utmp entries */
#ifdef ERRDEBUG
		gdebug("auto add users\n");
#endif
		ut_auto_add();
		return;
	case 15:		/* delete utmp entries */
#ifdef ERRDEBUG
		gdebug("auto delete users\n");
#endif
		ut_auto_delete();
		return;
	case 16:
#ifdef ERRDEBUG
		gdebug("find host\n");
#endif
		ut_tst_findhost();
		return;
	case 17:
#ifdef ERRDEBUG
		gdebug("scan host names\n");
#endif
		ut_tst_get_entry();
		return;
	default:
#ifdef ERRDEBUG
		gdebug("unknown selection\n");
#endif
		return;
	}
}

#ifdef HASH_UTMP

dump_db()
{
	char buf[80];
	datum k;

#ifdef ERRDEBUG
	gdebug("\ndump_db:\n");
#endif
	for (k = dbm_firstkey(db); k.dptr != NULL; k = dbm_nextkey(db)) {
		memcpy(buf, k.dptr, k.dsize);
		buf[k.dsize] = '\0';
		printf("key = '%s'\n", buf);
#ifdef ERRDEBUG
		gdebug("key = '%s'\n", buf);
#endif
	}
#ifdef ERRDEBUG
	gdebug("\n");
#endif
}

#endif /* HASH_UTMP */


ut_tst_utmpname()
{
	char buf[80];

	(void) printf("Enter file name for utmp file: ");
	(void) gets(buf);
	if (buf[0] = '\0')
		(void) printf("Error: null name\n");
	else
		utmpname(buf);
		
}

ut_tst_setutent()
{
	setutent();
}

ut_tst_endutent()
{
	endutent();
}

ut_tst_getutent()
{
	struct utmp *u;

	u = getutent();
	ut_show_entry(u);
	printf("_utmp_slot_num = %d\n", _utmp_slot_num);
}

ut_tst_getutid()
{
	struct utmp *u, b;

	ut_fill_entry(&b);
	u = getutid(&b);
	ut_show_entry(u);
	printf("_utmp_slot_num = %d\n", _utmp_slot_num);
}

ut_tst_getutline()
{
	struct utmp *u, b;

	ut_fill_entry(&b);
	u = getutline(&b);
	ut_show_entry(u);
	printf("_utmp_slot_num = %d\n", _utmp_slot_num);
}

ut_tst_getutpid()
{
	struct utmp *u, b;

	ut_fill_entry(&b);
	u = getutpid(&b);
	ut_show_entry(u);
	printf("_utmp_slot_num = %d\n", _utmp_slot_num);
}

ut_tst_pututline()
{
	struct utmp *u, b;

	ut_fill_entry(&b);
	u = pututline(&b);
	ut_show_entry(u);
	printf("_utmp_slot_num = %d\n", _utmp_slot_num);
}

#ifdef HASH_UTMP

ut_tst_ut_add_user()
{
	char user[80];
	char line[80];
	char host[80];
	char buf[80];
	int pid;
	struct utmp *u;

loop:
	(void) printf("Enter user: ");
	(void) gets(user);
	if (user[0] == '\0')
		goto loop;
	(void) printf("Enter line: ");
	(void) gets(line);
	(void) printf("Enter pid: ");
	(void) gets(buf);
	if (buf[0] == '\0')
		pid = 0;
	else
		pid = atoi(buf);
	(void) printf("Enter host: ");
	(void) gets(host);
#ifdef ERRDEBUG
	gdebug("ut_tst_add_user: ");
	if (user[0] == '\0')
		gdebug("user= NULL, ");
	else
		gdebug("user= '%s', ", user);
	if (line[0] == '\0')
		gdebug("line= NULL, pid= %d, ", pid);
	else
		gdebug("line= '%s', pid= %d, ", line, pid);
	if (host[0] == '\0')
		gdebug("host= NULL\n");
	else
		gdebug("host= '%s'\n", host);
#endif
	u = ut_add_user(user, line, pid, host);
	ut_show_entry(u);
	printf("_utmp_slot_num = %d\n", _utmp_slot_num);
}

ut_tst_ut_delete_user()
{
	char line[80];
	char buf[80];
	int pid;
	int e_term;
	int e_status;
	struct utmp *u;

	(void) printf("Enter line: ");
	(void) gets(line);
	(void) printf("Enter pid: ");
	(void) gets(buf);
	if (buf[0] = '\0')
		pid = 0;
	else
		pid = atoi(buf);
	(void) printf("Enter e_term: ");
	(void) gets(buf);
	if (buf[0] == '\0')
		e_term = 0;
	else
		e_term = atoi(buf);
	(void) printf("Enter e_status: ");
	(void) gets(buf);
	if (buf[0] == '\0')
		e_status = 0;
	else
		e_status = atoi(buf);
	u = ut_delete_user(line, pid, e_term, e_status);
	ut_show_entry(u);
	printf("_utmp_slot_num = %d\n", _utmp_slot_num);

}

#endif /* HASH_UTMP */

ut_dump_utmp()
{
	struct utmp *u;
	int i, j, m, s;
	char buf[80];

	do {
		(void) memset(buf, '\0', sizeof(buf));
		(void) printf("Maximum # entries to dump: ");
		(void) gets(buf);
	} while ((buf[0] < '0') || (buf[0] > '9'));

	m = atoi(buf);

	do {
		(void) memset(buf, '\0', sizeof(buf));
		(void) printf("First slot to dump: ");
		(void) gets(buf);
	} while ((buf[0] < '0') || (buf[0] > '9'));

	s = atoi(buf);

	setutent();
	i = j = 0;
	while ((u = getutent()) != ((struct utmp *) NULL)) {
		if (i >= s) {
			printf("\n** Utmp entry %d\n", i);
			ut_show_entry(u);
			j++;
			if (j > m)
				break;
		}
		i++;
	}
	printf("\nUtmp record count = %d\n", i);
}

#ifdef HASH_UTMP

ut_tst_findhost()
{
	char buf[80];
	char *h;

	(void) printf("enter tty line: ");
	(void) gets(buf);
	h = ut_find_host(buf);
	if (h == NULL)
		printf("ut_find_host('%s') returns NULL -- not found\n", buf);
	else
		printf("ut_find_host('%s') returns '%s'\n", buf, h);
}

ut_tst_get_entry()
{
	struct utmp *u;
	char *h;
	int i = 1;

	setutent();
	while (ut_get_entry(&u, &h) == 0) {
	    if (u->ut_line[0] != 0)
		printf("%d: line = %s host = %s\n", i, u->ut_line, h);
	    else
		printf("%d: line is null\n", i);
	    i++;
	}
}

ut_tst_getnumusers()
{
	int n;
	char buf[80];

	n = ut_get_numusers();
	(void) printf("ut_get_numusers returns %d\n", n);
	(void) printf("user limit now %d, change? [y/n]: ", debug_maxusers);
	(void) gets(buf);
	if (buf[0] == 'Y' || buf[0] == 'y') {
		(void) printf("Enter new user limit: ");
		(void) gets(buf);
		n = atoi(buf);
		if (n > 0)
			debug_maxusers = n;
	}
}

#endif /* HASH_UTMP */

ut_auto_add()
{
	char buf[80];
	char id[4], id_save[5];
	int count, offset, i;
	struct utmp u, *r;

#ifdef HASH_UTMP
	if (check_utmp_hash()) {
		(void) printf("Utmp hash files not open.\n");
		return;
	}
#endif

	(void) printf("Enter starting sequence (4 chars, A-Z, a-z): ");
	(void) gets(buf);
	i = strlen(buf);
	if (i < 4) {
		(void) printf("Need 4 chars.\n");
		return;
	}
	(void) strncpy(id, buf, 4);
	if (ck_az(id[0]) || ck_az(id[1]) || ck_az(id[2]) || ck_az(id[3])) {
		(void) printf("Characters must be a-z or A-Z\n");
		return;
	}
	(void) printf("How many entries to add? ");
	(void) gets(buf);
	count = atoi(buf);
	if (count <= 0 || count > 50000) {
		(void) printf("Min count is 0, max count is 50,000.\n");
		return;
	}

	printf("Enter offset for user & pid: ");
	gets(buf);
	offset = atoi(buf);
	if (offset < 0)
		return;

#ifdef HASH_UTMP
	printf("User count is now %d\n", ut_get_numusers());

	if (dbm_error(db)) {
		(void) printf("NOTE: dbm_error set before start.  CLEARING\n");
		(void) dbm_clearerr(db);
	}
#endif

	for (i= 0; i < count; i++) {
		sprintf(buf, "usr%d", offset + i);
		strncpy(u.ut_user, buf, sizeof(u.ut_user));
		strncpy(u.ut_id, id, 4);
		strncpy(buf, id, 4);
		strncpy(id_save, id, 4);
		buf[4] = '\0';
#ifdef ERRDEBUG
		gdebug("\nut_auto_add: adding user id '%s'\n", buf);
#endif
		buf[0] = 't';
		buf[1] = 't';
		buf[2] = 'y';
		buf[3] = id[0];
		buf[4] = id[2];
		buf[5] = '/';
		buf[6] = id[0];
		buf[7] = id[1];
		buf[8] = id[2];
		buf[9] = id[3];
		buf[10] = '\0';
		strcpy(u.ut_line, buf);
		u.ut_pid = offset + i;
		u.ut_type = USER_PROCESS;
		u.ut_exit.e_termination = 0;
		u.ut_exit.e_exit = 0;
		time(&u.ut_time);
		r = pututline(&u);
		if (r == (struct utmp *) NULL) {
			printf("pututline fails, entry %d (NULL)\n", i);
			break;
		}
		id[3]++;
		if ((id[3] > 'Z') && (id[3] < 'a'))
			id[3] = 'a';
		if (id[3] > 'z') {
			id[3] = 'A';
			id[2]++;
		}
		if ((id[2] > 'Z') && (id[2] < 'a'))
			id[2] = 'a';
		if (id[2] > 'z') {
			id[2] = 'A';
			id[1]++;
		}
		if ((id[1] > 'Z') && (id[1] < 'a'))
			id[1] = 'a';
		if (id[1] > 'z') {
			id[1] = 'A';
			id[0]++;
		}
		if ((id[0] > 'Z') && (id[0] < 'a'))
			id[0] = 'a';
		if (id[0] > 'z') {
			id[0] = 'A';
			id[3] = 'A';
			printf("\nut_auto_add: ID WRAP AROUND\n");
		}
	}

	printf("Added %d entries\n", i);
#ifdef HASH_UTMP
	printf("User count is now %d\n", ut_get_numusers());
	id_save[4] = '\0';
	printf("Final id added = '%s'\n", id_save);
	if (dbm_error(db)) {
		(void) printf("NOTE: dbm_error set after entries - clearing.\n");
		(void) dbm_clearerr(db);
	}
#endif
}


ut_auto_delete()
{
	char buf[80];
	char id[4], id_save[5];
	int count, i;
	struct utmp u, *uptr;

#ifdef HASH_UTMP
	if (check_utmp_hash()) {
		(void) printf("Utmp hash files not open.\n");
		return;
	}
#endif

	(void) printf("Enter starting sequence (4 chars, A-Z, a-z): ");
	(void) gets(buf);
	i = strlen(buf);
	if (i < 4) {
		(void) printf("Need 4 chars.\n");
		return;
	}
	(void) strncpy(id, buf, 4);
	if (ck_az(id[0]) || ck_az(id[1]) || ck_az(id[2]) || ck_az(id[3])) {
		(void) printf("Characters must be a-z or A-Z\n");
		return;
	}
	(void) printf("How many entries to delete? ");
	(void) gets(buf);
	count = atoi(buf);
	if (count <= 0 || count > 50000) {
		(void) printf("Min count is 0, max count is 50,000.\n");
		return;
	}

#ifdef HASH_UTMP
	printf("User count is now %d\n", ut_get_numusers());

	if (dbm_error(db)) {
		(void) printf("NOTE: dbm_error set before start.  CLEARING\n");
		(void) dbm_clearerr(db);
	}
#endif

	for (i= 0; i < count; i++) {
		(void) memset((char *) &u, '\0', sizeof(u));
		strncpy(u.ut_id, id, 4);
		strncpy(buf, id, 4);
		strncpy(id_save, id, 4);
		buf[4] = '\0';
#ifdef ERRDEBUG
		gdebug("\nut_auto_delete: deleting user id '%s'\n", buf);
#endif	
		u.ut_type = USER_PROCESS;
		if ((uptr = getutid(&u)) == (struct utmp *) NULL) {
			printf("getutid fails, entry %d\n", i);
			break;
		}
		uptr->ut_type = DEAD_PROCESS;
		uptr->ut_exit.e_termination = 2;
		uptr->ut_exit.e_exit = 3;
		time(&uptr->ut_time);
		if (pututline(uptr) == (struct utmp *) NULL) {
			printf("pututline fails, entry %d\n", i);
			break;
		}
		id[3]++;
		if ((id[3] > 'Z') && (id[3] < 'a'))
			id[3] = 'a';
		if (id[3] > 'z') {
			id[3] = 'A';
			id[2]++;
		}
		if ((id[2] > 'Z') && (id[2] < 'a'))
			id[2] = 'a';
		if (id[2] > 'z') {
			id[2] = 'A';
			id[1]++;
		}
		if ((id[1] > 'Z') && (id[1] < 'a'))
			id[1] = 'a';
		if (id[1] > 'z') {
			id[1] = 'A';
			id[0]++;
		}
		if ((id[0] > 'Z') && (id[0] < 'a'))
			id[0] = 'a';
		if (id[0] > 'z') {
			id[0] = 'A';
			id[3] = 'A';
			printf("\nut_auto_add: ID WRAP AROUND\n");
		}
	}

	printf("Deleted %d entries\n", i);
#ifdef HASH_UTMP
	printf("User count is now %d\n", ut_get_numusers());
	id_save[4] = '\0';
	printf("Final id deleted was '%s'\n", id_save);
	if (dbm_error(db)) {
		(void) printf("NOTE: dbm_error set after entries - clearing.\n");
		(void) dbm_clearerr(db);
	}
#endif
}

int ck_az(c)
	int c;
{
	if ((c >= 'A') && (c <= 'Z'))
		return(0);
	if ((c >= 'a') && (c <= 'z'))
		return(0);
	return(1);
}

ut_show_entry(u)
	struct utmp *u;
{
	char buf[80];

	if (u == NULL) {
		(void) printf("\nReturns struct *utmp == NULL\n");
		return;
	}

	(void) printf("\nReturns:\n");
	(void) memset(buf, '\0', sizeof(buf));
	(void) strncpy(buf, u->ut_user, sizeof(ubuf.ut_user));
	(void) printf("ut_user = '%s'\n", buf);
	(void) memset(buf, '\0', sizeof(buf));
	(void) strncpy(buf, u->ut_id, sizeof(ubuf.ut_id));
	(void) printf("ut_id = '%s'\n", buf);
	(void) memset(buf, '\0', sizeof(buf));
	(void) strncpy(buf, u->ut_line, sizeof(ubuf.ut_line));
	(void) printf("ut_line = '%s'\n", buf);
	(void) memset(buf, '\0', sizeof(buf));
	(void) printf("ut_pid = %d\n", u->ut_pid);
	(void) printf("ut_type = %d = ", u->ut_type);
	(void) memset(buf, '\0', sizeof(buf));
	switch (u->ut_type) {
	case RUN_LVL:
		(void) printf("RUN_LVL\n"); break;
	case BOOT_TIME:
		(void) printf("BOOT_TIME\n"); break;
	case OLD_TIME:
		(void) printf("OLD_TIME\n"); break;
	case NEW_TIME:
		(void) printf("NEW_TIME\n"); break;
	case INIT_PROCESS:
		(void) printf("INIT_PROCESS\n"); break;
	case LOGIN_PROCESS:
		(void) printf("LOGIN_PROCESS\n"); break;
	case USER_PROCESS:
		(void) printf("USER_PROCESS\n"); break;
	case DEAD_PROCESS:
		(void) printf("DEAD_PROCESS\n"); break;
	case EMPTY:
		(void) printf("EMPTY\n"); break;
	default:
		(void) printf("?error?\n"); break;
	}
	(void) printf("ut_exit.e_termination = %d\n", u->ut_exit.e_termination);
	(void) printf("ut_exit.e_exit = %d\n", u->ut_exit.e_exit);
	(void) printf("ut_time = %s", ctime(&u->ut_time));
}

ut_fill_entry(uptr)
	struct utmp *uptr;
{
	struct utmp u;
	int l;
	char buf[80];
	char user[80];
	char id[80];
	char line[80];
	char *p;

	(void) memset((char *) &u, '\0', sizeof(u));
	do {
		(void) printf("Enter name for user: ");
		(void) gets(user);
	} while (user[0] == '\0');

	l = strlen(user);
	if (l > sizeof(u.ut_user))
		l = sizeof(u.ut_user);
	(void) memcpy(u.ut_user, user, l);

	do {
		(void) memset(id, '\0', sizeof(id));
		(void) printf("Enter 4 characters for id: ");
		(void) gets(id);
	} while (id[0] == '\0');

	l = strlen(id);
	if (l > sizeof(u.ut_id))
		l = sizeof(u.ut_id);
	(void) memcpy(u.ut_id, id, l);

	do {
		(void) memset(line, '\0', sizeof(line));
		(void) printf("Enter name for terminal line: ");
		(void) gets(line);
	} while (line[0] == '\0');

	l = strlen(line);
	if (l > sizeof(u.ut_line))
		l = sizeof(u.ut_line);
	(void) memcpy(u.ut_line, line, l);

	do {
		(void) memset(buf, '\0', sizeof(buf));
		(void) printf("Enter pid: ");
		(void) gets(buf);
	} while (buf[0] == '\0');

	l = atoi(buf);
	u.ut_pid = l;

loop:

	do {
		(void) memset(buf, '\0', sizeof(buf));
		(void) printf("Enter entry type by name or value: ");
		(void) gets(buf);
	} while (buf[0] == '\0');

	if (buf[0] >= '0' && buf[0] <= '9') {
		l = atoi(buf);
		if (l >= EMPTY && l <= UTMAXTYPE)
			u.ut_type = l;
		else {
			(void) printf("Unknown value.\n");
			goto loop;
		}
	} else {
		p = buf;
		while (*p) {
			if (islower(*p))
				*p = toupper(*p);
			p++;
		}
		if (EQ(buf, "RUN_LVL")) u.ut_type = RUN_LVL;
		else if (EQ(buf, "BOOT_TIME")) u.ut_type = BOOT_TIME;
		else if (EQ(buf, "OLD_TIME")) u.ut_type = OLD_TIME;
		else if (EQ(buf, "NEW_TIME")) u.ut_type = NEW_TIME;
		else if (EQ(buf, "INIT_PROCESS")) u.ut_type = INIT_PROCESS;
		else if (EQ(buf, "INIT")) u.ut_type = INIT_PROCESS;
		else if (EQ(buf, "LOGIN_PROCESS")) u.ut_type = LOGIN_PROCESS;
		else if (EQ(buf, "LOGIN")) u.ut_type = LOGIN_PROCESS;
		else if (EQ(buf, "USER_PROCESS")) u.ut_type = USER_PROCESS;
		else if (EQ(buf, "USER")) u.ut_type = USER_PROCESS;
		else if (EQ(buf, "DEAD_PROCESS")) u.ut_type = DEAD_PROCESS;
		else if (EQ(buf, "DEAD")) u.ut_type = DEAD_PROCESS;
		else if (EQ(buf, "EMPTY")) u.ut_type = EMPTY;
		else if (EQ(buf, "ACCOUNTING")) u.ut_type = ACCOUNTING;
		else {
			(void) printf("Unknown type.\n");
			goto loop;
		}
	}


	do {
		(void) printf("Enter value for e_termination: ");
		(void) gets(buf);
	} while (buf[0] == '\0');

	l = atoi(buf);
	u.ut_exit.e_termination = l;

	do {
		(void) printf("Enter value for e_exit: ");
		(void) gets(buf);
	} while (buf[0] == '\0');

	l = atoi(buf);
	u.ut_exit.e_exit = l;

	(void) time(&u.ut_time);

	*uptr = u;

#ifdef ERRDEBUG
	gdebug("ut_fill_entry: user= '%s', id= '%s', line= '%s', pid= %d\n",
		user, id, line, u.ut_pid);
	gdebug("ut_fill_entry: type= %d, exit= %d %d, time= %s", u.ut_type,
	    u.ut_exit.e_termination, u.ut_exit.e_exit, ctime(&u.ut_time));
#endif
}


#endif /* DEBUG_HASH */
