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