/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 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	"@(#)users.c	1.6	92/07/14 SMI"	/* SVr4.0 1.12	*/

/*
 * users.c
 *
 *	This file contains the source for the administrative command
 *	"listusers" (available to the general user population) that
 *	produces a report containing user login-IDs and their "free
 *	field" (typically contains the user's name and other information).
 */

/*
 *  Header files referenced:
 *	sys/types.h	System type definitions
 *	stdio.h		Definitions for standard I/O functions and constants
 *	string.h	Definitions for string-handling functions
 *	grp.h		Definitions for referencing the /etc/group file
 *	pwd.h		Definitions for referencing the /etc/passwd file
 *	varargs.h	Definitions for using a variable argument list
 *	fmtmsg.h	Definitions for using the standard message formatting
 *			facility
 */

#include	<sys/types.h>
#include	<stdio.h>
#include	<string.h>
#include	<string.h>
#include	<grp.h>
#include	<pwd.h>
#include	<varargs.h>
#include	<fmtmsg.h>


/*
 *  Externals referenced (and not defined by a header file):
 *	malloc		Allocate memory from main memory
 *	getopt		Extract the next option from the command line
 *	optind		The argument count of the next option to extract from
 *			the command line
 *	optarg		A pointer to the argument of the option just extracted
 *			from the command line
 *	opterr		FLAG:  !0 tells getopt() to write an error message if
 *			it detects an error
 *	getpwent	Get next entry from the /etc/passwd file
 *	getgrent	Get next entry from the /etc/group file
 *	fmtmsg		Standard message generation facility
 *	putenv		Modify the environment
 *	exit		Exit the program
 */

extern	void	       *malloc();
extern	int		getopt();
extern	char	       *optarg;
extern	int		optind;
extern	int		opterr;
extern	struct passwd  *getpwent();
extern	struct group   *getgrent();
extern	int		fmtmsg();
extern	int		putenv();
extern	void		exit();

/*
 *  Local constant definitions
 */

#ifndef	FALSE
#define	FALSE			0
#endif

#ifndef	TRUE
#define	TRUE			('t')
#endif

#define	USAGE_MSG		"usage: listusers [-g groups] [-l logins]"
#define	MAXLOGINSIZE		14
#define	LOGINFIELDSZ		MAXLOGINSIZE+2

#define	isauserlogin(uid)	(uid >= 100)
#define isasystemlogin(uid)	(uid < 100)
#define	isausergroup(gid)	(gid >= 100)
#define	isasystemgroup(gid)	(gid < 100)

/*
 *  Local datatype definitions
 */

/*
 * This structure describes a specified group name
 * (from the -g groups option)
 */

struct	reqgrp {
	char	       *groupname;
	struct reqgrp  *next;
	int		found;
	gid_t		groupID;
};

/*
 * This structure describes a specified login name
 * (from the -l logins option)
 */

struct	reqlogin {
	char	        *loginname;
	struct reqlogin *next;
	int	 	 found;
};

/*
 *  These functions handle error and warning message writing.
 *  (This deals with UNIX(r) standard message generation, so
 *  the rest of the code doesn't have to.)
 *
 *  Functions included:
 *	initmsg		Initialize the message handling functions.
 *	wrtmsg		Write the message using the standard message
 *			generation facility.
 *
 *  Static data included:
 *	fcnlbl		The label for standard messages
 *	msgbuf		A buffer to contain the edited message
 */

static	char	fcnlbl[MM_MXLABELLN+1];	/* Buffer for message label */
static	char	msgbuf[MM_MXTXTLN+1];	/* Buffer for message text */

/*
 * void initmsg(p)
 *
 *	This function initializes the message handling functions.
 *
 *  Arguments:
 *	p	A pointer to a character string that is the name of the
 *		command, used to generate the label on messages.  If this
 *		string contains a slash ('/'), it only uses the characters
 *		beyond the last slash in the string (this permits argv[0]
 *		to be used).
 *
 *  Returns:  Void
 */

static void
initmsg(p)
	char   *p;		/* Ptr to command name */
{
	/* Automatic data */
	char   *q;		/* Local multi-use pointer */

	/* Use only the simple filename if there is a slash in the name */
	if ((q = strrchr(p, '/')) == (char *) NULL) q = p;
	else q++;

	/* Build the label for messages */
	(void) sprintf(fcnlbl, "UX:%s", q);

	/*
	 * Now that we've done all of that work, set things up so that
	 * only the text-component of a message is printed.  (This piece
	 * of code will most probably go away in SVR4.1.
	 */
	(void) putenv("MSGVERB=text");
}

