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>
48 #include "closestream.h"
53 #include "pathnames.h"
54 #include "logindefs.h"
60 struct lslogins_coldesc
{
63 const char *pretty_name
;
65 double whint
; /* width hint */
69 static int lslogins_flag
;
71 #define UL_UID_MIN 1000
72 #define UL_UID_MAX 60000
73 #define UL_SYS_UID_MIN 201
74 #define UL_SYS_UID_MAX 999
76 /* we use the value of outmode to determine
77 * appropriate flags for the libsmartcols table
78 * (e.g., a value of out_newline would imply a raw
79 * table with the column separator set to '\n').
94 struct lslogins_user
{
121 #ifdef HAVE_LIBSELINUX
122 security_context_t context
;
155 F_SELINUX
= (1 << 9),
194 static const char *const status
[] = {
195 [STATUS_FALSE
] = "0",
197 [STATUS_UNKNOWN
]= NULL
200 static const char *const pretty_status
[] = {
201 [STATUS_FALSE
] = N_("no"),
202 [STATUS_TRUE
] = N_("yes"),
203 [STATUS_UNKNOWN
]= NULL
206 #define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
208 static struct lslogins_coldesc coldescs
[] =
210 [COL_LOGIN
] = { "LOGIN", N_("user/system login"), N_("Login"), 0.2, SCOLS_FL_NOEXTREMES
},
211 [COL_UID
] = { "UID", N_("user ID"), "UID", 0.05, SCOLS_FL_RIGHT
},
212 [COL_NOPASSWD
] = { "NOPASSWD", N_("account has a password?"), N_("No password"), 1, SCOLS_FL_RIGHT
},
213 [COL_NOLOGIN
] = { "NOLOGIN", N_("account has a password?"), N_("No login"), 1, SCOLS_FL_RIGHT
},
214 [COL_LOCKED
] = { "LOCKED", N_("account has a password?"), N_("Locked"), 1, SCOLS_FL_RIGHT
},
215 [COL_PGRP
] = { "GROUP", N_("primary group name"), N_("Primary group"), 0.2 },
216 [COL_PGID
] = { "GID", N_("primary group ID"), "GID", 0.05, SCOLS_FL_RIGHT
},
217 [COL_SGRPS
] = { "SUPP-GROUPS", N_("supplementary group names"), N_("Supplementary groups"), 0.1 },
218 [COL_SGIDS
] = { "SUPP-GIDS", N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 },
219 [COL_HOME
] = { "HOMEDIR", N_("home directory"), N_("Home directory"), 0.3 },
220 [COL_SHELL
] = { "SHELL", N_("login shell"), N_("Shell"), 0.1 },
221 [COL_GECOS
] = { "GECOS", N_("full user name"), N_("Gecos field"), 0.3, SCOLS_FL_TRUNC
},
222 [COL_LAST_LOGIN
] = { "LAST-LOGIN", N_("date of last login"), N_("Last login"), 24 },
223 [COL_LAST_TTY
] = { "LAST-TTY", N_("last tty used"), N_("Last terminal"), 0.05 },
224 [COL_LAST_HOSTNAME
] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"), 0.2},
225 [COL_FAILED_LOGIN
] = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 24 },
226 [COL_FAILED_TTY
] = { "FAILED-TTY", N_("where did the login fail?"), N_("Failed login terminal"), 0.05 },
227 [COL_HUSH_STATUS
] = { "HUSHED", N_("User's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT
},
228 [COL_PWD_WARN
] = { "PWD-WARN", N_("password warn interval"), N_("Days to passwd warning"), 24 },
229 [COL_PWD_EXPIR
] = { "PWD-EXPIR", N_("password expiration date"), N_("Password expiration"), 24 },
230 [COL_PWD_CTIME
] = { "PWD-CHANGE", N_("date of last password change"), N_("Password changed"), 24 },
231 [COL_PWD_CTIME_MIN
] = { "PWD-MIN", N_("number of days required between changes"), N_("Minimal change time"), 24 },
232 [COL_PWD_CTIME_MAX
] = { "PWD-MAX", N_("max number of days a password may remain unchanged"), N_("Maximal change time"), 24 },
233 [COL_SELINUX
] = { "CONTEXT", N_("the user's security context"), N_("Selinux context"), 0.4 },
236 struct lslogins_control
{
251 int (*cmp_fn
) (const void *a
, const void *b
);
257 unsigned int time_mode
;
259 /* these have to remain global since there's no other
260 * reasonable way to pass them for each call of fill_table()
262 static struct libscols_table
*tb
;
263 static int columns
[ARRAY_SIZE(coldescs
)];
266 static int date_is_today(time_t t
)
269 gettimeofday(&tv
, NULL
);
270 return t
/ 86400 == tv
.tv_sec
/ 86400;
274 column_name_to_id(const char *name
, size_t namesz
)
278 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++) {
279 const char *cn
= coldescs
[i
].name
;
281 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
)) {
285 warnx(_("unknown column: %s"), name
);
288 static char *make_time(int mode
, time_t time
)
294 localtime_r(&time
, &tm
);
299 if (*(s
= buf
+ strlen(buf
) - 1) == '\n')
302 case TIME_SHORT_RELATIVE
:
303 if (date_is_today(time
))
304 strftime(buf
, 32, "%H:%M:%S", &tm
);
307 strftime(buf
, 32, "%a %b %d %Y", &tm
);
310 strftime(buf
, 32, "%Y-%m-%dT%H:%M:%S%z", &tm
);
319 static char *uidtostr(uid_t uid
)
321 char *str_uid
= NULL
;
322 xasprintf(&str_uid
, "%u", uid
);
326 static char *gidtostr(gid_t gid
)
328 char *str_gid
= NULL
;
329 xasprintf(&str_gid
, "%u", gid
);
333 static char *build_sgroups_string(gid_t
*sgroups
, size_t nsgroups
, int want_names
)
335 size_t n
= 0, maxlen
, len
;
338 len
= maxlen
= nsgroups
* 10;
339 res
= p
= xmalloc(maxlen
);
341 while (n
< nsgroups
) {
345 x
= snprintf(p
, len
, "%u,", sgroups
[n
]);
347 struct group
*grp
= getgrgid(sgroups
[n
]);
352 x
= snprintf(p
, len
, "%s,", grp
->gr_name
);
355 if (x
< 0 || (size_t) x
+ 1 > len
) {
356 size_t cur
= p
- res
;
359 res
= xrealloc(res
, maxlen
);
376 static struct utmp
*get_last_wtmp(struct lslogins_control
*ctl
, const char *username
)
384 len
= strlen(username
);
385 n
= ctl
->wtmp_size
- 1;
387 if (!strncmp(username
, ctl
->wtmp
[n
].ut_user
,
388 len
< UT_NAMESIZE
? len
: UT_NAMESIZE
))
389 return ctl
->wtmp
+ n
;
395 static struct utmp
*get_last_btmp(struct lslogins_control
*ctl
, const char *username
)
403 len
= strlen(username
);
404 n
= ctl
->btmp_size
- 1;
406 if (!strncmp(username
, ctl
->btmp
[n
].ut_user
,
407 len
< UT_NAMESIZE
? len
: UT_NAMESIZE
))
408 return ctl
->btmp
+ n
;
414 static int parse_wtmp(struct lslogins_control
*ctl
, char *path
)
418 rc
= read_utmp(path
, &ctl
->wtmp_size
, &ctl
->wtmp
);
419 if (rc
< 0 && errno
!= EACCES
)
420 err(EXIT_FAILURE
, "%s", path
);
424 static int parse_btmp(struct lslogins_control
*ctl
, char *path
)
428 rc
= read_utmp(path
, &ctl
->btmp_size
, &ctl
->btmp
);
429 if (rc
< 0 && errno
!= EACCES
)
430 err(EXIT_FAILURE
, "%s", path
);
434 static int get_sgroups(gid_t
**list
, size_t *len
, struct passwd
*pwd
)
441 /* first let's get a supp. group count */
442 getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, (int *) len
);
446 *list
= xcalloc(1, *len
* sizeof(gid_t
));
448 /* now for the actual list of GIDs */
449 if (-1 == getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, (int *) len
))
452 /* getgroups also returns the user's primary GID - dispose of it */
454 if ((*list
)[n
] == pwd
->pw_gid
)
459 (*list
)[n
] = (*list
)[--(*len
)];
463 static struct lslogins_user
*get_user_info(struct lslogins_control
*ctl
, const char *username
)
465 struct lslogins_user
*user
;
469 struct utmp
*user_wtmp
= NULL
, *user_btmp
= NULL
;
476 pwd
= getpwnam(username
);
484 /* nfsnobody is an exception to the UID_MAX limit.
485 * This is "nobody" on some systems; the decisive
486 * point is the UID - 65534 */
487 if ((lslogins_flag
& F_USRAC
) &&
488 strcmp("nfsnobody", pwd
->pw_name
)) {
489 if (uid
< ctl
->UID_MIN
|| uid
> ctl
->UID_MAX
) {
494 } else if (lslogins_flag
& F_SYSAC
) {
495 if (uid
< ctl
->SYS_UID_MIN
|| uid
> ctl
->SYS_UID_MAX
) {
501 user
= xcalloc(1, sizeof(struct lslogins_user
));
503 grp
= getgrgid(pwd
->pw_gid
);
508 user_wtmp
= get_last_wtmp(ctl
, pwd
->pw_name
);
510 user_btmp
= get_last_btmp(ctl
, pwd
->pw_name
);
512 /* sufficient permissions to get a shadow entry? */
515 shadow
= getspnam(pwd
->pw_name
);
520 err(EXIT_FAILURE
, "%s", strerror(errno
));
522 /* we want these dates in seconds */
523 shadow
->sp_lstchg
*= 86400;
524 shadow
->sp_expire
*= 86400;
527 while (n
< ncolumns
) {
528 switch (columns
[n
++]) {
530 user
->login
= xstrdup(pwd
->pw_name
);
533 user
->uid
= pwd
->pw_uid
;
536 user
->group
= xstrdup(grp
->gr_name
);
539 user
->gid
= pwd
->pw_gid
;
543 if (get_sgroups(&user
->sgroups
, &user
->nsgroups
, pwd
))
544 err(EXIT_FAILURE
, _("failed to get supplementary groups"));
547 user
->homedir
= xstrdup(pwd
->pw_dir
);
550 user
->shell
= xstrdup(pwd
->pw_shell
);
553 user
->gecos
= xstrdup(pwd
->pw_gecos
);
557 time
= user_wtmp
->ut_tv
.tv_sec
;
558 user
->last_login
= make_time(ctl
->time_mode
, time
);
563 user
->last_tty
= xstrdup(user_wtmp
->ut_line
);
565 case COL_LAST_HOSTNAME
:
567 user
->last_hostname
= xstrdup(user_wtmp
->ut_host
);
569 case COL_FAILED_LOGIN
:
571 time
= user_btmp
->ut_tv
.tv_sec
;
572 user
->failed_login
= make_time(ctl
->time_mode
, time
);
577 user
->failed_tty
= xstrdup(user_btmp
->ut_line
);
579 case COL_HUSH_STATUS
:
580 user
->hushed
= get_hushlogin_status(pwd
, 0);
581 if (user
->hushed
== -1)
582 user
->hushed
= STATUS_UNKNOWN
;
586 if (!*shadow
->sp_pwdp
) /* '\0' */
587 user
->nopasswd
= STATUS_TRUE
;
589 user
->nopasswd
= STATUS_UNKNOWN
;
592 if ((pwd
->pw_uid
&& !(close(open("/etc/nologin", O_RDONLY
)))) ||
593 strstr(pwd
->pw_shell
, "nologin")) {
599 if (*shadow
->sp_pwdp
== '!')
600 user
->locked
= STATUS_TRUE
;
602 user
->locked
= STATUS_UNKNOWN
;
605 if (shadow
&& shadow
->sp_warn
>= 0)
606 xasprintf(&user
->pwd_warn
, "%ld", shadow
->sp_warn
);
609 if (shadow
&& shadow
->sp_expire
>= 0)
610 user
->pwd_expire
= make_time(TIME_SHORT
, shadow
->sp_expire
);
613 /* sp_lstchg is specified in days, showing hours (especially in non-GMT
614 * timezones) would only serve to confuse */
616 user
->pwd_ctime
= make_time(TIME_SHORT
, shadow
->sp_lstchg
);
618 case COL_PWD_CTIME_MIN
:
620 if (shadow
->sp_min
<= 0)
621 user
->pwd_ctime_min
= xstrdup("unlimited");
623 xasprintf(&user
->pwd_ctime_min
, "%ld", shadow
->sp_min
);
626 case COL_PWD_CTIME_MAX
:
628 if (shadow
->sp_max
<= 0)
629 user
->pwd_ctime_max
= xstrdup("unlimited");
631 xasprintf(&user
->pwd_ctime_max
, "%ld", shadow
->sp_max
);
636 #ifdef HAVE_LIBSELINUX
637 /* typedefs and pointers are pure evil */
638 security_context_t con
= NULL
;
639 if (getcon(&con
) == 0)
645 /* something went very wrong here */
646 err(EXIT_FAILURE
, "fatal: unknown error");
649 /* check if we have the info needed to sort */
650 if (lslogins_flag
& F_SORT
) { /* sorting by username */
652 user
->login
= xstrdup(pwd
->pw_name
);
653 } else /* sorting by UID */
654 user
->uid
= pwd
->pw_uid
;
658 /* some UNIX implementations set errno iff a passwd/grp/...
659 * entry was not found. The original UNIX logins(1) utility always
660 * ignores invalid login/group names, so we're going to as well.*/
661 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
662 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
665 static void *user_in_tree(void **rootp, struct lslogins_user *u)
668 rc = tfind(u, rootp, ctl->cmp_fn);
670 tdelete(u, rootp, ctl->cmp_fn);
675 /* get a definitive list of users we want info about... */
677 static int str_to_uint(char *s
, unsigned int *ul
)
682 *ul
= strtoul(s
, &end
, 0);
688 static int get_ulist(struct lslogins_control
*ctl
, char *logins
, char *groups
)
691 size_t i
= 0, n
= 0, *arsiz
;
701 /* an arbitrary starting value */
703 *ar
= xcalloc(1, sizeof(char *) * (*arsiz
));
705 while ((u
= strtok(logins
, ","))) {
708 /* user specified by UID? */
709 if (!str_to_uint(u
, &uid
)) {
715 (*ar
)[i
++] = xstrdup(u
);
718 *ar
= xrealloc(*ar
, sizeof(char *) * (*arsiz
+= 32));
720 /* FIXME: this might lead to duplicit entries, although not visible
721 * in output, crunching a user's info multiple times is very redundant */
722 while ((g
= strtok(groups
, ","))) {
725 /* user specified by GID? */
726 if (!str_to_uint(g
, &gid
))
734 while ((u
= grp
->gr_mem
[n
++])) {
735 (*ar
)[i
++] = xstrdup(u
);
738 *ar
= xrealloc(*ar
, sizeof(char *) * (*arsiz
+= 32));
745 static void free_ctl(struct lslogins_control
*ctl
)
752 while (n
< ctl
->ulsiz
)
753 free(ctl
->ulist
[n
++]);
759 static struct lslogins_user
*get_next_user(struct lslogins_control
*ctl
)
761 struct lslogins_user
*u
;
763 while (!(u
= get_user_info(ctl
, NULL
))) {
764 /* no "false" errno-s here, iff we're unable to
765 * get a valid user entry for any reason, quit */
773 static int get_user(struct lslogins_control
*ctl
, struct lslogins_user
**user
,
774 const char *username
)
776 *user
= get_user_info(ctl
, username
);
778 if (IS_REAL_ERRNO(errno
))
783 static int create_usertree(struct lslogins_control
*ctl
)
785 struct lslogins_user
*user
= NULL
;
789 while (n
< ctl
->ulsiz
) {
790 if (get_user(ctl
, &user
, ctl
->ulist
[n
]))
792 if (user
) /* otherwise an invalid user name has probably been given */
793 tsearch(user
, &ctl
->usertree
, ctl
->cmp_fn
);
797 while ((user
= get_next_user(ctl
)))
798 tsearch(user
, &ctl
->usertree
, ctl
->cmp_fn
);
803 static int cmp_uname(const void *a
, const void *b
)
805 return strcmp(((struct lslogins_user
*)a
)->login
,
806 ((struct lslogins_user
*)b
)->login
);
809 static int cmp_uid(const void *a
, const void *b
)
811 uid_t x
= ((struct lslogins_user
*)a
)->uid
;
812 uid_t z
= ((struct lslogins_user
*)b
)->uid
;
813 return x
> z
? 1 : (x
< z
? -1 : 0);
816 static struct libscols_table
*setup_table(void)
818 struct libscols_table
*tb
= scols_new_table();
825 scols_table_enable_raw(tb
, 1);
826 scols_table_set_column_separator(tb
, ":");
829 scols_table_set_column_separator(tb
, "\n");
832 scols_table_enable_export(tb
, 1);
835 scols_table_set_line_separator(tb
, "\0");
838 scols_table_enable_raw(tb
, 1);
841 scols_table_enable_noheadings(tb
, 1);
846 while (n
< ncolumns
) {
847 if (!scols_table_new_column(tb
, coldescs
[columns
[n
]].name
,
848 coldescs
[columns
[n
]].whint
, coldescs
[columns
[n
]].flag
))
855 scols_unref_table(tb
);
859 static void fill_table(const void *u
, const VISIT which
, const int depth
__attribute__((unused
)))
861 struct libscols_line
*ln
;
862 struct lslogins_user
*user
= *(struct lslogins_user
**)u
;
865 if (which
== preorder
|| which
== endorder
)
868 ln
= scols_table_new_line(tb
, NULL
);
869 while (n
< ncolumns
) {
872 switch (columns
[n
]) {
874 rc
= scols_line_set_data(ln
, n
, user
->login
);
877 rc
= scols_line_refer_data(ln
, n
, uidtostr(user
->uid
));
880 rc
= scols_line_set_data(ln
, n
, get_status(user
->nopasswd
));
883 rc
= scols_line_set_data(ln
, n
, get_status(user
->nologin
));
886 rc
= scols_line_set_data(ln
, n
, get_status(user
->locked
));
889 rc
= scols_line_set_data(ln
, n
, user
->group
);
892 rc
= scols_line_refer_data(ln
, n
, gidtostr(user
->gid
));
895 rc
= scols_line_refer_data(ln
, n
,
896 build_sgroups_string(user
->sgroups
,
901 rc
= scols_line_refer_data(ln
, n
,
902 build_sgroups_string(user
->sgroups
,
907 rc
= scols_line_set_data(ln
, n
, user
->homedir
);
910 rc
= scols_line_set_data(ln
, n
, user
->shell
);
913 rc
= scols_line_set_data(ln
, n
, user
->gecos
);
916 rc
= scols_line_set_data(ln
, n
, user
->last_login
);
919 rc
= scols_line_set_data(ln
, n
, user
->last_tty
);
921 case COL_LAST_HOSTNAME
:
922 rc
= scols_line_set_data(ln
, n
, user
->last_hostname
);
924 case COL_FAILED_LOGIN
:
925 rc
= scols_line_set_data(ln
, n
, user
->failed_login
);
928 rc
= scols_line_set_data(ln
, n
, user
->failed_tty
);
930 case COL_HUSH_STATUS
:
931 rc
= scols_line_set_data(ln
, n
, get_status(user
->hushed
));
934 rc
= scols_line_set_data(ln
, n
, user
->pwd_warn
);
937 rc
= scols_line_set_data(ln
, n
, user
->pwd_expire
);
940 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime
);
942 case COL_PWD_CTIME_MIN
:
943 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime_min
);
945 case COL_PWD_CTIME_MAX
:
946 rc
= scols_line_set_data(ln
, n
, user
->pwd_ctime_max
);
948 #ifdef HAVE_LIBSELINUX
950 rc
= scols_line_set_data(ln
, n
, user
->context
);
954 /* something went very wrong here */
955 err(EXIT_FAILURE
, _("internal error: unknown column"));
959 err(EXIT_FAILURE
, _("failed to set data"));
965 static int print_pretty(struct libscols_table
*tb
)
967 struct libscols_iter
*itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
968 struct libscols_column
*col
;
969 struct libscols_cell
*data
;
970 struct libscols_line
*ln
;
971 const char *hstr
, *dstr
;
974 ln
= scols_table_get_line(tb
, 0);
975 while (!scols_table_next_column(tb
, itr
, &col
)) {
977 data
= scols_line_get_cell(ln
, n
);
979 hstr
= _(coldescs
[columns
[n
]].pretty_name
);
980 dstr
= scols_cell_get_data(data
);
983 printf("%s:%*c%-36s\n", hstr
, 26 - (int)strlen(hstr
), ' ', dstr
);
987 scols_free_iter(itr
);
992 static int print_user_table(struct lslogins_control
*ctl
)
998 twalk(ctl
->usertree
, fill_table
);
999 if (outmode
== OUT_PRETTY
)
1002 scols_print_table(tb
);
1006 static void free_user(void *f
)
1008 struct lslogins_user
*u
= f
;
1015 free(u
->pwd_ctime_min
);
1016 free(u
->pwd_ctime_max
);
1017 free(u
->last_login
);
1019 free(u
->last_hostname
);
1020 free(u
->failed_login
);
1021 free(u
->failed_tty
);
1024 free(u
->pwd_status
);
1025 #ifdef HAVE_LIBSELINUX
1026 freecon(u
->context
);
1031 struct lslogins_timefmt
{
1036 static struct lslogins_timefmt timefmts
[] = {
1037 { "short", TIME_SHORT_RELATIVE
},
1038 { "full", TIME_FULL
},
1039 { "iso", TIME_ISO
},
1042 static void __attribute__((__noreturn__
)) usage(FILE *out
)
1046 fputs(USAGE_HEADER
, out
);
1047 fprintf(out
, _(" %s [options]\n"), program_invocation_short_name
);
1049 fputs(USAGE_OPTIONS
, out
);
1050 fputs(_(" -a, --acc-expiration Display data\n"), out
);
1051 fputs(_(" -c, --colon-separate Display data in a format similar to /etc/passwd\n"), out
);
1052 fputs(_(" -e, --export Display in an export-able output format\n"), out
);
1053 fputs(_(" -f, --failed Display data about the last users' failed logins\n"), out
);
1054 fputs(_(" --fulltimes Show dates in a long format\n"), out
);
1055 fputs(_(" -g, --groups=<groups> Display users belonging to a group in <groups>\n"), out
);
1056 fputs(_(" -l, --logins=<logins> Display only users from <logins>\n"), out
);
1057 fputs(_(" --last Show info about the users' last login sessions\n"), out
);
1058 fputs(_(" -m, --supp-groups Display supplementary groups as well\n"), out
);
1059 fputs(_(" -n, --newline Display each piece of information on a new line\n"), out
);
1060 fputs(_(" --notruncate Don't truncate output\n"), out
);
1061 fputs(_(" -o, --output[=<list>] Define the columns to output\n"), out
);
1062 fputs(_(" -r, --raw Display the raw table\n"), out
);
1063 fputs(_(" -s, --system-accs Display system accounts\n"), out
);
1064 fputs(_(" -t, --sort-by-name Sort output by login instead of UID\n"), out
);
1065 fputs(_(" --time-format=<type> Display dates in type <type>, where type is one of short|full|iso\n"), out
);
1066 fputs(_(" -u, --user-accs Display user accounts\n"), out
);
1067 fputs(_(" -x, --extra Display extra information\n"), out
);
1068 fputs(_(" -z, --print0 Delimit user entries with a nul character\n"), out
);
1069 fputs(_(" -Z, --context Display the users' security context\n"), out
);
1070 fputs(_(" --wtmp-file <path> Set an alternate path for wtmp\n"), out
);
1071 fputs(_(" --btmp-file <path> Set an alternate path for btmp\n"), out
);
1072 fputs(USAGE_SEPARATOR
, out
);
1073 fputs(USAGE_HELP
, out
);
1074 fputs(USAGE_VERSION
, out
);
1076 fprintf(out
, _("\nAvailable columns:\n"));
1078 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
1079 fprintf(out
, " %14s %s\n", coldescs
[i
].name
, _(coldescs
[i
].help
));
1081 fprintf(out
, _("\nFor more details see lslogins(1).\n"));
1083 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
1086 int main(int argc
, char *argv
[])
1088 int c
, want_wtmp
= 0, want_btmp
= 0;
1089 char *logins
= NULL
, *groups
= NULL
;
1090 char *path_wtmp
= _PATH_WTMP
, *path_btmp
= _PATH_BTMP
;
1091 struct lslogins_control
*ctl
= xcalloc(1, sizeof(struct lslogins_control
));
1093 /* long only options. */
1095 OPT_LAST
= CHAR_MAX
+ 1,
1104 static const struct option longopts
[] = {
1105 { "acc-expiration", no_argument
, 0, 'a' },
1106 { "colon-separate", no_argument
, 0, 'c' },
1107 { "export", no_argument
, 0, 'e' },
1108 { "failed", no_argument
, 0, 'f' },
1109 { "fulltimes", no_argument
, 0, OPT_FULLT
},
1110 { "groups", required_argument
, 0, 'g' },
1111 { "help", no_argument
, 0, 'h' },
1112 { "logins", required_argument
, 0, 'l' },
1113 { "supp-groups", no_argument
, 0, 'm' },
1114 { "newline", no_argument
, 0, 'n' },
1115 { "notruncate", no_argument
, 0, OPT_NOTRUNC
},
1116 { "output", required_argument
, 0, 'o' },
1117 { "last", no_argument
, 0, OPT_LAST
},
1118 { "raw", no_argument
, 0, 'r' },
1119 { "system-accs", no_argument
, 0, 's' },
1120 { "sort-by-name", no_argument
, 0, 't' },
1121 { "time-format", required_argument
, 0, OPT_TIME_FMT
},
1122 { "user-accs", no_argument
, 0, 'u' },
1123 { "version", no_argument
, 0, OPT_VER
},
1124 { "extra", no_argument
, 0, 'x' },
1125 { "print0", no_argument
, 0, 'z' },
1126 /* TODO: find a reasonable way to do this for passwd/group/shadow,
1127 * as libc itself doesn't supply any way to get a specific
1128 * entry from a user-specified file */
1129 { "wtmp-file", required_argument
, 0, OPT_WTMP
},
1130 { "btmp-file", required_argument
, 0, OPT_BTMP
},
1131 #ifdef HAVE_LIBSELINUX
1132 { "context", no_argument
, 0, 'Z' },
1137 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
1138 { 'c','e','n','r','z' },
1139 { 'i', OPT_FULLT
, OPT_TIME_FMT
},
1142 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1144 setlocale(LC_ALL
, "");
1145 bindtextdomain(PACKAGE
, LOCALEDIR
);
1146 textdomain(PACKAGE
);
1147 atexit(close_stdout
);
1149 ctl
->cmp_fn
= cmp_uid
;
1150 ctl
->time_mode
= TIME_SHORT_RELATIVE
;
1152 while ((c
= getopt_long(argc
, argv
, "acefg:hl:mno:rstuxzZ",
1153 longopts
, NULL
)) != -1) {
1155 err_exclusive_options(c
, longopts
, excl
, excl_st
);
1159 lslogins_flag
|= F_EXPIR
;
1162 outmode
= OUT_COLON
;
1165 outmode
= OUT_EXPORT
;
1168 lslogins_flag
|= F_FAIL
;
1180 lslogins_flag
|= F_MORE
;
1183 outmode
= OUT_NEWLINE
;
1189 ncolumns
= string_to_idarray(optarg
,
1190 columns
, ARRAY_SIZE(columns
),
1193 return EXIT_FAILURE
;
1200 lslogins_flag
|= F_LAST
;
1203 ctl
->SYS_UID_MIN
= getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN
);
1204 ctl
->SYS_UID_MAX
= getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX
);
1205 lslogins_flag
|= F_SYSAC
;
1208 ctl
->cmp_fn
= cmp_uname
;
1209 lslogins_flag
|= F_SORT
;
1212 ctl
->UID_MIN
= getlogindefs_num("UID_MIN", UL_UID_MIN
);
1213 ctl
->UID_MAX
= getlogindefs_num("UID_MAX", UL_UID_MAX
);
1214 lslogins_flag
|= F_USRAC
;
1217 printf(_("%s from %s\n"), program_invocation_short_name
,
1219 return EXIT_SUCCESS
;
1221 lslogins_flag
|= F_EXTRA
;
1233 coldescs
[COL_GECOS
].flag
= 0;
1236 ctl
->time_mode
= TIME_FULL
;
1242 for (i
= 0; i
< ARRAY_SIZE(timefmts
); i
++) {
1243 if (strcmp(timefmts
[i
].name
, optarg
) == 0) {
1244 ctl
->time_mode
= timefmts
[i
].val
;
1248 if (ctl
->time_mode
== TIME_INVALID
)
1253 #ifdef HAVE_LIBSELINUX
1254 lslogins_flag
|= F_SELINUX
;
1255 ctl
->sel_enabled
= is_selinux_enabled();
1256 if (ctl
->sel_enabled
== -1)
1265 if (argc
- optind
== 1) {
1266 if (strchr(argv
[optind
], ','))
1267 errx(EXIT_FAILURE
, _("Only one user may be specified. Use -l for multiple users."));
1268 logins
= argv
[optind
];
1269 outmode
= OUT_PRETTY
;
1270 } else if (argc
!= optind
)
1273 /* lslogins -u -s == lslogins */
1274 if (lslogins_flag
& F_USRAC
&& lslogins_flag
& F_SYSAC
)
1275 lslogins_flag
&= ~(F_USRAC
| F_SYSAC
);
1277 if (!ncolumns
&& outmode
== OUT_PRETTY
) {
1281 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
1282 columns
[ncolumns
++] = i
;
1284 } else if (!ncolumns
) {
1285 if (lslogins_flag
& F_SORT
) {
1286 columns
[ncolumns
++] = COL_LOGIN
;
1287 columns
[ncolumns
++] = COL_UID
;
1289 columns
[ncolumns
++] = COL_UID
;
1290 columns
[ncolumns
++] = COL_LOGIN
;
1292 columns
[ncolumns
++] = COL_PGRP
;
1293 columns
[ncolumns
++] = COL_PGID
;
1294 columns
[ncolumns
++] = COL_LAST_LOGIN
;
1298 if (lslogins_flag
& F_NOPWD
)
1299 columns
[ncolumns
++] = COL_NOPASSWD
;
1300 if (lslogins_flag
& F_MORE
)
1301 columns
[ncolumns
++] = COL_SGRPS
;
1302 if (lslogins_flag
& F_EXPIR
) {
1303 columns
[ncolumns
++] = COL_PWD_CTIME
;
1304 columns
[ncolumns
++] = COL_PWD_EXPIR
;
1306 if (lslogins_flag
& F_LAST
) {
1307 columns
[ncolumns
++] = COL_LAST_TTY
;
1308 columns
[ncolumns
++] = COL_LAST_HOSTNAME
;
1310 if (lslogins_flag
& F_FAIL
) {
1311 columns
[ncolumns
++] = COL_FAILED_LOGIN
;
1312 columns
[ncolumns
++] = COL_FAILED_TTY
;
1315 if (lslogins_flag
& F_EXTRA
) {
1316 columns
[ncolumns
++] = COL_HOME
;
1317 columns
[ncolumns
++] = COL_SHELL
;
1318 columns
[ncolumns
++] = COL_GECOS
;
1319 columns
[ncolumns
++] = COL_NOPASSWD
;
1320 columns
[ncolumns
++] = COL_NOLOGIN
;
1321 columns
[ncolumns
++] = COL_LOCKED
;
1322 columns
[ncolumns
++] = COL_HUSH_STATUS
;
1323 columns
[ncolumns
++] = COL_PWD_WARN
;
1324 columns
[ncolumns
++] = COL_PWD_CTIME_MIN
; /*?*/
1325 columns
[ncolumns
++] = COL_PWD_CTIME_MAX
; /*?*/
1327 if (lslogins_flag
& F_SELINUX
)
1328 columns
[ncolumns
++] = COL_SELINUX
;
1331 while (n
< ncolumns
) {
1333 if (i
<= COL_LAST_HOSTNAME
&& i
>= COL_LAST_LOGIN
)
1335 if (i
== COL_FAILED_TTY
&& i
>= COL_FAILED_LOGIN
)
1341 parse_wtmp(ctl
, path_wtmp
);
1343 parse_btmp(ctl
, path_btmp
);
1345 get_ulist(ctl
, logins
, groups
);
1347 if (create_usertree(ctl
))
1348 return EXIT_FAILURE
;
1350 print_user_table(ctl
);
1352 scols_unref_table(tb
);
1353 tdestroy(ctl
->usertree
, free_user
);
1357 return EXIT_SUCCESS
;