xref: /titanic_52/usr/src/cmd/users/users.c (revision 1a7c1b724419d3cb5fa6eea75123c6b2060ba31b)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 /*
34  * users.c
35  *
36  *	This file contains the source for the administrative command
37  *	"listusers" (available to the general user population) that
38  *	produces a report containing user login-IDs and their "free
39  *	field" (typically contains the user's name and other information).
40  */
41 
42 /*
43  *  Header files referenced:
44  *	sys/types.h	System type definitions
45  *	stdio.h		Definitions for standard I/O functions and constants
46  *	string.h	Definitions for string-handling functions
47  *	grp.h		Definitions for referencing the /etc/group file
48  *	pwd.h		Definitions for referencing the /etc/passwd file
49  *	varargs.h	Definitions for using a variable argument list
50  *	fmtmsg.h	Definitions for using the standard message formatting
51  *			facility
52  */
53 
54 #include	<sys/types.h>
55 #include	<stdio.h>
56 #include	<string.h>
57 #include	<string.h>
58 #include	<grp.h>
59 #include	<pwd.h>
60 #include	<stdarg.h>
61 #include	<fmtmsg.h>
62 
63 
64 /*
65  *  Externals referenced (and not defined by a header file):
66  *	malloc		Allocate memory from main memory
67  *	getopt		Extract the next option from the command line
68  *	optind		The argument count of the next option to extract from
69  *			the command line
70  *	optarg		A pointer to the argument of the option just extracted
71  *			from the command line
72  *	opterr		FLAG:  !0 tells getopt() to write an error message if
73  *			it detects an error
74  *	getpwent	Get next entry from the /etc/passwd file
75  *	getgrent	Get next entry from the /etc/group file
76  *	fmtmsg		Standard message generation facility
77  *	putenv		Modify the environment
78  *	exit		Exit the program
79  */
80 
81 extern	void	       *malloc();
82 extern	int		getopt();
83 extern	char	       *optarg;
84 extern	int		optind;
85 extern	int		opterr;
86 extern	struct passwd  *getpwent();
87 extern	struct group   *getgrent();
88 extern	int		fmtmsg();
89 extern	int		putenv();
90 extern	void		exit();
91 
92 /*
93  *  Local constant definitions
94  */
95 
96 #ifndef	FALSE
97 #define	FALSE			0
98 #endif
99 
100 #ifndef	TRUE
101 #define	TRUE			('t')
102 #endif
103 
104 #define	USAGE_MSG		"usage: listusers [-g groups] [-l logins]"
105 #define	MAXLOGINSIZE		14
106 #define	LOGINFIELDSZ		MAXLOGINSIZE+2
107 
108 #define	isauserlogin(uid)	(uid >= 100)
109 #define isasystemlogin(uid)	(uid < 100)
110 #define	isausergroup(gid)	(gid >= 100)
111 #define	isasystemgroup(gid)	(gid < 100)
112 
113 /*
114  *  Local datatype definitions
115  */
116 
117 /*
118  * This structure describes a specified group name
119  * (from the -g groups option)
120  */
121 
122 struct	reqgrp {
123 	char	       *groupname;
124 	struct reqgrp  *next;
125 	int		found;
126 	gid_t		groupID;
127 };
128 
129 /*
130  * This structure describes a specified login name
131  * (from the -l logins option)
132  */
133 
134 struct	reqlogin {
135 	char	        *loginname;
136 	struct reqlogin *next;
137 	int	 	 found;
138 };
139 
140 /*
141  *  These functions handle error and warning message writing.
142  *  (This deals with UNIX(r) standard message generation, so
143  *  the rest of the code doesn't have to.)
144  *
145  *  Functions included:
146  *	initmsg		Initialize the message handling functions.
147  *	wrtmsg		Write the message using the standard message
148  *			generation facility.
149  *
150  *  Static data included:
151  *	fcnlbl		The label for standard messages
152  *	msgbuf		A buffer to contain the edited message
153  */
154 
155 static	char	fcnlbl[MM_MXLABELLN+1];	/* Buffer for message label */
156 static	char	msgbuf[MM_MXTXTLN+1];	/* Buffer for message text */
157 
158 /*
159  * void initmsg(p)
160  *
161  *	This function initializes the message handling functions.
162  *
163  *  Arguments:
164  *	p	A pointer to a character string that is the name of the
165  *		command, used to generate the label on messages.  If this
166  *		string contains a slash ('/'), it only uses the characters
167  *		beyond the last slash in the string (this permits argv[0]
168  *		to be used).
169  *
170  *  Returns:  Void
171  */
172 
173 static void
174 initmsg(p)
175 	char   *p;		/* Ptr to command name */
176 {
177 	/* Automatic data */
178 	char   *q;		/* Local multi-use pointer */
179 
180 	/* Use only the simple filename if there is a slash in the name */
181 	if ((q = strrchr(p, '/')) == (char *) NULL) q = p;
182 	else q++;
183 
184 	/* Build the label for messages */
185 	(void) snprintf(fcnlbl, sizeof (fcnlbl), "UX:%s", q);
186 
187 	/*
188 	 * Now that we've done all of that work, set things up so that
189 	 * only the text-component of a message is printed.  (This piece
190 	 * of code will most probably go away in SVR4.1.
191 	 */
192 	(void) putenv("MSGVERB=text");
193 }
194 
195 /*
196  *  void wrtmsg(severity, action, tag, text[, txtarg1[, txtarg2[, ...]]])
197  *
198  *	This function writes a message using the standard message
199  * 	generation facility.
200  *
201  *  Arguments:
202  *	severity	The severity-component of the message
203  *	action		The action-string used to generate the action-
204  *			component of the message
205  *	tag		Tag-component of the message
206  *	text		The text-string used to generate the text-component
207  *			of the message
208  *	txtarg		Arguments to be inserted into the "text" string using
209  *			vsnprintf()
210  *
211  *  Returns:  Void
212  */
213 
214 /* VARARGS4 */
215 
216 static void
217 wrtmsg(int severity, char *action, char *tag, char *text, ...)
218 {
219 	/* Automatic data */
220 	int		errorflg;	/* FLAG:  True if error writing msg */
221 	va_list		argp;		/* Pointer into vararg list */
222 
223 	errorflg = FALSE;
224 
225 	/* Generate the error message */
226 	va_start(argp, text);
227 	if (text != MM_NULLTXT)
228 	{
229 		errorflg = vsnprintf(msgbuf, sizeof (msgbuf), text, argp) >
230 			 MM_MXTXTLN;
231 	}
232 	(void) fmtmsg(MM_PRINT, fcnlbl, severity,
233 		      (text == MM_NULLTXT) ? MM_NULLTXT : msgbuf,
234 		      action, tag);
235 	va_end(argp);
236 
237 	/*
238 	 *  If there would have been a buffer overflow generating the error
239 	 *  message, the message will be truncated, so write a message and quit.
240 	 */
241 
242 	if (errorflg) {
243 	    (void) fmtmsg(MM_PRINT, fcnlbl, MM_WARNING,
244 			  "Internal message buffer overflow",
245 			  MM_NULLACT, MM_NULLTAG);
246 	    exit(100);
247 	}
248 }
249 /* ARGSUSED */
250 
251 /*
252  *  These functions allocate space for the information we gather.
253  *  It works by having a memory heap with strings allocated from
254  *  the end of the heap and structures (aligned data) allocated
255  *  from the beginning of the heap.  It begins with a 4k block of
256  *  memory then allocates memory in 4k chunks.  These functions
257  *  should never fail.  If they do, they report the problem and
258  *  exit with an exit code of 101.
259  *
260  *  Functions contained:
261  *	allocblk	Allocates a block of memory, aligned on a
262  *			4-byte (double-word) boundary.
263  *	allocstr	Allocates a block of memory with no particular
264  *			alignment
265  *
266  *  Constant definitions:
267  *	ALLOCBLKSZ	Size of a chunk of main memory allocated using
268  *			malloc()
269  *
270  *  Static data:
271  *	nextblkaddr	Address of the next available chunk of aligned
272  *			space in the heap
273  *	laststraddr	Address of the last chunk of unaligned space
274  *			allocated from the heap
275  *	toomuchspace	Message to write if someone attempts to allocate
276  *			too much space (>ALLOCBLKSZ bytes)
277  *	memallocdif	Message to write if there is a problem allocating
278  *			main memory.
279  */
280 
281 #define	ALLOCBLKSZ	4096
282 
283 static	char   *nextblkaddr = (char *) NULL;
284 static	char   *laststraddr = (char *) NULL;
285 static  char   *memallocdif = "Memory allocation difficulty.  Command terminates";
286 static	char   *toomuchspace = "Internal space allocation error.  Command terminates";
287 
288 /*
289  *  void *allocblk(size)
290  *	unsigned int	size
291  *
292  *	This function allocates a block of aligned (4-byte or double-
293  *	word boundary) memory from the program's heap.  It returns a
294  *	pointer to that block of allocated memory.
295  *
296  *  Arguments:
297  *	size		Minimum number of bytes to allocate (will
298  *			round up to multiple of 4)
299  *
300  *  Returns:  void *
301  *	Pointer to the allocated block of memory
302  */
303 
304 static void *
305 allocblk(size)
306 	unsigned int	size;
307 {
308 	/* Automatic data */
309 	char   *rtnval;
310 
311 
312 	/* Make sure the sizes are aligned correctly */
313 	if ((size = size + (4 - (size % 4))) > ALLOCBLKSZ) {
314 	    wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, toomuchspace);
315 	    exit(101);
316 	}
317 
318 	/* Set up the value we're going to return */
319 	rtnval = nextblkaddr;
320 
321 	/* Get the space we need off of the heap */
322 	if ((nextblkaddr += size) >= laststraddr) {
323 	    if ((rtnval = (char *) malloc(ALLOCBLKSZ)) == (char *) NULL) {
324 		wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, memallocdif);
325 		exit(101);
326 	    }
327 	    laststraddr = rtnval + ALLOCBLKSZ;
328 	    nextblkaddr = rtnval + size;
329 	}
330 
331 	/* We're through */
332 	return((void *) rtnval);
333 }
334 
335 /*
336  *  char *allocstr(nbytes)
337  *	unsigned int	nbytes
338  *
339  *	This function allocates a block of unaligned memory from the
340  *	program's heap.  It returns a pointer to that block of allocated
341  *	memory.
342  *
343  *  Arguments:
344  *	nbytes		Number of bytes to allocate
345  *
346  *  Returns:  char *
347  *	Pointer to the allocated block of memory
348  */
349 
350 static char *
351 allocstr(nchars)
352 	unsigned int	nchars;
353 {
354 	if (nchars > ALLOCBLKSZ) {
355 	    wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, toomuchspace);
356 	    exit(101);
357 	}
358 	if ((laststraddr -= nchars) < nextblkaddr) {
359 	    if ((nextblkaddr = (char *) malloc(ALLOCBLKSZ)) == (char *) NULL) {
360 		wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, memallocdif);
361 		exit(101);
362 	    }
363 	    laststraddr = nextblkaddr + ALLOCBLKSZ - nchars;
364 	}
365 	return(laststraddr);
366 }
367 
368 /*
369  *  These functions control the group membership list, as found in the
370  *  /etc/group file.
371  *
372  *  Functions included:
373  *	initmembers		Initialize the membership list (to NULL)
374  *	addmember		Adds a member to the membership list
375  *	isamember		Looks for a particular login-ID in the list
376  *				of members
377  *
378  *  Datatype Definitions:
379  *	struct grpmember	Describes a group member
380  *
381  *  Static Data:
382  *	membershead		Pointer to the head of the list of group members
383  */
384 
385 struct	grpmember {
386 	char	               *membername;
387 	struct grpmember       *next;
388 };
389 
390 static	struct grpmember       *membershead;
391 
392 /*
393  *  void initmembers()
394  *
395  *	This function initializes the list of members of specified groups.
396  *
397  *  Arguments:  None
398  *
399  *  Returns:  Void
400  */
401 
402 static void
403 initmembers()
404 {
405 	/* Set up the members list to be a null member's list */
406 	membershead = (struct grpmember *) NULL;
407 }
408 
409 /*
410  *  void addmember(p)
411  *	char   *p
412  *
413  *	This function adds a member to the group member's list.  The
414  *	group members list is a list of structures containing a pointer
415  *	to the member-name and a pointer to the next item in the structure.
416  *	The structure is not ordered in any particular way.
417  *
418  *  Arguments:
419  *	p	Pointer to the member name
420  *
421  *  Returns:  Void
422  */
423 
424 static void
425 addmember(p)
426 	char   *p;
427 {
428 	/* Automatic data */
429 	struct grpmember       *new;	/* Member being added */
430 
431 	new = (struct grpmember *) allocblk(sizeof(struct grpmember));
432 	new->membername = strcpy(allocstr((unsigned int) strlen(p)+1), p);
433 	new->next = membershead;
434 	membershead = new;
435 }
436 
437 /*
438  *  init isamember(p)
439  *	char   *p
440  *
441  *	This function examines the list of group-members for the string
442  *	referenced by 'p'.  If 'p' is a member of the members list, the
443  *	function returns TRUE.  Otherwise it returns FALSE.
444  *
445  *  Arguments:
446  *	p	Pointer to the name to search for.
447  *
448  *  Returns:  int
449  *	TRUE	If 'p' is found in the members list,
450  *	FALSE	otherwise
451  */
452 
453 static int
454 isamember(p)
455 	char   *p;
456 {
457 	/* Automatic Data */
458 	int			found;	/* FLAG:  TRUE if login found */
459 	struct grpmember       *pmem;	/* Pointer to group member */
460 
461 
462 	/* Search the membership list for the 'p' */
463 	found = FALSE;
464 	for (pmem = membershead ; !found && pmem ; pmem = pmem->next) {
465 	    if (strcmp(p, pmem->membername) == 0) found = TRUE;
466 	}
467 
468 	return (found);
469 }
470 
471 /*
472  *  These functions handle the display list.  The display list contains
473  *  all of the information we're to display.  The list contains a pointer
474  *  to the login-name, a pointer to the free-field (comment), and a pointer
475  *  to the next item in the list.  The list is ordered alphabetically
476  *  (ascending) on the login-name field.  The list initially contains a
477  *  dummy field (to make insertion easier) that contains a login-name of "".
478  *
479  *  Functions included:
480  *	initdisp	Initializes the display list
481  *	adddisp		Adds information to the display list
482  *	genreport	Generates a report from the items in the display list
483  *
484  *  Datatypes Defined:
485  *	struct display	Describes the structure that contains the
486  *			information to be displayed.  Includes pointers
487  *			to the login-ID, free-field (comment), and the
488  *			next structure in the list.
489  *
490  *  Static Data:
491  *	displayhead	Pointer to the head of the list of login-IDs to
492  *			be displayed.  Initially references the null-item
493  *			on the head of the list.
494  */
495 
496 struct	display {
497 	char	       *loginID;
498 	char	       *freefield;
499 	struct display *next;
500 };
501 
502 static	struct display *displayhead;
503 
504 /*
505  *  void initdisp()
506  *
507  *	Initializes the display list.  An empty display list contains a
508  *	single element, the dummy element.
509  *
510  *  Arguments:  None
511  *
512  *  Returns:  Void
513  */
514 
515 static void
516 initdisp()
517 {
518 	displayhead = (struct display *) allocblk(sizeof(struct display));
519 	displayhead->next = (struct display *) NULL;
520 	displayhead->loginID = "";
521 	displayhead->freefield = "";
522 }
523 
524 /*
525  *  void adddisp(pwent)
526  *	struct passwd  *pwent
527  *
528  *	This function adds the appropriate information from the login
529  *	description referenced by 'pwent' to the list if information
530  *	to be displayed.  It only adds the information if the login-ID
531  *	(user-name) is unique.  It inserts the information in the list
532  *	in such a way that the list remains ordered alphabetically
533  *	(ascending) according to the login-ID (user-name).
534  *
535  *  Arguments:
536  *	pwent		Points to the (struct passwd) structure that
537  *			contains all of the login information on the
538  *			login being added to the list.  The only
539  *			information that this function uses is the
540  *			login-ID (user-name) and the free-field
541  *			(comment field).
542  *
543  *  Returns:  Void
544  */
545 
546 static void
547 adddisp(pwent)
548 	struct passwd  *pwent;
549 {
550 	/* Automatic data */
551 	struct display	       *new;		/* Display item being added */
552 	struct display	       *prev;		/* Previous display item */
553 	struct display	       *current;	/* Next display item */
554 	int			found;		/* FLAG, insertion point found */
555 	int			compare = 1;	/* strcmp() compare value */
556 
557 
558 	/* Find where this value belongs in the list */
559 	prev = displayhead;
560 	current = displayhead->next;
561 	found = FALSE;
562 	while (!found && current) {
563 	    if ((compare = strcmp(current->loginID, pwent->pw_name)) >= 0)
564 		found = TRUE;
565 	    else {
566 		prev = current;
567 		current = current->next;
568 	    }
569 	}
570 
571 	/* Insert this value in the list, only if it is unique though */
572 	if (compare != 0) {
573 	    /* Build a display structure containing the value to add to the list, and add to the list */
574 	    new = (struct display *) allocblk(sizeof(struct display));
575 	    new->loginID = strcpy(allocstr((unsigned int) strlen(pwent->pw_name)+1), pwent->pw_name);
576 	    if (pwent->pw_comment && pwent->pw_comment[0] != '\0')
577 		    new->freefield =
578 			    strcpy(allocstr((unsigned int)
579 					    strlen(pwent->pw_comment)+1),
580 				   pwent->pw_comment);
581 	    else
582 		    new->freefield =
583 			    strcpy(allocstr((unsigned int)
584 					    strlen(pwent->pw_gecos)+1),
585 				   pwent->pw_gecos);
586 	    new->next = current;
587 	    prev->next = new;
588 	}
589 }
590 
591 /*
592  *  void genreport()
593  *
594  *	This function generates a report on the standard output stream
595  *	(stdout) containing the login-IDs and the free-fields of the
596  *	logins that match the list criteria (-g and -l options)
597  *
598  *  Arguments:  None
599  *
600  *  Returns:  Void
601  */
602 
603 static void
604 genreport()
605 {
606 
607 	/* Automatic data */
608 	struct display	       *current;	/* Value to display */
609 	int			i;		/* Counter of characters */
610 
611 	/*
612 	 *  Initialization for loop.
613 	 *  (NOTE:  The first element in the list of logins to display
614 	 *  is a dummy element.)
615 	 */
616 	current = displayhead;
617 
618 	/*
619 	 *  Display elements in the list
620 	 */
621 	for (current = displayhead->next ; current ; current = current->next) {
622 	    (void) fputs(current->loginID, stdout);
623 	    for (i = LOGINFIELDSZ - strlen(current->loginID) ; --i >= 0 ; (void) putc(' ', stdout)) ;
624 	    (void) fputs(current->freefield, stdout);
625 	    (void) putc('\n', stdout);
626 	}
627 }
628 
629 /*
630  * listusers [-l logins] [-g groups]
631  *
632  *	This command generates a list of user login-IDs.  Specific login-IDs
633  *	can be listed, as can logins belonging in specific groups.
634  *
635  *	-l logins	specifies the login-IDs to display.  "logins" is a
636  *			comma-list of login-IDs.
637  *	-g groups	specifies the names of the groups to which a login-ID
638  *			must belong before it is included in the generated list.
639  *			"groups" is a comma-list of group names.
640  * Exit Codes:
641  *	0	All's well that ends well
642  *	1	Usage error
643  */
644 
645 main(argc, argv)
646 	int	argc;
647 	char   *argv[];
648 {
649 
650 	/* Automatic data */
651 
652 	struct reqgrp	       *reqgrphead;	/* Head of the req'd group list */
653 	struct reqgrp	       *pgrp;		/* Current item in the req'd group list */
654 	struct reqgrp	       *qgrp;		/* Prev item in the req'd group list */
655 	struct reqgrp	       *rgrp;		/* Running ptr for scanning group list */
656 	struct reqlogin	       *reqloginhead;	/* Head of req'd login list */
657 	struct reqlogin	       *plogin;		/* Current item in the req'd login list */
658 	struct reqlogin	       *qlogin;		/* Previous item in the req'd login list */
659 	struct reqlogin	       *rlogin;		/* Running ptr for scanning login list */
660 	struct passwd	       *pwent;		/* Ptr to an /etc/passwd entry */
661 	struct group	       *grent;		/* Ptr to an /etc/group entry */
662 	char		       *token;		/* Ptr to a token extracted by strtok() */
663 	char		      **pp;		/* Ptr to a member of a group */
664 	char		       *g_arg;		/* Ptr to the -g option's argument */
665 	char		       *l_arg;		/* Ptr to the -l option's argument */
666 	int	 	 	g_seen;		/* FLAG, true if -g on cmd */
667 	int			l_seen;		/* FLAG, TRUE if -l is on the command line */
668 	int			errflg;		/* FLAG, TRUE if there is a command-line problem */
669 	int			done;		/* FLAG, TRUE if the process (?) is complete */
670 	int			groupcount;	/* Number of groups specified by the user */
671 	int			rc;		/* Return code from strcmp() */
672 	int			c;		/* Character returned from getopt() */
673 
674 
675 	/* Initializations */
676 	initmsg(argv[0]);
677 
678 	/* Command-line processing */
679 	g_seen = FALSE;
680 	l_seen = FALSE;
681 	errflg = FALSE;
682 	opterr = 0;
683 	while (!errflg && ((c = getopt(argc, argv, "g:l:")) != EOF)) {
684 
685 	    /* Case on the option character */
686 	    switch(c) {
687 
688 	    case 'g':
689 		if (g_seen) errflg = TRUE;
690 		else {
691 		    g_seen = TRUE;
692 		    g_arg = optarg;
693 		}
694 		break;
695 
696 	    case 'l':
697 		if (l_seen) errflg = TRUE;
698 		else {
699 		    l_seen = TRUE;
700 		    l_arg = optarg;
701 		}
702 		break;
703 
704 	    default:
705 		errflg = TRUE;
706 	    }
707 	}
708 
709 	/* Write out a usage message if necessary and quit */
710 	if (errflg || (optind != argc)) {
711 	    wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, USAGE_MSG);
712 	    exit(1);
713 	}
714 
715 
716 	/*
717 	 *  If the -g groups option was on the command line, build a
718 	 *  list containing groups we're to list logins for.
719 	 */
720 	if (g_seen) {
721 
722 	    /* Begin with an empty list */
723 	    groupcount = 0;
724 	    reqgrphead = (struct reqgrp *) NULL;
725 
726 	    /* Extract the first token putting an element on the list */
727 	    if ((token = strtok(g_arg, ",")) != (char *) NULL) {
728 		pgrp = (struct reqgrp *) allocblk(sizeof(struct reqgrp));
729 		pgrp->groupname = token;
730 		pgrp->found = FALSE;
731 		pgrp->next = (struct reqgrp *) NULL;
732 		groupcount++;
733 		reqgrphead = pgrp;
734 		qgrp = pgrp;
735 
736 		/*
737 		 * Extract subsequent tokens (group names), avoiding duplicate
738 		 * names (note, list is NOT empty)
739 		 */
740 		while (token = strtok((char *) NULL, ",")) {
741 
742 		    /* Check for duplication */
743 		    rgrp = reqgrphead;
744 		    while (rgrp && (rc = strcmp(token, rgrp->groupname))) rgrp = rgrp->next;
745 		    if (rc != 0) {
746 
747 			/* Not a duplicate.  Add on the list */
748 			pgrp = (struct reqgrp *) allocblk(sizeof(struct reqgrp));
749 			pgrp->groupname = token;
750 			pgrp->found = FALSE;
751 			pgrp->next = (struct reqgrp *) NULL;
752 			groupcount++;
753 			qgrp->next = pgrp;
754 			qgrp = pgrp;
755 		    }
756 		}
757 	    }
758 	}
759 
760 	/*
761 	 *  If -l logins is on the command line, build a list of logins
762 	 *  we're to generate reports for.
763 	 */
764 	if (l_seen) {
765 
766 	    /* Begin with a null list */
767 	    reqloginhead = (struct reqlogin *) NULL;
768 
769 	    /* Extract the first token from the argument to the -l option */
770 	    if (token = strtok(l_arg, ",")) {
771 
772 		/* Put the first element in the list */
773 		plogin = (struct reqlogin *) allocblk(sizeof(struct reqlogin));
774 		plogin->loginname = token;
775 		plogin->found = FALSE;
776 		plogin->next = (struct reqlogin *) NULL;
777 		reqloginhead = plogin;
778 		qlogin = plogin;
779 
780 		/*
781 		 * For each subsequent token in the -l argument's
782 		 * comma list ...
783 		 */
784 
785 		while (token = strtok((char *) NULL, ",")) {
786 
787 		    /* Check for duplication (list is not empty) */
788 		    rlogin = reqloginhead;
789 		    while (rlogin && (rc = strcmp(token, rlogin->loginname)))
790 			rlogin = rlogin->next;
791 
792 		    /* If it's not a duplicate, add it to the list */
793 		    if (rc != 0) {
794 			plogin = (struct reqlogin *) allocblk(sizeof(struct reqlogin));
795 			plogin->loginname = token;
796 			plogin->found = FALSE;
797 			plogin->next = (struct reqlogin *) NULL;
798 			qlogin->next = plogin;
799 			qlogin = plogin;
800 		    }
801 		}
802 	    }
803 	}
804 
805 
806 	/*
807 	 *  If the user requested that only logins be listed in that belong
808 	 *  to certain groups, compile a list of logins that belong in that
809 	 *  group.  If the user also requested specific logins, that list
810 	 *  will be limited to those logins.
811 	 */
812 
813 	/* Initialize the login list */
814 	initmembers();
815 	if (g_seen) {
816 
817 	    /* For each group in the /etc/group file ... */
818 	    while (grent = getgrent()) {
819 
820 		/* For each group mentioned with the -g option ... */
821 		for (pgrp = reqgrphead ; (groupcount > 0) && pgrp ; pgrp = pgrp->next) {
822 
823 		    if (pgrp->found == FALSE) {
824 
825 			/*
826 			 * If the mentioned group is found in the
827 			 * /etc/group file ...
828 			 */
829 			if (strcmp(grent->gr_name, pgrp->groupname) == 0) {
830 
831 			    /* Mark the entry is found, remembering the
832 			     * group-ID for later */
833 			    pgrp->found = TRUE;
834 			    groupcount--;
835 			    pgrp->groupID = grent->gr_gid;
836 			    if (isausergroup(pgrp->groupID))
837 				for (pp = grent->gr_mem ; *pp ; pp++) addmember(*pp);
838 			}
839 		    }
840 		}
841 	    }
842 
843 	    /* If any groups weren't found, write a message
844 	     * indicating such, then continue */
845 	    qgrp = (struct reqgrp *) NULL;
846 	    for (pgrp = reqgrphead ; pgrp ; pgrp = pgrp->next) {
847 		if (!pgrp->found) {
848 		    wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s was not found", pgrp->groupname);
849 		    if (!qgrp) reqgrphead = pgrp->next;
850 		    else qgrp->next = pgrp->next;
851 		}
852 		else if (isasystemgroup(pgrp->groupID)) {
853 		    wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s is not a user group", pgrp->groupname);
854 		    if (!qgrp) reqgrphead = pgrp->next;
855 		    else qgrp->next = pgrp->next;
856 		}
857 		else qgrp = pgrp;
858 	    }
859 	}
860 
861 
862 	/* Initialize the list of logins to display */
863 	initdisp();
864 
865 
866 	/*
867 	 *  Loop through the /etc/passwd file squirelling away the
868 	 *  information we need for the display.
869 	 */
870 	while (pwent = getpwent()) {
871 
872 	    /* The login from /etc/passwd hasn't been included in
873 	     * the display yet */
874 	    done = FALSE;
875 
876 
877 	    /*
878 	     * If the login was explicitly requested, include it in
879 	     * the display if it is a user login
880 	     */
881 
882 	    if (l_seen) {
883 		for (plogin = reqloginhead ; !done && plogin ; plogin = plogin->next) {
884 		    if (strcmp(pwent->pw_name, plogin->loginname) == 0) {
885 			plogin->found = TRUE;
886 			if (isauserlogin(pwent->pw_uid)) adddisp(pwent);
887 			else
888 			    wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG,
889 				   "%s is not a user login", plogin->loginname);
890 			done = TRUE;
891 		    }
892 		}
893 	    }
894 
895 
896 	    /*
897 	     *  If the login-ID isn't already on the list, if its primary
898 	     *  group-ID is one of those groups requested, or it is a member
899 	     *  of the groups requested, include it in the display if it is
900 	     *  a user login (uid >= 100).
901 	     */
902 
903 	    if (isauserlogin(pwent->pw_uid)) {
904 
905 		if (!done && g_seen) {
906 		    for (pgrp = reqgrphead ; !done && pgrp ; pgrp = pgrp->next)
907 			if (pwent->pw_gid == pgrp->groupID) {
908 			    adddisp(pwent);
909 			    done = TRUE;
910 		    }
911 		    if (!done && isamember(pwent->pw_name)) {
912 			adddisp(pwent);
913 			done = TRUE;
914 		    }
915 		}
916 
917 
918 		/*
919 		 *  If neither -l nor -g is on the command-line and the login-ID
920 		 *  is a user login, include it in the display.
921 		 */
922 
923 		if (!l_seen && !g_seen) adddisp(pwent);
924 	    }
925 	}
926 
927 	/* Let the user know about logins they requested that don't exist */
928 	if (l_seen) for (plogin = reqloginhead ; plogin ; plogin = plogin->next)
929 	    if (!plogin->found)
930 		wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s was not found", plogin->loginname);
931 
932 
933 	/*
934 	 * Generate a report from this display items we've squirreled away
935 	 */
936 	genreport();
937 
938 	/*
939 	 *  We're through!
940 	 */
941 	exit(0);
942 
943 #ifdef	lint
944 	return(0);
945 #endif
946 }
947