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.
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.
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]
22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
26 /* All Rights Reserved */
29 #pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.15.1.2 */
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.
39 #include <sys/types.h>
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
69 #define TRUE ((int)'t')
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"
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
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,
94 #define isauserlogin(pw) (pw->pw_uid > MAXSYSTEMLOGIN)
95 #define isasystemlogin(pw) (pw->pw_uid <= MAXSYSTEMLOGIN)
99 * Local datatype definitions
100 * struct reqgrp Describes a group as requested through the
102 * struct reqlogin Describes a login-ID as requested through
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
109 /* Describes a specified group name (from the -g groups option) */
111 char *groupname
; /* Requested group name */
112 struct reqgrp
*next
; /* Next item in the list */
113 gid_t groupID
; /* Group's ID */
116 /* Describes a specified login name (from the -l logins option) */
118 char *loginname
; /* Requested login name */
119 struct reqlogin
*next
; /* Next item in the list */
120 int found
; /* TRUE if login in /etc/passwd */
124 * This structure describes a password's information
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) */
137 /* This structure describes secondary groups that a user belongs to */
139 char *groupname
; /* Name of the group */
140 struct secgrp
*next
; /* Next item in the list */
141 gid_t groupID
; /* Group-ID */
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.)
150 * Functions included:
151 * initmsg Initialize the message handling functions.
152 * wrtmsg Write the message using fmtmsg().
154 * Static data included:
155 * fcnlbl The label for standard messages
156 * msgbuf A buffer to contain the edited message
159 static char fcnlbl
[MM_MXLABELLN
+1]; /* Buffer for message label */
160 static char msgbuf
[MM_MXTXTLN
+1]; /* Buffer for message text */
166 * This function initializes the message handling functions.
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]
181 char *q
; /* Local multi-use pointer */
183 /* Use only the simple filename if there is a slash in the name */
184 if (!(q
= strrchr(p
, '/'))) {
190 /* Build the label for messages */
191 (void) snprintf(fcnlbl
, MM_MXLABELLN
, "UX:%s", q
);
193 /* Restrict messages to the text-component */
194 (void) putenv("MSGVERB=text");
199 * void wrtmsg(severity, action, tag, text[, txtarg1[, txtarg2[, ...]]])
201 * This function writes a message using fmtmsg()
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()
217 wrtmsg(int severity
, char *action
, char *tag
, char *text
, ...)
219 int errorflg
; /* TRUE if problem generating message */
220 va_list argp
; /* Pointer into vararg list */
223 /* No problems yet */
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
;
232 (void) fmtmsg(MM_PRINT
, fcnlbl
, severity
,
233 (text
== MM_NULLTXT
) ? MM_NULLTXT
: msgbuf
, action
, tag
);
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
242 (void) fmtmsg(MM_PRINT
, fcnlbl
, MM_WARNING
,
243 gettext("Internal message buffer overflow"),
244 MM_NULLACT
, MM_NULLTAG
);
250 * These functions control the group membership list, as found in
251 * the /etc/group file.
253 * Functions included:
254 * addmember Adds a member to the membership list
255 * isamember Looks for a particular login-ID in the
258 * Datatype Definitions:
259 * struct grpmember Describes a group member
262 * membershead Pointer to the head of the list of
268 struct grpmember
*next
;
271 static struct grpmember
*membershead
;
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.
283 * p Pointer to the member name
291 struct grpmember
*new; /* Member being added */
293 new = malloc(sizeof (struct grpmember
));
294 new->membername
= strdup(p
);
295 new->next
= membershead
;
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.
309 * p Pointer to the name to search for.
312 * TRUE If 'p' is found in the members list,
319 int found
; /* TRUE if login found in list */
320 struct grpmember
*pmem
; /* Group member being examined */
323 /* Search the membership list for 'p' */
325 for (pmem
= membershead
; !found
&& pmem
; pmem
= pmem
->next
) {
326 if (strcmp(p
, pmem
->membername
) == 0)
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
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
348 * genreport Generates a report from the items in the display
350 * applygroup Add group information to the items in the display
352 * applypasswd Add extended password information to the items
353 * in the display list
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
362 * displayhead Pointer to the head of the display list. Initially
363 * references the null-item on the head of the list.
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 */
380 static struct display
*displayhead
;
386 * Initializes the display list. An empty display list contains
387 * a single element, the dummy element.
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;
407 * void adddisp(pwent)
408 * struct passwd *pwent
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).
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).
427 adddisp(struct passwd
*pwent
)
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 */
436 /* Find where this value belongs in the list */
439 while (!found
&& (current
= prev
->nextlogin
)) {
440 if ((compare
= strcmp(current
->loginID
, pwent
->pw_name
)) >= 0) {
447 /* Insert this value in the list, only if it is unique though */
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
);
454 new->freefield
= strdup(pwent
->pw_gecos
);
456 if (!pwent
->pw_shell
|| !(*pwent
->pw_shell
)) {
457 new->shell
= "/sbin/sh";
459 new->shell
= strdup(pwent
->pw_shell
);
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
;
468 /* Add new display item to the list ordered by login-ID */
469 new->nextlogin
= current
;
470 prev
->nextlogin
= new;
473 * Find the appropriate place for the new item in the list
478 while (!found
&& (current
= prev
->nextuid
)) {
479 if (current
->userID
> pwent
->pw_uid
) {
486 /* Add the item into the list that is ordered by user-ID */
487 new->nextuid
= current
;
494 * int isuidindisp(pwent)
495 * struct passwd *pwent
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
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.
507 * pwent Structure that contains the user-ID we're to
511 * TRUE if the user-ID was found, FALSE otherwise.
515 isuidindisp(struct passwd
*pwent
)
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
527 for (dp
= displayhead
->nextuid
; dp
&& (dp
->userID
< pwent
->pw_uid
);
533 * If the pointer "dp" points to a structure that has a
534 * matching user-ID, return TRUE. Otherwise FALSE
536 return (dp
&& (dp
->userID
== pwent
->pw_uid
));
541 * void applygroup(allgroups)
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.
549 * allgroups FLAG. TRUE if secondary group info is to be
550 * applied -- FALSE otherwise.
556 applygroup(int allgroups
)
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 */
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
);
577 /* For each group-ID in the /etc/group file ... */
578 while (grent
= getgrent()) {
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
586 for (dp
= displayhead
->nextuid
; dp
; dp
= dp
->nextuid
) {
587 if ((dp
->groupID
== grent
->gr_gid
) && !dp
->groupname
) {
589 p
= strdup(grent
->gr_name
);
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.
602 for (pp
= grent
->gr_mem
; *pp
; pp
++) {
604 for (dp
= displayhead
->nextlogin
; !done
&& dp
;
605 dp
= dp
->nextlogin
) {
606 if (((compare
= strcmp(dp
->loginID
,
608 !(grent
->gr_gid
== dp
->groupID
)) {
610 p
= strdup(grent
->gr_name
);
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
;
620 for (psrch
= dp
->secgrplist
;
622 psrch
= psrch
->next
) {
625 psrch
->next
= psecgrp
;
628 } else if (compare
> 0) {
635 /* Close the /etc/group file */
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.
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 */
664 /* Make sure the shadow file is rewound */
669 * For each item in the display list...
672 for (dp
= displayhead
->nextuid
; dp
; dp
= dp
->nextuid
) {
674 /* Allocate structure space for the password description */
675 ppasswd
= malloc(sizeof (struct pwdinfo
));
676 dp
->passwdinfo
= ppasswd
;
679 * If there's no entry in the /etc/shadow file, assume
680 * that the login is locked
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;
693 * Otherwise, fill in the password information from the
694 * info in the shadow entry
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";
709 ppasswd
->passwdstatus
= "UN";
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
716 * the number of days a login can be inactive before
717 * it expires, and the login expiration date
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
;
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)
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...
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));
750 ppasswd
->datechg
= 0L;
754 * Convert the passwd expiration date from days-since-epoch
755 * to mmddyy (long integer) form using the same algorithm and
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));
766 ppasswd
->expdate
= 0L;
770 /* Close the shadow password file */
776 * int hasnopasswd(pwent)
777 * struct passwd *pwent
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
787 * pwent Login to examine.
790 * TRUE if the login can be used without a password, FALSE
795 hasnopasswd(struct passwd
*pwent
)
797 struct spwd
*psp
; /* /etc/shadow file struct */
798 int nopwflag
; /* TRUE if login has no passwd */
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 ("")
808 /* Get the login's entry in the shadow password file */
809 if (psp
= getspnam(pwent
->pw_name
)) {
811 /* Look at the encrypted password in that entry */
812 if (psp
->sp_pwdp
== (char *)0 ||
813 *psp
->sp_pwdp
== '\0') {
828 * void writeunformatted(current, xtndflag, expflag)
829 * struct display *current
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.
842 * current Structure containing information to write.
843 * xtndflag TRUE if extended information is to be written,
845 * expflag TRUE if password expiration information is to
846 * be written, FALSE otherwise
852 writeunformatted(struct display
*current
, int xtndflag
, int expflag
)
854 struct secgrp
*psecgrp
; /* Secondary group info */
855 struct pwdinfo
*pwdinfo
; /* Password aging info */
857 /* Write the general information */
858 (void) fprintf(stdout
, "%s:%u:%s:%u:%s",
861 current
->groupname
== NULL
? "" : current
->groupname
,
866 * If the group information is there, write it (it's only
867 * there if it's supposed to be written)
869 for (psecgrp
= current
->secgrplist
; psecgrp
; psecgrp
= psecgrp
->next
) {
870 (void) fprintf(stdout
, ":%s:%u",
871 psecgrp
->groupname
, psecgrp
->groupID
);
874 /* If the extended info flag is TRUE, write the extended information */
876 pwdinfo
= current
->passwdinfo
;
877 (void) fprintf(stdout
, ":%s:%s:%s:%6.6ld:%ld:%ld:%ld",
878 current
->iwd
, current
->shell
,
879 pwdinfo
->passwdstatus
,
881 pwdinfo
->mindaystilchg
, pwdinfo
->maxdaystilchg
,
882 pwdinfo
->warninterval
);
885 /* If the password expiration information is requested, write it. */
887 pwdinfo
= current
->passwdinfo
;
888 (void) fprintf(stdout
, ":%ld:%ld",
889 pwdinfo
->inactive
, pwdinfo
->expdate
);
892 /* Terminate the information with a new-line */
893 (void) putc('\n', stdout
);
898 * void writeformatted(current, xtndflag, expflag)
899 * struct display *current
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.
912 * current Structure containing info to write.
913 * xtndflag TRUE if extended information to be written,
915 * expflag TRUE if password expiration information to be written,
922 writeformatted(struct display
*current
, int xtndflag
, int expflag
)
924 struct secgrp
*psecgrp
; /* Secondary group info */
925 struct pwdinfo
*pwdinfo
; /* Password aging info */
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
);
934 * Write information about secondary groups if the info exists
935 * (it only exists if it is to be written)
937 for (psecgrp
= current
->secgrplist
; psecgrp
; psecgrp
= psecgrp
->next
) {
938 (void) fprintf(stdout
, " %-14s %-6u\n",
939 psecgrp
->groupname
, psecgrp
->groupID
);
943 * If the extended information flag is TRUE,
944 * write the extended information
948 pwdinfo
= current
->passwdinfo
;
949 (void) fprintf(stdout
, " %s\n",
951 (void) fprintf(stdout
, " %s\n",
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
);
962 * If the password expiration info flag is TRUE,
963 * write that information
966 pwdinfo
= current
->passwdinfo
;
967 (void) fprintf(stdout
, " %ld %6.6ld\n",
968 pwdinfo
->inactive
, pwdinfo
->expdate
);
974 * void genuidreport(pipeflag, xtndflag, expflag)
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.
992 * TRUE if a parsable report is needed,
993 * FALSE if a columnar report is needed
995 * TRUE if extended set of info is to be displayed,
998 * TRUE if password expiration information is to be
999 * displayed, FALSE otherwise
1005 genuidreport(int pipeflag
, int xtndflag
, int expflag
)
1008 struct display
*current
; /* Data being displayed */
1012 * Initialization for loop.
1013 * (NOTE: The first element in the list of logins to display is
1016 current
= displayhead
;
1019 * Display elements in the list
1022 for (current
= displayhead
->nextuid
; current
;
1023 current
= current
->nextuid
) {
1024 writeunformatted(current
, xtndflag
, expflag
);
1027 for (current
= displayhead
->nextuid
; current
;
1028 current
= current
->nextuid
) {
1029 writeformatted(current
, xtndflag
, expflag
);
1036 * void genlogreport(pipeflag, xtndflag, expflag)
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.
1054 * TRUE if a parsable report is needed,
1055 * FALSE if a columnar report is needed
1057 * TRUE if extended set of info is to be displayed,
1060 * TRUE if password expiration information is to
1061 * be displayed, FALSE otherwise
1067 genlogreport(int pipeflag
, int xtndflag
, int expflag
)
1069 struct display
*p
; /* Value being displayed */
1073 * Initialization for loop.
1074 * (NOTE: The first element in the list of logins to display is
1080 * Display elements in the list
1083 for (p
= displayhead
->nextlogin
; p
; p
= p
->nextlogin
) {
1084 writeunformatted(p
, xtndflag
, expflag
);
1087 for (p
= displayhead
->nextlogin
; p
; p
= p
->nextlogin
) {
1088 writeformatted(p
, xtndflag
, expflag
);
1094 struct localpw
*next
;
1098 struct localpw
*pwtable
= NULL
;
1100 /* Local passwd pointer for getpwent() -- -1 means not in use, NULL for EOF */
1101 struct localpw
*pwptr
;
1103 int in_localgetpwent
= 0; /* Set if in local_getpwent */
1105 static struct localpw
*
1106 fill_localpw(struct localpw
*lpw
, struct passwd
*pw
) {
1107 struct localpw
*cur
;
1110 * Copy the data -- we have to alloc areas for it all
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
);
1123 lpw
->next
= malloc(sizeof (struct localpw
));
1128 build_localpw(struct reqlogin
*req_head
)
1130 struct localpw
*next
, *cur
;
1132 struct reqlogin
*req_next
;
1134 next
= malloc(sizeof (struct localpw
));
1138 req_next
= req_head
;
1140 while (req_next
!= NULL
) {
1141 if ((pw
= getpwnam(req_next
->loginname
)) != NULL
) {
1143 * Copy the data -- we have to alloc areas for it all
1145 cur
= fill_localpw(next
, pw
);
1146 req_next
->found
= TRUE
;
1150 req_next
= req_next
->next
;
1153 if (req_head
== NULL
) {
1154 while ((pw
= getpwent()) != NULL
) {
1156 * Copy the data -- we have to alloc areas for it all
1158 cur
= fill_localpw(next
, pw
);
1163 if (pwtable
== next
) {
1174 local_getpwent(void)
1176 if (!in_localgetpwent
) {
1177 in_localgetpwent
= 1;
1179 } else if (pwptr
!= NULL
) {
1180 pwptr
= pwptr
->next
;
1184 return (&(pwptr
->pw
));
1190 local_endpwent(void)
1192 in_localgetpwent
= 0;
1198 return ((long)pwptr
);
1202 local_pwseek(long ptr
)
1204 pwptr
= (struct localpw
*)ptr
;
1208 * logins [-admopstux] [-l logins] [-g groups]
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.
1217 * -a Display password expiration information.
1218 * -d list all logins that share user-IDs with another
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
1228 * -o generate a report as a colon-list instead of in a
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
1241 * 0 All's well that ends well
1246 main(int argc
, char *argv
[])
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() */
1279 (void) setlocale(LC_ALL
, "");
1281 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
1282 #define TEXT_DOMAIN "SYS_TEST"
1284 (void) textdomain(TEXT_DOMAIN
);
1286 /* Initializations */
1291 /* Command-line processing */
1293 /* Initializations */
1307 while (!errflg
&& ((c
= getopt(argc
, argv
, OPTSTR
)) != EOF
)) {
1309 /* Case on the option character */
1314 * Display password expiration information
1326 * Display logins which share user-IDs with other logins
1337 * -g <groups> option:
1338 * Display the specified groups
1351 * -l <logins> option:
1352 * Display the specified logins
1366 * Display multiple group information
1378 * Display information as a colon-list
1390 * Select logins that have no password
1402 * Select system logins
1414 * Sort alphabetically by login-ID instead of numerically
1427 * Select user logins
1439 * Display extended info (init working dir, shell, pwd info)
1449 default: /* Oops.... */
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
));
1461 * The following section does preparation work, setting up for
1462 * building the list of logins to display
1467 * If -l logins is on the command line, build a list of
1468 * logins we're to generate reports for.
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
;
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
;
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.
1494 build_localpw(reqloginhead
);
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.
1503 build_localpw(NULL
);
1507 * If the -g groups option was on the command line, build a
1508 * list containing groups we're to list logins for.
1514 if (token
= strtok(g_arg
, ",")) {
1515 pgrp
= malloc(sizeof (struct reqgrp
));
1516 pgrp
->groupname
= token
;
1521 while (token
= strtok(NULL
, ",")) {
1522 pgrp
= malloc(sizeof (struct reqgrp
));
1523 pgrp
->groupname
= token
;
1534 * Generate the list of login information to display
1537 /* Initialize the login list */
1542 * If -g groups was specified, generate a list of members
1543 * of the specified groups
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
) {
1552 * Remembering the group-ID for later
1556 pgrp
->groupID
= grent
->gr_gid
;
1557 for (pp
= grent
->gr_mem
; *pp
; pp
++) {
1561 wrtmsg(MM_WARNING
, MM_NULLACT
, MM_NULLTAG
,
1562 gettext("%s was not found"),
1569 /* Initialize the list of logins to display */
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.)
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.
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.
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.
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:
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
1620 /* For each entry in the passwd database ... */
1621 while (plookpwd
= local_getpwent()) {
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.
1627 if (!isuidindisp(plookpwd
)) {
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.
1637 lookpos
= local_pwtell();
1640 * For each record in the passwd database
1641 * beyond the searching record ...
1643 while (pwent
= local_getpwent()) {
1646 * If there's a match between the
1647 * searcher's user-ID and the
1648 * searchee's user-ID ...
1650 if (pwent
->pw_uid
== plookpwd
->pw_uid
) {
1652 * If this is the first
1653 * duplicate of this searcher
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")
1661 if (done
== FALSE
) {
1667 * Now add the searchee's
1674 /* Reposition to searcher record */
1675 local_pwseek(lookpos
);
1684 * Loop through the passwd database squirelling away the
1685 * information we need for the display.
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).
1691 doall
= !(s_seen
|| u_seen
|| p_seen
|| d_seen
|| l_seen
|| g_seen
);
1693 if (doall
|| s_seen
|| u_seen
|| p_seen
|| l_seen
|| g_seen
) {
1695 while (pwent
= local_getpwent()) {
1699 * If no user-specific options were specified,
1700 * include this login-ID
1708 * If the user specified system login-IDs,
1709 * and this is a system ID, include it
1712 if (isasystemlogin(pwent
)) {
1719 * If the user specified user login-IDs,
1720 * and this is a user ID, include it
1723 if (isauserlogin(pwent
)) {
1730 * If the user is asking for login-IDs that have
1731 * no password, and this one has no password, include it
1734 if (hasnopasswd(pwent
)) {
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.
1746 for (plogin
= reqloginhead
; !done
&& plogin
;
1747 plogin
= plogin
->next
) {
1748 if (strcmp(pwent
->pw_name
,
1749 plogin
->loginname
) == 0) {
1750 plogin
->found
= TRUE
;
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.
1767 for (pgrp
= reqgrphead
; !done
&& pgrp
;
1768 pgrp
= pgrp
->next
) {
1769 if (pwent
->pw_gid
== pgrp
->groupID
) {
1774 if (!done
&& isamember(pwent
->pw_name
)) {
1786 /* Let the user know about logins they requested that don't exist */
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"),
1797 /* Apply group information */
1802 * Apply password information (only needed if the extended
1803 * set of information has been requested)
1805 if (x_seen
|| a_seen
)
1810 * Generate a report from this display items we've squirreled away
1814 genlogreport(o_seen
, x_seen
, a_seen
);
1816 genuidreport(o_seen
, x_seen
, a_seen
);
1818 /* We're through! */