]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lslogins: add functionality
authorOndrej Oprala <ooprala@redhat.com>
Mon, 7 Apr 2014 16:24:19 +0000 (18:24 +0200)
committerKarel Zak <kzak@redhat.com>
Thu, 15 May 2014 11:55:59 +0000 (13:55 +0200)
Signed-off-by: Ondrej Oprala <ooprala@redhat.com>
.gitignore
include/Makemodule.am
lib/Makemodule.am
login-utils/Makemodule.am
login-utils/login.c
login-utils/logindefs.c
login-utils/logindefs.h
login-utils/lslogins.1
login-utils/lslogins.c

index 968a0890d482a45c6647c4a51c8894512b0f90ae..b04276989d8c107fe9911fa00122a8ca1d35aca5 100644 (file)
@@ -116,6 +116,7 @@ update.log
 /lsblk
 /lscpu
 /lslocks
+/lslogins
 /mcookie
 /mesg
 /mkfs
index 7f461c6fe9669400474a2e71e618a1c0678a5215..08479fa0f29ea6dc5e3f9a35a96c497fb907ca43 100644 (file)
@@ -37,6 +37,7 @@ dist_noinst_HEADERS += \
        include/pathnames.h \
        include/procutils.h \
        include/randutils.h \
+       include/readutmp.h \
        include/rpmatch.h \
        include/setproctitle.h \
        include/strutils.h \
index b4ad0e9ae2caccc308101b96f45bbe08c6dc329f..a16509670bf3cbe4e3d68af2d34d2df4f260493f 100644 (file)
@@ -26,7 +26,8 @@ libcommon_la_SOURCES = \
        lib/timeutils.c \
        lib/ttyutils.c \
        lib/xgetpass.c \
-       lib/exec_shell.c
+       lib/exec_shell.c \
+       lib/readutmp.c
 
 if LINUX
 libcommon_la_SOURCES += \
index 0275a6952cde07961f2ea22134da2eec4b5dfc3d..1370672953522f7a69f55adfd13b3afcef72077b 100644 (file)
@@ -165,6 +165,19 @@ newgrp_LDADD += -lcrypt
 endif
 endif # BUILD_NEWGRP
 
+if BUILD_LSLOGINS
+usrbin_exec_PROGRAMS += lslogins
+dist_man_MANS += login-utils/lslogins.1
+lslogins_SOURCES = \
+       login-utils/lslogins.c \
+       login-utils/logindefs.c \
+       login-utils/logindefs.h
+lslogins_LDADD = $(LDADD) libcommon.la libsmartcols.la
+lslogins_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+if HAVE_SELINUX
+lslogins_LDADD += -lselinux
+endif
+endif # BUILD_LSLOGINS
 
 if BUILD_VIPW
 usrsbin_exec_PROGRAMS += vipw
@@ -207,16 +220,3 @@ endif
 if BUILD_VIPW
        cd $(DESTDIR)$(usrsbin_execdir) && ln -sf vipw vigr
 endif
-
-if BUILD_LSLOGINS
-usrbin_exec_PROGRAMS += lslogins
-dist_man_MANS += login-utils/lslogins.1
-lslogins_SOURCES = \
-       login-utils/lslogins.c \
-       login-utils/lslogins.h
-lslogins_LDADD = $(LDADD) libcommon.la libsmartcols.la
-lslogins_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
-if HAVE_SELINUX
-lslogins_LDADD += -lselinux
-endif
-endif # BUILD_LSLOGINS
index b16a8123156e5a1429c2976af025a3864c71a796..470076f8cb0486e05add128f41f6b99227259e27 100644 (file)
@@ -1248,7 +1248,7 @@ int main(int argc, char **argv)
 
        endpwent();
 
-       cxt.quiet = get_hushlogin_status(pwd);
+       cxt.quiet = get_hushlogin_status(pwd, 1);
 
        log_utmp(&cxt);
        log_audit(&cxt, 1);
index 804ab3e31fb9b05431694dac0de9776150253063..f02c4752db603d35a1a27721ffc1ca18b0d5dfd1 100644 (file)
@@ -307,7 +307,7 @@ int effective_access(const char *path, int mode)
  * BSD setreuid().
  */
 
-int get_hushlogin_status(struct passwd *pwd)
+int get_hushlogin_status(struct passwd *pwd, int force_check)
 {
        const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL };
        const char *file;
@@ -358,12 +358,13 @@ int get_hushlogin_status(struct passwd *pwd)
                /* per-account setting */
                if (strlen(pwd->pw_dir) + sizeof(file) + 2 > sizeof(buf))
                        continue;
-               else {
+
+               sprintf(buf, "%s/%s", pwd->pw_dir, file);
+
+               if (force_check) {
                        uid_t ruid = getuid();
                        gid_t egid = getegid();
 
-                       sprintf(buf, "%s/%s", pwd->pw_dir, file);
-
                        if (setregid(-1, pwd->pw_gid) == 0 &&
                            setreuid(0, pwd->pw_uid) == 0)
                                ok = effective_access(buf, O_RDONLY) == 0;
@@ -377,6 +378,15 @@ int get_hushlogin_status(struct passwd *pwd)
                        if (ok)
                                return 1;       /* enabled by user */
                }
+               else {
+                       int rc;
+                       rc = effective_access(buf, O_RDONLY);
+                       if (rc == 0)
+                               return 1;
+                       else if (rc == -1 && errno == EACCES)
+                                       return -1;
+               }
+
        }
 
        return 0;
index 6a72762f4e62c45d8b9192ddc1f9626b0dff3411..f05d733babf207d73df5f448ced2617a85f1dba6 100644 (file)
@@ -9,6 +9,6 @@ extern const char *getlogindefs_str(const char *name, const char *dflt);
 extern void free_getlogindefs_data(void);
 extern int logindefs_setenv(const char *name, const char *conf, const char *dflt);
 extern int effective_access(const char *path, int mode);
-extern int get_hushlogin_status(struct passwd *pwd);
+extern int get_hushlogin_status(struct passwd *pwd, int force_check);
 
 #endif /* UTIL_LINUX_LOGINDEFS_H */