/*
 *  void wrtmsg(severity, action, tag, text[, txtarg1[, txtarg2[, ...]]])
 *
 *	This function writes a message using the standard message
 * 	generation facility.
 *
 *  Arguments:
 *	severity	The severity-component of the message
 *	action		The action-string used to generate the action-
 *			component of the message
 *	tag		Tag-component of the message
 *	text		The text-string used to generate the text-component
 *			of the message
 *	txtarg		Arguments to be inserted into the "text" string using
 *			vsprintf()
 *
 *  Returns:  Void
 */

/* VARARGS4 */

static void
wrtmsg(severity, action, tag, text, va_alist)
	int		severity;	/* Severity component in the message */
	char	       *action;		/* Action component */
	char	       *tag;		/* Tag component */
	char	       *text;		/* String for the text component */
	va_dcl				/* Vararg list dcl */
{
	/* Automatic data */
	int		errorflg;	/* FLAG:  True if error writing msg */
	va_list		argp;		/* Pointer into vararg list */

	errorflg = FALSE;

	/* Generate the error message */
	va_start(argp);
	if (text != MM_NULLTXT) errorflg = vsprintf(msgbuf, text, argp) > MM_MXTXTLN;
	(void) fmtmsg(MM_PRINT, fcnlbl, severity,
		      (text == MM_NULLTXT) ? MM_NULLTXT : msgbuf,
		      action, tag);
	va_end(argp);

	/*
	 *  If there was a buffer overflow generating the error message,
	 *  write a message and quit (things are probably corrupt in the
	 *  static data space now)
	 */

	if (errorflg) {
	    (void) fmtmsg(MM_PRINT, fcnlbl, MM_WARNING,
			  "Internal message buffer overflow",
			  MM_NULLACT, MM_NULLTAG);
	    exit(100);
	}
}
/* ARGSUSED */

/*
 *  These functions allocate space for the information we gather.
 *  It works by having a memory heap with strings allocated from
 *  the end of the heap and structures (aligned data) allocated
 *  from the beginning of the heap.  It begins with a 4k block of
 *  memory then allocates memory in 4k chunks.  These functions
 *  should never fail.  If they do, they report the problem and
 *  exit with an exit code of 101.
 *
 *  Functions contained:
 *	allocblk	Allocates a block of memory, aligned on a
 *			4-byte (double-word) boundary.
 *	allocstr	Allocates a block of memory with no particular
 *			alignment
 *
 *  Constant definitions:
 *	ALLOCBLKSZ	Size of a chunk of main memory allocated using
 *			malloc()
 *
 *  Static data:
 *	nextblkaddr	Address of the next available chunk of aligned
 *			space in the heap
 *	laststraddr	Address of the last chunk of unaligned space
 *			allocated from the heap
 *	toomuchspace	Message to write if someone attempts to allocate
 *			too much space (>ALLOCBLKSZ bytes)
 *	memallocdif	Message to write if there is a problem allocating
 *			main memory.
 */

#define	ALLOCBLKSZ	4096

static	char   *nextblkaddr = (char *) NULL;
static	char   *laststraddr = (char *) NULL;
static  char   *memallocdif = "Memory allocation difficulty.  Command terminates";
static	char   *toomuchspace = "Internal space allocation error.  Command terminates";

/*
 *  void *allocblk(size)
 *	unsigned int	size
 *
 *	This function allocates a block of aligned (4-byte or double-
 *	word boundary) memory from the program's heap.  It returns a
 *	pointer to that block of allocated memory.
 *
 *  Arguments:
 *	size		Minimum number of bytes to allocate (will
 *			round up to multiple of 4)
 *
 *  Returns:  void *
 *	Pointer to the allocated block of memory
 */

static void *
allocblk(size)
	unsigned int	size;
{
	/* Automatic data */
	char   *rtnval;


	/* Make sure the sizes are aligned correctly */
	if ((size = size + (4 - (size % 4))) > ALLOCBLKSZ) {
	    wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, toomuchspace);
	    exit(101);
	}

	/* Set up the value we're going to return */
	rtnval = nextblkaddr;

	/* Get the space we need off of the heap */
	if ((nextblkaddr += size) >= laststraddr) {
	    if ((rtnval = (char *) malloc(ALLOCBLKSZ)) == (char *) NULL) {
		wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, memallocdif);
		exit(101);
	    }
	    laststraddr = rtnval + ALLOCBLKSZ;
	    nextblkaddr = rtnval + size;
	}

	/* We're through */
	return((void *) rtnval);
}

