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)  */
110struct	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)  */
117struct	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
127struct	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 */
138struct	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
159static	char	fcnlbl[MM_MXLABELLN+1];	/* Buffer for message label */
160static	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
178static void
179initmsg(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*/
216static void
217wrtmsg(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
266struct	grpmember {
267	char			*membername;
268	struct grpmember	*next;
269};
270
271static	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
288static void
289addmember(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
316static int
317isamember(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
366struct	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
380static	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
394static void
395initdisp(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
426static void
427adddisp(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
514static int
515isuidindisp(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
555static void
556applygroup(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
653static void
654applypasswd(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
794static int
795hasnopasswd(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
851static void
852writeunformatted(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
921static void
922writeformatted(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
1004static void
1005genuidreport(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
1066static void
1067genlogreport(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
1093struct localpw {
1094	struct localpw *next;
1095	struct passwd pw;
1096};
1097
1098struct localpw *pwtable = NULL;
1099
1100/* Local passwd pointer for getpwent() -- -1 means not in use, NULL for EOF */
1101struct localpw *pwptr;
1102
1103int in_localgetpwent = 0;	/* Set if in local_getpwent */
1104
1105static struct localpw *
1106fill_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
1127void
1128build_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
1173struct passwd *
1174local_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
1189void
1190local_endpwent(void)
1191{
1192	in_localgetpwent = 0;
1193}
1194
1195long
1196local_pwtell(void)
1197{
1198	return ((long)pwptr);
1199}
1200
1201void
1202local_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
1245int
1246main(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