2 * lslogins - List information about users on the system
4 * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
5 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it would be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include <sys/types.h>
28 #include <sys/syslog.h>
41 #include <libsmartcols.h>
42 #ifdef HAVE_LIBSELINUX
43 # include <selinux/selinux.h>
46 #ifdef HAVE_LIBSYSTEMD
47 # include <systemd/sd-journal.h>
52 #include "closestream.h"
57 #include "pathnames.h"
58 #include "fileutils.h"
59 #include "logindefs.h"
61 #include "timeutils.h"
66 struct lslogins_coldesc
{
67 const char * const name
;
69 const char *pretty_name
;
71 double whint
; /* width hint */
75 static int lslogins_flag
;
77 #define UL_UID_MIN 1000
78 #define UL_UID_MAX 60000
79 #define UL_SYS_UID_MIN 101
80 #define UL_SYS_UID_MAX 999
82 /* we use the value of outmode to determine
83 * appropriate flags for the libsmartcols table
84 * (e.g., a value of out_newline would imply a raw
85 * table with the column separator set to '\n').
106 struct lslogins_user
{
126 const char *pwd_method
;
135 #ifdef HAVE_LIBSELINUX
198 #define is_wtmp_col(x) ((x) == COL_LAST_LOGIN || \
199 (x) == COL_LAST_TTY || \
200 (x) == COL_LAST_HOSTNAME)
202 #define is_btmp_col(x) ((x) == COL_FAILED_LOGIN || \
203 (x) == COL_FAILED_TTY)
211 static const char *const status
[] = {
212 [STATUS_FALSE
] = "0",
214 [STATUS_UNKNOWN
]= NULL
217 static const char *const pretty_status
[] = {
218 [STATUS_FALSE
] = N_("no"),
219 [STATUS_TRUE
] = N_("yes"),
220 [STATUS_UNKNOWN
]= NULL
223 #define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
225 static const struct lslogins_coldesc coldescs
[] =
227 [COL_USER
] = { "USER", N_("user name"), N_("Username"), 0.1, SCOLS_FL_NOEXTREMES
},
228 [COL_UID
] = { "UID", N_("user ID"), "UID", 1, SCOLS_FL_RIGHT
},
229 [COL_PWDEMPTY
] = { "PWD-EMPTY", N_("password not defined"), N_("Password not required (empty)"), 1, SCOLS_FL_RIGHT
},
230 [COL_PWDDENY
] = { "PWD-DENY", N_("login by password disabled"), N_("Login by password disabled"), 1, SCOLS_FL_RIGHT
},
231 [COL_PWDLOCK
] = { "PWD-LOCK", N_("password defined, but locked"), N_("Password is locked"), 1, SCOLS_FL_RIGHT
},
232 [COL_PWDMETHOD
] = { "PWD-METHOD", N_("password encryption method"), N_("Password encryption method"), 0.1 },
233 [COL_NOLOGIN
] = { "NOLOGIN", N_("log in disabled by nologin(8) or pam_nologin(8)"), N_("No login"), 1, SCOLS_FL_RIGHT
},
234 [COL_GROUP
] = { "GROUP", N_("primary group name"), N_("Primary group"), 0.1 },
235 [COL_GID
] = { "GID", N_("primary group ID"), "GID", 1, SCOLS_FL_RIGHT
},
236 [COL_SGROUPS
] = { "SUPP-GROUPS", N_("supplementary group names"), N_("Supplementary groups"), 0.1 },
237 [COL_SGIDS
] = { "SUPP-GIDS", N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 },
238 [COL_HOME
] = { "HOMEDIR", N_("home directory"), N_("Home directory"), 0.1 },
239 [COL_SHELL
] = { "SHELL", N_("login shell"), N_("Shell"), 0.1 },
240 [COL_GECOS
] = { "GECOS", N_("full user name"), N_("Gecos field"), 0.1, SCOLS_FL_TRUNC
},
241 [COL_LAST_LOGIN
] = { "LAST-LOGIN", N_("date of last login"), N_("Last login"), 0.1, SCOLS_FL_RIGHT
},
242 [COL_LAST_TTY
] = { "LAST-TTY", N_("last tty used"), N_("Last terminal"), 0.05 },
243 [COL_LAST_HOSTNAME
] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"), 0.1},
244 [COL_FAILED_LOGIN
] = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 0.1 },
245 [COL_FAILED_TTY
] = { "FAILED-TTY", N_("where did the login fail?"), N_("Failed login terminal"), 0.05 },
246 [COL_HUSH_STATUS
] = { "HUSHED", N_("user's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT
},
247 [COL_PWD_WARN
] = { "PWD-WARN", N_("days user is warned of password expiration"), N_("Password expiration warn interval"), 0.1, SCOLS_FL_RIGHT
},
248 [COL_PWD_EXPIR
] = { "PWD-EXPIR", N_("password expiration date"), N_("Password expiration"), 0.1, SCOLS_FL_RIGHT
},
249 [COL_PWD_CTIME
] = { "PWD-CHANGE", N_("date of last password change"), N_("Password changed"), 0.1, SCOLS_FL_RIGHT
},
250 [COL_PWD_CTIME_MIN
] = { "PWD-MIN", N_("number of days required between changes"), N_("Minimum change time"), 0.1, SCOLS_FL_RIGHT
},
251 [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
},
252 [COL_SELINUX
] = { "CONTEXT", N_("the user's security context"), N_("Selinux context"), 0.1 },
253 [COL_NPROCS
] = { "PROC", N_("number of processes run by the user"), N_("Running processes"), 1, SCOLS_FL_RIGHT
},
256 struct lslogins_control
{
277 unsigned int time_mode
;
279 const char *journal_path
;
281 unsigned int selinux_enabled
: 1,
282 fail_on_unknown
: 1, /* fail if user does not exist */
289 /* these have to remain global since there's no other reasonable way to pass
290 * them for each call of fill_table() via twalk() */
291 static struct libscols_table
*tb
;
293 /* columns[] array specifies all currently wanted output column. The columns
294 * are defined by coldescs[] array and you can specify (on command line) each
295 * column twice. That's enough, dynamically allocated array of the columns is
296 * unnecessary overkill and over-engineering in this case */
297 static int columns
[ARRAY_SIZE(coldescs
) * 2];
298 static size_t ncolumns
;
300 static inline size_t err_columns_index(size_t arysz
, size_t idx
)
303 errx(EXIT_FAILURE
, _("too many columns specified, "
304 "the limit is %zu columns"),
309 #define add_column(ary, n, id) \
310 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
312 static int column_name_to_id(const char *name
, size_t namesz
)
316 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++) {
317 const char *cn
= coldescs
[i
].name
;
319 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
322 warnx(_("unknown column: %s"), name
);
326 static struct timeval now
;
328 static char *make_time(int mode
, time_t time
)
338 localtime_r(&time
, &tm
);
341 if (*(s
= buf
+ strlen(buf
) - 1) == '\n')
347 rc
= strtime_short(&time
, &now
, UL_SHORTTIME_THISYEAR_HHMM
,
351 rc
= strtime_iso(&time
, ISO_TIMESTAMP_T
, buf
, sizeof(buf
));
354 rc
= strtime_iso(&time
, ISO_DATE
, buf
, sizeof(buf
));
357 errx(EXIT_FAILURE
, _("unsupported time type"));
361 errx(EXIT_FAILURE
, _("failed to compose time string"));
367 static char *uidtostr(uid_t uid
)
369 char *str_uid
= NULL
;
370 xasprintf(&str_uid
, "%u", uid
);
374 static char *gidtostr(gid_t gid
)
376 char *str_gid
= NULL
;
377 xasprintf(&str_gid
, "%u", gid
);
381 static char *build_sgroups_string(gid_t
*sgroups
, size_t nsgroups
, int want_names
)
383 size_t n
= 0, maxlen
, len
;
389 len
= maxlen
= nsgroups
* 10;
390 res
= p
= xmalloc(maxlen
);
392 while (n
< nsgroups
) {
396 x
= snprintf(p
, len
, "%u,", sgroups
[n
]);
398 struct group
*grp
= getgrgid(sgroups
[n
]);
403 x
= snprintf(p
, len
, "%s,", grp
->gr_name
);
406 if (x
< 0 || (size_t) x
>= len
) {
407 size_t cur
= p
- res
;
410 res
= xrealloc(res
, maxlen
);
427 static struct utmpx
*get_last_wtmp(struct lslogins_control
*ctl
, const char *username
)
434 n
= ctl
->wtmp_size
- 1;
436 if (!strncmp(username
, ctl
->wtmp
[n
].ut_user
,
437 sizeof(ctl
->wtmp
[0].ut_user
)))
438 return ctl
->wtmp
+ n
;
444 static int require_wtmp(void)
447 for (i
= 0; i
< ncolumns
; i
++)
448 if (is_wtmp_col(columns
[i
]))
453 static int require_btmp(void)
456 for (i
= 0; i
< ncolumns
; i
++)
457 if (is_btmp_col(columns
[i
]))
462 static struct utmpx
*get_last_btmp(struct lslogins_control
*ctl
, const char *username
)
469 n
= ctl
->btmp_size
- 1;
471 if (!strncmp(username
, ctl
->btmp
[n
].ut_user
,
472 sizeof(ctl
->wtmp
[0].ut_user
)))
473 return ctl
->btmp
+ n
;
479 static int parse_utmpx(const char *path
, size_t *nrecords
, struct utmpx
**records
)
482 struct utmpx
*ary
= NULL
;
488 if (utmpxname(path
) < 0)
491 /* optimize allocation according to file size, the realloc() below is
492 * just fallback only */
493 if (stat(path
, &st
) == 0 && (size_t) st
.st_size
>= sizeof(struct utmpx
)) {
494 imax
= st
.st_size
/ sizeof(struct utmpx
);
495 ary
= xreallocarray(NULL
, imax
, sizeof(struct utmpx
));
508 ary
= xreallocarray(ary
, imax
*= 2, sizeof(struct utmpx
));
521 err(EXIT_FAILURE
, "%s", path
);
527 static void get_lastlog(struct lslogins_control
*ctl
, uid_t uid
, void *dst
, int what
)
531 if (ctl
->lastlogin_fd
< 0 ||
532 pread(ctl
->lastlogin_fd
, (void *)&ll
, sizeof(ll
), uid
* sizeof(ll
)) != sizeof(ll
))
537 time_t *t
= (time_t *)dst
;
542 mem2strcpy(dst
, ll
.ll_line
, sizeof(ll
.ll_line
), sizeof(ll
.ll_line
) + 1);
545 mem2strcpy(dst
, ll
.ll_host
, sizeof(ll
.ll_host
), sizeof(ll
.ll_host
) + 1);
552 static int get_sgroups(gid_t
**list
, size_t *len
, struct passwd
*pwd
)
560 /* first let's get a supp. group count */
561 getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, &ngroups
);
565 *list
= xcalloc(1, ngroups
* sizeof(gid_t
));
567 /* now for the actual list of GIDs */
568 if (-1 == getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, &ngroups
))
571 *len
= (size_t) ngroups
;
573 /* getgroups also returns the user's primary GID - dispose of it */
575 if ((*list
)[n
] == pwd
->pw_gid
)
581 (*list
)[n
] = (*list
)[--(*len
)];
587 static int get_nprocs(const uid_t uid
)
593 dir
= opendir(_PATH_PROC
);
597 while ((d
= xreaddir(dir
))) {
598 if (procfs_dirent_match_uid(dir
, d
, uid
))
607 static const char *get_pwd_method(const char *str
, const char **next
)
610 const char *res
= NULL
;
612 if (!p
|| *p
++ != '$')
648 if (*(p
+ 1) == 'y') {
650 res
= "gost-yescrypt";
669 #define is_invalid_pwd_char(x) (isspace((unsigned char) (x)) || \
670 (x) == ':' || (x) == ';' || (x) == '*' || \
671 (x) == '!' || (x) == '\\')
672 #define is_valid_pwd_char(x) (isascii((unsigned char) (x)) && !is_invalid_pwd_char(x))
675 * This function do not accept empty passwords or locked accouns.
677 static int valid_pwd(const char *str
)
685 if (get_pwd_method(str
, &p
) == NULL
)
696 if (!is_valid_pwd_char(*p
))
704 if (!is_valid_pwd_char(*p
)) {
711 static struct lslogins_user
*get_user_info(struct lslogins_control
*ctl
, const char *username
)
713 struct lslogins_user
*user
;
717 struct utmpx
*user_wtmp
= NULL
, *user_btmp
= NULL
;
724 pwd
= username
? getpwnam(username
) : getpwent();
728 ctl
->uid
= uid
= pwd
->pw_uid
;
730 /* nfsnobody is an exception to the UID_MAX limit. This is "nobody" on
731 * some systems; the decisive point is the UID - 65534 */
732 if ((lslogins_flag
& F_USRAC
) &&
733 strcmp("nfsnobody", pwd
->pw_name
) != 0 &&
735 if (uid
< ctl
->UID_MIN
|| uid
> ctl
->UID_MAX
) {
740 } else if ((lslogins_flag
& F_SYSAC
) &&
741 (uid
< ctl
->SYS_UID_MIN
|| uid
> ctl
->SYS_UID_MAX
)) {
747 grp
= getgrgid(pwd
->pw_gid
);
751 user
= xcalloc(1, sizeof(struct lslogins_user
));
754 user_wtmp
= get_last_wtmp(ctl
, pwd
->pw_name
);
756 user_btmp
= get_last_btmp(ctl
, pwd
->pw_name
);
759 shadow
= getspnam(pwd
->pw_name
);
762 /* required by tseach() stuff */
763 user
->uid
= pwd
->pw_uid
;
765 while (n
< ncolumns
) {
766 switch (columns
[n
++]) {
768 user
->login
= xstrdup(pwd
->pw_name
);
771 user
->uid
= pwd
->pw_uid
;
774 user
->group
= xstrdup(grp
->gr_name
);
777 user
->gid
= pwd
->pw_gid
;
781 if (!user
->nsgroups
&&
782 get_sgroups(&user
->sgroups
, &user
->nsgroups
, pwd
) < 0)
783 err(EXIT_FAILURE
, _("failed to get supplementary groups"));
786 user
->homedir
= xstrdup(pwd
->pw_dir
);
789 user
->shell
= xstrdup(pwd
->pw_shell
);
792 user
->gecos
= xstrdup(pwd
->pw_gecos
);
796 time
= user_wtmp
->ut_tv
.tv_sec
;
797 user
->last_login
= make_time(ctl
->time_mode
, time
);
800 get_lastlog(ctl
, pwd
->pw_uid
, &time
, LASTLOG_TIME
);
802 user
->last_login
= make_time(ctl
->time_mode
, time
);
806 user
->last_tty
= xcalloc(1, sizeof(user_wtmp
->ut_line
) + 1);
808 mem2strcpy(user
->last_tty
, user_wtmp
->ut_line
,
809 sizeof(user_wtmp
->ut_line
),
810 sizeof(user_wtmp
->ut_line
) + 1);;
812 get_lastlog(ctl
, user
->uid
, user
->last_tty
, LASTLOG_LINE
);
814 case COL_LAST_HOSTNAME
:
815 user
->last_hostname
= xcalloc(1, sizeof(user_wtmp
->ut_host
) + 1);
817 mem2strcpy(user
->last_hostname
, user_wtmp
->ut_host
,
818 sizeof(user_wtmp
->ut_host
),
819 sizeof(user_wtmp
->ut_host
) + 1);;
821 get_lastlog(ctl
, user
->uid
, user
->last_hostname
, LASTLOG_HOST
);
823 case COL_FAILED_LOGIN
:
825 time
= user_btmp
->ut_tv
.tv_sec
;
826 user
->failed_login
= make_time(ctl
->time_mode
, time
);
831 user
->failed_tty
= xmalloc(sizeof(user_btmp
->ut_line
) + 1);
832 mem2strcpy(user
->failed_tty
, user_btmp
->ut_line
,
833 sizeof(user_btmp
->ut_line
),
834 sizeof(user_btmp
->ut_line
) + 1);;
837 case COL_HUSH_STATUS
:
838 user
->hushed
= get_hushlogin_status(pwd
, 0);
839 if (user
->hushed
== -1)
840 user
->hushed
= STATUS_UNKNOWN
;
844 const char *p
= shadow
->sp_pwdp
;
846 while (p
&& (*p
== '!' || *p
== '*'))
850 user
->pwd_empty
= STATUS_TRUE
;
852 user
->pwd_empty
= STATUS_UNKNOWN
;
856 const char *p
= shadow
->sp_pwdp
;
858 while (p
&& (*p
== '!' || *p
== '*'))
861 if (p
&& *p
&& p
!= shadow
->sp_pwdp
&& !valid_pwd(p
))
862 user
->pwd_deny
= STATUS_TRUE
;
864 user
->pwd_deny
= STATUS_UNKNOWN
;
868 const char *p
= shadow
->sp_pwdp
;
871 /* 'passwd --lock' uses two exclamation marks,
872 * shadow(5) describes the lock as "field which
873 * starts with an exclamation mark". Let's
874 * support more '!' ...
876 while (p
&& *p
== '!')
879 if (i
!= 0 && (!*p
|| valid_pwd(p
)))
880 user
->pwd_lock
= STATUS_TRUE
;
882 user
->pwd_lock
= STATUS_UNKNOWN
;
886 const char *p
= shadow
->sp_pwdp
;
888 while (p
&& (*p
== '!' || *p
== '*'))
890 user
->pwd_method
= get_pwd_method(p
, NULL
);
892 user
->pwd_method
= NULL
;
895 if (strstr(pwd
->pw_shell
, "nologin"))
897 else if (pwd
->pw_uid
)
898 user
->nologin
= access(_PATH_NOLOGIN
, F_OK
) == 0 ||
899 access(_PATH_VAR_NOLOGIN
, F_OK
) == 0;
902 if (shadow
&& shadow
->sp_warn
>= 0)
903 xasprintf(&user
->pwd_warn
, "%ld", shadow
->sp_warn
);
906 if (shadow
&& shadow
->sp_expire
>= 0)
907 user
->pwd_expire
= make_time(ctl
->time_mode
== TIME_ISO
?
908 TIME_ISO_SHORT
: ctl
->time_mode
,
909 shadow
->sp_expire
* 86400);
912 /* sp_lstchg is specified in days, showing hours
913 * (especially in non-GMT timezones) would only serve
916 user
->pwd_ctime
= make_time(ctl
->time_mode
== TIME_ISO
?
917 TIME_ISO_SHORT
: ctl
->time_mode
,
918 shadow
->sp_lstchg
* 86400);
920 case COL_PWD_CTIME_MIN
:
921 if (shadow
&& shadow
->sp_min
> 0)
922 xasprintf(&user
->pwd_ctime_min
, "%ld", shadow
->sp_min
);
924 case COL_PWD_CTIME_MAX
:
925 if (shadow
&& shadow
->sp_max
> 0)
926 xasprintf(&user
->pwd_ctime_max
, "%ld", shadow
->sp_max
);
929 #ifdef HAVE_LIBSELINUX
930 if (!ctl
->selinux_enabled
|| getcon(&user
->context
) != 0)
931 user
->context
= NULL
;
937 xasprintf(&user
->nprocs
, "%d", get_nprocs(pwd
->pw_uid
));
941 /* something went very wrong here */
942 err(EXIT_FAILURE
, "fatal: unknown error");
950 static int str_to_uint(char *s
, unsigned int *ul
)
958 *ul
= strtoul(s
, &end
, 0);
959 if (errno
== 0 && end
&& !*end
)
964 /* get a definitive list of users we want info about... */
965 static int get_ulist(struct lslogins_control
*ctl
, char *logins
, char *groups
)
968 size_t i
= 0, n
= 0, *arsiz
;
978 /* an arbitrary starting value */
980 *ar
= xcalloc(1, sizeof(char *) * (*arsiz
));
983 while ((u
= strtok(logins
, ","))) {
986 /* user specified by UID? */
987 if (!str_to_uint(u
, &uid
)) {
993 (*ar
)[i
++] = xstrdup(u
);
996 *ar
= xreallocarray(*ar
, *arsiz
+= 32, sizeof(char *));
1002 /* FIXME: this might lead to duplicate entries, although not visible
1003 * in output, crunching a user's info multiple times is very redundant */
1004 while ((g
= strtok(groups
, ","))) {
1008 /* user specified by GID? */
1009 if (!str_to_uint(g
, &gid
))
1010 grp
= getgrgid(gid
);
1017 while ((u
= grp
->gr_mem
[n
++])) {
1018 (*ar
)[i
++] = xstrdup(u
);
1021 *ar
= xreallocarray(*ar
, *arsiz
+= 32, sizeof(char *));
1030 static void free_ctl(struct lslogins_control
*ctl
)
1040 while (n
< ctl
->ulsiz
)
1041 free(ctl
->ulist
[n
++]);
1047 static struct lslogins_user
*get_next_user(struct lslogins_control
*ctl
)
1049 struct lslogins_user
*u
;
1051 while (!(u
= get_user_info(ctl
, NULL
))) {
1052 /* no "false" errno-s here, iff we're unable to
1053 * get a valid user entry for any reason, quit */
1054 if (errno
== EAGAIN
)
1061 /* some UNIX implementations set errno iff a passwd/grp/...
1062 * entry was not found. The original UNIX logins(1) utility always
1063 * ignores invalid login/group names, so we're going to as well.*/
1064 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
1065 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
1067 static int get_user(struct lslogins_control
*ctl
, struct lslogins_user
**user
,
1068 const char *username
)
1070 *user
= get_user_info(ctl
, username
);
1071 if (!*user
&& IS_REAL_ERRNO(errno
))
1076 static int cmp_uid(const void *a
, const void *b
)
1078 uid_t x
= ((const struct lslogins_user
*)a
)->uid
;
1079 uid_t z
= ((const struct lslogins_user
*)b
)->uid
;
1080 return x
> z
? 1 : (x
< z
? -1 : 0);
1083 static int create_usertree(struct lslogins_control
*ctl
)
1085 struct lslogins_user
*user
= NULL
;
1088 if (ctl
->ulist_on
) {
1089 for (n
= 0; n
< ctl
->ulsiz
; n
++) {
1090 int rc
= get_user(ctl
, &user
, ctl
->ulist
[n
]);
1092 if (ctl
->fail_on_unknown
&& !user
) {
1093 warnx(_("cannot found '%s'"), ctl
->ulist
[n
]);
1098 tsearch(user
, &ctl
->usertree
, cmp_uid
);
1101 while ((user
= get_next_user(ctl
)))
1102 tsearch(user
, &ctl
->usertree
, cmp_uid
);
1107 static struct libscols_table
*setup_table(struct lslogins_control
*ctl
)
1109 struct libscols_table
*table
= scols_new_table();
1113 err(EXIT_FAILURE
, _("failed to allocate output table"));
1114 if (ctl
->noheadings
)
1115 scols_table_enable_noheadings(table
, 1);
1117 scols_table_enable_shellvar(table
, 1);
1121 scols_table_enable_raw(table
, 1);
1122 scols_table_set_column_separator(table
, ":");
1125 scols_table_set_column_separator(table
, "\n");
1128 scols_table_enable_export(table
, 1);
1131 scols_table_set_line_separator(table
, "\0");
1134 scols_table_enable_raw(table
, 1);
1137 scols_table_enable_noheadings(table
, 1);
1142 while (n
< ncolumns
) {
1143 int flags
= coldescs
[columns
[n
]].flag
;
1146 flags
&= ~SCOLS_FL_TRUNC
;
1148 if (!scols_table_new_column(table
,
1149 coldescs
[columns
[n
]].name
,
1150 coldescs
[columns
[n
]].whint
,
1158 scols_unref_table(table
);
1162 static void fill_table(const void *u
, const VISIT which
, const int depth
__attribute__((unused
)))
1164 struct libscols_line
*ln
;
1165 const struct lslogins_user
*user
= *(struct lslogins_user
* const *)u
;
1168 if (which
== preorder
|| which
== endorder
)
1171 ln
= scols_table_new_line(tb
, NULL
);
1173 err(EXIT_FAILURE
, _("failed to allocate output line"));
1175 while (n
< ncolumns
) {
1178 switch (columns
[n
]) {
1180 rc
= scols_line_set_data(ln
, n
, user
->login
);
1183 rc
= scols_line_refer_data(ln
, n
, uidtostr(user
->uid
));
1186 rc
= scols_line_set_data(ln
, n
, get_status(user
->pwd_empty
));
1189 rc
= scols_line_set_data(ln
, n
, get_status(user
->nologin
));
1192 rc
= scols_line_set_data(ln
, n
, get_status(user
->pwd_lock
));
1195 rc
= scols_line_set_data(ln
, n
, get_status(user
->pwd_deny
));
1198 rc
= scols_line_set_data(ln
, n
, user
->pwd_method
);
1201 rc
= scols_line_set_data(ln
, n
, user
->group
);
1204 rc
= scols_line_refer_data(ln
, n
, gidtostr(user
->gid
));
1207 rc
= scols_line_refer_data(ln
, n
,
1208 build_sgroups_string(user
->sgroups
,
1213 rc
= scols_line_refer_data(ln
, n
,
1214 build_sgroups_string(user
->sgroups
,
1219 rc
= scols_line_set_data(ln
, n
, user
->homedir
);
1222 rc
= scols_line_set_data(ln
, n
, user
->shell
);
1225 rc
= scols_line_set_data(ln
, n
, user
->gecos
);
1227 case COL_LAST_LOGIN
:
1228 rc
= scols_line_set_data(ln
, n
, user
->last_login
);
1231 rc
= scols_line_set_data(ln
, n
, user
->last_tty
);
1233 case COL_LAST_HOSTNAME
:
1234 rc
= scols_line_set_data(ln
, n
, user
->last_hostname
);
1236 case COL_FAILED_LOGIN
:
1237 rc
= scols_line_set_data(ln
, n
, user
->failed_login
);
1239 case COL_FAILED_TTY
:
1240 rc
= scols_line_set_data(ln
, n
, user
->failed_tty
);
1242 case COL_HUSH_STATUS
:
1243 rc
= scols_line_set_data(ln
, n
, get_status(user
->hushed
));
1246 rc
= scols_line_set_data(ln
, n
, user
->pwd_warn
);
1249 rc
= scols_line_set_data(ln
, n
, user
->pwd_expire
);
1252 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime
);
1254 case COL_PWD_CTIME_MIN
:
1255 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime_min
);
1257 case COL_PWD_CTIME_MAX
:
1258 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime_max
);
1261 #ifdef HAVE_LIBSELINUX
1262 rc
= scols_line_set_data(ln
, n
, user
->context
);
1268 rc
= scols_line_set_data(ln
, n
, user
->nprocs
);
1272 /* something went very wrong here */
1273 err(EXIT_FAILURE
, _("internal error: unknown column"));
1277 err(EXIT_FAILURE
, _("failed to add output data"));
1281 #ifdef HAVE_LIBSYSTEMD
1282 static char *get_journal_data(sd_journal
*j
, const char *name
)
1284 const char *data
= NULL
, *p
;
1287 if (sd_journal_get_data(j
, name
, (const void **) &data
, &len
) < 0
1291 /* Get rid of journal entry field identifiers */
1292 p
= strnchr(data
, len
, '=');
1293 if (!p
|| !*(p
+ 1))
1297 return xstrndup(p
, len
- (p
- data
));
1300 static void print_journal_tail(const char *journal_path
, uid_t uid
, size_t len
, int time_mode
)
1306 sd_journal_open_directory(&j
, journal_path
, 0);
1308 sd_journal_open(&j
, SD_JOURNAL_LOCAL_ONLY
);
1310 xasprintf(&match
, "_UID=%d", uid
);
1312 sd_journal_add_match(j
, match
, 0);
1313 sd_journal_seek_tail(j
);
1314 sd_journal_previous_skip(j
, len
);
1317 char *id
, *pid
, *msg
, *ts
;
1321 sd_journal_get_realtime_usec(j
, &x
);
1323 ts
= make_time(time_mode
, t
);
1325 id
= get_journal_data(j
, "SYSLOG_IDENTIFIER");
1326 pid
= get_journal_data(j
, "_PID");
1327 msg
= get_journal_data(j
, "MESSAGE");
1329 if (ts
&& id
&& pid
&& msg
)
1330 fprintf(stdout
, "%s %s[%s]: %s\n", ts
, id
, pid
, msg
);
1336 } while (sd_journal_next(j
));
1339 sd_journal_flush_matches(j
);
1340 sd_journal_close(j
);
1344 static int print_pretty(struct libscols_table
*table
)
1346 struct libscols_iter
*itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
1347 struct libscols_column
*col
;
1348 struct libscols_cell
*data
;
1349 struct libscols_line
*ln
;
1350 const char *hstr
, *dstr
;
1353 ln
= scols_table_get_line(table
, 0);
1354 while (!scols_table_next_column(table
, itr
, &col
)) {
1356 data
= scols_line_get_cell(ln
, n
);
1358 hstr
= _(coldescs
[columns
[n
]].pretty_name
);
1359 dstr
= scols_cell_get_data(data
);
1362 printf("%s:%*c%-36s\n", hstr
, 35 - (int)strlen(hstr
), ' ', dstr
);
1366 scols_free_iter(itr
);
1371 static int print_user_table(struct lslogins_control
*ctl
)
1373 tb
= setup_table(ctl
);
1377 twalk(ctl
->usertree
, fill_table
);
1378 if (outmode
== OUT_PRETTY
) {
1380 #ifdef HAVE_LIBSYSTEMD
1381 fprintf(stdout
, _("\nLast logs:\n"));
1382 print_journal_tail(ctl
->journal_path
, ctl
->uid
, 3, ctl
->time_mode
);
1383 fputc('\n', stdout
);
1386 scols_print_table(tb
);
1390 static void free_user(void *f
)
1392 struct lslogins_user
*u
= f
;
1400 free(u
->pwd_ctime_min
);
1401 free(u
->pwd_ctime_max
);
1402 free(u
->last_login
);
1404 free(u
->last_hostname
);
1405 free(u
->failed_login
);
1406 free(u
->failed_tty
);
1409 free(u
->pwd_status
);
1410 #ifdef HAVE_LIBSELINUX
1411 freecon(u
->context
);
1416 static int parse_time_mode(const char *s
)
1418 struct lslogins_timefmt
{
1422 static const struct lslogins_timefmt timefmts
[] = {
1424 {"full", TIME_FULL
},
1425 {"short", TIME_SHORT
},
1429 for (i
= 0; i
< ARRAY_SIZE(timefmts
); i
++) {
1430 if (strcmp(timefmts
[i
].name
, s
) == 0)
1431 return timefmts
[i
].val
;
1433 errx(EXIT_FAILURE
, _("unknown time format: %s"), s
);
1436 static void __attribute__((__noreturn__
)) usage(void)
1441 fputs(USAGE_HEADER
, out
);
1442 fprintf(out
, _(" %s [options] [<username>]\n"), program_invocation_short_name
);
1444 fputs(USAGE_SEPARATOR
, out
);
1445 fputs(_("Display information about known users in the system.\n"), out
);
1447 fputs(USAGE_OPTIONS
, out
);
1448 fputs(_(" -a, --acc-expiration display info about passwords expiration\n"), out
);
1449 fputs(_(" -c, --colon-separate display data in a format similar to /etc/passwd\n"), out
);
1450 fputs(_(" -e, --export display in an export-able output format\n"), out
);
1451 fputs(_(" -f, --failed display data about the users' last failed logins\n"), out
);
1452 fputs(_(" -G, --supp-groups display information about groups\n"), out
);
1453 fputs(_(" -g, --groups=<groups> display users belonging to a group in <groups>\n"), out
);
1454 fputs(_(" -L, --last show info about the users' last login sessions\n"), out
);
1455 fputs(_(" -l, --logins=<logins> display only users from <logins>\n"), out
);
1456 fputs(_(" -n, --newline display each piece of information on a new line\n"), out
);
1457 fputs(_(" --noheadings don't print headings\n"), out
);
1458 fputs(_(" --notruncate don't truncate output\n"), out
);
1459 fputs(_(" -o, --output[=<list>] define the columns to output\n"), out
);
1460 fputs(_(" --output-all output all columns\n"), out
);
1461 fputs(_(" -p, --pwd display information related to login by password.\n"), out
);
1462 fputs(_(" -r, --raw display in raw mode\n"), out
);
1463 fputs(_(" -s, --system-accs display system accounts\n"), out
);
1464 fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out
);
1465 fputs(_(" -u, --user-accs display user accounts\n"), out
);
1466 fputs(_(" -y, --shell use column names to be usable as shell variable identifiers\n"), out
);
1467 fputs(_(" -Z, --context display SELinux contexts\n"), out
);
1468 fputs(_(" -z, --print0 delimit user entries with a nul character\n"), out
);
1469 fputs(_(" --wtmp-file <path> set an alternate path for wtmp\n"), out
);
1470 fputs(_(" --btmp-file <path> set an alternate path for btmp\n"), out
);
1471 fputs(_(" --lastlog <path> set an alternate path for lastlog\n"), out
);
1472 fputs(USAGE_SEPARATOR
, out
);
1473 fprintf(out
, USAGE_HELP_OPTIONS(26));
1475 fputs(USAGE_COLUMNS
, out
);
1476 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
1477 fprintf(out
, " %14s %s\n", coldescs
[i
].name
, _(coldescs
[i
].help
));
1479 fprintf(out
, USAGE_MAN_TAIL("lslogins(1)"));
1484 int main(int argc
, char *argv
[])
1487 char *logins
= NULL
, *groups
= NULL
, *outarg
= NULL
;
1488 char *path_lastlog
= _PATH_LASTLOG
, *path_wtmp
= _PATH_WTMP
, *path_btmp
= _PATH_BTMP
;
1489 struct lslogins_control
*ctl
= xcalloc(1, sizeof(struct lslogins_control
));
1492 /* long only options. */
1494 OPT_WTMP
= CHAR_MAX
+ 1,
1503 static const struct option longopts
[] = {
1504 { "acc-expiration", no_argument
, 0, 'a' },
1505 { "colon-separate", no_argument
, 0, 'c' },
1506 { "export", no_argument
, 0, 'e' },
1507 { "shell", no_argument
, 0, 'y' },
1508 { "failed", no_argument
, 0, 'f' },
1509 { "groups", required_argument
, 0, 'g' },
1510 { "help", no_argument
, 0, 'h' },
1511 { "logins", required_argument
, 0, 'l' },
1512 { "supp-groups", no_argument
, 0, 'G' },
1513 { "newline", no_argument
, 0, 'n' },
1514 { "notruncate", no_argument
, 0, OPT_NOTRUNC
},
1515 { "noheadings", no_argument
, 0, OPT_NOHEAD
},
1516 { "output", required_argument
, 0, 'o' },
1517 { "output-all", no_argument
, 0, OPT_OUTPUT_ALL
},
1518 { "last", no_argument
, 0, 'L', },
1519 { "raw", no_argument
, 0, 'r' },
1520 { "system-accs", no_argument
, 0, 's' },
1521 { "time-format", required_argument
, 0, OPT_TIME_FMT
},
1522 { "user-accs", no_argument
, 0, 'u' },
1523 { "version", no_argument
, 0, 'V' },
1524 { "pwd", no_argument
, 0, 'p' },
1525 { "print0", no_argument
, 0, 'z' },
1526 { "wtmp-file", required_argument
, 0, OPT_WTMP
},
1527 { "btmp-file", required_argument
, 0, OPT_BTMP
},
1528 { "lastlog-file", required_argument
, 0, OPT_LASTLOG
},
1529 #ifdef HAVE_LIBSELINUX
1530 { "context", no_argument
, 0, 'Z' },
1535 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
1540 { 'c','n','r','z' },
1544 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1546 setlocale(LC_ALL
, "");
1547 bindtextdomain(PACKAGE
, LOCALEDIR
);
1548 textdomain(PACKAGE
);
1549 close_stdout_atexit();
1551 ctl
->time_mode
= TIME_SHORT
;
1553 /* very basic default */
1554 add_column(columns
, ncolumns
++, COL_UID
);
1555 add_column(columns
, ncolumns
++, COL_USER
);
1557 while ((c
= getopt_long(argc
, argv
, "acefGg:hLl:no:prsuVyzZ",
1558 longopts
, NULL
)) != -1) {
1560 err_exclusive_options(c
, longopts
, excl
, excl_st
);
1564 add_column(columns
, ncolumns
++, COL_PWD_WARN
);
1565 add_column(columns
, ncolumns
++, COL_PWD_CTIME_MIN
);
1566 add_column(columns
, ncolumns
++, COL_PWD_CTIME_MAX
);
1567 add_column(columns
, ncolumns
++, COL_PWD_CTIME
);
1568 add_column(columns
, ncolumns
++, COL_PWD_EXPIR
);
1571 outmode
= OUT_COLON
;
1574 outmode
= OUT_EXPORT
;
1577 add_column(columns
, ncolumns
++, COL_FAILED_LOGIN
);
1578 add_column(columns
, ncolumns
++, COL_FAILED_TTY
);
1581 add_column(columns
, ncolumns
++, COL_GID
);
1582 add_column(columns
, ncolumns
++, COL_GROUP
);
1583 add_column(columns
, ncolumns
++, COL_SGIDS
);
1584 add_column(columns
, ncolumns
++, COL_SGROUPS
);
1593 add_column(columns
, ncolumns
++, COL_LAST_TTY
);
1594 add_column(columns
, ncolumns
++, COL_LAST_HOSTNAME
);
1595 add_column(columns
, ncolumns
++, COL_LAST_LOGIN
);
1601 outmode
= OUT_NEWLINE
;
1608 case OPT_OUTPUT_ALL
:
1609 for (ncolumns
= 0; ncolumns
< ARRAY_SIZE(coldescs
); ncolumns
++)
1610 columns
[ncolumns
] = ncolumns
;
1616 ctl
->SYS_UID_MIN
= getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN
);
1617 ctl
->SYS_UID_MAX
= getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX
);
1618 lslogins_flag
|= F_SYSAC
;
1621 ctl
->UID_MIN
= getlogindefs_num("UID_MIN", UL_UID_MIN
);
1622 ctl
->UID_MAX
= getlogindefs_num("UID_MAX", UL_UID_MAX
);
1623 lslogins_flag
|= F_USRAC
;
1626 add_column(columns
, ncolumns
++, COL_PWDEMPTY
);
1627 add_column(columns
, ncolumns
++, COL_PWDLOCK
);
1628 add_column(columns
, ncolumns
++, COL_PWDDENY
);
1629 add_column(columns
, ncolumns
++, COL_NOLOGIN
);
1630 add_column(columns
, ncolumns
++, COL_HUSH_STATUS
);
1631 add_column(columns
, ncolumns
++, COL_PWDMETHOD
);
1640 path_lastlog
= optarg
;
1652 ctl
->noheadings
= 1;
1655 ctl
->time_mode
= parse_time_mode(optarg
);
1658 print_version(EXIT_SUCCESS
);
1661 #ifdef HAVE_LIBSELINUX
1662 int sl
= is_selinux_enabled();
1664 warn(_("failed to request selinux state"));
1666 ctl
->selinux_enabled
= sl
== 1;
1668 add_column(columns
, ncolumns
++, COL_SELINUX
);
1672 errtryhelp(EXIT_FAILURE
);
1676 if (argc
- optind
== 1) {
1677 if (strchr(argv
[optind
], ','))
1678 errx(EXIT_FAILURE
, _("Only one user may be specified. Use -l for multiple users."));
1679 logins
= argv
[optind
];
1680 outmode
= OUT_PRETTY
;
1681 ctl
->fail_on_unknown
= 1;
1682 } else if (argc
!= optind
)
1683 errx(EXIT_FAILURE
, _("Only one user may be specified. Use -l for multiple users."));
1685 scols_init_debug(0);
1687 /* lslogins -u -s == lslogins */
1688 if (lslogins_flag
& F_USRAC
&& lslogins_flag
& F_SYSAC
)
1689 lslogins_flag
&= ~(F_USRAC
| F_SYSAC
);
1691 if (outmode
== OUT_PRETTY
) {
1692 /* all columns for lslogins <username> */
1693 for (ncolumns
= 0, i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
1694 columns
[ncolumns
++] = i
;
1696 } else if (ncolumns
== 2) {
1697 /* default columns */
1699 add_column(columns
, ncolumns
++, COL_NPROCS
);
1701 add_column(columns
, ncolumns
++, COL_PWDLOCK
);
1702 add_column(columns
, ncolumns
++, COL_PWDDENY
);
1703 add_column(columns
, ncolumns
++, COL_LAST_LOGIN
);
1704 add_column(columns
, ncolumns
++, COL_GECOS
);
1707 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
1708 &ncolumns
, column_name_to_id
) < 0)
1709 return EXIT_FAILURE
;
1711 if (require_wtmp()) {
1712 parse_utmpx(path_wtmp
, &ctl
->wtmp_size
, &ctl
->wtmp
);
1713 ctl
->lastlogin_fd
= open(path_lastlog
, O_RDONLY
, 0);
1716 parse_utmpx(path_btmp
, &ctl
->btmp_size
, &ctl
->btmp
);
1718 if (logins
|| groups
)
1719 get_ulist(ctl
, logins
, groups
);
1721 if (create_usertree(ctl
))
1722 return EXIT_FAILURE
;
1724 print_user_table(ctl
);
1726 scols_unref_table(tb
);
1727 tdestroy(ctl
->usertree
, free_user
);
1729 if (ctl
->lastlogin_fd
>= 0)
1730 close(ctl
->lastlogin_fd
);
1733 return EXIT_SUCCESS
;