From 29cc2a55686f7182ae16e949d6d2a2b2b44c8536 Mon Sep 17 00:00:00 2001 From: Ondrej Oprala Date: Mon, 7 Apr 2014 18:24:19 +0200 Subject: lslogins: add functionality Signed-off-by: Ondrej Oprala --- .gitignore | 1 + include/Makemodule.am | 1 + lib/Makemodule.am | 3 +- login-utils/Makemodule.am | 26 +- login-utils/login.c | 2 +- login-utils/logindefs.c | 18 +- login-utils/logindefs.h | 2 +- login-utils/lslogins.1 | 3 + login-utils/lslogins.c | 923 ++++++++++++++++++++++++++++++++++++++++++---- 9 files changed, 891 insertions(+), 88 deletions(-) diff --git a/.gitignore b/.gitignore index 968a0890d..b04276989 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,7 @@ update.log /lsblk /lscpu /lslocks +/lslogins /mcookie /mesg /mkfs diff --git a/include/Makemodule.am b/include/Makemodule.am index 7f461c6fe..08479fa0f 100644 --- a/include/Makemodule.am +++ b/include/Makemodule.am @@ -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 \ diff --git a/lib/Makemodule.am b/lib/Makemodule.am index b4ad0e9ae..a16509670 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -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 += \ diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am index 0275a6952..137067295 100644 --- a/login-utils/Makemodule.am +++ b/login-utils/Makemodule.am @@ -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 diff --git a/login-utils/login.c b/login-utils/login.c index b16a81231..470076f8c 100644 --- a/login-utils/login.c +++ b/login-utils/login.c @@ -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); diff --git a/login-utils/logindefs.c b/login-utils/logindefs.c index 804ab3e31..f02c4752d 100644 --- a/login-utils/logindefs.c +++ b/login-utils/logindefs.c @@ -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; diff --git a/login-utils/logindefs.h b/login-utils/logindefs.h index 6a72762f4..f05d733ba 100644 --- a/login-utils/logindefs.h +++ b/login-utils/logindefs.h @@ -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 */ diff --git a/login-utils/lslogins.1 b/login-utils/lslogins.1 index 9b62cb86c..a961efea4 100644 --- a/login-utils/lslogins.1 +++ b/login-utils/lslogins.1 @@ -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 diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index c9f812f36..657a408ab 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -23,17 +23,33 @@ #include #include #include +#include +#include #include +#include #include #include #include +#include +#include +#include +#include + +#include +#ifdef HAVE_LIBSELINUX +#include +#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= Display users belonging to a group in GROUPS\n"), out); fputs(_(" -l, --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[=] 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[=] 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[=] 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; } -- cgit v1.2.3-55-g7522