/*
 *  char *allocstr(nbytes)
 *	unsigned int	nbytes
 *
 *	This function allocates a block of unaligned memory from the
 *	program's heap.  It returns a pointer to that block of allocated
 *	memory.
 *
 *  Arguments:
 *	nbytes		Number of bytes to allocate
 *
 *  Returns:  char *
 *	Pointer to the allocated block of memory
 */

static char *
allocstr(nchars)
	unsigned int	nchars;
{
	if (nchars > ALLOCBLKSZ) {
	    wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, toomuchspace);
	    exit(101);
	}
	if ((laststraddr -= nchars) < nextblkaddr) {
	    if ((nextblkaddr = (char *) malloc(ALLOCBLKSZ)) == (char *) NULL) {
		wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, memallocdif);
		exit(101);
	    }
	    laststraddr = nextblkaddr + ALLOCBLKSZ - nchars;
	}
	return(laststraddr);
}

/*
 *  These functions control the group membership list, as found in the
 *  /etc/group file.
 *
 *  Functions included:
 *	initmembers		Initialize the membership list (to NULL)
 *	addmember		Adds a member to the membership list
 *	isamember		Looks for a particular login-ID in the list
 *				of members
 *
 *  Datatype Definitions:
 *	struct grpmember	Describes a group member
 *
 *  Static Data:
 *	membershead		Pointer to the head of the list of group members
 */

struct	grpmember {
	char	               *membername;
	struct grpmember       *next;
};

static	struct grpmember       *membershead;

/*
 *  void initmembers()
 *
 *	This function initializes the list of members of specified groups.
 *
 *  Arguments:  None
 *
 *  Returns:  Void
 */

static void
initmembers()
{
	/* Set up the members list to be a null member's list */
	membershead = (struct grpmember *) NULL;
}

/*
 *  void addmember(p)
 *	char   *p
 *
 *	This function adds a member to the group member's list.  The
 *	group members list is a list of structures containing a pointer
 *	to the member-name and a pointer to the next item in the structure.
 *	The structure is not ordered in any particular way.
 *
 *  Arguments:
 *	p	Pointer to the member name
 *
 *  Returns:  Void
 */

static void
addmember(p)
	char   *p;
{
	/* Automatic data */
	struct grpmember       *new;	/* Member being added */

	new = (struct grpmember *) allocblk(sizeof(struct grpmember));
	new->membername = strcpy(allocstr((unsigned int) strlen(p)+1), p);
	new->next = membershead;
	membershead = new;
}

/*
 *  init isamember(p)
 *	char   *p
 *
 *	This function examines the list of group-members for the string
 *	referenced by 'p'.  If 'p' is a member of the members list, the
 *	function returns TRUE.  Otherwise it returns FALSE.
 *
 *  Arguments:
 *	p	Pointer to the name to search for.
 *
 *  Returns:  int
 *	TRUE	If 'p' is found in the members list,
 *	FALSE	otherwise
 */

static int
isamember(p)
	char   *p;
{
	/* Automatic Data */
	int			found;	/* FLAG:  TRUE if login found */
	struct grpmember       *pmem;	/* Pointer to group member */


	/* Search the membership list for the 'p' */
	found = FALSE;
	for (pmem = membershead ; !found && pmem ; pmem = pmem->next) {
	    if (strcmp(p, pmem->membername) == 0) found = TRUE;
	}

	return (found);
}

/*
 *  These functions handle the display list.  The display list contains
 *  all of the information we're to display.  The list contains a pointer
 *  to the login-name, a pointer to the free-field (comment), and a pointer
 *  to the next item in the list.  The list is ordered alphabetically
 *  (ascending) on the login-name field.  The list initially contains a
 *  dummy field (to make insertion easier) that contains a login-name of "".
 *
 *  Functions included:
 *	initdisp	Initializes the display list
 *	adddisp		Adds information to the display list
 *	genreport	Generates a report from the items in the display list
 *
 *  Datatypes Defined:
 *	struct display	Describes the structure that contains the
 *			information to be displayed.  Includes pointers
 *			to the login-ID, free-field (comment), and the
 *			next structure in the list.
 *
 *  Static Data:
 *	displayhead	Pointer to the head of the list of login-IDs to
 *			be displayed.  Initially references the null-item
 *			on the head of the list.
 */

