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 "logindefs.h"
60 #include "procutils.h"
65 struct lslogins_coldesc
{
68 const char *pretty_name
;
70 double whint
; /* width hint */
74 static int lslogins_flag
;
76 #define UL_UID_MIN 1000
77 #define UL_UID_MAX 60000
78 #define UL_SYS_UID_MIN 201
79 #define UL_SYS_UID_MAX 999
81 /* we use the value of outmode to determine
82 * appropriate flags for the libsmartcols table
83 * (e.g., a value of out_newline would imply a raw
84 * table with the column separator set to '\n').
99 struct lslogins_user
{
127 #ifdef HAVE_LIBSELINUX
128 security_context_t context
;
188 #define is_wtmp_col(x) ((x) == COL_LAST_LOGIN || \
189 (x) == COL_LAST_TTY || \
190 (x) == COL_LAST_HOSTNAME)
192 #define is_btmp_col(x) ((x) == COL_FAILED_LOGIN || \
193 (x) == COL_FAILED_TTY)
201 static const char *const status
[] = {
202 [STATUS_FALSE
] = "0",
204 [STATUS_UNKNOWN
]= NULL
207 static const char *const pretty_status
[] = {
208 [STATUS_FALSE
] = N_("no"),
209 [STATUS_TRUE
] = N_("yes"),
210 [STATUS_UNKNOWN
]= NULL
213 #define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
215 static const struct lslogins_coldesc coldescs
[] =
217 [COL_USER
] = { "USER", N_("user name"), N_("Username"), 0.1, SCOLS_FL_NOEXTREMES
},
218 [COL_UID
] = { "UID", N_("user ID"), "UID", 1, SCOLS_FL_RIGHT
},
219 [COL_PWDEMPTY
] = { "PWD-EMPTY", N_("password not required"), N_("Password not required"), 1, SCOLS_FL_RIGHT
},
220 [COL_PWDDENY
] = { "PWD-DENY", N_("login by password disabled"), N_("Login by password disabled"), 1, SCOLS_FL_RIGHT
},
221 [COL_PWDLOCK
] = { "PWD-LOCK", N_("password defined, but locked"), N_("Password is locked"), 1, SCOLS_FL_RIGHT
},
222 [COL_NOLOGIN
] = { "NOLOGIN", N_("log in disabled by nologin(8) or pam_nologin(8)"), N_("No login"), 1, SCOLS_FL_RIGHT
},
223 [COL_GROUP
] = { "GROUP", N_("primary group name"), N_("Primary group"), 0.1 },
224 [COL_GID
] = { "GID", N_("primary group ID"), "GID", 1, SCOLS_FL_RIGHT
},
225 [COL_SGROUPS
] = { "SUPP-GROUPS", N_("supplementary group names"), N_("Supplementary groups"), 0.1 },
226 [COL_SGIDS
] = { "SUPP-GIDS", N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 },
227 [COL_HOME
] = { "HOMEDIR", N_("home directory"), N_("Home directory"), 0.1 },
228 [COL_SHELL
] = { "SHELL", N_("login shell"), N_("Shell"), 0.1 },
229 [COL_GECOS
] = { "GECOS", N_("full user name"), N_("Gecos field"), 0.1, SCOLS_FL_TRUNC
},
230 [COL_LAST_LOGIN
] = { "LAST-LOGIN", N_("date of last login"), N_("Last login"), 0.1, SCOLS_FL_RIGHT
},
231 [COL_LAST_TTY
] = { "LAST-TTY", N_("last tty used"), N_("Last terminal"), 0.05 },
232 [COL_LAST_HOSTNAME
] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"), 0.1},
233 [COL_FAILED_LOGIN
] = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 0.1 },
234 [COL_FAILED_TTY
] = { "FAILED-TTY", N_("where did the login fail?"), N_("Failed login terminal"), 0.05 },
235 [COL_HUSH_STATUS
] = { "HUSHED", N_("user's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT
},
236 [COL_PWD_WARN
] = { "PWD-WARN", N_("days user is warned of password expiration"), N_("Password expiration warn interval"), 0.1, SCOLS_FL_RIGHT
},
237 [COL_PWD_EXPIR
] = { "PWD-EXPIR", N_("password expiration date"), N_("Password expiration"), 0.1, SCOLS_FL_RIGHT
},
238 [COL_PWD_CTIME
] = { "PWD-CHANGE", N_("date of last password change"), N_("Password changed"), 0.1, SCOLS_FL_RIGHT
},
239 [COL_PWD_CTIME_MIN
] = { "PWD-MIN", N_("number of days required between changes"), N_("Minimum change time"), 0.1, SCOLS_FL_RIGHT
},
240 [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
},
241 [COL_SELINUX
] = { "CONTEXT", N_("the user's security context"), N_("Selinux context"), 0.1 },
242 [COL_NPROCS
] = { "PROC", N_("number of processes run by the user"), N_("Running processes"), 1, SCOLS_FL_RIGHT
},
245 struct lslogins_control
{
264 unsigned int time_mode
;
266 const char *journal_path
;
268 unsigned int selinux_enabled
: 1,
273 /* these have to remain global since there's no other reasonable way to pass
274 * them for each call of fill_table() via twalk() */
275 static struct libscols_table
*tb
;
277 /* columns[] array specifies all currently wanted output column. The columns
278 * are defined by coldescs[] array and you can specify (on command line) each
279 * column twice. That's enough, dynamically allocated array of the columns is
280 * unnecessary overkill and over-engineering in this case */
281 static int columns
[ARRAY_SIZE(coldescs
) * 2];
284 static inline size_t err_columns_index(size_t arysz
, size_t idx
)
287 errx(EXIT_FAILURE
, _("too many columns specified, "
288 "the limit is %zu columns"),
293 #define add_column(ary, n, id) \
294 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
296 static struct timeval now
;
298 static int date_is_today(time_t t
)
301 gettimeofday(&now
, NULL
);
302 return t
/ (3600 * 24) == now
.tv_sec
/ (3600 * 24);
305 static int date_is_thisyear(time_t t
)
308 gettimeofday(&now
, NULL
);
309 return t
/ (3600 * 24 * 365) == now
.tv_sec
/ (3600 * 24 * 365);
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 char *make_time(int mode
, time_t time
)
332 localtime_r(&time
, &tm
);
337 if (*(s
= buf
+ strlen(buf
) - 1) == '\n')
341 if (date_is_today(time
))
342 strftime(buf
, sizeof(buf
), "%H:%M:%S", &tm
);
343 else if (date_is_thisyear(time
))
344 strftime(buf
, sizeof(buf
), "%b%d/%H:%M", &tm
);
346 strftime(buf
, sizeof(buf
), "%Y-%b%d", &tm
);
349 strftime(buf
, sizeof(buf
), "%Y-%m-%dT%H:%M:%S%z", &tm
);
352 errx(EXIT_FAILURE
, _("unsupported time type"));
358 static char *uidtostr(uid_t uid
)
360 char *str_uid
= NULL
;
361 xasprintf(&str_uid
, "%u", uid
);
365 static char *gidtostr(gid_t gid
)
367 char *str_gid
= NULL
;
368 xasprintf(&str_gid
, "%u", gid
);
372 static char *build_sgroups_string(gid_t
*sgroups
, size_t nsgroups
, int want_names
)
374 size_t n
= 0, maxlen
, len
;
380 len
= maxlen
= nsgroups
* 10;
381 res
= p
= xmalloc(maxlen
);
383 while (n
< nsgroups
) {
387 x
= snprintf(p
, len
, "%u,", sgroups
[n
]);
389 struct group
*grp
= getgrgid(sgroups
[n
]);
394 x
= snprintf(p
, len
, "%s,", grp
->gr_name
);
397 if (x
< 0 || (size_t) x
+ 1 > len
) {
398 size_t cur
= p
- res
;
401 res
= xrealloc(res
, maxlen
);
418 static struct utmp
*get_last_wtmp(struct lslogins_control
*ctl
, const char *username
)
426 len
= strlen(username
);
427 n
= ctl
->wtmp_size
- 1;
429 if (!strncmp(username
, ctl
->wtmp
[n
].ut_user
,
430 len
< UT_NAMESIZE
? len
: UT_NAMESIZE
))
431 return ctl
->wtmp
+ n
;
437 static int require_wtmp(void)
440 for (i
= 0; i
< (size_t) ncolumns
; i
++)
441 if (is_wtmp_col(columns
[i
]))
446 static int require_btmp(void)
449 for (i
= 0; i
< (size_t) ncolumns
; i
++)
450 if (is_btmp_col(columns
[i
]))
455 static struct utmp
*get_last_btmp(struct lslogins_control
*ctl
, const char *username
)
463 len
= strlen(username
);
464 n
= ctl
->btmp_size
- 1;
466 if (!strncmp(username
, ctl
->btmp
[n
].ut_user
,
467 len
< UT_NAMESIZE
? len
: UT_NAMESIZE
))
468 return ctl
->btmp
+ n
;
474 static int parse_wtmp(struct lslogins_control
*ctl
, char *path
)
478 rc
= read_utmp(path
, &ctl
->wtmp_size
, &ctl
->wtmp
);
479 if (rc
< 0 && errno
!= EACCES
)
480 err(EXIT_FAILURE
, "%s", path
);
484 static int parse_btmp(struct lslogins_control
*ctl
, char *path
)
488 rc
= read_utmp(path
, &ctl
->btmp_size
, &ctl
->btmp
);
489 if (rc
< 0 && errno
!= EACCES
)
490 err(EXIT_FAILURE
, "%s", path
);
494 static int get_sgroups(gid_t
**list
, size_t *len
, struct passwd
*pwd
)
501 /* first let's get a supp. group count */
502 getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, (int *) len
);
506 *list
= xcalloc(1, *len
* sizeof(gid_t
));
508 /* now for the actual list of GIDs */
509 if (-1 == getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, (int *) len
))
512 /* getgroups also returns the user's primary GID - dispose of it */
514 if ((*list
)[n
] == pwd
->pw_gid
)
520 (*list
)[n
] = (*list
)[--(*len
)];
524 static int get_nprocs(const uid_t uid
)
528 struct proc_processes
*proc
= proc_open_processes();
530 proc_processes_filter_by_uid(proc
, uid
);
532 while (!proc_next_pid(proc
, &pid
))
535 proc_close_processes(proc
);
539 static int valid_pwd(const char *str
)
543 for (p
= str
; p
&& *p
; p
++)
544 if (!isalnum((unsigned int) *p
))
546 return p
> str
? 1 : 0;
549 static struct lslogins_user
*get_user_info(struct lslogins_control
*ctl
, const char *username
)
551 struct lslogins_user
*user
;
555 struct utmp
*user_wtmp
= NULL
, *user_btmp
= NULL
;
561 pwd
= username
? getpwnam(username
) : getpwent();
565 ctl
->uid
= uid
= pwd
->pw_uid
;
567 /* nfsnobody is an exception to the UID_MAX limit. This is "nobody" on
568 * some systems; the decisive point is the UID - 65534 */
569 if ((lslogins_flag
& F_USRAC
) &&
570 strcmp("nfsnobody", pwd
->pw_name
) != 0 &&
572 if (uid
< ctl
->UID_MIN
|| uid
> ctl
->UID_MAX
) {
577 } else if ((lslogins_flag
& F_SYSAC
) &&
578 (uid
< ctl
->SYS_UID_MIN
|| uid
> ctl
->SYS_UID_MAX
)) {
583 user
= xcalloc(1, sizeof(struct lslogins_user
));
585 grp
= getgrgid(pwd
->pw_gid
);
590 user_wtmp
= get_last_wtmp(ctl
, pwd
->pw_name
);
592 user_btmp
= get_last_btmp(ctl
, pwd
->pw_name
);
595 shadow
= getspnam(pwd
->pw_name
);
598 /* required by tseach() stuff */
599 user
->uid
= pwd
->pw_uid
;
601 while (n
< ncolumns
) {
602 switch (columns
[n
++]) {
604 user
->login
= xstrdup(pwd
->pw_name
);
607 user
->uid
= pwd
->pw_uid
;
610 user
->group
= xstrdup(grp
->gr_name
);
613 user
->gid
= pwd
->pw_gid
;
617 if (get_sgroups(&user
->sgroups
, &user
->nsgroups
, pwd
))
618 err(EXIT_FAILURE
, _("failed to get supplementary groups"));
621 user
->homedir
= xstrdup(pwd
->pw_dir
);
624 user
->shell
= xstrdup(pwd
->pw_shell
);
627 user
->gecos
= xstrdup(pwd
->pw_gecos
);
631 time
= user_wtmp
->ut_tv
.tv_sec
;
632 user
->last_login
= make_time(ctl
->time_mode
, time
);
637 user
->last_tty
= xstrdup(user_wtmp
->ut_line
);
639 case COL_LAST_HOSTNAME
:
641 user
->last_hostname
= xstrdup(user_wtmp
->ut_host
);
643 case COL_FAILED_LOGIN
:
645 time
= user_btmp
->ut_tv
.tv_sec
;
646 user
->failed_login
= make_time(ctl
->time_mode
, time
);
651 user
->failed_tty
= xstrdup(user_btmp
->ut_line
);
653 case COL_HUSH_STATUS
:
654 user
->hushed
= get_hushlogin_status(pwd
, 0);
655 if (user
->hushed
== -1)
656 user
->hushed
= STATUS_UNKNOWN
;
660 if (!*shadow
->sp_pwdp
) /* '\0' */
661 user
->pwd_empty
= STATUS_TRUE
;
663 user
->pwd_empty
= STATUS_UNKNOWN
;
667 if ((*shadow
->sp_pwdp
== '!' ||
668 *shadow
->sp_pwdp
== '*') &&
669 !valid_pwd(shadow
->sp_pwdp
+ 1))
670 user
->pwd_deny
= STATUS_TRUE
;
672 user
->pwd_deny
= STATUS_UNKNOWN
;
677 if (*shadow
->sp_pwdp
== '!' && valid_pwd(shadow
->sp_pwdp
+ 1))
678 user
->pwd_lock
= STATUS_TRUE
;
680 user
->pwd_lock
= STATUS_UNKNOWN
;
683 if (strstr(pwd
->pw_shell
, "nologin"))
685 else if (pwd
->pw_uid
)
686 user
->nologin
= access("/etc/nologin", F_OK
) == 0 ||
687 access("/var/run/nologin", F_OK
) == 0;
690 if (shadow
&& shadow
->sp_warn
>= 0)
691 xasprintf(&user
->pwd_warn
, "%ld", shadow
->sp_warn
);
694 if (shadow
&& shadow
->sp_expire
>= 0)
695 user
->pwd_expire
= make_time(TIME_SHORT
,
696 shadow
->sp_expire
* 86400);
699 /* sp_lstchg is specified in days, showing hours
700 * (especially in non-GMT timezones) would only serve
703 user
->pwd_ctime
= make_time(TIME_SHORT
,
704 shadow
->sp_lstchg
* 86400);
706 case COL_PWD_CTIME_MIN
:
707 if (shadow
&& shadow
->sp_min
> 0)
708 xasprintf(&user
->pwd_ctime_min
, "%ld", shadow
->sp_min
);
710 case COL_PWD_CTIME_MAX
:
711 if (shadow
&& shadow
->sp_max
> 0)
712 xasprintf(&user
->pwd_ctime_max
, "%ld", shadow
->sp_max
);
715 #ifdef HAVE_LIBSELINUX
716 if (ctl
->selinux_enabled
) {
717 /* typedefs and pointers are pure evil */
718 security_context_t con
= NULL
;
719 if (getcon(&con
) == 0)
725 xasprintf(&user
->nprocs
, "%d", get_nprocs(pwd
->pw_uid
));
728 /* something went very wrong here */
729 err(EXIT_FAILURE
, "fatal: unknown error");
737 /* some UNIX implementations set errno iff a passwd/grp/...
738 * entry was not found. The original UNIX logins(1) utility always
739 * ignores invalid login/group names, so we're going to as well.*/
740 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
741 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
743 /* get a definitive list of users we want info about... */
745 static int str_to_uint(char *s
, unsigned int *ul
)
750 *ul
= strtoul(s
, &end
, 0);
756 static int get_ulist(struct lslogins_control
*ctl
, char *logins
, char *groups
)
759 size_t i
= 0, n
= 0, *arsiz
;
769 /* an arbitrary starting value */
771 *ar
= xcalloc(1, sizeof(char *) * (*arsiz
));
773 while ((u
= strtok(logins
, ","))) {
776 /* user specified by UID? */
777 if (!str_to_uint(u
, &uid
)) {
783 (*ar
)[i
++] = xstrdup(u
);
786 *ar
= xrealloc(*ar
, sizeof(char *) * (*arsiz
+= 32));
788 /* FIXME: this might lead to duplicit entries, although not visible
789 * in output, crunching a user's info multiple times is very redundant */
790 while ((g
= strtok(groups
, ","))) {
793 /* user specified by GID? */
794 if (!str_to_uint(g
, &gid
))
802 while ((u
= grp
->gr_mem
[n
++])) {
803 (*ar
)[i
++] = xstrdup(u
);
806 *ar
= xrealloc(*ar
, sizeof(char *) * (*arsiz
+= 32));
813 static void free_ctl(struct lslogins_control
*ctl
)
820 while (n
< ctl
->ulsiz
)
821 free(ctl
->ulist
[n
++]);
827 static struct lslogins_user
*get_next_user(struct lslogins_control
*ctl
)
829 struct lslogins_user
*u
;
831 while (!(u
= get_user_info(ctl
, NULL
))) {
832 /* no "false" errno-s here, iff we're unable to
833 * get a valid user entry for any reason, quit */
841 static int get_user(struct lslogins_control
*ctl
, struct lslogins_user
**user
,
842 const char *username
)
844 *user
= get_user_info(ctl
, username
);
846 if (IS_REAL_ERRNO(errno
))
851 static int cmp_uid(const void *a
, const void *b
)
853 uid_t x
= ((struct lslogins_user
*)a
)->uid
;
854 uid_t z
= ((struct lslogins_user
*)b
)->uid
;
855 return x
> z
? 1 : (x
< z
? -1 : 0);
858 static int create_usertree(struct lslogins_control
*ctl
)
860 struct lslogins_user
*user
= NULL
;
864 while (n
< ctl
->ulsiz
) {
865 if (get_user(ctl
, &user
, ctl
->ulist
[n
]))
867 if (user
) /* otherwise an invalid user name has probably been given */
868 tsearch(user
, &ctl
->usertree
, cmp_uid
);
872 while ((user
= get_next_user(ctl
)))
873 tsearch(user
, &ctl
->usertree
, cmp_uid
);
878 static struct libscols_table
*setup_table(struct lslogins_control
*ctl
)
880 struct libscols_table
*tb
= scols_new_table();
884 errx(EXIT_FAILURE
, _("failed to initialize output table"));
886 scols_table_enable_noheadings(tb
, 1);
890 scols_table_enable_raw(tb
, 1);
891 scols_table_set_column_separator(tb
, ":");
894 scols_table_set_column_separator(tb
, "\n");
897 scols_table_enable_export(tb
, 1);
900 scols_table_set_line_separator(tb
, "\0");
903 scols_table_enable_raw(tb
, 1);
906 scols_table_enable_noheadings(tb
, 1);
911 while (n
< ncolumns
) {
912 int flags
= coldescs
[columns
[n
]].flag
;
915 flags
&= ~SCOLS_FL_TRUNC
;
917 if (!scols_table_new_column(tb
,
918 coldescs
[columns
[n
]].name
,
919 coldescs
[columns
[n
]].whint
,
927 scols_unref_table(tb
);
931 static void fill_table(const void *u
, const VISIT which
, const int depth
__attribute__((unused
)))
933 struct libscols_line
*ln
;
934 struct lslogins_user
*user
= *(struct lslogins_user
**)u
;
937 if (which
== preorder
|| which
== endorder
)
940 ln
= scols_table_new_line(tb
, NULL
);
941 while (n
< ncolumns
) {
944 switch (columns
[n
]) {
946 rc
= scols_line_set_data(ln
, n
, user
->login
);
949 rc
= scols_line_refer_data(ln
, n
, uidtostr(user
->uid
));
952 rc
= scols_line_set_data(ln
, n
, get_status(user
->pwd_empty
));
955 rc
= scols_line_set_data(ln
, n
, get_status(user
->nologin
));
958 rc
= scols_line_set_data(ln
, n
, get_status(user
->pwd_lock
));
961 rc
= scols_line_set_data(ln
, n
, get_status(user
->pwd_deny
));
964 rc
= scols_line_set_data(ln
, n
, user
->group
);
967 rc
= scols_line_refer_data(ln
, n
, gidtostr(user
->gid
));
970 rc
= scols_line_refer_data(ln
, n
,
971 build_sgroups_string(user
->sgroups
,
976 rc
= scols_line_refer_data(ln
, n
,
977 build_sgroups_string(user
->sgroups
,
982 rc
= scols_line_set_data(ln
, n
, user
->homedir
);
985 rc
= scols_line_set_data(ln
, n
, user
->shell
);
988 rc
= scols_line_set_data(ln
, n
, user
->gecos
);
991 rc
= scols_line_set_data(ln
, n
, user
->last_login
);
994 rc
= scols_line_set_data(ln
, n
, user
->last_tty
);
996 case COL_LAST_HOSTNAME
:
997 rc
= scols_line_set_data(ln
, n
, user
->last_hostname
);
999 case COL_FAILED_LOGIN
:
1000 rc
= scols_line_set_data(ln
, n
, user
->failed_login
);
1002 case COL_FAILED_TTY
:
1003 rc
= scols_line_set_data(ln
, n
, user
->failed_tty
);
1005 case COL_HUSH_STATUS
:
1006 rc
= scols_line_set_data(ln
, n
, get_status(user
->hushed
));
1009 rc
= scols_line_set_data(ln
, n
, user
->pwd_warn
);
1012 rc
= scols_line_set_data(ln
, n
, user
->pwd_expire
);
1015 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime
);
1017 case COL_PWD_CTIME_MIN
:
1018 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime_min
);
1020 case COL_PWD_CTIME_MAX
:
1021 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime_max
);
1024 #ifdef HAVE_LIBSELINUX
1025 rc
= scols_line_set_data(ln
, n
, user
->context
);
1029 rc
= scols_line_set_data(ln
, n
, user
->nprocs
);
1032 /* something went very wrong here */
1033 err(EXIT_FAILURE
, _("internal error: unknown column"));
1037 err(EXIT_FAILURE
, _("failed to set data"));
1042 #ifdef HAVE_LIBSYSTEMD
1043 static void print_journal_tail(const char *journal_path
, uid_t uid
, size_t len
)
1049 const char *identifier
, *pid
, *message
;
1050 size_t identifier_len
, pid_len
, message_len
;
1053 sd_journal_open_directory(&j
, journal_path
, 0);
1055 sd_journal_open(&j
, SD_JOURNAL_LOCAL_ONLY
);
1057 buf
= xmalloc(sizeof(char) * 16);
1058 xasprintf(&match
, "_UID=%d", uid
);
1060 sd_journal_add_match(j
, match
, 0);
1061 sd_journal_seek_tail(j
);
1062 sd_journal_previous_skip(j
, len
);
1065 if (0 > sd_journal_get_data(j
, "SYSLOG_IDENTIFIER",
1066 (const void **) &identifier
, &identifier_len
))
1068 if (0 > sd_journal_get_data(j
, "_PID",
1069 (const void **) &pid
, &pid_len
))
1071 if (0 > sd_journal_get_data(j
, "MESSAGE",
1072 (const void **) &message
, &message_len
))
1075 sd_journal_get_realtime_usec(j
, &x
);
1077 strftime(buf
, 16, "%b %d %H:%M:%S", localtime(&t
));
1079 fprintf(stdout
, "%s", buf
);
1081 identifier
= strchr(identifier
, '=') + 1;
1082 pid
= strchr(pid
, '=') + 1 ;
1083 message
= strchr(message
, '=') + 1;
1085 fprintf(stdout
, " %s", identifier
);
1086 fprintf(stdout
, "[%s]:", pid
);
1087 fprintf(stdout
, "%s\n", message
);
1088 } while (sd_journal_next(j
));
1093 sd_journal_flush_matches(j
);
1094 sd_journal_close(j
);
1098 static int print_pretty(struct libscols_table
*tb
)
1100 struct libscols_iter
*itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
1101 struct libscols_column
*col
;
1102 struct libscols_cell
*data
;
1103 struct libscols_line
*ln
;
1104 const char *hstr
, *dstr
;
1107 ln
= scols_table_get_line(tb
, 0);
1108 while (!scols_table_next_column(tb
, itr
, &col
)) {
1110 data
= scols_line_get_cell(ln
, n
);
1112 hstr
= _(coldescs
[columns
[n
]].pretty_name
);
1113 dstr
= scols_cell_get_data(data
);
1116 printf("%s:%*c%-36s\n", hstr
, 35 - (int)strlen(hstr
), ' ', dstr
);
1120 scols_free_iter(itr
);
1125 static int print_user_table(struct lslogins_control
*ctl
)
1127 tb
= setup_table(ctl
);
1131 twalk(ctl
->usertree
, fill_table
);
1132 if (outmode
== OUT_PRETTY
) {
1134 #ifdef HAVE_LIBSYSTEMD
1135 fprintf(stdout
, _("\nLast logs:\n"));
1136 print_journal_tail(ctl
->journal_path
, ctl
->uid
, 3);
1137 fputc('\n', stdout
);
1140 scols_print_table(tb
);
1144 static void free_user(void *f
)
1146 struct lslogins_user
*u
= f
;
1153 free(u
->pwd_ctime_min
);
1154 free(u
->pwd_ctime_max
);
1155 free(u
->last_login
);
1157 free(u
->last_hostname
);
1158 free(u
->failed_login
);
1159 free(u
->failed_tty
);
1162 free(u
->pwd_status
);
1163 #ifdef HAVE_LIBSELINUX
1164 freecon(u
->context
);
1169 struct lslogins_timefmt
{
1174 static struct lslogins_timefmt timefmts
[] = {
1175 { "short", TIME_SHORT
},
1176 { "full", TIME_FULL
},
1177 { "iso", TIME_ISO
},
1180 static void __attribute__((__noreturn__
)) usage(FILE *out
)
1184 fputs(USAGE_HEADER
, out
);
1185 fprintf(out
, _(" %s [options]\n"), program_invocation_short_name
);
1187 fputs(USAGE_OPTIONS
, out
);
1188 fputs(_(" -a, --acc-expiration display info about passwords expiration\n"), out
);
1189 fputs(_(" -c, --colon-separate display data in a format similar to /etc/passwd\n"), out
);
1190 fputs(_(" -e, --export display in an export-able output format\n"), out
);
1191 fputs(_(" -f, --failed display data about the users' last failed logins\n"), out
);
1192 fputs(_(" -G, --groups-info display information about groups\n"), out
);
1193 fputs(_(" -g, --groups=<groups> display users belonging to a group in <groups>\n"), out
);
1194 fputs(_(" -L, --last show info about the users' last login sessions\n"), out
);
1195 fputs(_(" -l, --logins=<logins> display only users from <logins>\n"), out
);
1196 fputs(_(" -m, --supp-groups display supplementary groups as well\n"), out
);
1197 fputs(_(" -n, --newline display each piece of information on a new line\n"), out
);
1198 fputs(_(" --noheadings don't print headings\n"), out
);
1199 fputs(_(" --notruncate don't truncate output\n"), out
);
1200 fputs(_(" -o, --output[=<list>] define the columns to output\n"), out
);
1201 fputs(_(" -p, --pwd display information related to login by password.\n"), out
);
1202 fputs(_(" -r, --raw display in raw mode\n"), out
);
1203 fputs(_(" -s, --system-accs display system accounts\n"), out
);
1204 fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out
);
1205 fputs(_(" -u, --user-accs display user accounts\n"), out
);
1206 fputs(_(" -Z, --context display SELinux contexts\n"), out
);
1207 fputs(_(" -z, --print0 delimit user entries with a nul character\n"), out
);
1208 fputs(_(" --wtmp-file <path> set an alternate path for wtmp\n"), out
);
1209 fputs(_(" --btmp-file <path> set an alternate path for btmp\n"), out
);
1210 fputs(USAGE_SEPARATOR
, out
);
1211 fputs(USAGE_HELP
, out
);
1212 fputs(USAGE_VERSION
, out
);
1214 fprintf(out
, _("\nAvailable columns:\n"));
1216 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
1217 fprintf(out
, " %14s %s\n", coldescs
[i
].name
,
1218 _(coldescs
[i
].help
));
1220 fprintf(out
, USAGE_MAN_TAIL("lslogins(1)"));
1222 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
1225 int main(int argc
, char *argv
[])
1228 char *logins
= NULL
, *groups
= NULL
;
1229 char *path_wtmp
= _PATH_WTMP
, *path_btmp
= _PATH_BTMP
;
1230 struct lslogins_control
*ctl
= xcalloc(1, sizeof(struct lslogins_control
));
1233 /* long only options. */
1235 OPT_VER
= CHAR_MAX
+ 1,
1243 static const struct option longopts
[] = {
1244 { "acc-expiration", no_argument
, 0, 'a' },
1245 { "colon-separate", no_argument
, 0, 'c' },
1246 { "export", no_argument
, 0, 'e' },
1247 { "failed", no_argument
, 0, 'f' },
1248 { "groups", required_argument
, 0, 'g' },
1249 { "help", no_argument
, 0, 'h' },
1250 { "logins", required_argument
, 0, 'l' },
1251 { "supp-groups", no_argument
, 0, 'G' },
1252 { "newline", no_argument
, 0, 'n' },
1253 { "notruncate", no_argument
, 0, OPT_NOTRUNC
},
1254 { "noheadings", no_argument
, 0, OPT_NOHEAD
},
1255 { "output", required_argument
, 0, 'o' },
1256 { "last", no_argument
, 0, 'L', },
1257 { "raw", no_argument
, 0, 'r' },
1258 { "system-accs", no_argument
, 0, 's' },
1259 { "time-format", required_argument
, 0, OPT_TIME_FMT
},
1260 { "user-accs", no_argument
, 0, 'u' },
1261 { "version", no_argument
, 0, 'V' },
1262 { "pwd", no_argument
, 0, 'p' },
1263 { "print0", no_argument
, 0, 'z' },
1264 { "wtmp-file", required_argument
, 0, OPT_WTMP
},
1265 { "btmp-file", required_argument
, 0, OPT_BTMP
},
1266 #ifdef HAVE_LIBSELINUX
1267 { "context", no_argument
, 0, 'Z' },
1272 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
1277 { 'c','n','r','z' },
1281 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1283 setlocale(LC_ALL
, "");
1284 bindtextdomain(PACKAGE
, LOCALEDIR
);
1285 textdomain(PACKAGE
);
1286 atexit(close_stdout
);
1288 ctl
->time_mode
= TIME_SHORT
;
1290 /* very basic default */
1291 add_column(columns
, ncolumns
++, COL_UID
);
1292 add_column(columns
, ncolumns
++, COL_USER
);
1294 while ((c
= getopt_long(argc
, argv
, "acfGg:hLl:no:prsuVxzZ",
1295 longopts
, NULL
)) != -1) {
1297 err_exclusive_options(c
, longopts
, excl
, excl_st
);
1301 add_column(columns
, ncolumns
++, COL_PWD_WARN
);
1302 add_column(columns
, ncolumns
++, COL_PWD_CTIME_MIN
);
1303 add_column(columns
, ncolumns
++, COL_PWD_CTIME_MAX
);
1304 add_column(columns
, ncolumns
++, COL_PWD_CTIME
);
1305 add_column(columns
, ncolumns
++, COL_PWD_EXPIR
);
1308 outmode
= OUT_COLON
;
1311 outmode
= OUT_EXPORT
;
1314 add_column(columns
, ncolumns
++, COL_FAILED_LOGIN
);
1315 add_column(columns
, ncolumns
++, COL_FAILED_TTY
);
1318 add_column(columns
, ncolumns
++, COL_GID
);
1319 add_column(columns
, ncolumns
++, COL_GROUP
);
1320 add_column(columns
, ncolumns
++, COL_SGIDS
);
1321 add_column(columns
, ncolumns
++, COL_SGROUPS
);
1330 add_column(columns
, ncolumns
++, COL_LAST_TTY
);
1331 add_column(columns
, ncolumns
++, COL_LAST_HOSTNAME
);
1332 add_column(columns
, ncolumns
++, COL_LAST_LOGIN
);
1338 outmode
= OUT_NEWLINE
;
1344 ncolumns
= string_to_idarray(optarg
,
1345 columns
, ARRAY_SIZE(columns
),
1348 return EXIT_FAILURE
;
1356 ctl
->SYS_UID_MIN
= getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN
);
1357 ctl
->SYS_UID_MAX
= getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX
);
1358 lslogins_flag
|= F_SYSAC
;
1361 ctl
->UID_MIN
= getlogindefs_num("UID_MIN", UL_UID_MIN
);
1362 ctl
->UID_MAX
= getlogindefs_num("UID_MAX", UL_UID_MAX
);
1363 lslogins_flag
|= F_USRAC
;
1366 add_column(columns
, ncolumns
++, COL_PWDEMPTY
);
1367 add_column(columns
, ncolumns
++, COL_PWDLOCK
);
1368 add_column(columns
, ncolumns
++, COL_PWDDENY
);
1369 add_column(columns
, ncolumns
++, COL_NOLOGIN
);
1370 add_column(columns
, ncolumns
++, COL_HUSH_STATUS
);
1385 ctl
->noheadings
= 1;
1391 for (i
= 0; i
< ARRAY_SIZE(timefmts
); i
++) {
1392 if (strcmp(timefmts
[i
].name
, optarg
) == 0) {
1393 ctl
->time_mode
= timefmts
[i
].val
;
1397 if (ctl
->time_mode
== TIME_INVALID
)
1402 printf(UTIL_LINUX_VERSION
);
1403 return EXIT_SUCCESS
;
1406 #ifdef HAVE_LIBSELINUX
1407 int sl
= is_selinux_enabled();
1409 warn(_("failed to request selinux state"));
1411 ctl
->selinux_enabled
= sl
== 1;
1413 add_column(columns
, ncolumns
++, COL_SELINUX
);
1421 if (argc
- optind
== 1) {
1422 if (strchr(argv
[optind
], ','))
1423 errx(EXIT_FAILURE
, _("Only one user may be specified. Use -l for multiple users."));
1424 logins
= argv
[optind
];
1425 outmode
= OUT_PRETTY
;
1426 } else if (argc
!= optind
)
1429 scols_init_debug(0);
1431 /* lslogins -u -s == lslogins */
1432 if (lslogins_flag
& F_USRAC
&& lslogins_flag
& F_SYSAC
)
1433 lslogins_flag
&= ~(F_USRAC
| F_SYSAC
);
1435 if (outmode
== OUT_PRETTY
&& !opt_o
) {
1436 /* all columns for lslogins <username> */
1437 for (ncolumns
= 0, i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
1438 columns
[ncolumns
++] = i
;
1440 } else if (ncolumns
== 2 && !opt_o
) {
1441 /* default colummns */
1442 add_column(columns
, ncolumns
++, COL_NPROCS
);
1443 add_column(columns
, ncolumns
++, COL_PWDLOCK
);
1444 add_column(columns
, ncolumns
++, COL_PWDDENY
);
1445 add_column(columns
, ncolumns
++, COL_LAST_LOGIN
);
1446 add_column(columns
, ncolumns
++, COL_GECOS
);
1450 parse_wtmp(ctl
, path_wtmp
);
1452 parse_btmp(ctl
, path_btmp
);
1454 get_ulist(ctl
, logins
, groups
);
1456 if (create_usertree(ctl
))
1457 return EXIT_FAILURE
;
1459 print_user_table(ctl
);
1461 scols_unref_table(tb
);
1462 tdestroy(ctl
->usertree
, free_user
);
1465 return EXIT_SUCCESS
;