xref: /illumos-gate/usr/src/cmd/logins/logins.c (revision 55381082)
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  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"	/* SVr4.0 1.15.1.2 */
31 
32 /*
33  * logins.c
34  *
35  *	This file contains the source for the administrative command
36  *	"logins" (available to the administrator) that produces a report
37  *	containing login-IDs and other requested information.
38  */
39 
40 #include <sys/types.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <string.h>
45 #include <ctype.h>
46 #include <grp.h>
47 #include <pwd.h>
48 #include <shadow.h>
49 #include <time.h>
50 #include <stdarg.h>
51 #include <fmtmsg.h>
52 #include <locale.h>
53 
54 /*
55  *  Local constant definitions
56  *	TRUE			Boolean constant
57  *	FALSE			Boolean constant
58  *	USAGE_MSG		Message used to display a usage error
59  *	MAXLOGINSIZE		Maximum length of a valid login-ID
60  *	MAXSYSTEMLOGIN		Maximum value of a system user-ID.
61  *	OPTSTR			Options to this command
62  *	ROOT_ID			The user-ID of an administrator
63  */
64 
65 #ifndef	FALSE
66 #define	FALSE			0
67 #endif
68 
69 #ifndef	TRUE
70 #define	TRUE			((int)'t')
71 #endif
72 
73 #define	USAGE_MSG	"usage: logins [-admopstux] [-g groups] [-l logins]"
74 #define	MAXLOGINSIZE	14
75 #define	MAXSYSTEMLOGIN	99
76 #define	OPTSTR		"adg:l:mopstux"
77 #define	ROOT_ID		0
78 
79 /*
80  *  The following macros do their function for now but will probably have
81  *  to be replaced by functions sometime in the near future.  The maximum
82  *  system login value may someday be administerable, in which case these
83  *  will have to be changed to become functions
84  *
85  *	isasystemlogin	Returns TRUE if the user-ID in the "struct passwd"
86  *			structure referenced by the function's argument is
87  *			less than or equal to the maximum value for a system
88  *			user-ID, FALSE otherwise.
89  *	isauserlogin	Returns TRUE if the user-ID in the "struct passwd"
90  *			structure referenced by the function's argument is
91  *			greater than the maximum value for a system user-ID,
92  *			FALSE otherwise.
93  */
94 
95 #define	isauserlogin(pw)	(pw->pw_uid > MAXSYSTEMLOGIN)
96 #define	isasystemlogin(pw)	(pw->pw_uid <= MAXSYSTEMLOGIN)
97 
98 
99 /*
100  *  Local datatype definitions
101  *	struct reqgrp		Describes a group as requested through the
102  *				-g option
103  *	struct reqlogin		Describes a login-ID as requested through
104  *				the -l option
105  *	struct pwdinfo		Describes a password's aging information,
106  *				as extracted from /etc/shadow
107  *	struct secgrp		Describes a login-ID's secondary group
108  */
109 
110 /*  Describes a specified group name (from the -g groups option)  */
111 struct	reqgrp {
112 	char		*groupname;	/* Requested group name */
113 	struct reqgrp	*next;		/* Next item in the list */
114 	gid_t		groupID;	/* Group's ID */
115 };
116 
117 /*  Describes a specified login name (from the -l logins option)  */
118 struct	reqlogin {
119 	char		*loginname;	/* Requested login name */
120 	struct reqlogin	*next;		/* Next item in the list */
121 	int		found;		/* TRUE if login in /etc/passwd */
122 };
123 
124 /*
125  * This structure describes a password's information
126  */
127 
128 struct	pwdinfo {
129 	long	datechg;	/* Date the password was changed (mmddyy) */
130 	char	*passwdstatus;	/* Password status */
131 	long	mindaystilchg;	/* Min days b4 pwd can change again */
132 	long	maxdaystilchg;	/* Max days b4 pwd can change again */
133 	long	warninterval;	/* Days before expire to warn user */
134 	long	inactive;	/* Lapsed days of inactivity before lock */
135 	long	expdate;	/* Date of expiration (mmddyy) */
136 };
137 
138 /* This structure describes secondary groups that a user belongs to */
139 struct	secgrp {
140 	char		*groupname;	/* Name of the group */
141 	struct secgrp	*next;		/* Next item in the list */
142 	gid_t		groupID;	/* Group-ID */
143 };
144 
145 
146 /*
147  *  These functions handle error and warning message writing.
148  *  (This deals with UNIX(r) standard message generation, so
149  *  the rest of the code doesn't have to.)
150  *
151  *  Functions included:
152  *	initmsg		Initialize the message handling functions.
153  *	wrtmsg		Write the message using fmtmsg().
154  *
155  *  Static data included:
156  *	fcnlbl		The label for standard messages
157  *	msgbuf		A buffer to contain the edited message
158  */
159 
160 static	char	fcnlbl[MM_MXLABELLN+1];	/* Buffer for message label */
161 static	char	msgbuf[MM_MXTXTLN+1];	/* Buffer for message text */
162 
163 
164 /*
165  * void initmsg(p)
166  *
167  *	This function initializes the message handling functions.
168  *
169  *  Arguments:
170  *	p	A pointer to a character string that is the name of the
171  *		function, used to generate the label on messages.  If this
172  *		string contains a slash ('/'), it only uses the characters
173  *		beyond the last slash in the string (this permits argv[0]
174  *		to be used).
175  *
176  *  Returns:  Void
177  */
178 
179 static void
180 initmsg(char *p)
181 {
182 	char   *q;	/* Local multi-use pointer */
183 
184 	/* Use only the simple filename if there is a slash in the name */
185 	if (!(q = strrchr(p, '/'))) {
186 		q = p;
187 	} else {
188 		q++;
189 	}
190 
191 	/* Build the label for messages */
192 	(void) snprintf(fcnlbl, MM_MXLABELLN, "UX:%s", q);
193 
194 	/* Restrict messages to the text-component */
195 	(void) putenv("MSGVERB=text");
196 }
197 
198 
199 /*
200  *  void wrtmsg(severity, action, tag, text[, txtarg1[, txtarg2[, ...]]])
201  *
202  *	This function writes a message using fmtmsg()
203  *
204  *  Arguments:
205  *	severity	The severity-component of the message
206  *	action		The action-string used to generate the
207  *			action-component of the message
208  *	tag		Tag-component of the message
209  *	text		The text-string used to generate the text-
210  *			component of the message
211  *	txtarg		Arguments to be inserted into the "text"
212  *			string using vsprintf()
213  *
214  *  Returns:  Void
215  */
216 /*PRINTFLIKE4*/
217 static void
218 wrtmsg(int severity, char *action, char *tag, char *text, ...)
219 {
220 	int	errorflg;	/* TRUE if problem generating message */
221 	va_list	argp;		/* Pointer into vararg list */
222 
223 
224 	/* No problems yet */
225 	errorflg = FALSE;
226 
227 	/* Generate the error message */
228 	va_start(argp, text);
229 	if (text != MM_NULLTXT) {
230 		errorflg = vsnprintf(msgbuf,
231 		    MM_MXTXTLN, text, argp) > MM_MXTXTLN;
232 	}
233 	(void) fmtmsg(MM_PRINT, fcnlbl, severity,
234 	    (text == MM_NULLTXT) ? MM_NULLTXT : msgbuf, action, tag);
235 	va_end(argp);
236 
237 	/*
238 	 *  If there was a buffer overflow generating the error message,
239 	 *  write a message and quit (things are probably corrupt in the
240 	 *  static data space now
241 	 */
242 	if (errorflg) {
243 		(void) fmtmsg(MM_PRINT, fcnlbl, MM_WARNING,
244 		    gettext("Internal message buffer overflow"),
245 		    MM_NULLACT, MM_NULLTAG);
246 		exit(100);
247 	}
248 }
249 
250 /*
251  *  These functions control the group membership list, as found in
252  *  the /etc/group file.
253  *
254  *  Functions included:
255  *	addmember		Adds a member to the membership list
256  *	isamember		Looks for a particular login-ID in the
257  *				list of members
258  *
259  *  Datatype Definitions:
260  *	struct grpmember	Describes a group member
261  *
262  *  Static Data:
263  *	membershead		Pointer to the head of the list of
264  *				group members
265  */
266 
267 struct	grpmember {
268 	char			*membername;
269 	struct grpmember	*next;
270 };
271 
272 static	struct grpmember	*membershead;
273 
274 /*
275  *  void addmember(p)
276  *	char   *p
277  *
278  *	This function adds a member to the group member's list.  The
279  *	group members list is a list of structures containing a pointer
280  *	to the member-name and a pointer to the next item in the
281  *	structure.  The structure is not ordered in any particular way.
282  *
283  *  Arguments:
284  *	p	Pointer to the member name
285  *
286  *  Returns:  Void
287  */
288 
289 static void
290 addmember(char *p)
291 {
292 	struct grpmember	*new;	/* Member being added */
293 
294 	new = malloc(sizeof (struct grpmember));
295 	new->membername = strdup(p);
296 	new->next = membershead;
297 	membershead = new;
298 }
299 
300 
301 /*
302  *  init isamember(p)
303  *	char   *p
304  *
305  *	This function examines the list of group-members for the string
306  *	referenced by 'p'.  If 'p' is a member of the members list, the
307  *	function returns TRUE.  Otherwise it returns FALSE.
308  *
309  *  Arguments:
310  *	p	Pointer to the name to search for.
311  *
312  *  Returns:  int
313  *	TRUE	If 'p' is found in the members list,
314  *	FALSE	otherwise
315  */
316 
317 static int
318 isamember(char *p)
319 {
320 	int			found;	/* TRUE if login found in list */
321 	struct grpmember	*pmem;	/* Group member being examined */
322 
323 
324 	/* Search the membership list for 'p' */
325 	found = FALSE;
326 	for (pmem = membershead; !found && pmem; pmem = pmem->next) {
327 		if (strcmp(p, pmem->membername) == 0)
328 			found = TRUE;
329 	}
330 
331 	return (found);
332 }
333 
334 
335 /*
336  *  These functions handle the display list.  The display list contains
337  *  all of the information we're to display.  The list contains a pointer
338  *  to the login-name, a pointer to the free-field (comment), and a
339  *  pointer to the next item in the list.  The list is ordered alpha-
340  *  betically (ascending) on the login-name field.  The list initially
341  *  contains a dummy field (to make insertion easier) that contains a
342  *  login-name of "".
343  *
344  *  Functions included:
345  *	initdisp	Initializes the display list
346  *	adddisp		Adds information to the display list
347  *	isuidindisp	Looks to see if a particular user-ID is in the
348  *			display list
349  *	genreport	Generates a report from the items in the display
350  *			list
351  *	applygroup	Add group information to the items in the display
352  *			list
353  *	applypasswd	Add extended password information to the items
354  *			in the display list
355  *
356  *  Datatypes Defined:
357  *	struct display	Describes the structure that contains the information
358  *			to be displayed.  Includes pointers to the login-ID,
359  *			free-field (comment), and the next structure in the
360  *			list.
361  *
362  *  Static Data:
363  *	displayhead	Pointer to the head of the display list.  Initially
364  *			references the null-item on the head of the list.
365  */
366 
367 struct	display {
368 	char		*loginID;	/* Login name */
369 	char		*freefield;	/* Free (comment) field */
370 	char		*groupname;	/* Name of the primary group */
371 	char		*iwd;		/* Initial working directory */
372 	char		*shell;		/* Shell after login (may be null) */
373 	struct pwdinfo	*passwdinfo;	/* Password information structure */
374 	struct secgrp 	*secgrplist; 	/* Head of the secondary group list */
375 	uid_t		userID;		/* User ID */
376 	gid_t		groupID;	/* Group ID of primary group */
377 	struct display	*nextlogin;	/* Next login in the list */
378 	struct display	*nextuid;	/* Next user-ID in the list */
379 };
380 
381 static	struct display	*displayhead;
382 
383 
384 /*
385  *  void initdisp()
386  *
387  *	Initializes the display list.  An empty display list contains
388  *	a single element, the dummy element.
389  *
390  *  Arguments:  None
391  *
392  *  Returns:  Void
393  */
394 
395 static void
396 initdisp(void)
397 {
398 	displayhead = malloc(sizeof (struct display));
399 	displayhead->nextlogin = NULL;
400 	displayhead->nextuid = NULL;
401 	displayhead->loginID = "";
402 	displayhead->freefield = "";
403 	displayhead->userID = -1;
404 }
405 
406 
407 /*
408  *  void adddisp(pwent)
409  *	struct passwd  *pwent
410  *
411  *	This function adds the appropriate information from the login
412  *	description referenced by 'pwent' to the list if information
413  *	to be displayed.  It only adds the information if the login-ID
414  *	(user-name) is unique.  It inserts the information in the list
415  *	in such a way that the list remains ordered alphabetically
416  *	(ascending) according to the login-ID (user-name).
417  *
418  *  Arguments:
419  *	pwent		Structure that contains all of the login information
420  *			of the login being added to the list.  The only
421  *			information that this function uses is the login-ID
422  *			(user-name) and the free-field (comment field).
423  *
424  *  Returns:  Void
425  */
426 
427 static void
428 adddisp(struct passwd *pwent)
429 {
430 	struct display *new;		/* Item being added to the list */
431 	struct display *prev;		/* Previous item in the list */
432 	struct display *current;	/* Next item in the list */
433 	int		found;		/* FLAG, insertion point found */
434 	int		compare = 1;	/* strcmp() compare value */
435 
436 
437 	/* Find where this value belongs in the list */
438 	prev = displayhead;
439 	found = FALSE;
440 	while (!found && (current = prev->nextlogin)) {
441 		if ((compare = strcmp(current->loginID, pwent->pw_name)) >= 0) {
442 			found = TRUE;
443 		} else {
444 			prev = current;
445 		}
446 
447 	}
448 	/* Insert this value in the list, only if it is unique though */
449 	if (compare != 0) {
450 		new = malloc(sizeof (struct display));
451 		new->loginID = strdup(pwent->pw_name);
452 		if (pwent->pw_comment && pwent->pw_comment[0] != '\0') {
453 			new->freefield = strdup(pwent->pw_comment);
454 		} else {
455 		    new->freefield = strdup(pwent->pw_gecos);
456 		}
457 		if (!pwent->pw_shell || !(*pwent->pw_shell)) {
458 			new->shell = "/sbin/sh";
459 		} else {
460 			new->shell = strdup(pwent->pw_shell);
461 		}
462 		new->iwd = strdup(pwent->pw_dir);
463 		new->userID = pwent->pw_uid;
464 		new->groupID = pwent->pw_gid;
465 		new->secgrplist = NULL;
466 		new->passwdinfo = NULL;
467 		new->groupname = NULL;
468 
469 		/* Add new display item to the list ordered by login-ID */
470 		new->nextlogin = current;
471 		prev->nextlogin = new;
472 
473 		/*
474 		 * Find the appropriate place for the new item in the list
475 		 * ordered by userID
476 		 */
477 		prev = displayhead;
478 		found = FALSE;
479 		while (!found && (current = prev->nextuid)) {
480 			if (current->userID > pwent->pw_uid) {
481 				found = TRUE;
482 			} else {
483 				prev = current;
484 			}
485 		}
486 
487 		/* Add the item into the list that is ordered by user-ID */
488 		new->nextuid = current;
489 		prev->nextuid = new;
490 	}
491 }
492 
493 
494 /*
495  *  int isuidindisp(pwent)
496  *	struct passwd  *pwent
497  *
498  *  This function examines the display list to see if the uid in
499  *  the (struct passwd) referenced by "pwent" is already in the
500  *  display list.  It returns TRUE if it is in the list, FALSE
501  *  otherwise.
502  *
503  *  Since the display list is ordered by user-ID, the search continues
504  *  until a match is found or a user-ID is found that is larger than
505  *  the one we're searching for.
506  *
507  *  Arguments:
508  *	pwent		Structure that contains the user-ID we're to
509  *			look for
510  *
511  *  Returns:  int
512  *	TRUE if the user-ID was found, FALSE otherwise.
513  */
514 
515 static int
516 isuidindisp(struct passwd *pwent)
517 {
518 	struct display *dp;
519 
520 
521 	/*
522 	 *  Search the list, beginning at the beginning (where else?)
523 	 *  and stopping when the user-ID is found or one is found that
524 	 *  is greater than the user-ID we're searching for.  Recall
525 	 *  that this list is ordered by user-ID
526 	 */
527 
528 	for (dp = displayhead->nextuid; dp && (dp->userID < pwent->pw_uid);
529 	    dp = dp->nextuid) {
530 		continue;
531 	}
532 
533 	/*
534 	 * If the pointer "dp" points to a structure that has a
535 	 * matching user-ID, return TRUE.  Otherwise FALSE
536 	 */
537 	return (dp && (dp->userID == pwent->pw_uid));
538 }
539 
540 
541 /*
542  *  void applygroup(allgroups)
543  *	int	allgroups
544  *
545  *  This function applies group information to the login-IDs in the
546  *  display list.  It always applies the primary group information.
547  *  If "allgroups" is TRUE, it applies secondary information as well.
548  *
549  *  Arguments:
550  * 	allgroups	FLAG.  TRUE if secondary group info is to be
551  *			applied -- FALSE otherwise.
552  *
553  *  Returns:  void
554  */
555 
556 static void
557 applygroup(int allgroups)
558 {
559 	struct display	*dp;		/* Display list running ptr */
560 	struct group	*grent;		/* Group info, from getgrent() */
561 	char		*p;		/* Temp char pointer */
562 	char		**pp;		/* Temp char * pointer */
563 	int		compare;	/* Value from strcmp() */
564 	int		done;		/* TRUE if finished, FALSE otherwise */
565 	struct secgrp	*psecgrp;	/* Block allocated for this info */
566 	struct secgrp	*psrch;		/* Secondary group -- for searching */
567 
568 	if (!allgroups) {
569 		/* short circute getting all the groups */
570 		for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
571 			if ((grent = getgrgid(dp->groupID)) != NULL) {
572 				dp->groupname = strdup(grent->gr_name);
573 			}
574 		}
575 		return;
576 	}
577 
578 	/* For each group-ID in the /etc/group file ... */
579 	while (grent = getgrent()) {
580 		/*
581 		 *  Set the primary group for the login-IDs in the display
582 		 *  list.  For each group-ID we get, leaf through the display
583 		 *  list and set the group-name if the group-IDs match
584 		 */
585 
586 		p = NULL;
587 		for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
588 			if ((dp->groupID == grent->gr_gid) && !dp->groupname) {
589 				if (!p) {
590 					p = strdup(grent->gr_name);
591 				}
592 				dp->groupname = p;
593 			}
594 		}
595 
596 		/*
597 		 *  If we're to be displaying secondary group membership,
598 		 *  leaf through the list of group members.  Then, attempt
599 		 *  to find that member in the display list.  When found,
600 		 *  attach secondary group info to the user.
601 		 */
602 
603 		for (pp = grent->gr_mem; *pp; pp++) {
604 			done = FALSE;
605 			for (dp = displayhead->nextlogin; !done && dp;
606 			    dp = dp->nextlogin) {
607 				if (((compare = strcmp(dp->loginID,
608 				    *pp)) == 0) &&
609 				    !(grent->gr_gid == dp->groupID)) {
610 					if (!p) {
611 						p = strdup(grent->gr_name);
612 					}
613 					psecgrp = malloc(
614 					    sizeof (struct secgrp));
615 					psecgrp->groupID = grent->gr_gid;
616 					psecgrp->groupname = p;
617 					psecgrp->next = NULL;
618 					if (!dp->secgrplist) {
619 						dp->secgrplist = psecgrp;
620 					} else {
621 						for (psrch = dp->secgrplist;
622 						    psrch->next;
623 						    psrch = psrch->next) {
624 							continue;
625 						}
626 						psrch->next = psecgrp;
627 					}
628 					done = TRUE;
629 				} else if (compare > 0) {
630 						done = TRUE;
631 				}
632 			}
633 		}
634 	}
635 
636 	/* Close the /etc/group file */
637 	endgrent();
638 }
639 
640 
641 /*
642  *  void applypasswd()
643  *
644  *	This function applies extended password information to an item
645  *	to be displayed.  It allocates space for a structure describing
646  *	the password, then fills in that structure from the information
647  *	in the /etc/shadow file.
648  *
649  *  Arguments:  None
650  *
651  *  Returns:  Void
652  */
653 
654 static void
655 applypasswd(void)
656 {
657 	struct pwdinfo	*ppasswd;	/* Ptr to pwd desc in current element */
658 	struct display	*dp;		/* Ptr to current element */
659 	struct spwd	*psp;		/* Pointer to a shadow-file entry */
660 	struct tm	*ptm;		/* Pointer to a time-of-day structure */
661 	time_t		pwchg;		/* System-time of last pwd chg */
662 	time_t		pwexp;		/* System-time of password expiration */
663 
664 
665 	/*  Make sure the shadow file is rewound  */
666 	setspent();
667 
668 
669 	/*
670 	 *  For each item in the display list...
671 	 */
672 
673 	for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
674 
675 		/* Allocate structure space for the password description */
676 		ppasswd = malloc(sizeof (struct pwdinfo));
677 		dp->passwdinfo = ppasswd;
678 
679 		/*
680 		 * If there's no entry in the /etc/shadow file, assume
681 		 * that the login is locked
682 		 */
683 
684 		if ((psp = getspnam(dp->loginID)) == NULL) {
685 			pwchg = 0L;			/* Epoch */
686 			ppasswd->passwdstatus = "LK";	/* LK, Locked */
687 			ppasswd->mindaystilchg = 0L;
688 			ppasswd->maxdaystilchg = 0L;
689 			ppasswd->warninterval = 0L;
690 			ppasswd->inactive = 0L;
691 			pwexp = 0L;
692 		} else {
693 			/*
694 			 * Otherwise, fill in the password information from the
695 			 * info in the shadow entry
696 			 */
697 			if (psp->sp_pwdp == NULL || (*psp->sp_pwdp) == '\0')
698 				ppasswd->passwdstatus = "NP";
699 			else if (strncmp(psp->sp_pwdp, LOCKSTRING,
700 			    sizeof (LOCKSTRING)-1) == 0)
701 				ppasswd->passwdstatus = "LK";
702 			else if (strncmp(psp->sp_pwdp, NOLOGINSTRING,
703 			    sizeof (NOLOGINSTRING)-1) == 0)
704 				ppasswd->passwdstatus = "NL";
705 			else if ((strlen(psp->sp_pwdp) == 13 &&
706 			    psp->sp_pwdp[0] != '$') ||
707 			    psp->sp_pwdp[0] == '$')
708 				ppasswd->passwdstatus = "PS";
709 			else
710 				ppasswd->passwdstatus = "UN";
711 			/*
712 			 * Set up the last-changed date,
713 			 * the minimum days between changes,
714 			 * the maximum life of a password,
715 			 * the interval before expiration that the user
716 			 * is warned,
717 			 * the number of days a login can be inactive before
718 			 * it expires, and the login expiration date
719 			 */
720 
721 			pwchg = psp->sp_lstchg;
722 			ppasswd->mindaystilchg = psp->sp_min;
723 			ppasswd->maxdaystilchg = psp->sp_max;
724 			ppasswd->warninterval = psp->sp_warn;
725 			ppasswd->inactive = psp->sp_inact;
726 			pwexp = psp->sp_expire;
727 		}
728 
729 		/*
730 		 * Convert the date of the last password change from days-
731 		 * since-epoch to mmddyy (integer) form.  Involves the
732 		 * intermediate step of converting the date from days-
733 		 * since-epoch to seconds-since-epoch.  We'll set this to
734 		 * somewhere near the middle of the day, since there are not
735 		 * always 24*60*60 seconds in a year.  (Yeech)
736 		 *
737 		 * Note:  The form mmddyy should probably be subject to
738 		 * internationalization -- Non-Americans will think that
739 		 * 070888 is 07 August 88 when the software is trying to say
740 		 * 08 July 88.  Systems Engineers seem to think that this isn't
741 		 * a problem though...
742 		 */
743 
744 		if (pwchg != -1L) {
745 			pwchg = (pwchg * DAY) + (DAY/2);
746 			ptm = localtime(&pwchg);
747 			ppasswd->datechg = ((long)(ptm->tm_mon+1) * 10000L) +
748 			    (long)((ptm->tm_mday * 100) +
749 			    (ptm->tm_year % 100));
750 		} else {
751 			ppasswd->datechg = 0L;
752 		}
753 
754 		/*
755 		 * Convert the passwd expiration date from days-since-epoch
756 		 * to mmddyy (long integer) form using the same algorithm and
757 		 * comments as above.
758 		 */
759 
760 		if (pwexp != -1L) {
761 			pwexp = (pwexp * DAY) + (DAY/2);
762 			ptm = localtime(&pwexp);
763 			ppasswd->expdate = ((long)(ptm->tm_mon+1) * 10000L) +
764 			    (long)((ptm->tm_mday * 100) +
765 			    (ptm->tm_year % 100));
766 		} else {
767 			ppasswd->expdate = 0L;
768 		}
769 	}
770 
771 	/* Close the shadow password file */
772 	endspent();
773 }
774 
775 
776 /*
777  * int hasnopasswd(pwent)
778  *	struct passwd  *pwent
779  *
780  *	This function examines a login's password-file entry
781  *	and, if necessary, its shadow password-file entry and
782  *	returns TRUE if that user-ID has no password, meaning
783  *	that the user-ID can be used to log into the system
784  *	without giving a password.  The function returns FALSE
785  *	otherwise.
786  *
787  *  Arguments:
788  *	pwent	Login to examine.
789  *
790  *  Returns:  int
791  *	TRUE if the login can be used without a password, FALSE
792  *	otherwise.
793  */
794 
795 static int
796 hasnopasswd(struct passwd *pwent)
797 {
798 	struct spwd    *psp;		/* /etc/shadow file struct */
799 	int		nopwflag;	/* TRUE if login has no passwd */
800 
801 	/*
802 	 *  A login has no password if:
803 	 *    1.  There exists an entry for that login in the
804 	 *	  shadow password-file (/etc/passwd), and
805 	 *    2.  The encrypted password in the structure describing
806 	 *	  that entry is either:	 NULL or a null string ("")
807 	 */
808 
809 	/* Get the login's entry in the shadow password file */
810 	if (psp = getspnam(pwent->pw_name)) {
811 
812 		/* Look at the encrypted password in that entry */
813 		if (psp->sp_pwdp == (char *)0 ||
814 		    *psp->sp_pwdp == '\0') {
815 			nopwflag = TRUE;
816 		} else {
817 			nopwflag = FALSE;
818 		}
819 	} else {
820 		nopwflag = FALSE;
821 	}
822 
823 	/* Done ... */
824 	return (nopwflag);
825 }
826 
827 
828 /*
829  *  void writeunformatted(current, xtndflag, expflag)
830  *	struct display *current
831  *	int		xtndflag
832  *	int		expflag
833  *
834  *  This function writes the data in the display structure "current"
835  *  to the standard output file.  It writes the information in the
836  *  form of a colon-list.  It writes secondary group information if
837  *  that information is in the structure, it writes extended
838  *  (initial working directory, shell, and password-aging) info
839  *  if the "xtndflag" is TRUE, and it writes password expiration
840  *  information if "expflag" is TRUE.
841  *
842  *  Arguments:
843  *	current		Structure containing information to write.
844  *	xtndflag	TRUE if extended information is to be written,
845  *			FALSE otherwise
846  *	expflag		TRUE if password expiration information is to
847  *			be written, FALSE otherwise
848  *
849  *  Returns:  void
850  */
851 
852 static void
853 writeunformatted(struct display *current, int xtndflag, int expflag)
854 {
855 	struct secgrp  *psecgrp;	/* Secondary group info */
856 	struct pwdinfo *pwdinfo;	/* Password aging info */
857 
858 	/* Write the general information */
859 	(void) fprintf(stdout, "%s:%ld:%s:%ld:%s",
860 	    current->loginID,
861 	    current->userID,
862 	    current->groupname == NULL ? "" : current->groupname,
863 	    current->groupID,
864 	    current->freefield);
865 
866 	/*
867 	 * If the group information is there, write it (it's only
868 	 * there if it's supposed to be written)
869 	 */
870 	for (psecgrp = current->secgrplist; psecgrp; psecgrp = psecgrp->next) {
871 		(void) fprintf(stdout, ":%s:%ld",
872 		    psecgrp->groupname, psecgrp->groupID);
873 	}
874 
875 	/* If the extended info flag is TRUE, write the extended information */
876 	if (xtndflag) {
877 		pwdinfo = current->passwdinfo;
878 		(void) fprintf(stdout, ":%s:%s:%s:%6.6ld:%ld:%ld:%ld",
879 		    current->iwd, current->shell,
880 		    pwdinfo->passwdstatus,
881 		    pwdinfo->datechg,
882 		    pwdinfo->mindaystilchg, pwdinfo->maxdaystilchg,
883 		    pwdinfo->warninterval);
884 	}
885 
886 	/* If the password expiration information is requested, write it.  */
887 	if (expflag) {
888 		pwdinfo = current->passwdinfo;
889 		(void) fprintf(stdout, ":%ld:%ld",
890 		    pwdinfo->inactive, pwdinfo->expdate);
891 	}
892 
893 	/* Terminate the information with a new-line */
894 	(void) putc('\n', stdout);
895 }
896 
897 
898 /*
899  *  void writeformatted(current, xtndflag, expflag)
900  *	struct display *current
901  *	int		xtndflag
902  *	int		expflag
903  *
904  *  This function writes the data in the display structure "current"
905  *  to the standard output file.  It writes the information in an
906  *  easily readable format.  It writes secondary group information
907  *  if that information is in the structure, it writes extended
908  *  (initial working directory, shell, and password-aging) info if
909  *  "xtndflag" is TRUE, and it write password expiration information
910  *  if "expflag" is TRUE.
911  *
912  *  Arguments:
913  *	current		Structure containing info to write.
914  *	xtndflag	TRUE if extended information to be written,
915  *			FALSE otherwise
916  *	expflag 	TRUE if password expiration information to be written,
917  *			FALSE otherwise
918  *
919  *  Returns:  void
920  */
921 
922 static void
923 writeformatted(struct display *current, int xtndflag, int expflag)
924 {
925 	struct secgrp  *psecgrp;	/* Secondary group info */
926 	struct pwdinfo *pwdinfo;	/* Password aging info */
927 
928 	/* Write general information */
929 	(void) fprintf(stdout, "%-14s  %-6ld  %-14s  %-6ld  %s\n",
930 	    current->loginID, current->userID,
931 	    current->groupname == NULL ? "" : current->groupname,
932 	    current->groupID, current->freefield);
933 
934 	/*
935 	 * Write information about secondary groups if the info exists
936 	 * (it only exists if it is to be written)
937 	 */
938 	for (psecgrp = current->secgrplist; psecgrp; psecgrp = psecgrp->next) {
939 	    (void) fprintf(stdout, "                        %-14s  %-6ld\n",
940 		psecgrp->groupname, psecgrp->groupID);
941 	}
942 
943 	/*
944 	 * If the extended information flag is TRUE,
945 	 * write the extended information
946 	 */
947 
948 	if (xtndflag) {
949 		pwdinfo = current->passwdinfo;
950 		(void) fprintf(stdout, "                        %s\n",
951 		    current->iwd);
952 		(void) fprintf(stdout, "                        %s\n",
953 		    current->shell);
954 		(void) fprintf(stdout, "                        %s "
955 		    "%6.6ld %ld %ld %ld\n",
956 		    pwdinfo->passwdstatus,
957 		    pwdinfo->datechg, pwdinfo->mindaystilchg,
958 		    pwdinfo->maxdaystilchg,
959 		    pwdinfo->warninterval);
960 	}
961 
962 	/*
963 	 * If the password expiration info flag is TRUE,
964 	 * write that information
965 	 */
966 	if (expflag) {
967 		pwdinfo = current->passwdinfo;
968 		(void) fprintf(stdout, "                        %ld %6.6ld\n",
969 		    pwdinfo->inactive, pwdinfo->expdate);
970 	}
971 }
972 
973 
974 /*
975  *  void genuidreport(pipeflag, xtndflag, expflag)
976  *	int	pipeflag
977  *	int	xtndflag
978  *	int	expflag
979  *
980  *	This function generates a report on the standard output
981  *	stream (stdout) containing the login-IDs in the list of
982  *	logins built by this command.  The list is ordered based
983  *	on user-ID.  If the <pipeflag> variable is not zero, it
984  *	will generate a report containing parsable records.
985  *	Otherwise, it will generate a columnarized report.  If
986  *	the <xtndflag> variable is not zero, it will include the
987  *	extended set of information (password aging info, home
988  *	directory, shell process, etc.).  If <expflag> is not
989  *	zero, it will display password expiration information.
990  *
991  *  Arguments:
992  *	pipeflag	int
993  *			TRUE if a parsable report is needed,
994  *			FALSE if a columnar report is needed
995  *	xtndflag	int
996  *			TRUE if extended set of info is to be displayed,
997  *			FALSE otherwise
998  *	expflag		int
999  *			TRUE if password expiration information is to be
1000  *			displayed, FALSE otherwise
1001  *
1002  *  Returns:  void
1003  */
1004 
1005 static void
1006 genuidreport(int pipeflag, int xtndflag, int expflag)
1007 {
1008 
1009 	struct display *current;	/* Data being displayed */
1010 
1011 
1012 	/*
1013 	 *  Initialization for loop.
1014 	 *  (NOTE:  The first element in the list of logins to	display is
1015 	 *  a dummy element.)
1016 	 */
1017 	current = displayhead;
1018 
1019 	/*
1020 	 *  Display elements in the list
1021 	 */
1022 	if (pipeflag) {
1023 		for (current = displayhead->nextuid; current;
1024 		    current = current->nextuid) {
1025 			writeunformatted(current, xtndflag, expflag);
1026 		}
1027 	} else {
1028 		for (current = displayhead->nextuid; current;
1029 		    current = current->nextuid) {
1030 			writeformatted(current, xtndflag, expflag);
1031 		}
1032 	}
1033 }
1034 
1035 
1036 /*
1037  *  void genlogreport(pipeflag, xtndflag, expflag)
1038  *	int	pipeflag
1039  *	int	xtndflag
1040  *	int	expflag
1041  *
1042  *	This function generates a report on the standard output
1043  *	stream (stdout) containing the login-IDs in the list of
1044  *	logins built by this command.  The list is ordered based
1045  *	on user name.  If the <pipeflag> variable is not zero, it
1046  *	will generate a report containing parsable records.
1047  *	Otherwise, it will generate a columnarized report.  If
1048  *	the <xtndflag> variable is not zero, it will include the
1049  *	extended set of information (password aging info, home
1050  *	directory, shell process, etc.).  If <expflag> is not
1051  *	zero, it will include password expiration information.
1052  *
1053  *  Arguments:
1054  *	pipeflag	int
1055  *			TRUE if a parsable report is needed,
1056  *			FALSE if a columnar report is needed
1057  *	xtndflag	int
1058  *			TRUE if extended set of info is to be displayed,
1059  *			FALSE otherwise
1060  *	expflag		int
1061  *			TRUE if password expiration information is to
1062  *			be displayed, FALSE otherwise
1063  *
1064  *  Returns:  void
1065  */
1066 
1067 static void
1068 genlogreport(int pipeflag, int xtndflag, int expflag)
1069 {
1070 	struct display *p;	/* Value being displayed */
1071 
1072 
1073 	/*
1074 	 *  Initialization for loop.
1075 	 *  (NOTE:  The first element in the list of logins to display is
1076 	 *  a dummy element.)
1077 	 */
1078 	p = displayhead;
1079 
1080 	/*
1081 	 *  Display elements in the list
1082 	 */
1083 	if (pipeflag) {
1084 		for (p = displayhead->nextlogin; p; p = p->nextlogin) {
1085 			writeunformatted(p, xtndflag, expflag);
1086 		}
1087 	} else {
1088 		for (p = displayhead->nextlogin; p; p = p->nextlogin) {
1089 			writeformatted(p, xtndflag, expflag);
1090 		}
1091 	}
1092 }
1093 
1094 struct localpw {
1095 	struct localpw *next;
1096 	struct passwd pw;
1097 };
1098 
1099 struct localpw *pwtable = NULL;
1100 
1101 /* Local passwd pointer for getpwent() -- -1 means not in use, NULL for EOF */
1102 struct localpw *pwptr;
1103 
1104 int in_localgetpwent = 0;	/* Set if in local_getpwent */
1105 
1106 static struct localpw *
1107 fill_localpw(struct localpw *lpw, struct passwd *pw) {
1108 	struct localpw *cur;
1109 
1110 	/*
1111 	 * Copy the data -- we have to alloc areas for it all
1112 	 */
1113 	lpw->pw.pw_name = strdup(pw->pw_name);
1114 	lpw->pw.pw_passwd = strdup(pw->pw_passwd);
1115 	lpw->pw.pw_uid = pw->pw_uid;
1116 	lpw->pw.pw_gid = pw->pw_gid;
1117 	lpw->pw.pw_age = strdup(pw->pw_age);
1118 	lpw->pw.pw_comment = strdup(pw->pw_comment);
1119 	lpw->pw.pw_gecos  = strdup(pw->pw_gecos);
1120 	lpw->pw.pw_dir = strdup(pw->pw_dir);
1121 	lpw->pw.pw_shell = strdup(pw->pw_shell);
1122 
1123 	cur = lpw;
1124 	lpw->next = malloc(sizeof (struct localpw));
1125 	return (cur);
1126 }
1127 
1128 void
1129 build_localpw(struct reqlogin *req_head)
1130 {
1131 	struct localpw *next, *cur;
1132 	struct passwd *pw;
1133 	struct reqlogin *req_next;
1134 
1135 	next = malloc(sizeof (struct localpw));
1136 
1137 	pwtable = next;
1138 
1139 	req_next = req_head;
1140 
1141 	while (req_next != NULL) {
1142 		if ((pw = getpwnam(req_next->loginname)) != NULL) {
1143 			/*
1144 			 * Copy the data -- we have to alloc areas for it all
1145 			 */
1146 			cur = fill_localpw(next, pw);
1147 			req_next->found = TRUE;
1148 			next = cur->next;
1149 		}
1150 
1151 		req_next = req_next->next;
1152 	}
1153 
1154 	if (req_head == NULL) {
1155 		while ((pw = getpwent()) != NULL) {
1156 			/*
1157 			 * Copy the data -- we have to alloc areas for it all
1158 			 */
1159 			cur = fill_localpw(next, pw);
1160 			next = cur->next;
1161 		}
1162 	}
1163 
1164 	if (pwtable == next) {
1165 		pwtable = NULL;
1166 	} else {
1167 		free(next);
1168 		cur->next = NULL;
1169 	}
1170 
1171 	endpwent();
1172 }
1173 
1174 struct passwd *
1175 local_getpwent(void)
1176 {
1177 	if (!in_localgetpwent) {
1178 		in_localgetpwent = 1;
1179 		pwptr = pwtable;
1180 	} else if (pwptr != NULL) {
1181 		pwptr = pwptr->next;
1182 	}
1183 
1184 	if (pwptr != NULL)
1185 		return (&(pwptr->pw));
1186 	else
1187 		return (NULL);
1188 }
1189 
1190 void
1191 local_endpwent(void)
1192 {
1193 	in_localgetpwent = 0;
1194 }
1195 
1196 long
1197 local_pwtell(void)
1198 {
1199 	return ((long)pwptr);
1200 }
1201 
1202 void
1203 local_pwseek(long ptr)
1204 {
1205 	pwptr = (struct localpw *)ptr;
1206 }
1207 
1208 /*
1209  * logins [-admopstux] [-l logins] [-g groups]
1210  *
1211  *	This command generates a report of logins administered on
1212  *	the system.  The list will contain logins that meet criteria
1213  *	described by the options in the list.  If there are no options,
1214  *	it will list all logins administered.  It is intended to be used
1215  *	only by administrators.
1216  *
1217  *  Options:
1218  *	-a		Display password expiration information.
1219  *	-d		list all logins that share user-IDs with another
1220  *			login.
1221  *	-g groups	specifies the names of the groups to which a login
1222  *			must belong before it is included in the generated
1223  *			list.  "groups" is a comma-list of group names.
1224  *	-l logins	specifies the logins to display.  "logins" is a
1225  *			comma-list of login names.
1226  *	-m		in addition to the usual information, for each
1227  *			login displayed, list all groups to which that
1228  *			login is member.
1229  *	-o		generate a report as a colon-list instead of in a
1230  *			columnar format
1231  *	-p		list all logins that have no password.
1232  *	-s		list all system logins
1233  *	-t		sort the report lexicographically by login name
1234  *			instead of by user-ID
1235  *	-u		list all user logins
1236  *	-x		in addition to the usual information, display an
1237  *			extended set of information that includes the home
1238  *			directory, initial process, and password status and
1239  *			aging information
1240  *
1241  * Exit Codes:
1242  *	0	All's well that ends well
1243  *	1	Usage error
1244  */
1245 
1246 int
1247 main(int argc, char *argv[])
1248 {
1249 	struct passwd	*plookpwd;	/* Ptr to searcher pw (-d) */
1250 	struct reqgrp	*reqgrphead;	/* Head of the req'd group list */
1251 	struct reqgrp	*pgrp;		/* Current item in req'd group list */
1252 	struct reqgrp	*qgrp;		/* Prev item in the req'd group list */
1253 	struct reqlogin *reqloginhead;	/* Head of req'd login list */
1254 	struct reqlogin *plogin;	/* Current item in req'd login list */
1255 	struct reqlogin *qlogin;	/* Prev item in req'd login list */
1256 	struct passwd	*pwent;		/* /etc/passwd entry */
1257 	struct group	*grent;		/* /etc/group entry */
1258 	char		*token;		/* Token extracted by strtok() */
1259 	char		**pp;		/* Group member */
1260 	char		*g_arg;		/* -g option's argument */
1261 	char		*l_arg;		/* -l option's argument */
1262 	long		lookpos;	/* File pos'n, rec we're looking for */
1263 	int		a_seen;		/* Is -a requested? */
1264 	int		d_seen;		/* Is -d requested? */
1265 	int		g_seen;		/* Is -g requested? */
1266 	int		l_seen;		/* Is -l requested? */
1267 	int		m_seen;		/* Is -m requested? */
1268 	int		o_seen;		/* Is -o requested? */
1269 	int		p_seen;		/* Is -p requested? */
1270 	int		s_seen;		/* Is -s requested? */
1271 	int		t_seen;		/* Is -t requested? */
1272 	int		u_seen;		/* Is -u requested? */
1273 	int		x_seen;		/* Is -x requested? */
1274 	int		errflg;		/* Is there a command-line problem */
1275 	int		done;		/* Is the process (?) is complete */
1276 	int		groupcount;	/* Number of groups specified */
1277 	int		doall;		/* Are all logins to be reported */
1278 	int		c;		/* Character returned from getopt() */
1279 
1280 	(void) setlocale(LC_ALL, "");
1281 
1282 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
1283 #define	TEXT_DOMAIN "SYS_TEST"
1284 #endif
1285 	(void) textdomain(TEXT_DOMAIN);
1286 
1287 	/* Initializations */
1288 	initmsg(argv[0]);
1289 
1290 
1291 
1292 	/*  Command-line processing */
1293 
1294 	/* Initializations */
1295 	a_seen = FALSE;
1296 	d_seen = FALSE;
1297 	g_seen = FALSE;
1298 	l_seen = FALSE;
1299 	m_seen = FALSE;
1300 	o_seen = FALSE;
1301 	p_seen = FALSE;
1302 	s_seen = FALSE;
1303 	t_seen = FALSE;
1304 	u_seen = FALSE;
1305 	x_seen = FALSE;
1306 	errflg = FALSE;
1307 	opterr = 0;
1308 	while (!errflg && ((c = getopt(argc, argv, OPTSTR)) != EOF)) {
1309 
1310 		/* Case on the option character */
1311 		switch (c) {
1312 
1313 		/*
1314 		 * -a option:
1315 		 * Display password expiration information
1316 		 */
1317 
1318 		case 'a':
1319 			if (a_seen)
1320 				errflg = TRUE;
1321 			else
1322 				a_seen = TRUE;
1323 			break;
1324 
1325 		/*
1326 		 * -d option:
1327 		 * Display logins which share user-IDs with other logins
1328 		 */
1329 
1330 		case 'd':
1331 			if (d_seen)
1332 				errflg = TRUE;
1333 			else
1334 				d_seen = TRUE;
1335 			break;
1336 
1337 		/*
1338 		 * -g <groups> option:
1339 		 * Display the specified groups
1340 		 */
1341 
1342 		case 'g':
1343 			if (g_seen) {
1344 				errflg = TRUE;
1345 			} else {
1346 				g_seen = TRUE;
1347 				g_arg = optarg;
1348 			}
1349 			break;
1350 
1351 		/*
1352 		 * -l <logins> option:
1353 		 * Display the specified logins
1354 		 */
1355 
1356 		case 'l':
1357 			if (l_seen) {
1358 				errflg = TRUE;
1359 			} else {
1360 				l_seen = TRUE;
1361 				l_arg = optarg;
1362 			}
1363 			break;
1364 
1365 		/*
1366 		 * -m option:
1367 		 * Display multiple group information
1368 		 */
1369 
1370 		case 'm':
1371 			if (m_seen)
1372 				errflg = TRUE;
1373 			else
1374 				m_seen = TRUE;
1375 			break;
1376 
1377 		/*
1378 		 * -o option:
1379 		 * Display information as a colon-list
1380 		 */
1381 
1382 		case 'o':
1383 			if (o_seen)
1384 				errflg = TRUE;
1385 			else
1386 				o_seen = TRUE;
1387 			break;
1388 
1389 		/*
1390 		 * -p option:
1391 		 * Select logins that have no password
1392 		 */
1393 
1394 		case 'p':
1395 			if (p_seen)
1396 				errflg = TRUE;
1397 			else
1398 				p_seen = TRUE;
1399 			break;
1400 
1401 		/*
1402 		 * -s option:
1403 		 * Select system logins
1404 		 */
1405 
1406 		case 's':
1407 			if (s_seen)
1408 				errflg = TRUE;
1409 			else
1410 				s_seen = TRUE;
1411 			break;
1412 
1413 		/*
1414 		 * -t option:
1415 		 * Sort alphabetically by login-ID instead of numerically
1416 		 * by user-ID
1417 		 */
1418 
1419 		case 't':
1420 			if (t_seen)
1421 				errflg = TRUE;
1422 			else
1423 				t_seen = TRUE;
1424 			break;
1425 
1426 		/*
1427 		 * -u option:
1428 		 * Select user logins
1429 		 */
1430 
1431 		case 'u':
1432 			if (u_seen)
1433 				errflg = TRUE;
1434 			else
1435 				u_seen = TRUE;
1436 			break;
1437 
1438 		/*
1439 		 * -x option:
1440 		 * Display extended info (init working dir, shell, pwd info)
1441 		 */
1442 
1443 		case 'x':
1444 			if (x_seen)
1445 				errflg = TRUE;
1446 			else
1447 				x_seen = TRUE;
1448 			break;
1449 
1450 		default:		/* Oops.... */
1451 			errflg = TRUE;
1452 		}
1453 	}
1454 
1455 	/* Write out a usage message if necessary and quit */
1456 	if (errflg || (optind != argc)) {
1457 		wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, gettext(USAGE_MSG));
1458 		exit(1);
1459 	}
1460 
1461 	/*
1462 	 *  The following section does preparation work, setting up for
1463 	 *  building the list of logins to display
1464 	 */
1465 
1466 
1467 	/*
1468 	 *  If -l logins is on the command line, build a list of
1469 	 *  logins we're to generate reports for.
1470 	 */
1471 
1472 	if (l_seen) {
1473 		reqloginhead = NULL;
1474 		if (token = strtok(l_arg, ",")) {
1475 			plogin = malloc(sizeof (struct reqlogin));
1476 			plogin->loginname = token;
1477 			plogin->found = FALSE;
1478 			plogin->next = NULL;
1479 			reqloginhead = plogin;
1480 			qlogin = plogin;
1481 			while (token = strtok(NULL, ",")) {
1482 				plogin = malloc(sizeof (struct reqlogin));
1483 				plogin->loginname = token;
1484 				plogin->found = FALSE;
1485 				plogin->next = NULL;
1486 				qlogin->next = plogin;
1487 				qlogin = plogin;
1488 			}
1489 		}
1490 		/*
1491 		 * Build an in-core structure of just the passwd database
1492 		 * entries requested.  This greatly reduces the time
1493 		 * to get all entries and filter later.
1494 		 */
1495 		build_localpw(reqloginhead);
1496 	} else {
1497 		/*
1498 		 * Build an in-core structure of all passwd database
1499 		 * entries.  This is important since we have to assume that
1500 		 * getpwent() is going out to one or more network name
1501 		 * services that could be changing on the fly.  This will
1502 		 * limit us to one pass through the network data.
1503 		 */
1504 		build_localpw(NULL);
1505 	}
1506 
1507 	/*
1508 	 *  If the -g groups option was on the command line, build a
1509 	 *  list containing groups we're to list logins for.
1510 	 */
1511 
1512 	if (g_seen) {
1513 		groupcount = 0;
1514 		reqgrphead = NULL;
1515 		if (token = strtok(g_arg, ",")) {
1516 			pgrp = malloc(sizeof (struct reqgrp));
1517 			pgrp->groupname = token;
1518 			pgrp->next = NULL;
1519 			groupcount++;
1520 			reqgrphead = pgrp;
1521 			qgrp = pgrp;
1522 			while (token = strtok(NULL, ",")) {
1523 				pgrp = malloc(sizeof (struct reqgrp));
1524 				pgrp->groupname = token;
1525 				pgrp->next = NULL;
1526 				groupcount++;
1527 				qgrp->next = pgrp;
1528 				qgrp = pgrp;
1529 			}
1530 		}
1531 	}
1532 
1533 
1534 	/*
1535 	 *  Generate the list of login information to display
1536 	 */
1537 
1538 	/* Initialize the login list */
1539 	membershead = NULL;
1540 
1541 
1542 	/*
1543 	 *  If -g groups was specified, generate a list of members
1544 	 *  of the specified groups
1545 	 */
1546 
1547 	if (g_seen) {
1548 		/* For each group mentioned with the -g option ... */
1549 		for (pgrp = reqgrphead; (groupcount > 0) && pgrp;
1550 		    pgrp = pgrp->next) {
1551 			if ((grent = getgrnam(pgrp->groupname)) != NULL) {
1552 				/*
1553 				 * Remembering the group-ID for later
1554 				 */
1555 
1556 				groupcount--;
1557 				pgrp->groupID = grent->gr_gid;
1558 				for (pp = grent->gr_mem; *pp; pp++) {
1559 					addmember(*pp);
1560 				}
1561 			} else {
1562 				wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG,
1563 				    gettext("%s was not found"),
1564 				    pgrp->groupname);
1565 			}
1566 		}
1567 	}
1568 
1569 
1570 	/* Initialize the list of logins to display */
1571 	initdisp();
1572 
1573 
1574 	/*
1575 	 *  Add logins that have user-IDs that are used more than once,
1576 	 *  if requested.  This command is pretty slow, since the algorithm
1577 	 *  reads from the /etc/passwd file 1+2+3+...+n times where n is the
1578 	 *  number of login-IDs in the /etc/passwd file.  (Actually, this
1579 	 *  can be optimized so it's not quite that bad, but the order or
1580 	 *  magnitude stays the same.)
1581 	 *
1582 	 *  Note:  This processing needs to be done before any other options
1583 	 *	   are processed -- the algorithm contains an optimization
1584 	 *	   that insists on the display list being empty before this
1585 	 *	   option is processed.
1586 	 */
1587 
1588 	if (d_seen) {
1589 
1590 		/*
1591 		 * The following code is a quick&dirty reimplementation of the
1592 		 * original algorithm, which opened the password file twice (to
1593 		 * get two file pointer into the data) and then used fgetpwent()
1594 		 * in undocumented ways to scan through the file, checking for
1595 		 * duplicates.  This does not work when getpwent() is used to
1596 		 * go out over the network, since there is not file pointer.
1597 		 *
1598 		 * Instead an in-memory list of passwd structures is built,
1599 		 * and then this list is scanned.  The routines
1600 		 * Local_getpwent(), etc., are designed to mimic the standard
1601 		 * library routines, so this code does not have to be
1602 		 * extensively modified.
1603 		 */
1604 
1605 		/*
1606 		 * For reference, here is the original comment about the next
1607 		 * section of code.  Some of the code has changed, but the
1608 		 * algorithm is the same:
1609 		 *
1610 		 * Open the system password file once.  This instance will be
1611 		 * used to leaf through the file once, reading each entry once,
1612 		 * and searching the remainder of the file for another login-ID
1613 		 * that has the same user-ID.  Note that there are lots of
1614 		 * contortions one has to go through when reading two instances
1615 		 * of the /etc/passwd file.  That's why there's some seeking,
1616 		 * re-reading of the same record, and other junk.  Luckily, this
1617 		 * feature won't be requested very often, and still isn't too
1618 		 * slow...
1619 		 */
1620 
1621 		/* For each entry in the passwd database ... */
1622 		while (plookpwd = local_getpwent()) {
1623 			/*
1624 			 * Optimization -- If the login's user-ID is already
1625 			 * in the display list, there's no reason to process
1626 			 * this  entry -- it's already there.
1627 			 */
1628 			if (!isuidindisp(plookpwd)) {
1629 				/*
1630 				 * Rememeber the current entry's position,
1631 				 * so when we finish scanning through the
1632 				 * database looking for duplicates we can
1633 				 * return to the current place, so that the
1634 				 * enclosing loop will march in an orderly
1635 				 * fashion through the passwd database.
1636 				 */
1637 				done = FALSE;
1638 				lookpos = local_pwtell();
1639 
1640 				/*
1641 				 * For each record in the passwd database
1642 				 * beyond the searching record ...
1643 				 */
1644 				while (pwent = local_getpwent()) {
1645 
1646 					/*
1647 					 * If there's a match between the
1648 					 * searcher's user-ID and the
1649 					 * searchee's user-ID ...
1650 					 */
1651 					if (pwent->pw_uid == plookpwd->pw_uid) {
1652 						/*
1653 						 * If this is the first
1654 						 * duplicate of this searcher
1655 						 * that we find,
1656 						 * add the searcher's
1657 						 * record to the display list
1658 						 * (It wants to be on the
1659 						 * list first to avoid
1660 						 * ordering "flakeyness")
1661 						 */
1662 						if (done == FALSE) {
1663 							adddisp(plookpwd);
1664 							done = TRUE;
1665 						}
1666 
1667 						/*
1668 						 * Now add the searchee's
1669 						 * record
1670 						 */
1671 						adddisp(pwent);
1672 
1673 					}
1674 				}
1675 				/* Reposition to searcher record */
1676 				local_pwseek(lookpos);
1677 			}
1678 		}
1679 
1680 		local_endpwent();
1681 	}
1682 
1683 
1684 	/*
1685 	 *  Loop through the passwd database squirelling away the
1686 	 *  information we need for the display.
1687 	 *
1688 	 *  NOTE:  Once a login is added to the list, the rest of the
1689 	 *	   body of the loop is bypassed (via a continue statement).
1690 	 */
1691 
1692 	doall = !(s_seen || u_seen || p_seen || d_seen || l_seen || g_seen);
1693 
1694 	if (doall || s_seen || u_seen || p_seen || l_seen || g_seen) {
1695 
1696 		while (pwent = local_getpwent()) {
1697 			done = FALSE;
1698 
1699 			/*
1700 			 * If no user-specific options were specified,
1701 			 * include this login-ID
1702 			 */
1703 			if (doall) {
1704 				adddisp(pwent);
1705 				continue;
1706 			}
1707 
1708 			/*
1709 			 * If the user specified system login-IDs,
1710 			 * and this is a system ID, include it
1711 			 */
1712 			if (s_seen) {
1713 				if (isasystemlogin(pwent)) {
1714 					adddisp(pwent);
1715 					continue;
1716 				}
1717 			}
1718 
1719 			/*
1720 			 * If the user specified user login-IDs,
1721 			 * and this is a user ID, include it
1722 			 */
1723 			if (u_seen) {
1724 				if (isauserlogin(pwent)) {
1725 					adddisp(pwent);
1726 					continue;
1727 				}
1728 			}
1729 
1730 			/*
1731 			 * If the user is asking for login-IDs that have
1732 			 * no password, and this one has no password, include it
1733 			 */
1734 			if (p_seen) {
1735 				if (hasnopasswd(pwent)) {
1736 					adddisp(pwent);
1737 					continue;
1738 				}
1739 			}
1740 
1741 			/*
1742 			 * If specific logins were requested, leaf through
1743 			 * the list of logins they requested.  If this login
1744 			 * is on the list, include it.
1745 			 */
1746 			if (l_seen) {
1747 				for (plogin = reqloginhead; !done && plogin;
1748 				    plogin = plogin->next) {
1749 					if (strcmp(pwent->pw_name,
1750 					    plogin->loginname) == 0) {
1751 						plogin->found = TRUE;
1752 						adddisp(pwent);
1753 						done = TRUE;
1754 					}
1755 				}
1756 				if (done)
1757 					continue;
1758 			}
1759 
1760 			/*
1761 			 * If specific groups were requested, leaf through the
1762 			 * list of login-IDs that belong to those groups.
1763 			 * If this login-ID is in that list, or its primary
1764 			 * group is one of those requested, include it.
1765 			 */
1766 
1767 			if (g_seen) {
1768 				for (pgrp = reqgrphead; !done && pgrp;
1769 				    pgrp = pgrp->next) {
1770 					if (pwent->pw_gid == pgrp->groupID) {
1771 						adddisp(pwent);
1772 						done = TRUE;
1773 					}
1774 				}
1775 				if (!done && isamember(pwent->pw_name)) {
1776 					adddisp(pwent);
1777 					done = TRUE;
1778 				}
1779 			}
1780 			if (done)
1781 				continue;
1782 		}
1783 
1784 		local_endpwent();
1785 	}
1786 
1787 	/* Let the user know about logins they requested that don't exist */
1788 	if (l_seen) {
1789 		for (plogin = reqloginhead; plogin; plogin = plogin->next) {
1790 			if (!plogin->found) {
1791 				wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG,
1792 				    gettext("%s was not found"),
1793 				    plogin->loginname);
1794 			}
1795 		}
1796 	}
1797 
1798 	/*  Apply group information */
1799 	applygroup(m_seen);
1800 
1801 
1802 	/*
1803 	 * Apply password information (only needed if the extended
1804 	 * set of information has been requested)
1805 	 */
1806 	if (x_seen || a_seen)
1807 		applypasswd();
1808 
1809 
1810 	/*
1811 	 * Generate a report from this display items we've squirreled away
1812 	 */
1813 
1814 	if (t_seen)
1815 		genlogreport(o_seen, x_seen, a_seen);
1816 	else
1817 		genuidreport(o_seen, x_seen, a_seen);
1818 
1819 	/*  We're through! */
1820 	return (0);
1821 }
1822