struct	display {
	char	       *loginID;
	char	       *freefield;
	struct display *next;
};

static	struct display *displayhead;

/*
 *  void initdisp()
 *
 *	Initializes the display list.  An empty display list contains a
 *	single element, the dummy element.
 *
 *  Arguments:  None
 *
 *  Returns:  Void
 */

static void
initdisp()
{
	displayhead = (struct display *) allocblk(sizeof(struct display));
	displayhead->next = (struct display *) NULL;
	displayhead->loginID = "";
	displayhead->freefield = "";
}

/*
 *  void adddisp(pwent)
 *	struct passwd  *pwent
 *
 *	This function adds the appropriate information from the login
 *	description referenced by 'pwent' to the list if information
 *	to be displayed.  It only adds the information if the login-ID
 *	(user-name) is unique.  It inserts the information in the list
 *	in such a way that the list remains ordered alphabetically
 *	(ascending) according to the login-ID (user-name).
 *
 *  Arguments:
 *	pwent		Points to the (struct passwd) structure that
 *			contains all of the login information on the
 *			login being added to the list.  The only
 *			information that this function uses is the
 *			login-ID (user-name) and the free-field
 *			(comment field).
 *
 *  Returns:  Void
 */

static void
adddisp(pwent)
	struct passwd  *pwent;
{
	/* Automatic data */
	struct display	       *new;		/* Display item being added */
	struct display	       *prev;		/* Previous display item */
	struct display	       *current;	/* Next display item */
	int			found;		/* FLAG, insertion point found */
	int			compare = 1;	/* strcmp() compare value */


	/* Find where this value belongs in the list */
	prev = displayhead;
	current = displayhead->next;
	found = FALSE;
	while (!found && current) {
	    if ((compare = strcmp(current->loginID, pwent->pw_name)) >= 0)
		found = TRUE;
	    else {
		prev = current;
		current = current->next;
	    }
	}

	/* Insert this value in the list, only if it is unique though */
	if (compare != 0) {
	    /* Build a display structure containing the value to add to the list, and add to the list */
	    new = (struct display *) allocblk(sizeof(struct display));
	    new->loginID = strcpy(allocstr((unsigned int) strlen(pwent->pw_name)+1), pwent->pw_name);
	    if (pwent->pw_comment && pwent->pw_comment[0] != '\0')
		    new->freefield =
			    strcpy(allocstr((unsigned int)
					    strlen(pwent->pw_comment)+1),
				   pwent->pw_comment);
	    else
		    new->freefield =
			    strcpy(allocstr((unsigned int)
					    strlen(pwent->pw_gecos)+1),
				   pwent->pw_gecos);
	    new->next = current;
	    prev->next = new;
	}
}

/*
 *  void genreport()
 *
 *	This function generates a report on the standard output stream
 *	(stdout) containing the login-IDs and the free-fields of the
 *	logins that match the list criteria (-g and -l options)
 *
 *  Arguments:  None
 *
 *  Returns:  Void
 */

static void
genreport()
{

	/* Automatic data */
	struct display	       *current;	/* Value to display */
	int			i;		/* Counter of characters */

	/*
	 *  Initialization for loop.
	 *  (NOTE:  The first element in the list of logins to display
	 *  is a dummy element.)
	 */
	current = displayhead;

	/*
	 *  Display elements in the list
	 */
	for (current = displayhead->next ; current ; current = current->next) {
	    (void) fputs(current->loginID, stdout);
	    for (i = LOGINFIELDSZ - strlen(current->loginID) ; --i >= 0 ; (void) putc(' ', stdout)) ;
	    (void) fputs(current->freefield, stdout);
	    (void) putc('\n', stdout);
	}
}

/*
 * listusers [-l logins] [-g groups]
 *
 *	This command generates a list of user login-IDs.  Specific login-IDs
 *	can be listed, as can logins belonging in specific groups.
 *
 *	-l logins	specifies the login-IDs to display.  "logins" is a
 *			comma-list of login-IDs.
 *	-g groups	specifies the names of the groups to which a login-ID
 *			must belong before it is included in the generated list.
 *			"groups" is a comma-list of group names.
 * Exit Codes:
 *	0	All's well that ends well
 *	1	Usage error
 */

