root/usr/src/cmd/logins/logins.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * 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 = (uid_t)-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:%u:%s:%u:%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:%u",
                    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  %-6u  %-14s  %-6u  %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  %-6u\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);
}