summaryrefslogtreecommitdiffstats
path: root/login-utils/lslogins.c
diff options
context:
space:
mode:
Diffstat (limited to 'login-utils/lslogins.c')
-rw-r--r--login-utils/lslogins.c1716
1 files changed, 1716 insertions, 0 deletions
diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c
new file mode 100644
index 0000000..3cb30b7
--- /dev/null
+++ b/login-utils/lslogins.c
@@ -0,0 +1,1716 @@
+/*
+ * lslogins - List information about users on the system
+ *
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#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 <utmpx.h>
+#include <signal.h>
+#include <err.h>
+#include <limits.h>
+#include <search.h>
+#include <lastlog.h>
+
+#include <libsmartcols.h>
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+#endif
+
+#ifdef HAVE_LIBSYSTEMD
+# include <systemd/sd-journal.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 "fileutils.h"
+#include "logindefs.h"
+#include "procfs.h"
+#include "timeutils.h"
+
+/*
+ * column description
+ */
+struct lslogins_coldesc {
+ const char *name;
+ const char *help;
+ const char *pretty_name;
+
+ double whint; /* width hint */
+ long flag;
+};
+
+static int lslogins_flag;
+
+#define UL_UID_MIN 1000
+#define UL_UID_MAX 60000
+#define UL_SYS_UID_MIN 101
+#define UL_SYS_UID_MAX 999
+
+/* 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
+ * table with the column separator set to '\n').
+ */
+static int outmode;
+/*
+ * output modes
+ */
+enum {
+ OUT_COLON = 1,
+ OUT_EXPORT,
+ OUT_NEWLINE,
+ OUT_RAW,
+ OUT_NUL,
+ OUT_PRETTY
+};
+
+enum {
+ LASTLOG_TIME,
+ LASTLOG_LINE,
+ LASTLOG_HOST
+};
+
+struct lslogins_user {
+ char *login;
+ uid_t uid;
+ char *group;
+ gid_t gid;
+ char *gecos;
+
+ int pwd_empty;
+ int nologin;
+ int pwd_lock;
+ int pwd_deny;
+
+ gid_t *sgroups;
+ size_t nsgroups;
+
+ char *pwd_ctime;
+ char *pwd_warn;
+ char *pwd_expire;
+ char *pwd_ctime_min;
+ char *pwd_ctime_max;
+ const char *pwd_method;
+
+ char *last_login;
+ char *last_tty;
+ char *last_hostname;
+
+ char *failed_login;
+ char *failed_tty;
+
+#ifdef HAVE_LIBSELINUX
+ char *context;
+#endif
+ char *homedir;
+ char *shell;
+ char *pwd_status;
+ int hushed;
+ char *nprocs;
+
+};
+
+/*
+ * time modes
+ * */
+enum {
+ TIME_INVALID = 0,
+ TIME_SHORT,
+ TIME_FULL,
+ TIME_ISO,
+ TIME_ISO_SHORT,
+};
+
+/*
+ * flags
+ */
+enum {
+ F_SYSAC = (1 << 3),
+ F_USRAC = (1 << 4),
+};
+
+/*
+ * IDs
+ */
+enum {
+ COL_USER = 0,
+ COL_UID,
+ COL_GECOS,
+ COL_HOME,
+ COL_SHELL,
+ COL_NOLOGIN,
+ COL_PWDLOCK,
+ COL_PWDEMPTY,
+ COL_PWDDENY,
+ COL_PWDMETHOD,
+ COL_GROUP,
+ COL_GID,
+ COL_SGROUPS,
+ COL_SGIDS,
+ COL_LAST_LOGIN,
+ COL_LAST_TTY,
+ COL_LAST_HOSTNAME,
+ COL_FAILED_LOGIN,
+ COL_FAILED_TTY,
+ COL_HUSH_STATUS,
+ COL_PWD_WARN,
+ COL_PWD_CTIME,
+ COL_PWD_CTIME_MIN,
+ COL_PWD_CTIME_MAX,
+ COL_PWD_EXPIR,
+ COL_SELINUX,
+ COL_NPROCS,
+};
+
+#define is_wtmp_col(x) ((x) == COL_LAST_LOGIN || \
+ (x) == COL_LAST_TTY || \
+ (x) == COL_LAST_HOSTNAME)
+
+#define is_btmp_col(x) ((x) == COL_FAILED_LOGIN || \
+ (x) == COL_FAILED_TTY)
+
+enum {
+ STATUS_FALSE = 0,
+ STATUS_TRUE,
+ STATUS_UNKNOWN
+};
+
+static const char *const status[] = {
+ [STATUS_FALSE] = "0",
+ [STATUS_TRUE] = "1",
+ [STATUS_UNKNOWN]= NULL
+};
+
+static const char *const pretty_status[] = {
+ [STATUS_FALSE] = N_("no"),
+ [STATUS_TRUE] = N_("yes"),
+ [STATUS_UNKNOWN]= NULL
+};
+
+#define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
+
+static const struct lslogins_coldesc coldescs[] =
+{
+ [COL_USER] = { "USER", N_("user name"), N_("Username"), 0.1, SCOLS_FL_NOEXTREMES },
+ [COL_UID] = { "UID", N_("user ID"), "UID", 1, SCOLS_FL_RIGHT},
+ [COL_PWDEMPTY] = { "PWD-EMPTY", N_("password not defined"), N_("Password not required (empty)"), 1, SCOLS_FL_RIGHT },
+ [COL_PWDDENY] = { "PWD-DENY", N_("login by password disabled"), N_("Login by password disabled"), 1, SCOLS_FL_RIGHT },
+ [COL_PWDLOCK] = { "PWD-LOCK", N_("password defined, but locked"), N_("Password is locked"), 1, SCOLS_FL_RIGHT },
+ [COL_PWDMETHOD] = { "PWD-METHOD", N_("password encryption method"), N_("Password encryption method"), 0.1 },
+ [COL_NOLOGIN] = { "NOLOGIN", N_("log in disabled by nologin(8) or pam_nologin(8)"), N_("No login"), 1, SCOLS_FL_RIGHT },
+ [COL_GROUP] = { "GROUP", N_("primary group name"), N_("Primary group"), 0.1 },
+ [COL_GID] = { "GID", N_("primary group ID"), "GID", 1, SCOLS_FL_RIGHT },
+ [COL_SGROUPS] = { "SUPP-GROUPS", N_("supplementary group names"), N_("Supplementary groups"), 0.1 },
+ [COL_SGIDS] = { "SUPP-GIDS", N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 },
+ [COL_HOME] = { "HOMEDIR", N_("home directory"), N_("Home directory"), 0.1 },
+ [COL_SHELL] = { "SHELL", N_("login shell"), N_("Shell"), 0.1 },
+ [COL_GECOS] = { "GECOS", N_("full user name"), N_("Gecos field"), 0.1, SCOLS_FL_TRUNC },
+ [COL_LAST_LOGIN] = { "LAST-LOGIN", N_("date of last login"), N_("Last login"), 0.1, SCOLS_FL_RIGHT },
+ [COL_LAST_TTY] = { "LAST-TTY", N_("last tty used"), N_("Last terminal"), 0.05 },
+ [COL_LAST_HOSTNAME] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"), 0.1},
+ [COL_FAILED_LOGIN] = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 0.1 },
+ [COL_FAILED_TTY] = { "FAILED-TTY", N_("where did the login fail?"), N_("Failed login terminal"), 0.05 },
+ [COL_HUSH_STATUS] = { "HUSHED", N_("user's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT },
+ [COL_PWD_WARN] = { "PWD-WARN", N_("days user is warned of password expiration"), N_("Password expiration warn interval"), 0.1, SCOLS_FL_RIGHT },
+ [COL_PWD_EXPIR] = { "PWD-EXPIR", N_("password expiration date"), N_("Password expiration"), 0.1, SCOLS_FL_RIGHT },
+ [COL_PWD_CTIME] = { "PWD-CHANGE", N_("date of last password change"), N_("Password changed"), 0.1, SCOLS_FL_RIGHT},
+ [COL_PWD_CTIME_MIN] = { "PWD-MIN", N_("number of days required between changes"), N_("Minimum change time"), 0.1, SCOLS_FL_RIGHT },
+ [COL_PWD_CTIME_MAX] = { "PWD-MAX", N_("max number of days a password may remain unchanged"), N_("Maximum change time"), 0.1, SCOLS_FL_RIGHT },
+ [COL_SELINUX] = { "CONTEXT", N_("the user's security context"), N_("Selinux context"), 0.1 },
+ [COL_NPROCS] = { "PROC", N_("number of processes run by the user"), N_("Running processes"), 1, SCOLS_FL_RIGHT },
+};
+
+struct lslogins_control {
+ struct utmpx *wtmp;
+ size_t wtmp_size;
+
+ struct utmpx *btmp;
+ size_t btmp_size;
+
+ int lastlogin_fd;
+
+ void *usertree;
+
+ uid_t uid;
+ uid_t UID_MIN;
+ uid_t UID_MAX;
+
+ uid_t SYS_UID_MIN;
+ uid_t SYS_UID_MAX;
+
+ char **ulist;
+ size_t ulsiz;
+
+ unsigned int time_mode;
+
+ const char *journal_path;
+
+ unsigned int selinux_enabled : 1,
+ fail_on_unknown : 1, /* fail if user does not exist */
+ ulist_on : 1,
+ shellvar : 1,
+ noheadings : 1,
+ notrunc : 1;
+};
+
+/* these have to remain global since there's no other reasonable way to pass
+ * them for each call of fill_table() via twalk() */
+static struct libscols_table *tb;
+
+/* columns[] array specifies all currently wanted output column. The columns
+ * are defined by coldescs[] array and you can specify (on command line) each
+ * column twice. That's enough, dynamically allocated array of the columns is
+ * unnecessary overkill and over-engineering in this case */
+static int columns[ARRAY_SIZE(coldescs) * 2];
+static size_t ncolumns;
+
+static inline size_t err_columns_index(size_t arysz, size_t idx)
+{
+ if (idx >= arysz)
+ errx(EXIT_FAILURE, _("too many columns specified, "
+ "the limit is %zu columns"),
+ arysz - 1);
+ return idx;
+}
+
+#define add_column(ary, n, id) \
+ ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
+ const char *cn = coldescs[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+ return -1;
+}
+
+static struct timeval now;
+
+static char *make_time(int mode, time_t time)
+{
+ int rc = 0;
+ char buf[64] = {0};
+
+ switch(mode) {
+ case TIME_FULL:
+ {
+ char *s;
+ struct tm tm;
+ localtime_r(&time, &tm);
+
+ asctime_r(&tm, buf);
+ if (*(s = buf + strlen(buf) - 1) == '\n')
+ *s = '\0';
+ rc = 0;
+ break;
+ }
+ case TIME_SHORT:
+ rc = strtime_short(&time, &now, UL_SHORTTIME_THISYEAR_HHMM,
+ buf, sizeof(buf));
+ break;
+ case TIME_ISO:
+ rc = strtime_iso(&time, ISO_TIMESTAMP_T, buf, sizeof(buf));
+ break;
+ case TIME_ISO_SHORT:
+ rc = strtime_iso(&time, ISO_DATE, buf, sizeof(buf));
+ break;
+ default:
+ errx(EXIT_FAILURE, _("unsupported time type"));
+ }
+
+ if (rc)
+ errx(EXIT_FAILURE, _("failed to compose time string"));
+
+ return xstrdup(buf);
+}
+
+
+static char *uidtostr(uid_t uid)
+{
+ char *str_uid = NULL;
+ xasprintf(&str_uid, "%u", uid);
+ return str_uid;
+}
+
+static char *gidtostr(gid_t gid)
+{
+ char *str_gid = NULL;
+ xasprintf(&str_gid, "%u", gid);
+ return str_gid;
+}
+
+static char *build_sgroups_string(gid_t *sgroups, size_t nsgroups, int want_names)
+{
+ size_t n = 0, maxlen, len;
+ char *res, *p;
+
+ if (!nsgroups)
+ return NULL;
+
+ len = maxlen = nsgroups * 10;
+ res = p = xmalloc(maxlen);
+
+ while (n < nsgroups) {
+ int x;
+again:
+ if (!want_names)
+ x = snprintf(p, len, "%u,", sgroups[n]);
+ else {
+ struct group *grp = getgrgid(sgroups[n]);
+ if (!grp) {
+ free(res);
+ return NULL;
+ }
+ x = snprintf(p, len, "%s,", grp->gr_name);
+ }
+
+ if (x < 0 || (size_t) x >= len) {
+ size_t cur = p - res;
+
+ maxlen *= 2;
+ res = xrealloc(res, maxlen);
+ p = res + cur;
+ len = maxlen - cur;
+ goto again;
+ }
+
+ len -= x;
+ p += x;
+ ++n;
+ }
+
+ if (p > res)
+ *(p - 1) = '\0';
+
+ return res;
+}
+
+static struct utmpx *get_last_wtmp(struct lslogins_control *ctl, const char *username)
+{
+ size_t n = 0;
+
+ if (!username)
+ return NULL;
+
+ n = ctl->wtmp_size - 1;
+ do {
+ if (!strncmp(username, ctl->wtmp[n].ut_user,
+ sizeof(ctl->wtmp[0].ut_user)))
+ return ctl->wtmp + n;
+ } while (n--);
+ return NULL;
+
+}
+
+static int require_wtmp(void)
+{
+ size_t i;
+ for (i = 0; i < ncolumns; i++)
+ if (is_wtmp_col(columns[i]))
+ return 1;
+ return 0;
+}
+
+static int require_btmp(void)
+{
+ size_t i;
+ for (i = 0; i < ncolumns; i++)
+ if (is_btmp_col(columns[i]))
+ return 1;
+ return 0;
+}
+
+static struct utmpx *get_last_btmp(struct lslogins_control *ctl, const char *username)
+{
+ size_t n = 0;
+
+ if (!username)
+ return NULL;
+
+ n = ctl->btmp_size - 1;
+ do {
+ if (!strncmp(username, ctl->btmp[n].ut_user,
+ sizeof(ctl->wtmp[0].ut_user)))
+ return ctl->btmp + n;
+ }while (n--);
+ return NULL;
+
+}
+
+static int parse_utmpx(const char *path, size_t *nrecords, struct utmpx **records)
+{
+ size_t i, imax = 0;
+ struct utmpx *ary = NULL;
+ struct stat st;
+
+ *nrecords = 0;
+ *records = NULL;
+
+ if (utmpxname(path) < 0)
+ return -errno;
+
+ /* optimize allocation according to file size, the realloc() below is
+ * just fallback only */
+ if (stat(path, &st) == 0 && (size_t) st.st_size >= sizeof(struct utmpx)) {
+ imax = st.st_size / sizeof(struct utmpx);
+ ary = xmalloc(imax * sizeof(struct utmpx));
+ }
+
+ for (i = 0; ; i++) {
+ struct utmpx *u;
+ errno = 0;
+ u = getutxent();
+ if (!u) {
+ if (errno)
+ goto fail;
+ break;
+ }
+ if (i == imax)
+ ary = xrealloc(ary, (imax *= 2) * sizeof(struct utmpx));
+ ary[i] = *u;
+ }
+
+ *nrecords = i;
+ *records = ary;
+ endutxent();
+ return 0;
+fail:
+ endutxent();
+ free(ary);
+ if (errno) {
+ if (errno != EACCES)
+ err(EXIT_FAILURE, "%s", path);
+ return -errno;
+ }
+ return -EINVAL;
+}
+
+static void get_lastlog(struct lslogins_control *ctl, uid_t uid, void *dst, int what)
+{
+ struct lastlog ll;
+
+ if (ctl->lastlogin_fd < 0 ||
+ pread(ctl->lastlogin_fd, (void *)&ll, sizeof(ll), uid * sizeof(ll)) != sizeof(ll))
+ return;
+
+ switch (what) {
+ case LASTLOG_TIME: {
+ time_t *t = (time_t *)dst;
+ *t = ll.ll_time;
+ break;
+ }
+ case LASTLOG_LINE:
+ mem2strcpy(dst, ll.ll_line, sizeof(ll.ll_line), sizeof(ll.ll_line) + 1);
+ break;
+ case LASTLOG_HOST:
+ mem2strcpy(dst, ll.ll_host, sizeof(ll.ll_host), sizeof(ll.ll_host) + 1);
+ break;
+ default:
+ abort();
+ }
+}
+
+static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
+{
+ size_t n = 0;
+ int ngroups = 0;
+
+ *len = 0;
+ *list = NULL;
+
+ /* first let's get a supp. group count */
+ getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups);
+ if (!ngroups)
+ return -1;
+
+ *list = xcalloc(1, ngroups * sizeof(gid_t));
+
+ /* now for the actual list of GIDs */
+ if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups))
+ return -1;
+
+ *len = (size_t) ngroups;
+
+ /* getgroups also returns the user's primary GID - dispose of it */
+ while (n < *len) {
+ if ((*list)[n] == pwd->pw_gid)
+ break;
+ ++n;
+ }
+
+ if (*len)
+ (*list)[n] = (*list)[--(*len)];
+
+ return 0;
+}
+
+#ifdef __linux__
+static int get_nprocs(const uid_t uid)
+{
+ DIR *dir;
+ struct dirent *d;
+ int nprocs = 0;
+
+ dir = opendir(_PATH_PROC);
+ if (!dir)
+ return 0;
+
+ while ((d = xreaddir(dir))) {
+ if (procfs_dirent_match_uid(dir, d, uid))
+ ++nprocs;
+ }
+
+ closedir(dir);
+ return nprocs;
+}
+#endif
+
+static const char *get_pwd_method(const char *str, const char **next, unsigned int *sz)
+{
+ const char *p = str;
+ const char *res = NULL;
+
+ if (!p || *p++ != '$')
+ return NULL;
+
+ if (sz)
+ *sz = 0;
+
+ switch (*p) {
+ case '1':
+ res = "MD5";
+ if (sz)
+ *sz = 22;
+ break;
+ case '2':
+ p++;
+ if (*p == 'a' || *p == 'y')
+ res = "Blowfish";
+ break;
+ case '5':
+ res = "SHA-256";
+ if (sz)
+ *sz = 43;
+ break;
+ case '6':
+ res = "SHA-512";
+ if (sz)
+ *sz = 86;
+ break;
+ default:
+ return NULL;
+ }
+ p++;
+
+ if (*p != '$')
+ return NULL;
+ if (next)
+ *next = ++p;
+ return res;
+}
+
+#define is_valid_pwd_char(x) (isalnum((unsigned char) (x)) || (x) == '.' || (x) == '/')
+
+/*
+ * This function do not accept empty passwords or locked accouns.
+ */
+static int valid_pwd(const char *str)
+{
+ const char *p = str;
+ unsigned int sz = 0, n;
+
+ if (!str || !*str)
+ return 0;
+
+ /* $id$ */
+ if (get_pwd_method(str, &p, &sz) == NULL)
+ return 0;
+ if (!p || !*p)
+ return 0;
+
+ /* salt$ */
+ for (; *p; p++) {
+ if (*p == '$') {
+ p++;
+ break;
+ }
+ if (!is_valid_pwd_char(*p))
+ return 0;
+ }
+ if (!*p)
+ return 0;
+
+ /* encrypted */
+ for (n = 0; *p; p++, n++) {
+ if (!is_valid_pwd_char(*p))
+ return 0;
+ }
+
+ if (sz && n != sz)
+ return 0;
+ return 1;
+}
+
+static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username)
+{
+ struct lslogins_user *user;
+ struct passwd *pwd;
+ struct group *grp;
+ struct spwd *shadow;
+ struct utmpx *user_wtmp = NULL, *user_btmp = NULL;
+ size_t n = 0;
+ time_t time;
+ uid_t uid;
+ errno = 0;
+
+ errno = 0;
+ pwd = username ? getpwnam(username) : getpwent();
+ if (!pwd)
+ return NULL;
+
+ ctl->uid = uid = pwd->pw_uid;
+
+ /* nfsnobody is an exception to the UID_MAX limit. This is "nobody" on
+ * some systems; the decisive point is the UID - 65534 */
+ if ((lslogins_flag & F_USRAC) &&
+ strcmp("nfsnobody", pwd->pw_name) != 0 &&
+ uid != 0) {
+ if (uid < ctl->UID_MIN || uid > ctl->UID_MAX) {
+ errno = EAGAIN;
+ return NULL;
+ }
+
+ } else if ((lslogins_flag & F_SYSAC) &&
+ (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX)) {
+ errno = EAGAIN;
+ return NULL;
+ }
+
+ errno = 0;
+ grp = getgrgid(pwd->pw_gid);
+ if (!grp)
+ return NULL;
+
+ user = xcalloc(1, sizeof(struct lslogins_user));
+
+ if (ctl->wtmp)
+ user_wtmp = get_last_wtmp(ctl, pwd->pw_name);
+ if (ctl->btmp)
+ user_btmp = get_last_btmp(ctl, pwd->pw_name);
+
+ lckpwdf();
+ shadow = getspnam(pwd->pw_name);
+ ulckpwdf();
+
+ /* required by tseach() stuff */
+ user->uid = pwd->pw_uid;
+
+ while (n < ncolumns) {
+ switch (columns[n++]) {
+ case COL_USER:
+ user->login = xstrdup(pwd->pw_name);
+ break;
+ case COL_UID:
+ user->uid = pwd->pw_uid;
+ break;
+ case COL_GROUP:
+ user->group = xstrdup(grp->gr_name);
+ break;
+ case COL_GID:
+ user->gid = pwd->pw_gid;
+ break;
+ case COL_SGROUPS:
+ case COL_SGIDS:
+ if (!user->nsgroups &&
+ get_sgroups(&user->sgroups, &user->nsgroups, pwd) < 0)
+ err(EXIT_FAILURE, _("failed to get supplementary groups"));
+ break;
+ case COL_HOME:
+ user->homedir = xstrdup(pwd->pw_dir);
+ break;
+ case COL_SHELL:
+ user->shell = xstrdup(pwd->pw_shell);
+ break;
+ case COL_GECOS:
+ user->gecos = xstrdup(pwd->pw_gecos);
+ break;
+ case COL_LAST_LOGIN:
+ if (user_wtmp) {
+ time = user_wtmp->ut_tv.tv_sec;
+ user->last_login = make_time(ctl->time_mode, time);
+ } else {
+ time = 0;
+ get_lastlog(ctl, pwd->pw_uid, &time, LASTLOG_TIME);
+ if (time)
+ user->last_login = make_time(ctl->time_mode, time);
+ }
+ break;
+ case COL_LAST_TTY:
+ user->last_tty = xcalloc(1, sizeof(user_wtmp->ut_line) + 1);
+ if (user_wtmp) {
+ mem2strcpy(user->last_tty, user_wtmp->ut_line,
+ sizeof(user_wtmp->ut_line),
+ sizeof(user_wtmp->ut_line) + 1);;
+ } else
+ get_lastlog(ctl, user->uid, user->last_tty, LASTLOG_LINE);
+ break;
+ case COL_LAST_HOSTNAME:
+ user->last_hostname = xcalloc(1, sizeof(user_wtmp->ut_host) + 1);
+ if (user_wtmp) {
+ mem2strcpy(user->last_hostname, user_wtmp->ut_host,
+ sizeof(user_wtmp->ut_host),
+ sizeof(user_wtmp->ut_host) + 1);;
+ } else
+ get_lastlog(ctl, user->uid, user->last_hostname, LASTLOG_HOST);
+ break;
+ case COL_FAILED_LOGIN:
+ if (user_btmp) {
+ time = user_btmp->ut_tv.tv_sec;
+ user->failed_login = make_time(ctl->time_mode, time);
+ }
+ break;
+ case COL_FAILED_TTY:
+ if (user_btmp) {
+ user->failed_tty = xmalloc(sizeof(user_btmp->ut_line) + 1);
+ mem2strcpy(user->failed_tty, user_btmp->ut_line,
+ sizeof(user_btmp->ut_line),
+ sizeof(user_btmp->ut_line) + 1);;
+ }
+ break;
+ case COL_HUSH_STATUS:
+ user->hushed = get_hushlogin_status(pwd, 0);
+ if (user->hushed == -1)
+ user->hushed = STATUS_UNKNOWN;
+ break;
+ case COL_PWDEMPTY:
+ if (shadow) {
+ const char *p = shadow->sp_pwdp;
+
+ while (p && (*p == '!' || *p == '*'))
+ p++;
+
+ if (!p || !*p)
+ user->pwd_empty = STATUS_TRUE;
+ } else
+ user->pwd_empty = STATUS_UNKNOWN;
+ break;
+ case COL_PWDDENY:
+ if (shadow) {
+ const char *p = shadow->sp_pwdp;
+
+ while (p && (*p == '!' || *p == '*'))
+ p++;
+
+ if (p && *p && p != shadow->sp_pwdp && !valid_pwd(p))
+ user->pwd_deny = STATUS_TRUE;
+ } else
+ user->pwd_deny = STATUS_UNKNOWN;
+ break;
+ case COL_PWDLOCK:
+ if (shadow) {
+ const char *p = shadow->sp_pwdp;
+ int i = 0;
+
+ /* 'passwd --lock' uses two exclamation marks,
+ * shadow(5) describes the lock as "field which
+ * starts with an exclamation mark". Let's
+ * support more '!' ...
+ */
+ while (p && *p == '!')
+ p++, i++;
+
+ if (i != 0 && (!*p || valid_pwd(p)))
+ user->pwd_lock = STATUS_TRUE;
+ } else
+ user->pwd_lock = STATUS_UNKNOWN;
+ break;
+ case COL_PWDMETHOD:
+ if (shadow) {
+ const char *p = shadow->sp_pwdp;
+
+ while (p && (*p == '!' || *p == '*'))
+ p++;
+ user->pwd_method = get_pwd_method(p, NULL, NULL);
+ } else
+ user->pwd_method = NULL;
+ break;
+ case COL_NOLOGIN:
+ if (strstr(pwd->pw_shell, "nologin"))
+ user->nologin = 1;
+ else if (pwd->pw_uid)
+ user->nologin = access(_PATH_NOLOGIN, F_OK) == 0 ||
+ access(_PATH_VAR_NOLOGIN, F_OK) == 0;
+ break;
+ case COL_PWD_WARN:
+ if (shadow && shadow->sp_warn >= 0)
+ xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
+ break;
+ case COL_PWD_EXPIR:
+ if (shadow && shadow->sp_expire >= 0)
+ user->pwd_expire = make_time(ctl->time_mode == TIME_ISO ?
+ TIME_ISO_SHORT : ctl->time_mode,
+ shadow->sp_expire * 86400);
+ break;
+ case COL_PWD_CTIME:
+ /* sp_lstchg is specified in days, showing hours
+ * (especially in non-GMT timezones) would only serve
+ * to confuse */
+ if (shadow)
+ user->pwd_ctime = make_time(ctl->time_mode == TIME_ISO ?
+ TIME_ISO_SHORT : ctl->time_mode,
+ shadow->sp_lstchg * 86400);
+ break;
+ case COL_PWD_CTIME_MIN:
+ if (shadow && shadow->sp_min > 0)
+ xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
+ break;
+ case COL_PWD_CTIME_MAX:
+ if (shadow && shadow->sp_max > 0)
+ xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
+ break;
+ case COL_SELINUX:
+#ifdef HAVE_LIBSELINUX
+ if (!ctl->selinux_enabled || getcon(&user->context) != 0)
+ user->context = NULL;
+#endif
+ break;
+ case COL_NPROCS:
+#ifdef __linux__
+
+ xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
+#endif
+ break;
+ default:
+ /* something went very wrong here */
+ err(EXIT_FAILURE, "fatal: unknown error");
+ break;
+ }
+ }
+
+ return user;
+}
+
+static int str_to_uint(char *s, unsigned int *ul)
+{
+ char *end = NULL;
+
+ if (!s || !*s)
+ return -1;
+
+ errno = 0;
+ *ul = strtoul(s, &end, 0);
+ if (errno == 0 && end && !*end)
+ return 0;
+ return 1;
+}
+
+/* get a definitive list of users we want info about... */
+static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
+{
+ char *u, *g;
+ size_t i = 0, n = 0, *arsiz;
+ struct group *grp;
+ struct passwd *pwd;
+ char ***ar;
+ uid_t uid;
+ gid_t gid;
+
+ ar = &ctl->ulist;
+ arsiz = &ctl->ulsiz;
+
+ /* an arbitrary starting value */
+ *arsiz = 32;
+ *ar = xcalloc(1, sizeof(char *) * (*arsiz));
+
+ if (logins) {
+ while ((u = strtok(logins, ","))) {
+ logins = NULL;
+
+ /* user specified by UID? */
+ if (!str_to_uint(u, &uid)) {
+ pwd = getpwuid(uid);
+ if (!pwd)
+ continue;
+ u = pwd->pw_name;
+ }
+ (*ar)[i++] = xstrdup(u);
+
+ if (i == *arsiz)
+ *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
+ }
+ ctl->ulist_on = 1;
+ }
+
+ if (groups) {
+ /* FIXME: this might lead to duplicate entries, although not visible
+ * in output, crunching a user's info multiple times is very redundant */
+ while ((g = strtok(groups, ","))) {
+ n = 0;
+ groups = NULL;
+
+ /* user specified by GID? */
+ if (!str_to_uint(g, &gid))
+ grp = getgrgid(gid);
+ else
+ grp = getgrnam(g);
+
+ if (!grp)
+ continue;
+
+ while ((u = grp->gr_mem[n++])) {
+ (*ar)[i++] = xstrdup(u);
+
+ if (i == *arsiz)
+ *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
+ }
+ }
+ ctl->ulist_on = 1;
+ }
+ *arsiz = i;
+ return 0;
+}
+
+static void free_ctl(struct lslogins_control *ctl)
+{
+ size_t n = 0;
+
+ if (!ctl)
+ return;
+
+ free(ctl->wtmp);
+ free(ctl->btmp);
+
+ while (n < ctl->ulsiz)
+ free(ctl->ulist[n++]);
+
+ free(ctl->ulist);
+ free(ctl);
+}
+
+static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
+{
+ struct lslogins_user *u;
+ errno = 0;
+ while (!(u = get_user_info(ctl, NULL))) {
+ /* no "false" errno-s here, iff we're unable to
+ * get a valid user entry for any reason, quit */
+ if (errno == EAGAIN)
+ continue;
+ return NULL;
+ }
+ return u;
+}
+
+/* some UNIX implementations set errno iff a passwd/grp/...
+ * entry was not found. The original UNIX logins(1) 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)
+
+static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
+ const char *username)
+{
+ *user = get_user_info(ctl, username);
+ if (!*user && IS_REAL_ERRNO(errno))
+ return -1;
+ return 0;
+}
+
+static int cmp_uid(const void *a, const void *b)
+{
+ uid_t x = ((const struct lslogins_user *)a)->uid;
+ uid_t z = ((const struct lslogins_user *)b)->uid;
+ return x > z ? 1 : (x < z ? -1 : 0);
+}
+
+static int create_usertree(struct lslogins_control *ctl)
+{
+ struct lslogins_user *user = NULL;
+ size_t n = 0;
+
+ if (ctl->ulist_on) {
+ for (n = 0; n < ctl->ulsiz; n++) {
+ int rc = get_user(ctl, &user, ctl->ulist[n]);
+
+ if (ctl->fail_on_unknown && !user) {
+ warnx(_("cannot found '%s'"), ctl->ulist[n]);
+ return -1;
+ }
+ if (rc || !user)
+ continue;
+ tsearch(user, &ctl->usertree, cmp_uid);
+ }
+ } else {
+ while ((user = get_next_user(ctl)))
+ tsearch(user, &ctl->usertree, cmp_uid);
+ }
+ return 0;
+}
+
+static struct libscols_table *setup_table(struct lslogins_control *ctl)
+{
+ struct libscols_table *table = scols_new_table();
+ size_t n = 0;
+
+ if (!table)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+ if (ctl->noheadings)
+ scols_table_enable_noheadings(table, 1);
+ if (ctl->shellvar)
+ scols_table_enable_shellvar(table, 1);
+
+ switch(outmode) {
+ case OUT_COLON:
+ scols_table_enable_raw(table, 1);
+ scols_table_set_column_separator(table, ":");
+ break;
+ case OUT_NEWLINE:
+ scols_table_set_column_separator(table, "\n");
+ /* fallthrough */
+ case OUT_EXPORT:
+ scols_table_enable_export(table, 1);
+ break;
+ case OUT_NUL:
+ scols_table_set_line_separator(table, "\0");
+ /* fallthrough */
+ case OUT_RAW:
+ scols_table_enable_raw(table, 1);
+ break;
+ case OUT_PRETTY:
+ scols_table_enable_noheadings(table, 1);
+ default:
+ break;
+ }
+
+ while (n < ncolumns) {
+ int flags = coldescs[columns[n]].flag;
+
+ if (ctl->notrunc)
+ flags &= ~SCOLS_FL_TRUNC;
+
+ if (!scols_table_new_column(table,
+ coldescs[columns[n]].name,
+ coldescs[columns[n]].whint,
+ flags))
+ goto fail;
+ ++n;
+ }
+
+ return table;
+fail:
+ scols_unref_table(table);
+ return NULL;
+}
+
+static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
+{
+ struct libscols_line *ln;
+ const struct lslogins_user *user = *(struct lslogins_user * const *)u;
+ size_t n = 0;
+
+ if (which == preorder || which == endorder)
+ return;
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ while (n < ncolumns) {
+ int rc = 0;
+
+ switch (columns[n]) {
+ case COL_USER:
+ rc = scols_line_set_data(ln, n, user->login);
+ break;
+ case COL_UID:
+ rc = scols_line_refer_data(ln, n, uidtostr(user->uid));
+ break;
+ case COL_PWDEMPTY:
+ rc = scols_line_set_data(ln, n, get_status(user->pwd_empty));
+ break;
+ case COL_NOLOGIN:
+ rc = scols_line_set_data(ln, n, get_status(user->nologin));
+ break;
+ case COL_PWDLOCK:
+ rc = scols_line_set_data(ln, n, get_status(user->pwd_lock));
+ break;
+ case COL_PWDDENY:
+ rc = scols_line_set_data(ln, n, get_status(user->pwd_deny));
+ break;
+ case COL_PWDMETHOD:
+ rc = scols_line_set_data(ln, n, user->pwd_method);
+ break;
+ case COL_GROUP:
+ rc = scols_line_set_data(ln, n, user->group);
+ break;
+ case COL_GID:
+ rc = scols_line_refer_data(ln, n, gidtostr(user->gid));
+ break;
+ case COL_SGROUPS:
+ rc = scols_line_refer_data(ln, n,
+ build_sgroups_string(user->sgroups,
+ user->nsgroups,
+ TRUE));
+ break;
+ case COL_SGIDS:
+ rc = scols_line_refer_data(ln, n,
+ build_sgroups_string(user->sgroups,
+ user->nsgroups,
+ FALSE));
+ break;
+ case COL_HOME:
+ rc = scols_line_set_data(ln, n, user->homedir);
+ break;
+ case COL_SHELL:
+ rc = scols_line_set_data(ln, n, user->shell);
+ break;
+ case COL_GECOS:
+ rc = scols_line_set_data(ln, n, user->gecos);
+ break;
+ case COL_LAST_LOGIN:
+ rc = scols_line_set_data(ln, n, user->last_login);
+ break;
+ case COL_LAST_TTY:
+ rc = scols_line_set_data(ln, n, user->last_tty);
+ break;
+ case COL_LAST_HOSTNAME:
+ rc = scols_line_set_data(ln, n, user->last_hostname);
+ break;
+ case COL_FAILED_LOGIN:
+ rc = scols_line_set_data(ln, n, user->failed_login);
+ break;
+ case COL_FAILED_TTY:
+ rc = scols_line_set_data(ln, n, user->failed_tty);
+ break;
+ case COL_HUSH_STATUS:
+ rc = scols_line_set_data(ln, n, get_status(user->hushed));
+ break;
+ case COL_PWD_WARN:
+ rc = scols_line_set_data(ln, n, user->pwd_warn);
+ break;
+ case COL_PWD_EXPIR:
+ rc = scols_line_set_data(ln, n, user->pwd_expire);
+ break;
+ case COL_PWD_CTIME:
+ rc = scols_line_set_data(ln, n, user->pwd_ctime);
+ break;
+ case COL_PWD_CTIME_MIN:
+ rc = scols_line_set_data(ln, n, user->pwd_ctime_min);
+ break;
+ case COL_PWD_CTIME_MAX:
+ rc = scols_line_set_data(ln, n, user->pwd_ctime_max);
+ break;
+ case COL_SELINUX:
+#ifdef HAVE_LIBSELINUX
+ rc = scols_line_set_data(ln, n, user->context);
+#endif
+ break;
+ case COL_NPROCS:
+#ifdef __linux__
+
+ rc = scols_line_set_data(ln, n, user->nprocs);
+#endif
+ break;
+ default:
+ /* something went very wrong here */
+ err(EXIT_FAILURE, _("internal error: unknown column"));
+ }
+
+ if (rc)
+ err(EXIT_FAILURE, _("failed to add output data"));
+ ++n;
+ }
+}
+#ifdef HAVE_LIBSYSTEMD
+static char *get_journal_data(sd_journal *j, const char *name)
+{
+ const char *data = NULL, *p;
+ size_t len = 0;
+
+ if (sd_journal_get_data(j, name, (const void **) &data, &len) < 0
+ || !data || !len)
+ return NULL;
+
+ /* Get rid of journal entry field identifiers */
+ p = strnchr(data, len, '=');
+ if (!p || !*(p + 1))
+ return NULL;
+ p++;
+
+ return xstrndup(p, len - (p - data));
+}
+
+static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
+{
+ sd_journal *j;
+ char *match;
+
+ if (journal_path)
+ sd_journal_open_directory(&j, journal_path, 0);
+ else
+ sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+
+ xasprintf(&match, "_UID=%d", uid);
+
+ sd_journal_add_match(j, match, 0);
+ sd_journal_seek_tail(j);
+ sd_journal_previous_skip(j, len);
+
+ do {
+ char *id, *pid, *msg, *ts;
+ uint64_t x;
+ time_t t;
+
+ sd_journal_get_realtime_usec(j, &x);
+ t = x / 1000000;
+ ts = make_time(time_mode, t);
+
+ id = get_journal_data(j, "SYSLOG_IDENTIFIER");
+ pid = get_journal_data(j, "_PID");
+ msg = get_journal_data(j, "MESSAGE");
+
+ if (ts && id && pid && msg)
+ fprintf(stdout, "%s %s[%s]: %s\n", ts, id, pid, msg);
+
+ free(ts);
+ free(id);
+ free(pid);
+ free(msg);
+ } while (sd_journal_next(j));
+
+ free(match);
+ sd_journal_flush_matches(j);
+ sd_journal_close(j);
+}
+#endif
+
+static int print_pretty(struct libscols_table *table)
+{
+ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
+ struct libscols_column *col;
+ struct libscols_cell *data;
+ struct libscols_line *ln;
+ const char *hstr, *dstr;
+ int n = 0;
+
+ ln = scols_table_get_line(table, 0);
+ while (!scols_table_next_column(table, itr, &col)) {
+
+ data = scols_line_get_cell(ln, n);
+
+ hstr = _(coldescs[columns[n]].pretty_name);
+ dstr = scols_cell_get_data(data);
+
+ if (dstr)
+ printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
+ ++n;
+ }
+
+ scols_free_iter(itr);
+ return 0;
+
+}
+
+static int print_user_table(struct lslogins_control *ctl)
+{
+ tb = setup_table(ctl);
+ if (!tb)
+ return -1;
+
+ twalk(ctl->usertree, fill_table);
+ if (outmode == OUT_PRETTY) {
+ print_pretty(tb);
+#ifdef HAVE_LIBSYSTEMD
+ fprintf(stdout, _("\nLast logs:\n"));
+ print_journal_tail(ctl->journal_path, ctl->uid, 3, ctl->time_mode);
+ fputc('\n', stdout);
+#endif
+ } else
+ scols_print_table(tb);
+ return 0;
+}
+
+static void free_user(void *f)
+{
+ struct lslogins_user *u = f;
+ free(u->login);
+ free(u->group);
+ free(u->nprocs);
+ free(u->gecos);
+ free(u->sgroups);
+ free(u->pwd_ctime);
+ free(u->pwd_warn);
+ free(u->pwd_ctime_min);
+ free(u->pwd_ctime_max);
+ free(u->last_login);
+ free(u->last_tty);
+ free(u->last_hostname);
+ free(u->failed_login);
+ free(u->failed_tty);
+ free(u->homedir);
+ free(u->shell);
+ free(u->pwd_status);
+#ifdef HAVE_LIBSELINUX
+ freecon(u->context);
+#endif
+ free(u);
+}
+
+static int parse_time_mode(const char *s)
+{
+ struct lslogins_timefmt {
+ const char *name;
+ const int val;
+ };
+ static const struct lslogins_timefmt timefmts[] = {
+ {"iso", TIME_ISO},
+ {"full", TIME_FULL},
+ {"short", TIME_SHORT},
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
+ if (strcmp(timefmts[i].name, s) == 0)
+ return timefmts[i].val;
+ }
+ errx(EXIT_FAILURE, _("unknown time format: %s"), s);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<username>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Display information about known users in the system.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -a, --acc-expiration display info about passwords expiration\n"), out);
+ fputs(_(" -c, --colon-separate display data in a format similar to /etc/passwd\n"), out);
+ fputs(_(" -e, --export display in an export-able output format\n"), out);
+ fputs(_(" -f, --failed display data about the users' last failed logins\n"), out);
+ fputs(_(" -G, --supp-groups display information about groups\n"), out);
+ fputs(_(" -g, --groups=<groups> display users belonging to a group in <groups>\n"), out);
+ fputs(_(" -L, --last show info about the users' last login sessions\n"), out);
+ fputs(_(" -l, --logins=<logins> display only users from <logins>\n"), out);
+ fputs(_(" -n, --newline display each piece of information on a new line\n"), out);
+ fputs(_(" --noheadings don't print headings\n"), out);
+ fputs(_(" --notruncate don't truncate output\n"), out);
+ fputs(_(" -o, --output[=<list>] define the columns to output\n"), out);
+ fputs(_(" --output-all output all columns\n"), out);
+ fputs(_(" -p, --pwd display information related to login by password.\n"), out);
+ fputs(_(" -r, --raw display in raw mode\n"), out);
+ fputs(_(" -s, --system-accs display system accounts\n"), out);
+ fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out);
+ fputs(_(" -u, --user-accs display user accounts\n"), out);
+ fputs(_(" -y, --shell use column names to be usable as shell variable identifiers\n"), out);
+ fputs(_(" -Z, --context display SELinux contexts\n"), out);
+ fputs(_(" -z, --print0 delimit user entries with a nul character\n"), out);
+ fputs(_(" --wtmp-file <path> set an alternate path for wtmp\n"), out);
+ fputs(_(" --btmp-file <path> set an alternate path for btmp\n"), out);
+ fputs(_(" --lastlog <path> set an alternate path for lastlog\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(26));
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++)
+ fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ printf(USAGE_MAN_TAIL("lslogins(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ char *logins = NULL, *groups = NULL, *outarg = NULL;
+ char *path_lastlog = _PATH_LASTLOG, *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP;
+ struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control));
+ size_t i;
+
+ /* long only options. */
+ enum {
+ OPT_WTMP = CHAR_MAX + 1,
+ OPT_BTMP,
+ OPT_LASTLOG,
+ OPT_NOTRUNC,
+ OPT_NOHEAD,
+ OPT_TIME_FMT,
+ OPT_OUTPUT_ALL,
+ };
+
+ static const struct option longopts[] = {
+ { "acc-expiration", no_argument, 0, 'a' },
+ { "colon-separate", no_argument, 0, 'c' },
+ { "export", no_argument, 0, 'e' },
+ { "shell", no_argument, 0, 'y' },
+ { "failed", no_argument, 0, 'f' },
+ { "groups", required_argument, 0, 'g' },
+ { "help", no_argument, 0, 'h' },
+ { "logins", required_argument, 0, 'l' },
+ { "supp-groups", no_argument, 0, 'G' },
+ { "newline", no_argument, 0, 'n' },
+ { "notruncate", no_argument, 0, OPT_NOTRUNC },
+ { "noheadings", no_argument, 0, OPT_NOHEAD },
+ { "output", required_argument, 0, 'o' },
+ { "output-all", no_argument, 0, OPT_OUTPUT_ALL },
+ { "last", no_argument, 0, 'L', },
+ { "raw", no_argument, 0, 'r' },
+ { "system-accs", no_argument, 0, 's' },
+ { "time-format", required_argument, 0, OPT_TIME_FMT },
+ { "user-accs", no_argument, 0, 'u' },
+ { "version", no_argument, 0, 'V' },
+ { "pwd", no_argument, 0, 'p' },
+ { "print0", no_argument, 0, 'z' },
+ { "wtmp-file", required_argument, 0, OPT_WTMP },
+ { "btmp-file", required_argument, 0, OPT_BTMP },
+ { "lastlog-file", required_argument, 0, OPT_LASTLOG },
+#ifdef HAVE_LIBSELINUX
+ { "context", no_argument, 0, 'Z' },
+#endif
+ { NULL, 0, 0, 0 }
+ };
+
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'G', 'o' },
+ { 'L', 'o' },
+ { 'Z', 'o' },
+ { 'a', 'o' },
+ { 'c','n','r','z' },
+ { 'o', 'p' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ ctl->time_mode = TIME_SHORT;
+
+ /* very basic default */
+ add_column(columns, ncolumns++, COL_UID);
+ add_column(columns, ncolumns++, COL_USER);
+
+ while ((c = getopt_long(argc, argv, "acefGg:hLl:no:prsuVyzZ",
+ longopts, NULL)) != -1) {
+
+ err_exclusive_options(c, longopts, excl, excl_st);
+
+ switch (c) {
+ case 'a':
+ add_column(columns, ncolumns++, COL_PWD_WARN);
+ add_column(columns, ncolumns++, COL_PWD_CTIME_MIN);
+ add_column(columns, ncolumns++, COL_PWD_CTIME_MAX);
+ add_column(columns, ncolumns++, COL_PWD_CTIME);
+ add_column(columns, ncolumns++, COL_PWD_EXPIR);
+ break;
+ case 'c':
+ outmode = OUT_COLON;
+ break;
+ case 'e':
+ outmode = OUT_EXPORT;
+ break;
+ case 'f':
+ add_column(columns, ncolumns++, COL_FAILED_LOGIN);
+ add_column(columns, ncolumns++, COL_FAILED_TTY);
+ break;
+ case 'G':
+ add_column(columns, ncolumns++, COL_GID);
+ add_column(columns, ncolumns++, COL_GROUP);
+ add_column(columns, ncolumns++, COL_SGIDS);
+ add_column(columns, ncolumns++, COL_SGROUPS);
+ break;
+ case 'g':
+ groups = optarg;
+ break;
+ case 'h':
+ usage();
+ break;
+ case 'L':
+ add_column(columns, ncolumns++, COL_LAST_TTY);
+ add_column(columns, ncolumns++, COL_LAST_HOSTNAME);
+ add_column(columns, ncolumns++, COL_LAST_LOGIN);
+ break;
+ case 'l':
+ logins = optarg;
+ break;
+ case 'n':
+ outmode = OUT_NEWLINE;
+ break;
+ case 'o':
+ if (*optarg == '=')
+ optarg++;
+ outarg = optarg;
+ break;
+ case OPT_OUTPUT_ALL:
+ for (ncolumns = 0; ncolumns < ARRAY_SIZE(coldescs); ncolumns++)
+ columns[ncolumns] = ncolumns;
+ break;
+ case 'r':
+ outmode = OUT_RAW;
+ break;
+ case 's':
+ ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN);
+ ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX);
+ lslogins_flag |= F_SYSAC;
+ break;
+ case 'u':
+ ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN);
+ ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX);
+ lslogins_flag |= F_USRAC;
+ break;
+ case 'p':
+ add_column(columns, ncolumns++, COL_PWDEMPTY);
+ add_column(columns, ncolumns++, COL_PWDLOCK);
+ add_column(columns, ncolumns++, COL_PWDDENY);
+ add_column(columns, ncolumns++, COL_NOLOGIN);
+ add_column(columns, ncolumns++, COL_HUSH_STATUS);
+ add_column(columns, ncolumns++, COL_PWDMETHOD);
+ break;
+ case 'y':
+ ctl->shellvar = 1;
+ break;
+ case 'z':
+ outmode = OUT_NUL;
+ break;
+ case OPT_LASTLOG:
+ path_lastlog = optarg;
+ break;
+ case OPT_WTMP:
+ path_wtmp = optarg;
+ break;
+ case OPT_BTMP:
+ path_btmp = optarg;
+ break;
+ case OPT_NOTRUNC:
+ ctl->notrunc = 1;
+ break;
+ case OPT_NOHEAD:
+ ctl->noheadings = 1;
+ break;
+ case OPT_TIME_FMT:
+ ctl->time_mode = parse_time_mode(optarg);
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'Z':
+ {
+#ifdef HAVE_LIBSELINUX
+ int sl = is_selinux_enabled();
+ if (sl < 0)
+ warn(_("failed to request selinux state"));
+ else
+ ctl->selinux_enabled = sl == 1;
+#endif
+ add_column(columns, ncolumns++, COL_SELINUX);
+ break;
+ }
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (argc - optind == 1) {
+ if (strchr(argv[optind], ','))
+ errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
+ logins = argv[optind];
+ outmode = OUT_PRETTY;
+ ctl->fail_on_unknown = 1;
+ } else if (argc != optind)
+ errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
+
+ scols_init_debug(0);
+
+ /* lslogins -u -s == lslogins */
+ if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
+ lslogins_flag &= ~(F_USRAC | F_SYSAC);
+
+ if (outmode == OUT_PRETTY) {
+ /* all columns for lslogins <username> */
+ for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
+ columns[ncolumns++] = i;
+
+ } else if (ncolumns == 2) {
+ /* default columns */
+#ifdef __linux__
+ add_column(columns, ncolumns++, COL_NPROCS);
+#endif
+ add_column(columns, ncolumns++, COL_PWDLOCK);
+ add_column(columns, ncolumns++, COL_PWDDENY);
+ add_column(columns, ncolumns++, COL_LAST_LOGIN);
+ add_column(columns, ncolumns++, COL_GECOS);
+ }
+
+ if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ if (require_wtmp()) {
+ parse_utmpx(path_wtmp, &ctl->wtmp_size, &ctl->wtmp);
+ ctl->lastlogin_fd = open(path_lastlog, O_RDONLY, 0);
+ }
+ if (require_btmp())
+ parse_utmpx(path_btmp, &ctl->btmp_size, &ctl->btmp);
+
+ if (logins || groups)
+ get_ulist(ctl, logins, groups);
+
+ if (create_usertree(ctl))
+ return EXIT_FAILURE;
+
+ print_user_table(ctl);
+
+ scols_unref_table(tb);
+ tdestroy(ctl->usertree, free_user);
+
+ if (ctl->lastlogin_fd >= 0)
+ close(ctl->lastlogin_fd);
+ free_ctl(ctl);
+
+ return EXIT_SUCCESS;
+}