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