1601 lines
38 KiB
C
1601 lines
38 KiB
C
/*
|
|
* 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 <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 "logindefs.h"
|
|
#include "procutils.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
|
|
};
|
|
|
|
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
|
|
security_context_t 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 required"), N_("Password not required"), 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;
|
|
|
|
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,
|
|
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 read_utmp(char const *file, size_t *nents, struct utmpx **res)
|
|
{
|
|
size_t n_read = 0, n_alloc = 0;
|
|
struct utmpx *utmp = NULL, *u;
|
|
|
|
if (utmpxname(file) < 0)
|
|
return -errno;
|
|
|
|
setutxent();
|
|
errno = 0;
|
|
|
|
while ((u = getutxent()) != NULL) {
|
|
if (n_read == n_alloc) {
|
|
n_alloc += 32;
|
|
utmp = xrealloc(utmp, n_alloc * sizeof (struct utmpx));
|
|
}
|
|
utmp[n_read++] = *u;
|
|
}
|
|
if (!u && errno) {
|
|
free(utmp);
|
|
return -errno;
|
|
}
|
|
|
|
endutxent();
|
|
|
|
*nents = n_read;
|
|
*res = utmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_wtmp(struct lslogins_control *ctl, char *path)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = read_utmp(path, &ctl->wtmp_size, &ctl->wtmp);
|
|
if (rc < 0 && errno != EACCES)
|
|
err(EXIT_FAILURE, "%s", path);
|
|
return rc;
|
|
}
|
|
|
|
static int parse_btmp(struct lslogins_control *ctl, char *path)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = read_utmp(path, &ctl->btmp_size, &ctl->btmp);
|
|
if (rc < 0 && errno != EACCES)
|
|
err(EXIT_FAILURE, "%s", path);
|
|
return rc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int get_nprocs(const uid_t uid)
|
|
{
|
|
int nprocs = 0;
|
|
pid_t pid;
|
|
struct proc_processes *proc = proc_open_processes();
|
|
|
|
proc_processes_filter_by_uid(proc, uid);
|
|
|
|
while (!proc_next_pid(proc, &pid))
|
|
++nprocs;
|
|
|
|
proc_close_processes(proc);
|
|
return nprocs;
|
|
}
|
|
|
|
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 (get_sgroups(&user->sgroups, &user->nsgroups, pwd))
|
|
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);
|
|
}
|
|
break;
|
|
case COL_LAST_TTY:
|
|
if (user_wtmp)
|
|
user->last_tty = xstrdup(user_wtmp->ut_line);
|
|
break;
|
|
case COL_LAST_HOSTNAME:
|
|
if (user_wtmp)
|
|
user->last_hostname = xstrdup(user_wtmp->ut_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 = xstrdup(user_btmp->ut_line);
|
|
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) {
|
|
if (!*shadow->sp_pwdp) /* '\0' */
|
|
user->pwd_empty = STATUS_TRUE;
|
|
} else
|
|
user->pwd_empty = STATUS_UNKNOWN;
|
|
break;
|
|
case COL_PWDDENY:
|
|
if (shadow) {
|
|
if ((*shadow->sp_pwdp == '!' ||
|
|
*shadow->sp_pwdp == '*') &&
|
|
!valid_pwd(shadow->sp_pwdp + 1))
|
|
user->pwd_deny = STATUS_TRUE;
|
|
} else
|
|
user->pwd_deny = STATUS_UNKNOWN;
|
|
break;
|
|
case COL_PWDLOCK:
|
|
if (shadow) {
|
|
if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1))
|
|
user->pwd_lock = STATUS_TRUE;
|
|
} else
|
|
user->pwd_lock = STATUS_UNKNOWN;
|
|
break;
|
|
case COL_PWDMETHOD:
|
|
if (shadow) {
|
|
const char *p = shadow->sp_pwdp;
|
|
|
|
if (*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) {
|
|
/* typedefs and pointers are pure evil */
|
|
security_context_t con = NULL;
|
|
if (getcon(&con) == 0)
|
|
user->context = con;
|
|
}
|
|
#endif
|
|
break;
|
|
case COL_NPROCS:
|
|
xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
|
|
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;
|
|
if (!s || !*s)
|
|
return -1;
|
|
*ul = strtoul(s, &end, 0);
|
|
if (!*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;
|
|
|
|
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);
|
|
|
|
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:
|
|
rc = scols_line_set_data(ln, n, user->nprocs);
|
|
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;
|
|
}
|
|
return;
|
|
}
|
|
#ifdef HAVE_LIBSYSTEMD
|
|
static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
|
|
{
|
|
sd_journal *j;
|
|
char *match, *timestamp;
|
|
uint64_t x;
|
|
time_t t;
|
|
const char *identifier, *pid, *message;
|
|
size_t identifier_len, pid_len, message_len;
|
|
|
|
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 {
|
|
if (0 > sd_journal_get_data(j, "SYSLOG_IDENTIFIER",
|
|
(const void **) &identifier, &identifier_len))
|
|
goto done;
|
|
if (0 > sd_journal_get_data(j, "_PID",
|
|
(const void **) &pid, &pid_len))
|
|
goto done;
|
|
if (0 > sd_journal_get_data(j, "MESSAGE",
|
|
(const void **) &message, &message_len))
|
|
goto done;
|
|
|
|
sd_journal_get_realtime_usec(j, &x);
|
|
t = x / 1000000;
|
|
timestamp = make_time(time_mode, t);
|
|
/* Get rid of journal entry field identifiers */
|
|
identifier = strchr(identifier, '=') + 1;
|
|
pid = strchr(pid, '=') + 1;
|
|
message = strchr(message, '=') + 1;
|
|
|
|
fprintf(stdout, "%s %s[%s]: %s\n", timestamp, identifier, pid,
|
|
message);
|
|
free(timestamp);
|
|
} while (sd_journal_next(j));
|
|
|
|
done:
|
|
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->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(_(" -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(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_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_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' },
|
|
{ "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 },
|
|
#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:prsuVzZ",
|
|
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 'z':
|
|
outmode = OUT_NUL;
|
|
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 */
|
|
add_column(columns, ncolumns++, COL_NPROCS);
|
|
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_wtmp(ctl, path_wtmp);
|
|
if (require_btmp())
|
|
parse_btmp(ctl, path_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);
|
|
free_ctl(ctl);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|