index 9b62cb86cfedd3cacdd58c4d45fcc3562afcb1a3..a961efea4242bcb33d473679e51ca494c2111b40 100644 (file)
@@ -73,6 +73,9 @@ Show extra information about users - home diretory, default login shell, passwor
 \fB\-z\fR, \fB\-\-print0\fR
 Delimit user entries with a nul character, instead of a newline.
 .TP
+\fB\-Z\fR, \fB\-\-context\fR
+Display the users' security context.
+.TP
 \fB\-h\fR, \fB\-\-help\fR
 Display help information and exit.
 \fB\-v\fR, \fB\-\-version\fR
index c9f812f36d26178b2e0c53ba52091b74753664d0..657a408ab02e8c8782b21e7eb95ca9265f588738 100644 (file)
 #include <unistd.h>
 #include <getopt.h>
 #include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/syslog.h>
 #include <pwd.h>
+#include <grp.h>
 #include <shadow.h>
 #include <paths.h>
 #include <time.h>
+#include <utmp.h>
+#include <signal.h>
+#include <err.h>
+#include <limits.h>
+
+#include <libsmartcols.h>
+#ifdef HAVE_LIBSELINUX
+#include <selinux/selinux.h>
+#endif
 
 #include "c.h"
 #include "nls.h"
 #include "closestream.h"
 #include "xalloc.h"
+#include "list.h"
 #include "strutils.h"
 #include "optutils.h"
+#include "pathnames.h"
+#include "logindefs.h"
+#include "readutmp.h"
 
 /*
  * column description
@@ -42,12 +58,27 @@ struct lslogins_coldesc {
        const char *name;
        const char *help;
 
-       unsigned int  is_abbr:1;        /* name is abbreviation */
+       double whint;   /* width hint */
 };
 
+static struct list_head userlist;
 /* the most uber of flags */
 static int uberflag;
 
+struct utmp *wtmp, *btmp;
+size_t wtmp_size, btmp_size;
+int want_wtmp, want_btmp;
+
+#define UL_UID_MIN "1000"
+#define UL_UID_MAX "60000"
+#define UL_SYS_UID_MIN "201"
+#define UL_SYS_UID_MAX "999"
+
+uid_t UID_MIN;
+uid_t UID_MAX;
+
+uid_t SYS_UID_MIN;
+uid_t SYS_UID_MAX;
 /* we use the value of outmode to determine
  * appropriate flags for the libsmartcols table
  * (e.g., a value of out_newline would imply a raw
@@ -58,7 +89,7 @@ static int outmode;
  * output modes
  */
 enum {
-       out_colon = 0,
+       out_colon = 1,
        out_export,
        out_newline,
        out_raw,
@@ -72,40 +103,48 @@ struct lslogins_user {
        gid_t gid;
        char *gecos;
 
-       int nopasswd:1;
+       int nopasswd;
+       int nologin;
+       int locked;
 
        char *sgroups;
 
-       struct tm *pwd_ctime;
-       struct tm *pwd_expir;
+       char *pwd_ctime;
+       char *pwd_expir;
+       char *pwd_ctime_min;
+       char *pwd_ctime_max;
 
-       struct tm *last_login;
-       char * last_tty;
-       char * last_hostname;
+       char *last_login;
+       char *last_tty;
+       char *last_hostname;
 
-       struct tm *failed_login;
-       struct tm *failed_tty;
+       char *failed_login;
+       char *failed_tty;
 
+#ifdef HAVE_LIBSELINUX
+       security_context_t context;
+#endif
        char *homedir;
        char *shell;
        char *pwd_status;
-       char *hush_status;
+       int   hushed;
+
+       struct list_head ulist;
 };
 /*
  * flags
  */
 enum {
        F_EXPIR = (1 << 0),
-       F_DUP   = (1 << 1),
-       F_EXPRT = (1 << 2),
-       F_MORE  = (1 << 3),
-       F_NOPWD = (1 << 4),
-       F_SYSAC = (1 << 5),
-       F_USRAC = (1 << 6),
-       F_SORT  = (1 << 7),
-       F_EXTRA = (1 << 8),
-       F_FAIL  = (1 << 9),
-       F_LAST  = (1 << 10),
+       F_MORE  = (1 << 1),
+       F_NOPWD = (1 << 2),
+       F_SYSAC = (1 << 3),
+       F_USRAC = (1 << 4),
+       F_SORT  = (1 << 5),
+       F_EXTRA = (1 << 6),
+       F_FAIL  = (1 << 7),
+       F_LAST  = (1 << 8),
+       F_SELINUX = (1 << 9),
 };
 
 /*
@@ -114,7 +153,6 @@ enum {
 enum {
        COL_LOGIN = 0,
        COL_UID,
-       COL_NOPASSWD,
        COL_PGRP,
        COL_PGID,
        COL_SGRPS,
@@ -127,35 +165,41 @@ enum {
        COL_FAILED_LOGIN,
        COL_FAILED_TTY,
        COL_HUSH_STATUS,
-       COL_PWD_STATUS,
+       COL_NOLOGIN,
+       COL_LOCKED,
+       COL_NOPASSWD,
        COL_PWD_EXPIR,
        COL_PWD_CTIME,
-       /*COL_PWD_CTIME_MAX,
-       COL_PWD_CTIME_MIN,*/
+       COL_PWD_CTIME_MIN,
+       COL_PWD_CTIME_MAX,
+       COL_SELINUX,
 };
 
