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