main(argc, argv)
	int	argc;
	char   *argv[];
{

	/* Automatic data */

	struct reqgrp	       *reqgrphead;	/* Head of the req'd group list */
	struct reqgrp	       *pgrp;		/* Current item in the req'd group list */
	struct reqgrp	       *qgrp;		/* Prev item in the req'd group list */
	struct reqgrp	       *rgrp;		/* Running ptr for scanning group list */
	struct reqlogin	       *reqloginhead;	/* Head of req'd login list */
	struct reqlogin	       *plogin;		/* Current item in the req'd login list */
	struct reqlogin	       *qlogin;		/* Previous item in the req'd login list */
	struct reqlogin	       *rlogin;		/* Running ptr for scanning login list */
	struct passwd	       *pwent;		/* Ptr to an /etc/passwd entry */
	struct group	       *grent;		/* Ptr to an /etc/group entry */
	char		       *token;		/* Ptr to a token extracted by strtok() */
	char		      **pp;		/* Ptr to a member of a group */
	char		       *g_arg;		/* Ptr to the -g option's argument */
	char		       *l_arg;		/* Ptr to the -l option's argument */
	int	 	 	g_seen;		/* FLAG, true if -g on cmd */
	int			l_seen;		/* FLAG, TRUE if -l is on the command line */
	int			errflg;		/* FLAG, TRUE if there is a command-line problem */
	int			done;		/* FLAG, TRUE if the process (?) is complete */
	int			groupcount;	/* Number of groups specified by the user */
	int			rc;		/* Return code from strcmp() */
	int			c;		/* Character returned from getopt() */


	/* Initializations */
	initmsg(argv[0]);

	/* Command-line processing */
	g_seen = FALSE;
	l_seen = FALSE;
	errflg = FALSE;
	opterr = 0;
	while (!errflg && ((c = getopt(argc, argv, "g:l:")) != EOF)) {

	    /* Case on the option character */
	    switch(c) {

	    case 'g':
		if (g_seen) errflg = TRUE;
		else {
		    g_seen = TRUE;
		    g_arg = optarg;
		}
		break;

	    case 'l':
		if (l_seen) errflg = TRUE;
		else {
		    l_seen = TRUE;
		    l_arg = optarg;
		}
		break;

	    default:
		errflg = TRUE;
	    }
	}

	/* Write out a usage message if necessary and quit */
	if (errflg || (optind != argc)) {
	    wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, USAGE_MSG);
	    exit(1);
	}


	/*
	 *  If the -g groups option was on the command line, build a
	 *  list containing groups we're to list logins for.
	 */
	if (g_seen) {

	    /* Begin with an empty list */
	    groupcount = 0;
	    reqgrphead = (struct reqgrp *) NULL;

	    /* Extract the first token putting an element on the list */
	    if ((token = strtok(g_arg, ",")) != (char *) NULL) {
		pgrp = (struct reqgrp *) allocblk(sizeof(struct reqgrp));
		pgrp->groupname = token;
		pgrp->found = FALSE;
		pgrp->next = (struct reqgrp *) NULL;
		groupcount++;
		reqgrphead = pgrp;
		qgrp = pgrp;

		/*
		 * Extract subsequent tokens (group names), avoiding duplicate
		 * names (note, list is NOT empty)
		 */
		while (token = strtok((char *) NULL, ",")) {

		    /* Check for duplication */
		    rgrp = reqgrphead;
		    while (rgrp && (rc = strcmp(token, rgrp->groupname))) rgrp = rgrp->next;
		    if (rc != 0) {

			/* Not a duplicate.  Add on the list */
			pgrp = (struct reqgrp *) allocblk(sizeof(struct reqgrp));
			pgrp->groupname = token;
			pgrp->found = FALSE;
			pgrp->next = (struct reqgrp *) NULL;
			groupcount++;
			qgrp->next = pgrp;
			qgrp = pgrp;
		    }
		}
	    }
	}

	/*
	 *  If -l logins is on the command line, build a list of logins
	 *  we're to generate reports for.
	 */
	if (l_seen) {

	    /* Begin with a null list */
	    reqloginhead = (struct reqlogin *) NULL;

	    /* Extract the first token from the argument to the -l option */
	    if (token = strtok(l_arg, ",")) {

		/* Put the first element in the list */
		plogin = (struct reqlogin *) allocblk(sizeof(struct reqlogin));
		plogin->loginname = token;
		plogin->found = FALSE;
		plogin->next = (struct reqlogin *) NULL;
		reqloginhead = plogin;
		qlogin = plogin;

		/*
		 * For each subsequent token in the -l argument's
		 * comma list ...
		 */

		while (token = strtok((char *) NULL, ",")) {

		    /* Check for duplication (list is not empty) */
		    rlogin = reqloginhead;
		    while (rlogin && (rc = strcmp(token, rlogin->loginname)))
			rlogin = rlogin->next;

		    /* If it's not a duplicate, add it to the list */
		    if (rc != 0) {
			plogin = (struct reqlogin *) allocblk(sizeof(struct reqlogin));
			plogin->loginname = token;
			plogin->found = FALSE;
			plogin->next = (struct reqlogin *) NULL;
			qlogin->next = plogin;
			qlogin = plogin;
		    }
		}
	    }
	}


	/*
	 *  If the user requested that only logins be listed in that belong
	 *  to certain groups, compile a list of logins that belong in that
	 *  group.  If the user also requested specific logins, that list
	 *  will be limited to those logins.
	 */

	/* Initialize the login list */
	initmembers();
	if (g_seen) {

	    /* For each group in the /etc/group file ... */
	    while (grent = getgrent()) {

		/* For each group mentioned with the -g option ... */
		for (pgrp = reqgrphead ; (groupcount > 0) && pgrp ; pgrp = pgrp->next) {

		    if (pgrp->found == FALSE) {

			/*
			 * If the mentioned group is found in the
			 * /etc/group file ...
			 */
			if (strcmp(grent->gr_name, pgrp->groupname) == 0) {

			    /* Mark the entry is found, remembering the
			     * group-ID for later */
			    pgrp->found = TRUE;
			    groupcount--;
			    pgrp->groupID = grent->gr_gid;
			    if (isausergroup(pgrp->groupID))
				for (pp = grent->gr_mem ; *pp ; pp++) addmember(*pp);
			}
		    }
		}
	    }

	    /* If any groups weren't found, write a message
	     * indicating such, then continue */
	    qgrp = (struct reqgrp *) NULL;
	    for (pgrp = reqgrphead ; pgrp ; pgrp = pgrp->next) {
		if (!pgrp->found) {
		    wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s was not found", pgrp->groupname);
		    if (!qgrp) reqgrphead = pgrp->next;
		    else qgrp->next = pgrp->next;
		}
		else if (isasystemgroup(pgrp->groupID)) {
		    wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s is not a user group", pgrp->groupname);
		    if (!qgrp) reqgrphead = pgrp->next;
		    else qgrp->next = pgrp->next;
		}
		else qgrp = pgrp;
	    }
	}


	/* Initialize the list of logins to display */
	initdisp();


	/*
	 *  Loop through the /etc/passwd file squirelling away the
	 *  information we need for the display.
	 */
	while (pwent = getpwent()) {

	    /* The login from /etc/passwd hasn't been included in
	     * the display yet */
	    done = FALSE;


	    /*
	     * If the login was explicitly requested, include it in
	     * the display if it is a user login
	     */

	    if (l_seen) {
		for (plogin = reqloginhead ; !done && plogin ; plogin = plogin->next) {
		    if (strcmp(pwent->pw_name, plogin->loginname) == 0) {
			plogin->found = TRUE;
			if (isauserlogin(pwent->pw_uid)) adddisp(pwent);
			else 
			    wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, 
				   "%s is not a user login", plogin->loginname);
			done = TRUE;
		    }
		}
	    }


	    /*
	     *  If the login-ID isn't already on the list, if its primary
	     *  group-ID is one of those groups requested, or it is a member
	     *  of the groups requested, include it in the display if it is
	     *  a user login (uid >= 100).
	     */

	    if (isauserlogin(pwent->pw_uid)) {

		if (!done && g_seen) {
		    for (pgrp = reqgrphead ; !done && pgrp ; pgrp = pgrp->next)
			if (pwent->pw_gid == pgrp->groupID) {
			    adddisp(pwent);
			    done = TRUE;
		    }
		    if (!done && isamember(pwent->pw_name)) {
			adddisp(pwent);
			done = TRUE;
		    }
		}


		/*
		 *  If neither -l nor -g is on the command-line and the login-ID
		 *  is a user login, include it in the display.
		 */

		if (!l_seen && !g_seen) adddisp(pwent);
	    }
	}

	/* Let the user know about logins they requested that don't exist */
	if (l_seen) for (plogin = reqloginhead ; plogin ; plogin = plogin->next)
	    if (!plogin->found)
		wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s was not found", plogin->loginname);


	/*
	 * Generate a report from this display items we've squirreled away
	 */
	genreport();

	/*
	 *  We're through!
	 */
	exit(0);

#ifdef	lint
	return(0);
#endif
}
