logins.c revision b816ddf83939c2b433da956720fad32dfb172096
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.15.1.2 */
/*
* logins.c
*
* This file contains the source for the administrative command
* "logins" (available to the administrator) that produces a report
* containing login-IDs and other requested information.
*/
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <grp.h>
#include <pwd.h>
#include <shadow.h>
#include <time.h>
#include <stdarg.h>
#include <fmtmsg.h>
#include <locale.h>
/*
* Local constant definitions
* TRUE Boolean constant
* FALSE Boolean constant
* USAGE_MSG Message used to display a usage error
* MAXLOGINSIZE Maximum length of a valid login-ID
* MAXSYSTEMLOGIN Maximum value of a system user-ID.
* OPTSTR Options to this command
* ROOT_ID The user-ID of an administrator
*/
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE ((int)'t')
#endif
#define USAGE_MSG "usage: logins [-admopstux] [-g groups] [-l logins]"
#define MAXLOGINSIZE 14
#define MAXSYSTEMLOGIN 99
#define OPTSTR "adg:l:mopstux"
#define ROOT_ID 0
/*
* The following macros do their function for now but will probably have
* to be replaced by functions sometime in the near future. The maximum
* system login value may someday be administerable, in which case these
* will have to be changed to become functions
*
* isasystemlogin Returns TRUE if the user-ID in the "struct passwd"
* structure referenced by the function's argument is
* less than or equal to the maximum value for a system
* user-ID, FALSE otherwise.
* isauserlogin Returns TRUE if the user-ID in the "struct passwd"
* structure referenced by the function's argument is
* greater than the maximum value for a system user-ID,
* FALSE otherwise.
*/
#define isauserlogin(pw) (pw->pw_uid > MAXSYSTEMLOGIN)
#define isasystemlogin(pw) (pw->pw_uid <= MAXSYSTEMLOGIN)
/*
* Local datatype definitions
* struct reqgrp Describes a group as requested through the
* -g option
* struct reqlogin Describes a login-ID as requested through
* the -l option
* struct pwdinfo Describes a password's aging information,
* as extracted from /etc/shadow
* struct secgrp Describes a login-ID's secondary group
*/
/* Describes a specified group name (from the -g groups option) */
struct reqgrp {
char *groupname; /* Requested group name */
struct reqgrp *next; /* Next item in the list */
gid_t groupID; /* Group's ID */
};
/* Describes a specified login name (from the -l logins option) */
struct reqlogin {
char *loginname; /* Requested login name */
struct reqlogin *next; /* Next item in the list */
int found; /* TRUE if login in /etc/passwd */
};
/*
* This structure describes a password's information
*/
struct pwdinfo {
long datechg; /* Date the password was changed (mmddyy) */
char *passwdstatus; /* Password status */
long mindaystilchg; /* Min days b4 pwd can change again */
long maxdaystilchg; /* Max days b4 pwd can change again */
long warninterval; /* Days before expire to warn user */
long inactive; /* Lapsed days of inactivity before lock */
long expdate; /* Date of expiration (mmddyy) */
};
/* This structure describes secondary groups that a user belongs to */
struct secgrp {
char *groupname; /* Name of the group */
struct secgrp *next; /* Next item in the list */
gid_t groupID; /* Group-ID */
};
/*
* These functions handle error and warning message writing.
* (This deals with UNIX(r) standard message generation, so
* the rest of the code doesn't have to.)
*
* Functions included:
* initmsg Initialize the message handling functions.
* wrtmsg Write the message using fmtmsg().
*
* Static data included:
* fcnlbl The label for standard messages
* msgbuf A buffer to contain the edited message
*/
static char fcnlbl[MM_MXLABELLN+1]; /* Buffer for message label */
static char msgbuf[MM_MXTXTLN+1]; /* Buffer for message text */
/*
* void initmsg(p)
*
* This function initializes the message handling functions.
*
* Arguments:
* p A pointer to a character string that is the name of the
* function, used to generate the label on messages. If this
* string contains a slash ('/'), it only uses the characters
* beyond the last slash in the string (this permits argv[0]
* to be used).
*
* Returns: Void
*/
static void
initmsg(char *p)
{
char *q; /* Local multi-use pointer */
/* Use only the simple filename if there is a slash in the name */
if (!(q = strrchr(p, '/'))) {
q = p;
} else {
q++;
}
/* Build the label for messages */
(void) snprintf(fcnlbl, MM_MXLABELLN, "UX:%s", q);
/* Restrict messages to the text-component */
(void) putenv("MSGVERB=text");
}
/*
* void wrtmsg(severity, action, tag, text[, txtarg1[, txtarg2[, ...]]])
*
* This function writes a message using fmtmsg()
*
* Arguments:
* severity The severity-component of the message
* action The action-string used to generate the
* action-component of the message
* tag Tag-component of the message
* text The text-string used to generate the text-
* component of the message
* txtarg Arguments to be inserted into the "text"
* string using vsprintf()
*
* Returns: Void
*/
/*PRINTFLIKE4*/
static void
wrtmsg(int severity, char *action, char *tag, char *text, ...)
{
int errorflg; /* TRUE if problem generating message */
va_list argp; /* Pointer into vararg list */
/* No problems yet */
errorflg = FALSE;
/* Generate the error message */
va_start(argp, text);
if (text != MM_NULLTXT) {
errorflg = vsnprintf(msgbuf,
MM_MXTXTLN, text, argp) > MM_MXTXTLN;
}
(void) fmtmsg(MM_PRINT, fcnlbl, severity,
(text == MM_NULLTXT) ? MM_NULLTXT : msgbuf, action, tag);
va_end(argp);
/*
* If there was a buffer overflow generating the error message,
* write a message and quit (things are probably corrupt in the
* static data space now
*/
if (errorflg) {
(void) fmtmsg(MM_PRINT, fcnlbl, MM_WARNING,
gettext("Internal message buffer overflow"),
MM_NULLACT, MM_NULLTAG);
exit(100);
}
}
/*
* These functions control the group membership list, as found in
* the /etc/group file.
*
* Functions included:
* addmember Adds a member to the membership list
* isamember Looks for a particular login-ID in the
* list of members
*
* Datatype Definitions:
* struct grpmember Describes a group member
*
* Static Data:
* membershead Pointer to the head of the list of
* group members
*/
struct grpmember {
char *membername;
struct grpmember *next;
};
static struct grpmember *membershead;
/*
* void addmember(p)
* char *p
*
* This function adds a member to the group member's list. The
* group members list is a list of structures containing a pointer
* to the member-name and a pointer to the next item in the
* structure. The structure is not ordered in any particular way.
*
* Arguments:
* p Pointer to the member name
*
* Returns: Void
*/
static void
addmember(char *p)
{
struct grpmember *new; /* Member being added */
new = malloc(sizeof (struct grpmember));
new->membername = strdup(p);
new->next = membershead;
membershead = new;
}
/*
* init isamember(p)
* char *p
*
* This function examines the list of group-members for the string
* referenced by 'p'. If 'p' is a member of the members list, the
* function returns TRUE. Otherwise it returns FALSE.
*
* Arguments:
* p Pointer to the name to search for.
*
* Returns: int
* TRUE If 'p' is found in the members list,
* FALSE otherwise
*/
static int
isamember(char *p)
{
int found; /* TRUE if login found in list */
struct grpmember *pmem; /* Group member being examined */
/* Search the membership list for 'p' */
found = FALSE;
for (pmem = membershead; !found && pmem; pmem = pmem->next) {
if (strcmp(p, pmem->membername) == 0)
found = TRUE;
}
return (found);
}
/*
* These functions handle the display list. The display list contains
* all of the information we're to display. The list contains a pointer
* to the login-name, a pointer to the free-field (comment), and a
* pointer to the next item in the list. The list is ordered alpha-
* betically (ascending) on the login-name field. The list initially
* contains a dummy field (to make insertion easier) that contains a
* login-name of "".
*
* Functions included:
* initdisp Initializes the display list
* adddisp Adds information to the display list
* isuidindisp Looks to see if a particular user-ID is in the
* display list
* genreport Generates a report from the items in the display
* list
* applygroup Add group information to the items in the display
* list
* applypasswd Add extended password information to the items
* in the display list
*
* Datatypes Defined:
* struct display Describes the structure that contains the information
* to be displayed. Includes pointers to the login-ID,
* free-field (comment), and the next structure in the
* list.
*
* Static Data:
* displayhead Pointer to the head of the display list. Initially
* references the null-item on the head of the list.
*/
struct display {
char *loginID; /* Login name */
char *freefield; /* Free (comment) field */
char *groupname; /* Name of the primary group */
char *iwd; /* Initial working directory */
char *shell; /* Shell after login (may be null) */
struct pwdinfo *passwdinfo; /* Password information structure */
struct secgrp *secgrplist; /* Head of the secondary group list */
uid_t userID; /* User ID */
gid_t groupID; /* Group ID of primary group */
struct display *nextlogin; /* Next login in the list */
struct display *nextuid; /* Next user-ID in the list */
};
static struct display *displayhead;
/*
* void initdisp()
*
* Initializes the display list. An empty display list contains
* a single element, the dummy element.
*
* Arguments: None
*
* Returns: Void
*/
static void
initdisp(void)
{
displayhead = malloc(sizeof (struct display));
displayhead->nextlogin = NULL;
displayhead->nextuid = NULL;
displayhead->loginID = "";
displayhead->freefield = "";
displayhead->userID = -1;
}
/*
* void adddisp(pwent)
* struct passwd *pwent
*
* This function adds the appropriate information from the login
* description referenced by 'pwent' to the list if information
* to be displayed. It only adds the information if the login-ID
* (user-name) is unique. It inserts the information in the list
* in such a way that the list remains ordered alphabetically
* (ascending) according to the login-ID (user-name).
*
* Arguments:
* pwent Structure that contains all of the login information
* of the login being added to the list. The only
* information that this function uses is the login-ID
* (user-name) and the free-field (comment field).
*
* Returns: Void
*/
static void
adddisp(struct passwd *pwent)
{
struct display *new; /* Item being added to the list */
struct display *prev; /* Previous item in the list */
struct display *current; /* Next item in the list */
int found; /* FLAG, insertion point found */
int compare = 1; /* strcmp() compare value */
/* Find where this value belongs in the list */
prev = displayhead;
found = FALSE;
while (!found && (current = prev->nextlogin)) {
if ((compare = strcmp(current->loginID, pwent->pw_name)) >= 0) {
found = TRUE;
} else {
prev = current;
}
}
/* Insert this value in the list, only if it is unique though */
if (compare != 0) {
new = malloc(sizeof (struct display));
new->loginID = strdup(pwent->pw_name);
if (pwent->pw_comment && pwent->pw_comment[0] != '\0') {
new->freefield = strdup(pwent->pw_comment);
} else {
new->freefield = strdup(pwent->pw_gecos);
}
if (!pwent->pw_shell || !(*pwent->pw_shell)) {
new->shell = "/sbin/sh";
} else {
new->shell = strdup(pwent->pw_shell);
}
new->iwd = strdup(pwent->pw_dir);
new->userID = pwent->pw_uid;
new->groupID = pwent->pw_gid;
new->secgrplist = NULL;
new->passwdinfo = NULL;
new->groupname = NULL;
/* Add new display item to the list ordered by login-ID */
new->nextlogin = current;
prev->nextlogin = new;
/*
* Find the appropriate place for the new item in the list
* ordered by userID
*/
prev = displayhead;
found = FALSE;
while (!found && (current = prev->nextuid)) {
if (current->userID > pwent->pw_uid) {
found = TRUE;
} else {
prev = current;
}
}
/* Add the item into the list that is ordered by user-ID */
new->nextuid = current;
prev->nextuid = new;
}
}
/*
* int isuidindisp(pwent)
* struct passwd *pwent
*
* This function examines the display list to see if the uid in
* the (struct passwd) referenced by "pwent" is already in the
* display list. It returns TRUE if it is in the list, FALSE
* otherwise.
*
* Since the display list is ordered by user-ID, the search continues
* until a match is found or a user-ID is found that is larger than
* the one we're searching for.
*
* Arguments:
* pwent Structure that contains the user-ID we're to
* look for
*
* Returns: int
* TRUE if the user-ID was found, FALSE otherwise.
*/
static int
isuidindisp(struct passwd *pwent)
{
struct display *dp;
/*
* Search the list, beginning at the beginning (where else?)
* and stopping when the user-ID is found or one is found that
* is greater than the user-ID we're searching for. Recall
* that this list is ordered by user-ID
*/
for (dp = displayhead->nextuid; dp && (dp->userID < pwent->pw_uid);
dp = dp->nextuid) {
continue;
}
/*
* If the pointer "dp" points to a structure that has a
* matching user-ID, return TRUE. Otherwise FALSE
*/
return (dp && (dp->userID == pwent->pw_uid));
}
/*
* void applygroup(allgroups)
* int allgroups
*
* This function applies group information to the login-IDs in the
* display list. It always applies the primary group information.
* If "allgroups" is TRUE, it applies secondary information as well.
*
* Arguments:
* allgroups FLAG. TRUE if secondary group info is to be
* applied -- FALSE otherwise.
*
* Returns: void
*/
static void
applygroup(int allgroups)
{
struct display *dp; /* Display list running ptr */
struct group *grent; /* Group info, from getgrent() */
char *p; /* Temp char pointer */
char **pp; /* Temp char * pointer */
int compare; /* Value from strcmp() */
int done; /* TRUE if finished, FALSE otherwise */
struct secgrp *psecgrp; /* Block allocated for this info */
struct secgrp *psrch; /* Secondary group -- for searching */
if (!allgroups) {
/* short circute getting all the groups */
for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
if ((grent = getgrgid(dp->groupID)) != NULL) {
dp->groupname = strdup(grent->gr_name);
}
}
return;
}
/* For each group-ID in the /etc/group file ... */
while (grent = getgrent()) {
/*
* Set the primary group for the login-IDs in the display
* list. For each group-ID we get, leaf through the display
* list and set the group-name if the group-IDs match
*/
p = NULL;
for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
if ((dp->groupID == grent->gr_gid) && !dp->groupname) {
if (!p) {
p = strdup(grent->gr_name);
}
dp->groupname = p;
}
}
/*
* If we're to be displaying secondary group membership,
* leaf through the list of group members. Then, attempt
* to find that member in the display list. When found,
* attach secondary group info to the user.
*/
for (pp = grent->gr_mem; *pp; pp++) {
done = FALSE;
for (dp = displayhead->nextlogin; !done && dp;
dp = dp->nextlogin) {
if (((compare = strcmp(dp->loginID,
*pp)) == 0) &&
!(grent->gr_gid == dp->groupID)) {
if (!p) {
p = strdup(grent->gr_name);
}
psecgrp = malloc(
sizeof (struct secgrp));
psecgrp->groupID = grent->gr_gid;
psecgrp->groupname = p;
psecgrp->next = NULL;
if (!dp->secgrplist) {
dp->secgrplist = psecgrp;
} else {
for (psrch = dp->secgrplist;
psrch->next;
psrch = psrch->next) {
continue;
}
psrch->next = psecgrp;
}
done = TRUE;
} else if (compare > 0) {
done = TRUE;
}
}
}
}
/* Close the /etc/group file */
endgrent();
}
/*
* void applypasswd()
*
* This function applies extended password information to an item
* to be displayed. It allocates space for a structure describing
* the password, then fills in that structure from the information
* in the /etc/shadow file.
*
* Arguments: None
*
* Returns: Void
*/
static void
applypasswd(void)
{
struct pwdinfo *ppasswd; /* Ptr to pwd desc in current element */
struct display *dp; /* Ptr to current element */
struct spwd *psp; /* Pointer to a shadow-file entry */
struct tm *ptm; /* Pointer to a time-of-day structure */
time_t pwchg; /* System-time of last pwd chg */
time_t pwexp; /* System-time of password expiration */
/* Make sure the shadow file is rewound */
setspent();
/*
* For each item in the display list...
*/
for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
/* Allocate structure space for the password description */
ppasswd = malloc(sizeof (struct pwdinfo));
dp->passwdinfo = ppasswd;
/*
* If there's no entry in the /etc/shadow file, assume
* that the login is locked
*/
if ((psp = getspnam(dp->loginID)) == NULL) {
pwchg = 0L; /* Epoch */
ppasswd->passwdstatus = "LK"; /* LK, Locked */
ppasswd->mindaystilchg = 0L;
ppasswd->maxdaystilchg = 0L;
ppasswd->warninterval = 0L;
ppasswd->inactive = 0L;
pwexp = 0L;
} else {
/*
* Otherwise, fill in the password information from the
* info in the shadow entry
*/
if (psp->sp_pwdp == NULL || (*psp->sp_pwdp) == '\0')
ppasswd->passwdstatus = "NP";
else if (strncmp(psp->sp_pwdp, LOCKSTRING,
sizeof (LOCKSTRING)-1) == 0)
ppasswd->passwdstatus = "LK";
else if (strncmp(psp->sp_pwdp, NOLOGINSTRING,
sizeof (NOLOGINSTRING)-1) == 0)
ppasswd->passwdstatus = "NL";
else if ((strlen(psp->sp_pwdp) == 13 &&
psp->sp_pwdp[0] != '$') ||
psp->sp_pwdp[0] == '$')
ppasswd->passwdstatus = "PS";
else
ppasswd->passwdstatus = "UN";
/*
* Set up the last-changed date,
* the minimum days between changes,
* the maximum life of a password,
* the interval before expiration that the user
* is warned,
* the number of days a login can be inactive before
* it expires, and the login expiration date
*/
pwchg = psp->sp_lstchg;
ppasswd->mindaystilchg = psp->sp_min;
ppasswd->maxdaystilchg = psp->sp_max;
ppasswd->warninterval = psp->sp_warn;
ppasswd->inactive = psp->sp_inact;
pwexp = psp->sp_expire;
}
/*
* Convert the date of the last password change from days-
* since-epoch to mmddyy (integer) form. Involves the
* intermediate step of converting the date from days-
* since-epoch to seconds-since-epoch. We'll set this to
* somewhere near the middle of the day, since there are not
* always 24*60*60 seconds in a year. (Yeech)
*
* Note: The form mmddyy should probably be subject to
* internationalization -- Non-Americans will think that
* 070888 is 07 August 88 when the software is trying to say
* 08 July 88. Systems Engineers seem to think that this isn't
* a problem though...
*/
if (pwchg != -1L) {
pwchg = (pwchg * DAY) + (DAY/2);
ptm = localtime(&pwchg);
ppasswd->datechg = ((long)(ptm->tm_mon+1) * 10000L) +
(long)((ptm->tm_mday * 100) +
(ptm->tm_year % 100));
} else {
ppasswd->datechg = 0L;
}
/*
* Convert the passwd expiration date from days-since-epoch
* to mmddyy (long integer) form using the same algorithm and
* comments as above.
*/
if (pwexp != -1L) {
pwexp = (pwexp * DAY) + (DAY/2);
ptm = localtime(&pwexp);
ppasswd->expdate = ((long)(ptm->tm_mon+1) * 10000L) +
(long)((ptm->tm_mday * 100) +
(ptm->tm_year % 100));
} else {
ppasswd->expdate = 0L;
}
}
/* Close the shadow password file */
endspent();
}
/*
* int hasnopasswd(pwent)
* struct passwd *pwent
*
* This function examines a login's password-file entry
* and, if necessary, its shadow password-file entry and
* returns TRUE if that user-ID has no password, meaning
* that the user-ID can be used to log into the system
* without giving a password. The function returns FALSE
* otherwise.
*
* Arguments:
* pwent Login to examine.
*
* Returns: int
* TRUE if the login can be used without a password, FALSE
* otherwise.
*/
static int
hasnopasswd(struct passwd *pwent)
{
struct spwd *psp; /* /etc/shadow file struct */
int nopwflag; /* TRUE if login has no passwd */
/*
* A login has no password if:
* 1. There exists an entry for that login in the
* shadow password-file (/etc/passwd), and
* 2. The encrypted password in the structure describing
* that entry is either: NULL or a null string ("")
*/
/* Get the login's entry in the shadow password file */
if (psp = getspnam(pwent->pw_name)) {
/* Look at the encrypted password in that entry */
if (psp->sp_pwdp == (char *)0 ||
*psp->sp_pwdp == '\0') {
nopwflag = TRUE;
} else {
nopwflag = FALSE;
}
} else {
nopwflag = FALSE;
}
/* Done ... */
return (nopwflag);
}
/*
* void writeunformatted(current, xtndflag, expflag)
* struct display *current
* int xtndflag
* int expflag
*
* This function writes the data in the display structure "current"
* to the standard output file. It writes the information in the
* form of a colon-list. It writes secondary group information if
* that information is in the structure, it writes extended
* (initial working directory, shell, and password-aging) info
* if the "xtndflag" is TRUE, and it writes password expiration
* information if "expflag" is TRUE.
*
* Arguments:
* current Structure containing information to write.
* xtndflag TRUE if extended information is to be written,
* FALSE otherwise
* expflag TRUE if password expiration information is to
* be written, FALSE otherwise
*
* Returns: void
*/
static void
writeunformatted(struct display *current, int xtndflag, int expflag)
{
struct secgrp *psecgrp; /* Secondary group info */
struct pwdinfo *pwdinfo; /* Password aging info */
/* Write the general information */
(void) fprintf(stdout, "%s:%ld:%s:%ld:%s",
current->loginID,
current->userID,
current->groupname == NULL ? "" : current->groupname,
current->groupID,
current->freefield);
/*
* If the group information is there, write it (it's only
* there if it's supposed to be written)
*/
for (psecgrp = current->secgrplist; psecgrp; psecgrp = psecgrp->next) {
(void) fprintf(stdout, ":%s:%ld",
psecgrp->groupname, psecgrp->groupID);
}
/* If the extended info flag is TRUE, write the extended information */
if (xtndflag) {
pwdinfo = current->passwdinfo;
(void) fprintf(stdout, ":%s:%s:%s:%6.6ld:%ld:%ld:%ld",
current->iwd, current->shell,
pwdinfo->passwdstatus,
pwdinfo->datechg,
pwdinfo->mindaystilchg, pwdinfo->maxdaystilchg,
pwdinfo->warninterval);
}
/* If the password expiration information is requested, write it. */
if (expflag) {
pwdinfo = current->passwdinfo;
(void) fprintf(stdout, ":%ld:%ld",
pwdinfo->inactive, pwdinfo->expdate);
}
/* Terminate the information with a new-line */
(void) putc('\n', stdout);
}
/*
* void writeformatted(current, xtndflag, expflag)
* struct display *current
* int xtndflag
* int expflag
*
* This function writes the data in the display structure "current"
* to the standard output file. It writes the information in an
* easily readable format. It writes secondary group information
* if that information is in the structure, it writes extended
* (initial working directory, shell, and password-aging) info if
* "xtndflag" is TRUE, and it write password expiration information
* if "expflag" is TRUE.
*
* Arguments:
* current Structure containing info to write.
* xtndflag TRUE if extended information to be written,
* FALSE otherwise
* expflag TRUE if password expiration information to be written,
* FALSE otherwise
*
* Returns: void
*/
static void
writeformatted(struct display *current, int xtndflag, int expflag)
{
struct secgrp *psecgrp; /* Secondary group info */
struct pwdinfo *pwdinfo; /* Password aging info */
/* Write general information */
(void) fprintf(stdout, "%-14s %-6ld %-14s %-6ld %s\n",
current->loginID, current->userID,
current->groupname == NULL ? "" : current->groupname,
current->groupID, current->freefield);
/*
* Write information about secondary groups if the info exists
* (it only exists if it is to be written)
*/
for (psecgrp = current->secgrplist; psecgrp; psecgrp = psecgrp->next) {
(void) fprintf(stdout, " %-14s %-6ld\n",
psecgrp->groupname, psecgrp->groupID);
}
/*
* If the extended information flag is TRUE,
* write the extended information
*/
if (xtndflag) {
pwdinfo = current->passwdinfo;
(void) fprintf(stdout, " %s\n",
current->iwd);
(void) fprintf(stdout, " %s\n",
current->shell);
(void) fprintf(stdout, " %s "
"%6.6ld %ld %ld %ld\n",
pwdinfo->passwdstatus,
pwdinfo->datechg, pwdinfo->mindaystilchg,
pwdinfo->maxdaystilchg,
pwdinfo->warninterval);
}
/*
* If the password expiration info flag is TRUE,
* write that information
*/
if (expflag) {
pwdinfo = current->passwdinfo;
(void) fprintf(stdout, " %ld %6.6ld\n",
pwdinfo->inactive, pwdinfo->expdate);
}
}
/*
* void genuidreport(pipeflag, xtndflag, expflag)
* int pipeflag
* int xtndflag
* int expflag
*
* This function generates a report on the standard output
* stream (stdout) containing the login-IDs in the list of
* logins built by this command. The list is ordered based
* on user-ID. If the <pipeflag> variable is not zero, it
* will generate a report containing parsable records.
* Otherwise, it will generate a columnarized report. If
* the <xtndflag> variable is not zero, it will include the
* extended set of information (password aging info, home
* directory, shell process, etc.). If <expflag> is not
* zero, it will display password expiration information.
*
* Arguments:
* pipeflag int
* TRUE if a parsable report is needed,
* FALSE if a columnar report is needed
* xtndflag int
* TRUE if extended set of info is to be displayed,
* FALSE otherwise
* expflag int
* TRUE if password expiration information is to be
* displayed, FALSE otherwise
*
* Returns: void
*/
static void
genuidreport(int pipeflag, int xtndflag, int expflag)
{
struct display *current; /* Data being displayed */
/*
* Initialization for loop.
* (NOTE: The first element in the list of logins to display is
* a dummy element.)
*/
current = displayhead;
/*
* Display elements in the list
*/
if (pipeflag) {
for (current = displayhead->nextuid; current;
current = current->nextuid) {
writeunformatted(current, xtndflag, expflag);
}
} else {
for (current = displayhead->nextuid; current;
current = current->nextuid) {
writeformatted(current, xtndflag, expflag);
}
}
}
/*
* void genlogreport(pipeflag, xtndflag, expflag)
* int pipeflag
* int xtndflag
* int expflag
*
* This function generates a report on the standard output
* stream (stdout) containing the login-IDs in the list of
* logins built by this command. The list is ordered based
* on user name. If the <pipeflag> variable is not zero, it
* will generate a report containing parsable records.
* Otherwise, it will generate a columnarized report. If
* the <xtndflag> variable is not zero, it will include the
* extended set of information (password aging info, home
* directory, shell process, etc.). If <expflag> is not
* zero, it will include password expiration information.
*
* Arguments:
* pipeflag int
* TRUE if a parsable report is needed,
* FALSE if a columnar report is needed
* xtndflag int
* TRUE if extended set of info is to be displayed,
* FALSE otherwise
* expflag int
* TRUE if password expiration information is to
* be displayed, FALSE otherwise
*
* Returns: void
*/
static void
genlogreport(int pipeflag, int xtndflag, int expflag)
{
struct display *p; /* Value being displayed */
/*
* Initialization for loop.
* (NOTE: The first element in the list of logins to display is
* a dummy element.)
*/
p = displayhead;
/*
* Display elements in the list
*/
if (pipeflag) {
for (p = displayhead->nextlogin; p; p = p->nextlogin) {
writeunformatted(p, xtndflag, expflag);
}
} else {
for (p = displayhead->nextlogin; p; p = p->nextlogin) {
writeformatted(p, xtndflag, expflag);
}
}
}
struct localpw {
struct localpw *next;
struct passwd pw;
};
struct localpw *pwtable = NULL;
/* Local passwd pointer for getpwent() -- -1 means not in use, NULL for EOF */
struct localpw *pwptr;
int in_localgetpwent = 0; /* Set if in local_getpwent */
static struct localpw *
fill_localpw(struct localpw *lpw, struct passwd *pw) {
struct localpw *cur;
/*
* Copy the data -- we have to alloc areas for it all
*/
lpw->pw.pw_name = strdup(pw->pw_name);
lpw->pw.pw_passwd = strdup(pw->pw_passwd);
lpw->pw.pw_uid = pw->pw_uid;
lpw->pw.pw_gid = pw->pw_gid;
lpw->pw.pw_age = strdup(pw->pw_age);
lpw->pw.pw_comment = strdup(pw->pw_comment);
lpw->pw.pw_gecos = strdup(pw->pw_gecos);
lpw->pw.pw_dir = strdup(pw->pw_dir);
lpw->pw.pw_shell = strdup(pw->pw_shell);
cur = lpw;
lpw->next = malloc(sizeof (struct localpw));
return (cur);
}
void
build_localpw(struct reqlogin *req_head)
{
struct localpw *next, *cur;
struct passwd *pw;
struct reqlogin *req_next;
next = malloc(sizeof (struct localpw));
pwtable = next;
req_next = req_head;
while (req_next != NULL) {
if ((pw = getpwnam(req_next->loginname)) != NULL) {
/*
* Copy the data -- we have to alloc areas for it all
*/
cur = fill_localpw(next, pw);
req_next->found = TRUE;
next = cur->next;
}
req_next = req_next->next;
}
if (req_head == NULL) {
while ((pw = getpwent()) != NULL) {
/*
* Copy the data -- we have to alloc areas for it all
*/
cur = fill_localpw(next, pw);
next = cur->next;
}
}
if (pwtable == next) {
pwtable = NULL;
} else {
free(next);
cur->next = NULL;
}
endpwent();
}
struct passwd *
local_getpwent(void)
{
if (!in_localgetpwent) {
in_localgetpwent = 1;
pwptr = pwtable;
} else if (pwptr != NULL) {
pwptr = pwptr->next;
}
if (pwptr != NULL)
return (&(pwptr->pw));
else
return (NULL);
}
void
local_endpwent(void)
{
in_localgetpwent = 0;
}
long
local_pwtell(void)
{
return ((long)pwptr);
}
void
local_pwseek(long ptr)
{
pwptr = (struct localpw *)ptr;
}
/*
* logins [-admopstux] [-l logins] [-g groups]
*
* This command generates a report of logins administered on
* the system. The list will contain logins that meet criteria
* described by the options in the list. If there are no options,
* it will list all logins administered. It is intended to be used
* only by administrators.
*
* Options:
* -a Display password expiration information.
* -d list all logins that share user-IDs with another
* login.
* -g groups specifies the names of the groups to which a login
* must belong before it is included in the generated
* list. "groups" is a comma-list of group names.
* -l logins specifies the logins to display. "logins" is a
* comma-list of login names.
* -m in addition to the usual information, for each
* login displayed, list all groups to which that
* login is member.
* -o generate a report as a colon-list instead of in a
* columnar format
* -p list all logins that have no password.
* -s list all system logins
* -t sort the report lexicographically by login name
* instead of by user-ID
* -u list all user logins
* -x in addition to the usual information, display an
* extended set of information that includes the home
* directory, initial process, and password status and
* aging information
*
* Exit Codes:
* 0 All's well that ends well
* 1 Usage error
*/
int
main(int argc, char *argv[])
{
struct passwd *plookpwd; /* Ptr to searcher pw (-d) */
struct reqgrp *reqgrphead; /* Head of the req'd group list */
struct reqgrp *pgrp; /* Current item in req'd group list */
struct reqgrp *qgrp; /* Prev item in the req'd group list */
struct reqlogin *reqloginhead; /* Head of req'd login list */
struct reqlogin *plogin; /* Current item in req'd login list */
struct reqlogin *qlogin; /* Prev item in req'd login list */
struct passwd *pwent; /* /etc/passwd entry */
struct group *grent; /* /etc/group entry */
char *token; /* Token extracted by strtok() */
char **pp; /* Group member */
char *g_arg; /* -g option's argument */
char *l_arg; /* -l option's argument */
long lookpos; /* File pos'n, rec we're looking for */
int a_seen; /* Is -a requested? */
int d_seen; /* Is -d requested? */
int g_seen; /* Is -g requested? */
int l_seen; /* Is -l requested? */
int m_seen; /* Is -m requested? */
int o_seen; /* Is -o requested? */
int p_seen; /* Is -p requested? */
int s_seen; /* Is -s requested? */
int t_seen; /* Is -t requested? */
int u_seen; /* Is -u requested? */
int x_seen; /* Is -x requested? */
int errflg; /* Is there a command-line problem */
int done; /* Is the process (?) is complete */
int groupcount; /* Number of groups specified */
int doall; /* Are all logins to be reported */
int c; /* Character returned from getopt() */
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
/* Initializations */
initmsg(argv[0]);
/* Command-line processing */
/* Initializations */
a_seen = FALSE;
d_seen = FALSE;
g_seen = FALSE;
l_seen = FALSE;
m_seen = FALSE;
o_seen = FALSE;
p_seen = FALSE;
s_seen = FALSE;
t_seen = FALSE;
u_seen = FALSE;
x_seen = FALSE;
errflg = FALSE;
opterr = 0;
while (!errflg && ((c = getopt(argc, argv, OPTSTR)) != EOF)) {
/* Case on the option character */
switch (c) {
/*
* -a option:
* Display password expiration information
*/
case 'a':
if (a_seen)
errflg = TRUE;
else
a_seen = TRUE;
break;
/*
* -d option:
* Display logins which share user-IDs with other logins
*/
case 'd':
if (d_seen)
errflg = TRUE;
else
d_seen = TRUE;
break;
/*
* -g <groups> option:
* Display the specified groups
*/
case 'g':
if (g_seen) {
errflg = TRUE;
} else {
g_seen = TRUE;
g_arg = optarg;
}
break;
/*
* -l <logins> option:
* Display the specified logins
*/
case 'l':
if (l_seen) {
errflg = TRUE;
} else {
l_seen = TRUE;
l_arg = optarg;
}
break;
/*
* -m option:
* Display multiple group information
*/
case 'm':
if (m_seen)
errflg = TRUE;
else
m_seen = TRUE;
break;
/*
* -o option:
* Display information as a colon-list
*/
case 'o':
if (o_seen)
errflg = TRUE;
else
o_seen = TRUE;
break;
/*
* -p option:
* Select logins that have no password
*/
case 'p':
if (p_seen)
errflg = TRUE;
else
p_seen = TRUE;
break;
/*
* -s option:
* Select system logins
*/
case 's':
if (s_seen)
errflg = TRUE;
else
s_seen = TRUE;
break;
/*
* -t option:
* Sort alphabetically by login-ID instead of numerically
* by user-ID
*/
case 't':
if (t_seen)
errflg = TRUE;
else
t_seen = TRUE;
break;
/*
* -u option:
* Select user logins
*/
case 'u':
if (u_seen)
errflg = TRUE;
else
u_seen = TRUE;
break;
/*
* -x option:
* Display extended info (init working dir, shell, pwd info)
*/
case 'x':
if (x_seen)
errflg = TRUE;
else
x_seen = TRUE;
break;
default: /* Oops.... */
errflg = TRUE;
}
}
/* Write out a usage message if necessary and quit */
if (errflg || (optind != argc)) {
wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, gettext(USAGE_MSG));
exit(1);
}
/*
* The following section does preparation work, setting up for
* building the list of logins to display
*/
/*
* If -l logins is on the command line, build a list of
* logins we're to generate reports for.
*/
if (l_seen) {
reqloginhead = NULL;
if (token = strtok(l_arg, ",")) {
plogin = malloc(sizeof (struct reqlogin));
plogin->loginname = token;
plogin->found = FALSE;
plogin->next = NULL;
reqloginhead = plogin;
qlogin = plogin;
while (token = strtok(NULL, ",")) {
plogin = malloc(sizeof (struct reqlogin));
plogin->loginname = token;
plogin->found = FALSE;
plogin->next = NULL;
qlogin->next = plogin;
qlogin = plogin;
}
}
/*
* Build an in-core structure of just the passwd database
* entries requested. This greatly reduces the time
* to get all entries and filter later.
*/
build_localpw(reqloginhead);
} else {
/*
* Build an in-core structure of all passwd database
* entries. This is important since we have to assume that
* getpwent() is going out to one or more network name
* services that could be changing on the fly. This will
* limit us to one pass through the network data.
*/
build_localpw(NULL);
}
/*
* If the -g groups option was on the command line, build a
* list containing groups we're to list logins for.
*/
if (g_seen) {
groupcount = 0;
reqgrphead = NULL;
if (token = strtok(g_arg, ",")) {
pgrp = malloc(sizeof (struct reqgrp));
pgrp->groupname = token;
pgrp->next = NULL;
groupcount++;
reqgrphead = pgrp;
qgrp = pgrp;
while (token = strtok(NULL, ",")) {
pgrp = malloc(sizeof (struct reqgrp));
pgrp->groupname = token;
pgrp->next = NULL;
groupcount++;
qgrp->next = pgrp;
qgrp = pgrp;
}
}
}
/*
* Generate the list of login information to display
*/
/* Initialize the login list */
membershead = NULL;
/*
* If -g groups was specified, generate a list of members
* of the specified groups
*/
if (g_seen) {
/* For each group mentioned with the -g option ... */
for (pgrp = reqgrphead; (groupcount > 0) && pgrp;
pgrp = pgrp->next) {
if ((grent = getgrnam(pgrp->groupname)) != NULL) {
/*
* Remembering the group-ID for later
*/
groupcount--;
pgrp->groupID = grent->gr_gid;
for (pp = grent->gr_mem; *pp; pp++) {
addmember(*pp);
}
} else {
wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG,
gettext("%s was not found"),
pgrp->groupname);
}
}
}
/* Initialize the list of logins to display */
initdisp();
/*
* Add logins that have user-IDs that are used more than once,
* if requested. This command is pretty slow, since the algorithm
* reads from the /etc/passwd file 1+2+3+...+n times where n is the
* number of login-IDs in the /etc/passwd file. (Actually, this
* can be optimized so it's not quite that bad, but the order or
* magnitude stays the same.)
*
* Note: This processing needs to be done before any other options
* are processed -- the algorithm contains an optimization
* that insists on the display list being empty before this
* option is processed.
*/
if (d_seen) {
/*
* The following code is a quick&dirty reimplementation of the
* original algorithm, which opened the password file twice (to
* get two file pointer into the data) and then used fgetpwent()
* in undocumented ways to scan through the file, checking for
* duplicates. This does not work when getpwent() is used to
* go out over the network, since there is not file pointer.
*
* Instead an in-memory list of passwd structures is built,
* and then this list is scanned. The routines
* Local_getpwent(), etc., are designed to mimic the standard
* library routines, so this code does not have to be
* extensively modified.
*/
/*
* For reference, here is the original comment about the next
* section of code. Some of the code has changed, but the
* algorithm is the same:
*
* Open the system password file once. This instance will be
* used to leaf through the file once, reading each entry once,
* and searching the remainder of the file for another login-ID
* that has the same user-ID. Note that there are lots of
* contortions one has to go through when reading two instances
* of the /etc/passwd file. That's why there's some seeking,
* re-reading of the same record, and other junk. Luckily, this
* feature won't be requested very often, and still isn't too
* slow...
*/
/* For each entry in the passwd database ... */
while (plookpwd = local_getpwent()) {
/*
* Optimization -- If the login's user-ID is already
* in the display list, there's no reason to process
* this entry -- it's already there.
*/
if (!isuidindisp(plookpwd)) {
/*
* Rememeber the current entry's position,
* so when we finish scanning through the
* database looking for duplicates we can
* return to the current place, so that the
* enclosing loop will march in an orderly
* fashion through the passwd database.
*/
done = FALSE;
lookpos = local_pwtell();
/*
* For each record in the passwd database
* beyond the searching record ...
*/
while (pwent = local_getpwent()) {
/*
* If there's a match between the
* searcher's user-ID and the
* searchee's user-ID ...
*/
if (pwent->pw_uid == plookpwd->pw_uid) {
/*
* If this is the first
* duplicate of this searcher
* that we find,
* add the searcher's
* record to the display list
* (It wants to be on the
* list first to avoid
* ordering "flakeyness")
*/
if (done == FALSE) {
adddisp(plookpwd);
done = TRUE;
}
/*
* Now add the searchee's
* record
*/
adddisp(pwent);
}
}
/* Reposition to searcher record */
local_pwseek(lookpos);
}
}
local_endpwent();
}
/*
* Loop through the passwd database squirelling away the
* information we need for the display.
*
* NOTE: Once a login is added to the list, the rest of the
* body of the loop is bypassed (via a continue statement).
*/
doall = !(s_seen || u_seen || p_seen || d_seen || l_seen || g_seen);
if (doall || s_seen || u_seen || p_seen || l_seen || g_seen) {
while (pwent = local_getpwent()) {
done = FALSE;
/*
* If no user-specific options were specified,
* include this login-ID
*/
if (doall) {
adddisp(pwent);
continue;
}
/*
* If the user specified system login-IDs,
* and this is a system ID, include it
*/
if (s_seen) {
if (isasystemlogin(pwent)) {
adddisp(pwent);
continue;
}
}
/*
* If the user specified user login-IDs,
* and this is a user ID, include it
*/
if (u_seen) {
if (isauserlogin(pwent)) {
adddisp(pwent);
continue;
}
}
/*
* If the user is asking for login-IDs that have
* no password, and this one has no password, include it
*/
if (p_seen) {
if (hasnopasswd(pwent)) {
adddisp(pwent);
continue;
}
}
/*
* If specific logins were requested, leaf through
* the list of logins they requested. If this login
* is on the list, include it.
*/
if (l_seen) {
for (plogin = reqloginhead; !done && plogin;
plogin = plogin->next) {
if (strcmp(pwent->pw_name,
plogin->loginname) == 0) {
plogin->found = TRUE;
adddisp(pwent);
done = TRUE;
}
}
if (done)
continue;
}
/*
* If specific groups were requested, leaf through the
* list of login-IDs that belong to those groups.
* If this login-ID is in that list, or its primary
* group is one of those requested, include it.
*/
if (g_seen) {
for (pgrp = reqgrphead; !done && pgrp;
pgrp = pgrp->next) {
if (pwent->pw_gid == pgrp->groupID) {
adddisp(pwent);
done = TRUE;
}
}
if (!done && isamember(pwent->pw_name)) {
adddisp(pwent);
done = TRUE;
}
}
if (done)
continue;
}
local_endpwent();
}
/* Let the user know about logins they requested that don't exist */
if (l_seen) {
for (plogin = reqloginhead; plogin; plogin = plogin->next) {
if (!plogin->found) {
wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG,
gettext("%s was not found"),
plogin->loginname);
}
}
}
/* Apply group information */
applygroup(m_seen);
/*
* Apply password information (only needed if the extended
* set of information has been requested)
*/
if (x_seen || a_seen)
applypasswd();
/*
* Generate a report from this display items we've squirreled away
*/
if (t_seen)
genlogreport(o_seen, x_seen, a_seen);
else
genuidreport(o_seen, x_seen, a_seen);
/* We're through! */
return (0);
}