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