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