+static const char *const status[] = { "0", "1", "-" };
 static struct lslogins_coldesc coldescs[] =
 {
-       [COL_LOGIN]             = { "LOGIN",            N_("user/system login"), 1 },
-       [COL_UID]               = { "UID",              N_("user UID") },
-       [COL_NOPASSWD]          = { "HAS PASSWORD",     N_("account has a password?") },
-       [COL_PGRP]              = { "GRP",              N_("primary group name") },
-       [COL_PGID]              = { "GRP_GID",          N_("primary group GID") },
-       [COL_SGRPS]             = { "SEC_GRPS",         N_("secondary group names and GIDs") },
-       [COL_HOME]              = { "HOMEDIR",          N_("home directory") },
-       [COL_SHELL]             = { "SHELL",            N_("login shell") },
-       [COL_FULLNAME]          = { "FULLNAME",         N_("full user name") },
-       [COL_LAST_LOGIN]        = { "LAST_LOGIN",       N_("date of last login") },
-       [COL_LAST_TTY]          = { "LAST_TTY",         N_("last tty used") },
-       [COL_LAST_HOSTNAME]     = { "LAST_HOSTNAME",    N_("hostname during the last session") },
-       [COL_FAILED_LOGIN]      = { "FAILED_LOGIN",     N_("date of last failed login") },
-       [COL_FAILED_TTY]        = { "FAILED_TTY",       N_("where did the login fail?") },
-       [COL_HUSH_STATUS]       = { "HUSH_STATUS",      N_("User's hush settings") },
-       [COL_PWD_STATUS]        = { "PWD_STATUS",       N_("password status - see the -x option description") },
-       [COL_PWD_EXPIR]         = { "PWD_EXPIR",        N_("password expiration date") },
-       [COL_PWD_CTIME]         = { "PWD_CHANGE",       N_("date of last password change") },
-       /*[COL_PWD_CTIME_MAX]   = { "PWD UNTIL",        N_("max number of days a password may remain unchanged") },
-       [COL_PWD_CTIME_MIN]     = { "PWD CAN CHANGE",   N_("number of days required between changes") },*/
+       [COL_LOGIN]             = { "LOGIN",            N_("user/system login"), 0.2 },
+       [COL_UID]               = { "UID",              N_("user UID"), 0.05 },
+       [COL_NOPASSWD]          = { "NOPASSWD",         N_("account has a password?"), 1 },
+       [COL_NOLOGIN]           = { "NOLOGIN",          N_("account has a password?"), 1 },
+       [COL_LOCKED]            = { "LOCKED",           N_("account has a password?"), 1 },
+       [COL_PGRP]              = { "GRP",              N_("primary group name"), 0.2 },
+       [COL_PGID]              = { "GID",              N_("primary group GID"), 0.05 },
+       [COL_SGRPS]             = { "SEC_GRPS",         N_("secondary group names and GIDs"), 0.5 },
+       [COL_HOME]              = { "HOMEDIR",          N_("home directory"), 0.3 },
+       [COL_SHELL]             = { "SHELL",            N_("login shell"), 0.1 },
+       [COL_FULLNAME]          = { "FULLNAME",         N_("full user name"), 0.3 },
+       [COL_LAST_LOGIN]        = { "LAST_LOGIN",       N_("date of last login"), 24 },
+       [COL_LAST_TTY]          = { "LAST_TTY",         N_("last tty used"), 0.05 },
+       [COL_LAST_HOSTNAME]     = { "LAST_HOSTNAME",    N_("hostname during the last session"), 0.2},
+       [COL_FAILED_LOGIN]      = { "FAILED_LOGIN",     N_("date of last failed login"), 24 },
+       [COL_FAILED_TTY]        = { "FAILED_TTY",       N_("where did the login fail?"), 0.05 },
+       [COL_HUSH_STATUS]       = { "HUSHED",           N_("User's hush settings"), 1 },
+       [COL_PWD_EXPIR]         = { "PWD_EXPIR",        N_("password expiration date"), 24 },
+       [COL_PWD_CTIME]         = { "PWD_CHANGE",       N_("date of last password change"), 24 },
+       [COL_PWD_CTIME_MIN]     = { "PWD_MIN",  N_("number of days required between changes"), 24 },
+       [COL_PWD_CTIME_MAX]     = { "PWD_MAX",  N_("max number of days a password may remain unchanged"), 24 },
+       [COL_SELINUX]           = { "CONTEXT",  N_("the user's security context"), 0.4 },
 };
 
 static int
@@ -166,13 +210,33 @@ column_name_to_id(const char *name, size_t namesz)
        for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
                const char *cn = coldescs[i].name;
 
-               if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+               if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) {
+                       if (i <= COL_LAST_HOSTNAME && i >= COL_LAST_LOGIN)
+                               want_wtmp = 1;
+                       if (i == COL_FAILED_TTY && i >= COL_FAILED_LOGIN)
+                               want_btmp = 1;
                        return i;
+               }
        }
        warnx(_("unknown column: %s"), name);
        return -1;
 }
 
+static char *make_time(struct tm *tm)
+{
+       char *t, *s;
+
+       if (!tm)
+               return NULL;
+
+       s = asctime(tm);
+       if (!s)
+               return NULL;
+
+       if (*(t = s + strlen(s) - 1) == '\n')
+               *t = '\0';
+       return strdup(s);
+}
 static void __attribute__((__noreturn__)) usage(FILE *out)
 {
        size_t i;
@@ -183,22 +247,21 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
        fputs(USAGE_OPTIONS, out);
        fputs(_(" -a, --acc-expiration     Display data\n"), out);
        fputs(_(" -c, --colon-separate     Display data in a format similar to /etc/passwd\n"), out);
-       fputs(_(" -d, --duplicates         Display users with duplicate UIDs\n"), out);
        fputs(_(" -e, --export             Display in an export-able output format\n"), out);
        fputs(_(" -f, --failed             Display data about the last users' failed logins\n"), out);
        fputs(_(" -g, --groups=<GROUPS>    Display users belonging to a group in GROUPS\n"), out);
        fputs(_(" -l, --logins=<LOGINS>    Display only users from LOGINS\n"), out);
-       fputs(_(" --last                   Show info about the last login sessions\n"), out);
+       fputs(_(" --last                   Show info about the users' last login sessions\n"), out);
        fputs(_(" -m, --more               Display secondary groups as well\n"), out);
        fputs(_(" -n, --newline            Display each piece of information on a new line\n"), out);
        fputs(_(" -o, --output[=<LIST>]    Define the columns to output\n"), out);
-       fputs(_(" -p, --no-password        Display users without a password\n"), out);
        fputs(_(" -r, --raw                Display the raw table\n"), out);
-       fputs(_(" -s, --sys-accs[=<UID>]   Display system accounts\n"), out);
+       fputs(_(" -s, --sys-accs           Display system accounts\n"), out);
        fputs(_(" -t, --sort               Sort output by login instead of UID\n"), out);
-       fputs(_(" -u, --user-accs[=<UID>]  Display user accounts\n"), out);
+       fputs(_(" -u, --user-accs          Display user accounts\n"), out);
        fputs(_(" -x, --extra              Display extra information\n"), out);
-       fputs(_(" -z, --print0             Delimit user entries with a nul character"), out);
+       fputs(_(" -z, --print0             Delimit user entries with a nul character\n"), out);
+       fputs(_(" -Z, --context            Display the users' security context\n"), out);
        fputs(USAGE_SEPARATOR, out);
        fputs(USAGE_HELP, out);
        fputs(USAGE_VERSION, out);
@@ -212,6 +275,699 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
 
        exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
 }
+struct lslogins_sgroups {
+       char *gid;
+       char *uname;
+       struct lslogins_sgroups *next;
+};
+
+static char *uidtostr(uid_t uid)
+{
+       char *str_uid = NULL;
+       return (0 > xasprintf(&str_uid, "%u", uid)) ? NULL : str_uid;
+}
+
+static char *gidtostr(gid_t gid)
+{
+       char *str_gid = NULL;
+       return (0 > xasprintf(&str_gid, "%u", gid)) ? NULL : str_gid;
+}
+
+static struct lslogins_sgroups *build_sgroups_list(int len, gid_t *list, int *slen)
+{
+       int n = 0;
+       struct lslogins_sgroups *sgrps, *retgrps;
+
+       *slen = 0;
+
+       if (!len || !list)
+               return NULL;
+
+       retgrps = sgrps = calloc(1, sizeof(struct lslogins_sgroups));
+       while (n < len) {
+               if (sgrps->next)
+                       sgrps = sgrps->next;
+
+               sgrps->gid = gidtostr(list[n]);
+               sgrps->uname = strdup(getgrgid(list[n])->gr_name);
+
+               *slen += strlen(sgrps->gid) + strlen(sgrps->uname);
+
+               sgrps->next = calloc(1, sizeof(struct lslogins_sgroups));
+
+               ++n;
+
+       }
+       /* a pair of parentheses for each group + (n - 1) commas in between */
+       slen += 3 * n - 1;
+
+       free (sgrps->next);
+       sgrps->next = NULL;
+
+       return retgrps;
+}
+static void free_sgroups_list(struct lslogins_sgroups *sgrps)
+{
+       struct lslogins_sgroups *tmp;
+
+       if (!sgrps)
+               return;
+
+       tmp = sgrps->next;
+       while (tmp) {
+               free(sgrps->gid);
+               free(sgrps->uname);
+               free(sgrps);
+               sgrps = tmp;
+               tmp = tmp->next;
+       }
+}
+static char *build_sgroups_string(int len, gid_t *list)
+{
+       char *ret = NULL, *slist;
+       int slen, prlen;
+       struct lslogins_sgroups *sgrps;
+
+       sgrps = build_sgroups_list(len, list, &slen);
+
+       if (!sgrps)
+               return NULL;
+
+       ret = slist = calloc (1, sizeof(char) * (slen + 1));
+
+       while (sgrps->next) {
+               prlen = sprintf(slist, "%s(%s),", sgrps->gid, sgrps->uname);
+               if (prlen < 0) {
+                       free_sgroups_list(sgrps);
+                       return NULL;
+               }
+               slist += prlen;
+               sgrps = sgrps->next;
+       }
+       prlen = sprintf(slist, "%s(%s)", sgrps->gid, sgrps->uname);
+
+       return ret;
+}
+
+static struct utmp *get_recent_wtmp(const char *username)
+{
+       size_t n = 0;
+       size_t len;
+
+       if (!username)
+               return NULL;
+
+       len = strlen(username);
+       n = wtmp_size - 1;
+       while (n) {
+               if (!strncmp(username, wtmp[n].ut_user, len < UT_NAMESIZE ? len : UT_NAMESIZE))
+                       return wtmp + n;
+               --n;
+       }
+       return NULL;
+
+}
+static struct utmp *get_recent_btmp(const char *username)
+{
+       size_t n = 0;
+       size_t len;
+
+       if (!username)
+               return NULL;
+
+       len = strlen(username);
+       n = btmp_size - 1;
+       while (n) {
+               if (!strncmp(username, btmp[n].ut_user, len < UT_NAMESIZE ? len : UT_NAMESIZE))
+                       return btmp + n;
+               --n;
+       }
+       return NULL;
+
+}
+
+static int parse_wtmp(void)
+{
+       int rc = 0;
+
+       rc = read_utmp(_PATH_WTMP, &wtmp_size, &wtmp);
+       if (rc < 0 && errno != EACCES)
+               err(EXIT_FAILURE, "%s: %s", _PATH_WTMP, strerror(errno));
+               return rc;
+
+       return rc;
+}
+static int parse_btmp(void)
+{
+       int rc = 0;
+
+       rc = read_utmp(_PATH_BTMP, &btmp_size, &btmp);
+       if (rc < 0 && errno != EACCES)
+               err(EXIT_FAILURE, "%s: %s", _PATH_BTMP, strerror(errno));
+               return rc;
+
+       return rc;
+}
+
+static int get_sgroups(int *len, gid_t **list, struct passwd *pwd)
+{
+       int n = 0;
+       gid_t *safelist;
+
+       getgrouplist(pwd->pw_name, pwd->pw_gid, *list, len);
+       if (!*len)
+               return -1;
+
+       *list = malloc(*len * sizeof(gid_t));
+
+       if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, len))
+               return -1;
+
+       while (n < *len) {
+               if ((*list)[n] == pwd->pw_gid)
+                       break;
+               ++n;
+       }
+       (*list)[n] = (*list)[--(*len)];
+
+       safelist = xrealloc (*list, *len * sizeof(gid_t));
+       if (!safelist && *len) {
+               free (*list);
+               return -1;
+       }
+       *list = safelist;
+
+       return 0;
+
+}
+
+static struct lslogins_user *get_user_info(const char *username, const int *columns, const int ncolumns)
+{
+       struct lslogins_user *user;
+       struct passwd *pwd;
+       struct group *grp;
+       struct spwd *shadow;
+       struct utmp *user_wtmp = NULL, *user_btmp = NULL;
+       int n = 0;
+       time_t time;
+       struct tm tm;
+       uid_t uid;
+
+       user = xcalloc(1, sizeof(struct lslogins_user));
+       if (!user) {
+               errno = ENOMEM;
+               return NULL;
+       }
+       INIT_LIST_HEAD(&user->ulist);
+
+       errno = 0;
+       pwd = username ? getpwnam(username) : getpwent();
+       if (!pwd)
+               return NULL;
+
+       uid = pwd->pw_uid;
+       if ((uberflag & F_USRAC) &&
+           strcmp("nfsnobody", pwd->pw_name)) {
+               if (uid < UID_MIN || uid > UID_MAX) {
+                       errno = EAGAIN;
+                       return NULL;
+               }
+       }
+       else if (uberflag & F_SYSAC) {
+               if (uid < SYS_UID_MIN || uid > SYS_UID_MAX) {
+                       errno = EAGAIN;
+                       return NULL;
+               }
+       }
+
+       grp = getgrgid(pwd->pw_gid);
+       if (!grp)
+               return NULL;
+
+       if (wtmp)
+               user_wtmp = get_recent_wtmp(pwd->pw_name);
+       if (btmp)
+               user_btmp = get_recent_btmp(pwd->pw_name);
+
+       /* sufficient permissions to get a shadow entry? */
+       errno = 0;
+       lckpwdf();
+       shadow = getspnam(pwd->pw_name);
+       ulckpwdf();
+       if (!shadow && errno != EACCES)
+               err(EXIT_FAILURE, "%s", strerror(errno));
+
+       if (shadow) {
+               /* we want these dates in seconds */
+               shadow->sp_min *= 86400;
+               shadow->sp_max*= 86400;
+               shadow->sp_lstchg *= 86400;
+       }
+       /* we store the uid unconditionally, for sorting */
+       /* TODO: only if not -t --> faster?? */
+       user->uid = pwd->pw_uid;
+
+       while (n < ncolumns) {
+               switch (columns[n++]) {
+                       case COL_LOGIN:
+                               user->login = strdup(pwd->pw_name);
+                               break;
+                       case COL_UID:
+                               /* just a placeholder, see above*/
+                               break;
+                       case COL_PGRP:
+                               user->group = strdup(grp->gr_name);
+                               break;
+                       case COL_PGID:
+                               user->gid = (pwd->pw_gid);
+                               break;
+                       case COL_SGRPS:
+                               {
+                                       int n = 0;
+                                       gid_t *list = NULL;
+
+                                       if (get_sgroups(&n, &list, pwd))
+                                               err(EXIT_FAILURE, "Unable to get secondary groups(%s)", strerror(errno));
+                                       user->sgroups = build_sgroups_string(n, list);
+                                       if (!user->sgroups)
+                                               user->sgroups = "";
+                                       break;
+                               }
+                       case COL_HOME:
+                               user->homedir = strdup(pwd->pw_dir);
+                               break;
+                       case COL_SHELL:
+                               user->shell = strdup(pwd->pw_shell);
+                               break;
+                       case COL_FULLNAME:
+                               user->gecos = strdup(pwd->pw_gecos);
+                               break;
+                               /* strdup us? ? */
+                       case COL_LAST_LOGIN:
+                               if (user_wtmp) {
+                                       time = user_wtmp->ut_tv.tv_sec;
+                                       localtime_r(&time, &tm);
+                                       user->last_login = make_time(&tm);
+                               }
+                               else
+                                       user->last_login = "-";
+                               break;
+                       case COL_LAST_TTY:
+                               if (user_wtmp)
+                                       user->last_tty = user_wtmp->ut_line;
+                               else
+                                       user->last_tty = "-";
+                               break;
+                       case COL_LAST_HOSTNAME:
+                               if (user_wtmp)
+                                       user->last_hostname = user_wtmp->ut_host;
+                               else
+                                       user->last_hostname = "-";
+                               break;
+                       case COL_FAILED_LOGIN:
+                               if (user_btmp) {
+                                       time = user_btmp->ut_tv.tv_sec;
+                                       localtime_r(&time, &tm);
+                                       user->failed_login = make_time(&tm);
+                               }
+                               else
+                                       user->failed_login = "-";
+                               break;
+                       case COL_FAILED_TTY:
+                               if (user_btmp)
+                                       user->failed_tty = user_btmp->ut_line;
+                               else
+                                       user->failed_tty = "-";
+                               break;
+                       case COL_HUSH_STATUS:
+                               user->hushed = get_hushlogin_status(pwd, 0);
+                               if (user->hushed == -1)
+                                       user->hushed = 2;
+                               break;
+                       case COL_NOPASSWD:
+                               if (shadow) {
+                                       if (!*shadow->sp_pwdp) /* '\0' */
+                                               user->nopasswd = 1;
+                                       else
+                                               user->nopasswd = 0;
+                               }
+                               else
+                                       user->nopasswd = 2;
+                               break;
+                       case COL_NOLOGIN:
+                               if ((pwd->pw_uid && !(close(open("/etc/nologin", O_RDONLY)))) ||
+                                   strstr(pwd->pw_shell, "nologin")) {
+                                       user->nologin = 1;
+                               }
+                               else
+                                       user->nologin = 0;
+                               break;
+                       case COL_LOCKED:
+                               if (shadow) {
+                                       if (*shadow->sp_pwdp == '!')
+                                               user->locked = 1;
+                                       else
+                                               user->locked = 0;
+                                       break;
+                               }
+                               else
+                                       user->locked = 2;
+                               break;
+                       case COL_PWD_EXPIR:
+                               if (shadow && shadow->sp_expire != -1) {
+                                       localtime_r(&shadow->sp_expire, &tm);
+                                       user->pwd_expir = make_time(&tm);
+                               }
+                               else
+                                       user->pwd_expir = "-";
+                               break;
+                       case COL_PWD_CTIME:
+                               if (shadow) {
+                                       localtime_r(&shadow->sp_lstchg, &tm);
+                                       user->pwd_ctime = make_time(&tm);
+                               }
+                               else
+                                       user->pwd_ctime = "-";
+                               break;
+                       case COL_PWD_CTIME_MIN:
+
+                               if (shadow) {
+                                       if (shadow->sp_min <= 0) {
+                                               user->pwd_ctime_min = "unlimited";
+                                       }
+                                       else {
+                                               localtime_r(&shadow->sp_min, &tm);
+                                               user->pwd_ctime_min = make_time(&tm);
+                                       }
+                               }
+                               else
+                                       user->pwd_ctime_min = "-";
+                               break;
+                       case COL_PWD_CTIME_MAX:
+                               if (shadow) {
+                                       if (shadow->sp_max <= 0)
+                                               user->pwd_ctime_max = "unlimited";
+                                       else {
+                                       localtime_r(&shadow->sp_max, &tm);
+                                       user->pwd_ctime_max = make_time(&tm);
+                                       }
+                               }
+                               else
+                                       user->pwd_ctime_max = "-";
+                               break;
+                       case COL_SELINUX:
+                               {
+#ifdef HAVE_LIBSELINUX
+                                       /* typedefs and pointers are pure evil */
+                                       security_context_t con = NULL;
+                                       if (getcon(&con))
+                                               return NULL;
+                                       user->context = con;
+#endif
+                               }
+                               break;
+                       default:
+                               /* something went very wrong here */
+                               err(EXIT_FAILURE, "fatal: unknown error");
+               }
+       }
+
+       return user;
+}
+/* some UNIX implementations set errno iff a passwd/grp/...
+ * entry was not found. The original UNIX logins utility always
+ * ignores invalid login/group names, so we're going to as well.*/
+#define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
+#define HANDLE_ERROR(e) do { if ((e) && IS_REAL_ERRNO(e)) { err(1,"%s",strerror(errno)); return -1;} } while (0)
+
+static struct lslogins_user *get_next_user(const int *columns, const int ncolumns)
+{
+       struct lslogins_user *u;
+       errno = 0;
+       while (!(u = get_user_info(NULL, columns, ncolumns))) {
+               /* no "false" errno-s here, if we're unable to
+                * get a valid user entry for any reason, quit */
+               if (errno == EAGAIN)
+                       continue;
+               return NULL;
+       }
+       return u;
+}
+
+static int get_user(struct lslogins_user **user,
+               const char *username, const int *columns, const int ncolumns)
+{
+       *user = get_user_info(username, columns, ncolumns);
+       if (!*user && errno)
+               HANDLE_ERROR(errno);
+       return 0;
+}
+
+static int user_in_list(struct list_head *head, char *uname)
+{
+       struct list_head *p;
+       list_for_each(p, head) {
+               if (!strcmp(uname, list_entry(p, struct lslogins_user, ulist)->login))
+                       return 1;
+       }
+       return 0;
+}
+
+static int create_userlist(char *logins, char *groups, const int *const columns, const int ncolumns)
+{
+       char *username, *gname;
+       struct lslogins_user *user = NULL;
+       struct group *grp;
+       int n = 0;
+
+       if (!logins && !groups) {
+               while ((user = get_next_user(columns, ncolumns)))
+                       list_add(&user->ulist, &userlist);
+
+               HANDLE_ERROR(errno);
+       }
+       if (logins) {
+               while ((username = strtok(logins, ",")) != NULL) {
+                       logins = NULL;
+                       if (get_user(&user, username, columns, ncolumns))
+                               return -1;
+                       if (user) /* otherwise an invalid user name has probably been given */
+                               list_add(&user->ulist, &userlist);
+               }
+       }
+       if (groups) {
+               while ((gname = strtok(groups, ",")) != NULL) {
+                       groups = NULL;
+                       errno = 0;
+                       grp = getgrnam(gname);
+                       if (!grp) {
+                               HANDLE_ERROR(errno);
+                               continue; /* not a "real" errno */
+                       }
+
+                       while ((username = grp->gr_mem[n++])) {
+
+                               if (!user_in_list(&userlist, username)) {
+                                       if (get_user(&user, username, columns, ncolumns))
+                                               return -1;
+                                       if (user) /* otherwise an invalid user name has probably been given */
+                                               list_add(&user->ulist, &userlist);
+                               }
+                       }
+               }
+       }
+       return 0;
+}
+static int cmp_uname(struct list_head *a, struct list_head *b, void *data __attribute__((unused)))
+{
+       return strcmp(list_entry(a,struct lslogins_user,ulist)->login,
+                     list_entry(b,struct lslogins_user,ulist)->login);
+}
+static int cmp_uid(struct list_head *a, struct list_head *b, void *data __attribute__((unused)))
+{
+       return bcmp(&list_entry(a,struct lslogins_user,ulist)->uid,
+                   &list_entry(b,struct lslogins_user,ulist)->uid,
+                   sizeof(uid_t));
+}
+static void sort_userlist(void)
+{
+       if (uberflag & F_SORT)
+               list_sort(&userlist,cmp_uname, NULL);
+       else
+               list_sort(&userlist,cmp_uid, NULL);
+}
+static struct libscols_table *setup_table(const int *const columns, const int ncolumns)
+{
+       struct libscols_table *tb = scols_new_table();
+       int n = 0;
+       if (!tb)
+               return NULL;
+
+       switch(outmode) {
+               case out_colon:
+                       scols_table_enable_raw(tb, 1);
+                       scols_table_set_column_separator(tb, ":");
+                       break;
+               case out_newline:
+                       scols_table_set_column_separator(tb, "\n");
+                       /* fallthrough */
+               case out_export:
+                       scols_table_enable_export(tb, 1);
+                       break;
+               case out_nul:
+                       scols_table_set_line_separator(tb, "\0");
+                       /* fallthrough */
+               case out_raw:
+                       scols_table_enable_raw(tb, 1);
+                       break;
+               default:
+                       break;
+       }
+
+       while (n < ncolumns) {
+               if (!scols_table_new_column(tb, coldescs[columns[n]].name,
+                                           coldescs[columns[n]].whint, 0))
+                       goto fail;
+               ++n;
+       }
+
+       return tb;
+fail:
+       scols_unref_table(tb);
+       return NULL;
+}
+
+static int print_user_table(const int *const columns, const int ncolumns)
+{
+       int n;
+       struct list_head *p;
+       struct libscols_table *tb;
+       struct libscols_line *ln;
+       struct lslogins_user *user;
+
+       tb = setup_table(columns, ncolumns);
+       if (!tb)
+               return -1;
+
+       sort_userlist();
+
+       list_for_each(p, &userlist) {
+               user = list_entry(p, struct lslogins_user, ulist);
+               n = 0;
+               ln = scols_table_new_line(tb, NULL);
+               while (n < ncolumns) {
+                       switch (columns[n]) {
+                               case COL_LOGIN:
+                                       if (scols_line_set_data(ln, n, user->login))
+                                               goto fail;
+                                       break;
+                               case COL_UID:
+                                       {
+                                               char *str_uid = uidtostr(user->uid);
+                                               if (!str_uid || scols_line_set_data(ln, n, str_uid))
+                                                       goto fail;
+                                               break;
+                                       }
+                               case COL_NOPASSWD:
+                                       if (scols_line_set_data(ln, n, status[user->nopasswd]))
+                                               goto fail;
+                                       break;
+                               case COL_NOLOGIN:
+                                       if (scols_line_set_data(ln, n, status[user->nologin]))
+                                               goto fail;
+                                       break;
+                               case COL_LOCKED:
+                                       if (scols_line_set_data(ln, n, status[user->locked]))
+                                               goto fail;
+                                       break;
+                               case COL_PGRP:
+                                       if (scols_line_set_data(ln, n, user->group))
+                                               goto fail;
+                                       break;
+                               case COL_PGID:
+                                       {
+                                               char *str_gid = gidtostr(user->gid);
+                                               if (!str_gid || scols_line_set_data(ln, n, str_gid))
+                                                       goto fail;
+                                               break;
+                                       }
+                               case COL_SGRPS:
+                                       if (scols_line_set_data(ln, n, user->sgroups))
+                                               goto fail;
+                                       break;
+                               case COL_HOME:
+                                       if (scols_line_set_data(ln, n, user->homedir))
+                                               goto fail;
+                                       break;
+                               case COL_SHELL:
+                                       if (scols_line_set_data(ln, n, user->shell))
+                                               goto fail;
+                                       break;
+                               case COL_FULLNAME:
+                                       if (scols_line_set_data(ln, n, user->gecos))
+                                               goto fail;
+                                       break;
+                               case COL_LAST_LOGIN:
+                                       if (scols_line_set_data(ln, n, user->last_login))
+                                               goto fail;
+                                       break;
+                               case COL_LAST_TTY:
+                                       if (scols_line_set_data(ln, n, user->last_tty))
+                                               goto fail;
+                                       break;
+                               case COL_LAST_HOSTNAME:
+                                       if (scols_line_set_data(ln, n, user->last_hostname))
+                                               goto fail;
+                                       break;
+                               case COL_FAILED_LOGIN:
+                                       if (scols_line_set_data(ln, n, user->failed_login))
+                                               goto fail;
+                                       break;
+                               case COL_FAILED_TTY:
+                                       if (scols_line_set_data(ln, n, user->failed_tty))
+                                               goto fail;
+                                       break;
+                               case COL_HUSH_STATUS:
+                                       if (scols_line_set_data(ln, n, status[user->hushed]))
+                                               goto fail;
+                                       break;
+#define PWD_TIME(S,L,T) strftime((S),(L), "%a %b %d %Y", (T))
+                               case COL_PWD_EXPIR:
+                                       if (scols_line_set_data(ln, n, user->pwd_expir))
+                                               goto fail;
+                                       break;
+                               case COL_PWD_CTIME:
+                                       if (scols_line_set_data(ln, n, user->pwd_ctime))
+                                               goto fail;
+                                       break;
+                               case COL_PWD_CTIME_MIN:
+                                       if (scols_line_set_data(ln, n, user->pwd_ctime_min))
+                                               goto fail;
+                                       break;
+                               case COL_PWD_CTIME_MAX:
+                                       if (scols_line_set_data(ln, n, user->pwd_ctime_max))
+                                               goto fail;
+                                       break;
+#ifdef HAVE_LIBSELINUX
+                               case COL_SELINUX:
+                                       if (scols_line_set_data(ln, n, user->context))
+                                               goto fail;
+                                       break;
+#endif
+                               default:
+                                       /* something went very wrong here */
+                                       err(EXIT_FAILURE, "fatal: unknown error");
+                       }
+                       ++n;
+               }
+       }
+       scols_print_table(tb);
+       scols_unref_table(tb);
+       return 0;
+fail:
+       return -1;
+}
+
 
 int main(int argc, char *argv[])
 {
@@ -227,7 +983,6 @@ int main(int argc, char *argv[])
        static const struct option longopts[] = {
                { "acc-expiration", no_argument,        0, 'a' },
                { "colon",          no_argument,        0, 'c' },
-               { "duplicates",     no_argument,        0, 'd' },
                { "export",         no_argument,        0, 'e' },
                { "failed",         no_argument,        0, 'f' },
                { "groups",         required_argument,  0, 'g' },
@@ -235,16 +990,18 @@ int main(int argc, char *argv[])
                { "logins",         required_argument,  0, 'l' },
                { "more",           no_argument,        0, 'm' },
                { "newline",        no_argument,        0, 'n' },
-               { "output",         optional_argument,  0, 'o' },
-               { "no-password",    no_argument,        0, 'p' },
+               { "output",         required_argument,  0, 'o' },
                { "last",           no_argument,        0, OPT_LAST },
                { "raw",            no_argument,        0, 'r' },
-               { "sys-accs",   optional_argument,      0, 's' },
+               { "sys-accs",       no_argument,        0, 's' },
                { "sort",           no_argument,        0, 't' },
-               { "user-accs",  optional_argument,      0, 'u' },
+               { "user-accs",      no_argument,        0, 'u' },
                { "version",        no_argument,        0, OPT_VER },
                { "extra",          no_argument,        0, 'x' },
                { "print0",         no_argument,        0, 'z' },
+#ifdef HAVE_LIBSELINUX
+               { "context",        no_argument,        0, 'Z' },
+#endif
                { NULL,             0,                  0,  0  }
        };
 
@@ -259,7 +1016,7 @@ int main(int argc, char *argv[])
        textdomain(PACKAGE);
        atexit(close_stdout);
 
-       while ((c = getopt_long(argc, argv, "acdefg:hl:mno::prs::tu::xz",
+       while ((c = getopt_long(argc, argv, "acefg:hl:mno:rstuxzZ",
                                longopts, NULL)) != -1) {
 
                err_exclusive_options(c, longopts, excl, excl_st);
@@ -271,9 +1028,8 @@ int main(int argc, char *argv[])
                        case 'c':
                                outmode = out_colon;
                                break;
-                       case 'd':
-                               uberflag |= F_DUP;
-                               break;
+                               /* a slow option; however the only reason to use it
+                                * is after hand-editing the /etc/passwd file */
                        case 'e':
                                outmode = out_export;
                                break;
@@ -309,9 +1065,6 @@ int main(int argc, char *argv[])
                                                return EXIT_FAILURE;
                                }
                                break;
-                       case 'p':
-                               uberflag |= F_NOPWD;
-                               break;
                        case 'r':
                                outmode = out_raw;
                                break;
@@ -319,12 +1072,16 @@ int main(int argc, char *argv[])
                                uberflag |= F_LAST;
                                break;
                        case 's':
+                               SYS_UID_MIN = strtoul(getlogindefs_str("SYS_UID_MIN", UL_SYS_UID_MIN), NULL, 0);
+                               SYS_UID_MAX = strtoul(getlogindefs_str("SYS_UID_MAX", UL_SYS_UID_MAX), NULL, 0);
                                uberflag |= F_SYSAC;
                                break;
                        case 't':
                                uberflag |= F_SORT;
                                break;
                        case 'u':
+                               UID_MIN = strtoul(getlogindefs_str("UID_MIN", UL_UID_MIN), NULL, 0);
+                               UID_MAX = strtoul(getlogindefs_str("UID_MAX", UL_UID_MAX), NULL, 0);
                                uberflag |= F_USRAC;
                                break;
                        case OPT_VER:
@@ -337,6 +1094,14 @@ int main(int argc, char *argv[])
                        case 'z':
                                outmode = out_nul;
                                break;
+                       case 'Z':
+#ifdef HAVE_LIBSELINUX
+                               if (0 < is_selinux_enabled())
+                                       uberflag |= F_SELINUX;
+                               else
+#endif
+                                       err(0, "warning: --context only works on a system with SELinux enabled");
+                               break;
                        default:
                                usage(stderr);
                }
@@ -344,9 +1109,13 @@ int main(int argc, char *argv[])
        if (argc != optind)
                usage(stderr);
 
+       /* lslogins -u -s == lslogins */
+       if (uberflag & F_USRAC && uberflag & F_SYSAC)
+               uberflag &= ~(F_USRAC & F_SYSAC);
+
        if (!ncolumns) {
                columns[ncolumns++] = COL_LOGIN;
-               columns[ncolumns++] = COL_UID;
+               columns[ncolumns++] = COL_UID;  /* TODO: always get this info, for sorting */
                columns[ncolumns++] = COL_PGRP;
                columns[ncolumns++] = COL_PGID;
                columns[ncolumns++] = COL_FULLNAME;
@@ -365,19 +1134,37 @@ int main(int argc, char *argv[])
                        columns[ncolumns++] = COL_LAST_LOGIN;
                        columns[ncolumns++] = COL_LAST_TTY;
                        columns[ncolumns++] = COL_LAST_HOSTNAME;
+                       want_wtmp = 1;
                }
                if (uberflag & F_FAIL) {
                        columns[ncolumns++] = COL_FAILED_LOGIN;
                        columns[ncolumns++] = COL_FAILED_TTY;
+                       want_btmp = 1;
                }
                if (uberflag & F_EXTRA) {
                        columns[ncolumns++] = COL_HOME;
                        columns[ncolumns++] = COL_SHELL;
-                       columns[ncolumns++] = COL_PWD_STATUS;
+                       columns[ncolumns++] = COL_NOPASSWD;
+                       columns[ncolumns++] = COL_NOLOGIN;
+                       columns[ncolumns++] = COL_LOCKED;
                        columns[ncolumns++] = COL_HUSH_STATUS;
-               /*      columns[ncolumns++] = COL_PWD_CTIME_MAX;
-                       columns[ncolumns++] = COL_PWD_CTIME_MIN; */
+                       columns[ncolumns++] = COL_PWD_CTIME_MIN;
+                       columns[ncolumns++] = COL_PWD_CTIME_MAX;
                }
+               if (uberflag & F_SELINUX)
+                       columns[ncolumns++] = COL_SELINUX;
        }
+
+       if (want_wtmp)
+               parse_wtmp();
+       if (want_btmp)
+               parse_btmp();
+
+       INIT_LIST_HEAD(&userlist);
+       if (create_userlist(logins, groups, columns, ncolumns))
+               return EXIT_FAILURE;
+
+       print_user_table(columns, ncolumns);
+
        return EXIT_SUCCESS;
 }