2 * lslogins - List information about users on the system
4 * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it would be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include <sys/types.h>
27 #include <sys/syslog.h>
40 #include <libsmartcols.h>
41 #ifdef HAVE_LIBSELINUX
42 #include <selinux/selinux.h>
47 #include "closestream.h"
52 #include "pathnames.h"
53 #include "logindefs.h"
59 struct lslogins_coldesc
{
62 const char *pretty_name
;
64 double whint
; /* width hint */
68 static int lslogins_flag
;
70 #define UL_UID_MIN 1000
71 #define UL_UID_MAX 60000
72 #define UL_SYS_UID_MIN 201
73 #define UL_SYS_UID_MAX 999
75 /* we use the value of outmode to determine
76 * appropriate flags for the libsmartcols table
77 * (e.g., a value of out_newline would imply a raw
78 * table with the column separator set to '\n').
93 struct lslogins_user
{
119 #ifdef HAVE_LIBSELINUX
120 security_context_t context
;
153 F_SELINUX
= (1 << 9),
185 static const char *const status
[] = { "0", "1", "-" };
186 static struct lslogins_coldesc coldescs
[] =
188 [COL_LOGIN
] = { "LOGIN", N_("user/system login"), "Login", 0.2, SCOLS_FL_NOEXTREMES
},
189 [COL_UID
] = { "UID", N_("user UID"), "UID", 0.05, SCOLS_FL_RIGHT
},
190 [COL_NOPASSWD
] = { "NOPASSWD", N_("account has a password?"), "No password", 1 },
191 [COL_NOLOGIN
] = { "NOLOGIN", N_("account has a password?"), "No login", 1 },
192 [COL_LOCKED
] = { "LOCKED", N_("account has a password?"), "Locked", 1 },
193 [COL_PGRP
] = { "GROUP", N_("primary group name"), "Primary group", 0.2 },
194 [COL_PGID
] = { "GID", N_("primary group GID"), "GID", 0.05, SCOLS_FL_RIGHT
},
195 [COL_SGRPS
] = { "SUPP-GROUPS", N_("secondary group names and GIDs"), "Secondary groups", 0.5 },
196 [COL_HOME
] = { "HOMEDIR", N_("home directory"), "Home directory", 0.3 },
197 [COL_SHELL
] = { "SHELL", N_("login shell"), "Shell", 0.1 },
198 [COL_GECOS
] = { "GECOS", N_("full user name"), "Comment field", 0.3, SCOLS_FL_TRUNC
},
199 [COL_LAST_LOGIN
] = { "LAST-LOGIN", N_("date of last login"), "Last login", 24 },
200 [COL_LAST_TTY
] = { "LAST-TTY", N_("last tty used"), "Last terminal", 0.05 },
201 [COL_LAST_HOSTNAME
] = { "LAST-HOSTNAME", N_("hostname during the last session"), "Last hostname", 0.2},
202 [COL_FAILED_LOGIN
] = { "FAILED-LOGIN", N_("date of last failed login"), "Failed login", 24 },
203 [COL_FAILED_TTY
] = { "FAILED-TTY", N_("where did the login fail?"), "Failed login terminal", 0.05 },
204 [COL_HUSH_STATUS
] = { "HUSHED", N_("User's hush settings"), "Hushed", 1 },
205 [COL_PWD_WARN
] = { "PWD-WARN", N_("password warn interval"), "Days to passwd warning", 24 },
206 [COL_PWD_EXPIR
] = { "PWD-EXPIR", N_("password expiration date"), "Password expiration", 24 },
207 [COL_PWD_CTIME
] = { "PWD-CHANGE", N_("date of last password change"), "Password changed", 24 },
208 [COL_PWD_CTIME_MIN
] = { "PWD-MIN", N_("number of days required between changes"), "Minimal change time", 24 },
209 [COL_PWD_CTIME_MAX
] = { "PWD-MAX", N_("max number of days a password may remain unchanged"), "Maximal change time", 24 },
210 [COL_SELINUX
] = { "CONTEXT", N_("the user's security context"), "Selinux context", 0.4 },
213 struct lslogins_control
{
228 int (*cmp_fn
) (const void *a
, const void *b
);
234 unsigned int time_mode
;
236 /* these have to remain global since there's no other
237 * reasonable way to pass them for each call of fill_table()
239 static struct libscols_table
*tb
;
240 static int columns
[ARRAY_SIZE(coldescs
)];
243 static int date_is_today(time_t t
)
246 gettimeofday(&tv
, NULL
);
247 return t
/ 86400 == tv
.tv_sec
/ 86400;
251 column_name_to_id(const char *name
, size_t namesz
)
255 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++) {
256 const char *cn
= coldescs
[i
].name
;
258 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
)) {
262 warnx(_("unknown column: %s"), name
);
265 static char *make_time(int mode
, time_t time
)
271 localtime_r(&time
, &tm
);
276 if (*(s
= buf
+ strlen(buf
) - 1) == '\n')
279 case TIME_SHORT_RELATIVE
:
280 if (date_is_today(time
))
281 strftime(buf
, 32, "%H:%M:%S", &tm
);
284 strftime(buf
, 32, "%a %b %d %Y", &tm
);
287 strftime(buf
, 32, "%Y-%m-%dT%H:%M:%S%z", &tm
);
295 static void __attribute__((__noreturn__
)) usage(FILE *out
)
299 fputs(USAGE_HEADER
, out
);
300 fprintf(out
, _(" %s [options]\n"), program_invocation_short_name
);
302 fputs(USAGE_OPTIONS
, out
);
303 fputs(_(" -a, --acc-expiration Display data\n"), out
);
304 fputs(_(" -c, --colon-separate Display data in a format similar to /etc/passwd\n"), out
);
305 fputs(_(" -e, --export Display in an export-able output format\n"), out
);
306 fputs(_(" -f, --failed Display data about the last users' failed logins\n"), out
);
307 fputs(_(" -fulltimes Show dates in a long format\n"), out
);
308 fputs(_(" -g, --groups=<groups> Display users belonging to a group in GROUPS\n"), out
);
309 fputs(_(" -i, --iso Display dates in the ISO-8601 format\n"), out
);
310 fputs(_(" -l, --logins=<logins> Display only users from LOGINS\n"), out
);
311 fputs(_(" --last Show info about the users' last login sessions\n"), out
);
312 fputs(_(" -m, --supp-groups Display supplementary groups as well\n"), out
);
313 fputs(_(" -n, --newline Display each piece of information on a new line\n"), out
);
314 fputs(_(" --notruncate Don't truncate output\n"), out
);
315 fputs(_(" -o, --output[=<list>] Define the columns to output\n"), out
);
316 fputs(_(" -r, --raw Display the raw table\n"), out
);
317 fputs(_(" -s, --system-accs Display system accounts\n"), out
);
318 fputs(_(" -t, --sort Sort output by login instead of UID\n"), out
);
319 fputs(_(" -u, --user-accs Display user accounts\n"), out
);
320 fputs(_(" -x, --extra Display extra information\n"), out
);
321 fputs(_(" -z, --print0 Delimit user entries with a nul character\n"), out
);
322 fputs(_(" -Z, --context Display the users' security context\n"), out
);
323 fputs(_(" --wtmp-file Set an alternate path for wtmp\n"), out
);
324 fputs(_(" --btmp-file Set an alternate path for btmp\n"), out
);
325 fputs(USAGE_SEPARATOR
, out
);
326 fputs(USAGE_HELP
, out
);
327 fputs(USAGE_VERSION
, out
);
329 fprintf(out
, _("\nAvailable columns:\n"));
331 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
332 fprintf(out
, " %14s %s\n", coldescs
[i
].name
, _(coldescs
[i
].help
));
334 fprintf(out
, _("\nFor more details see lslogins(1).\n"));
336 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
338 struct lslogins_sgroups
{
341 struct lslogins_sgroups
*next
;
344 static char *uidtostr(uid_t uid
)
346 char *str_uid
= NULL
;
347 return (0 > xasprintf(&str_uid
, "%u", uid
)) ? NULL
: str_uid
;
350 static char *gidtostr(gid_t gid
)
352 char *str_gid
= NULL
;
353 return (0 > xasprintf(&str_gid
, "%u", gid
)) ? NULL
: str_gid
;
356 static struct lslogins_sgroups
*build_sgroups_list(int len
, gid_t
*list
, int *slen
)
359 struct lslogins_sgroups
*sgrps
, *retgrps
;
368 retgrps
= sgrps
= xcalloc(1, sizeof(struct lslogins_sgroups
));
373 sgrps
->gid
= gidtostr(list
[n
]);
375 grp
= getgrgid(list
[n
]);
380 sgrps
->uname
= xstrdup(grp
->gr_name
);
382 *slen
+= strlen(sgrps
->gid
) + strlen(sgrps
->uname
);
384 sgrps
->next
= xcalloc(1, sizeof(struct lslogins_sgroups
));
389 /* space for a pair of parentheses for each gname + (n - 1) commas in between */
399 static void free_sgroups_list(struct lslogins_sgroups
*sgrps
)
401 struct lslogins_sgroups
*tmp
;
416 static char *build_sgroups_string(int len
, gid_t
*list
)
418 char *ret
= NULL
, *slist
;
420 struct lslogins_sgroups
*sgrps
;
422 sgrps
= build_sgroups_list(len
, list
, &slen
);
427 ret
= slist
= xcalloc(1, sizeof(char) * (slen
+ 1));
429 while (sgrps
->next
) {
430 prlen
= sprintf(slist
, "%s(%s),", sgrps
->gid
, sgrps
->uname
);
432 free_sgroups_list(sgrps
);
438 prlen
= sprintf(slist
, "%s(%s)", sgrps
->gid
, sgrps
->uname
);
440 free_sgroups_list(sgrps
);
444 static struct utmp
*get_last_wtmp(struct lslogins_control
*ctl
, const char *username
)
452 len
= strlen(username
);
453 n
= ctl
->wtmp_size
- 1;
455 if (!strncmp(username
, ctl
->wtmp
[n
].ut_user
,
456 len
< UT_NAMESIZE
? len
: UT_NAMESIZE
))
457 return ctl
->wtmp
+ n
;
462 static struct utmp
*get_last_btmp(struct lslogins_control
*ctl
, const char *username
)
470 len
= strlen(username
);
471 n
= ctl
->btmp_size
- 1;
473 if (!strncmp(username
, ctl
->btmp
[n
].ut_user
,
474 len
< UT_NAMESIZE
? len
: UT_NAMESIZE
))
475 return ctl
->btmp
+ n
;
481 static int parse_wtmp(struct lslogins_control
*ctl
, char *path
)
485 rc
= read_utmp(path
, &ctl
->wtmp_size
, &ctl
->wtmp
);
486 if (rc
< 0 && errno
!= EACCES
)
487 err(EXIT_FAILURE
, "%s", path
);
491 static int parse_btmp(struct lslogins_control
*ctl
, char *path
)
495 rc
= read_utmp(path
, &ctl
->btmp_size
, &ctl
->btmp
);
496 if (rc
< 0 && errno
!= EACCES
)
497 err(EXIT_FAILURE
, "%s", path
);
500 static int get_sgroups(int *len
, gid_t
**list
, struct passwd
*pwd
)
507 /* first let's get a supp. group count */
508 getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, len
);
512 *list
= xcalloc(1, *len
* sizeof(gid_t
));
514 /* now for the actual list of GIDs */
515 if (-1 == getgrouplist(pwd
->pw_name
, pwd
->pw_gid
, *list
, len
))
518 /* getgroups also returns the user's primary GID - dispose of it */
520 if ((*list
)[n
] == pwd
->pw_gid
)
524 (*list
)[n
] = (*list
)[--(*len
)];
526 /* probably too costly to do for sizeof(gid_t) worth of memory */
527 //*list = xrealloc(*list, *len * sizeof(gid_t));
532 static struct lslogins_user
*get_user_info(struct lslogins_control
*ctl
, const char *username
)
534 struct lslogins_user
*user
;
538 struct utmp
*user_wtmp
= NULL
, *user_btmp
= NULL
;
545 pwd
= getpwnam(username
);
553 /* nfsnobody is an exception to the UID_MAX limit.
554 * This is "nobody" on some systems; the decisive
555 * point is the UID - 65534 */
556 if ((lslogins_flag
& F_USRAC
) &&
557 strcmp("nfsnobody", pwd
->pw_name
)) {
558 if (uid
< ctl
->UID_MIN
|| uid
> ctl
->UID_MAX
) {
563 else if (lslogins_flag
& F_SYSAC
) {
564 if (uid
< ctl
->SYS_UID_MIN
|| uid
> ctl
->SYS_UID_MAX
) {
570 user
= xcalloc(1, sizeof(struct lslogins_user
));
572 grp
= getgrgid(pwd
->pw_gid
);
577 user_wtmp
= get_last_wtmp(ctl
, pwd
->pw_name
);
579 user_btmp
= get_last_btmp(ctl
, pwd
->pw_name
);
581 /* sufficient permissions to get a shadow entry? */
584 shadow
= getspnam(pwd
->pw_name
);
589 err(EXIT_FAILURE
, "%s", strerror(errno
));
592 /* we want these dates in seconds */
593 shadow
->sp_lstchg
*= 86400;
594 shadow
->sp_expire
*= 86400;
597 while (n
< ncolumns
) {
598 switch (columns
[n
++]) {
600 user
->login
= xstrdup(pwd
->pw_name
);
603 user
->uid
= pwd
->pw_uid
;
606 user
->group
= xstrdup(grp
->gr_name
);
609 user
->gid
= pwd
->pw_gid
;
616 if (get_sgroups(&n
, &list
, pwd
))
619 user
->sgroups
= build_sgroups_string(n
, list
);
622 user
->sgroups
= xstrdup(status
[2]);
626 user
->homedir
= xstrdup(pwd
->pw_dir
);
629 user
->shell
= xstrdup(pwd
->pw_shell
);
632 user
->gecos
= xstrdup(pwd
->pw_gecos
);
636 time
= user_wtmp
->ut_tv
.tv_sec
;
637 user
->last_login
= make_time(ctl
->time_mode
, time
);
640 user
->last_login
= xstrdup(status
[2]);
644 user
->last_tty
= xstrdup(user_wtmp
->ut_line
);
646 user
->last_tty
= xstrdup(status
[2]);
648 case COL_LAST_HOSTNAME
:
650 user
->last_hostname
= xstrdup(user_wtmp
->ut_host
);
652 user
->last_hostname
= xstrdup(status
[2]);
654 case COL_FAILED_LOGIN
:
656 time
= user_btmp
->ut_tv
.tv_sec
;
657 user
->failed_login
= make_time(ctl
->time_mode
, time
);
660 user
->failed_login
= xstrdup(status
[2]);
664 user
->failed_tty
= xstrdup(user_btmp
->ut_line
);
666 user
->failed_tty
= xstrdup(status
[2]);
668 case COL_HUSH_STATUS
:
669 user
->hushed
= get_hushlogin_status(pwd
, 0);
670 if (user
->hushed
== -1)
675 if (!*shadow
->sp_pwdp
) /* '\0' */
682 if ((pwd
->pw_uid
&& !(close(open("/etc/nologin", O_RDONLY
)))) ||
683 strstr(pwd
->pw_shell
, "nologin")) {
689 if (*shadow
->sp_pwdp
== '!')
696 if (shadow
&& shadow
->sp_warn
>= 0) {
697 xasprintf(&user
->pwd_warn
, "%ld", shadow
->sp_warn
);
700 user
->pwd_warn
= xstrdup(status
[2]);
703 if (shadow
&& shadow
->sp_expire
>= 0)
704 user
->pwd_expire
= make_time(TIME_SHORT
, shadow
->sp_expire
);
706 user
->pwd_expire
= xstrdup(status
[2]);
709 /* sp_lstchg is specified in days, showing hours (especially in non-GMT
710 * timezones) would only serve to confuse */
712 user
->pwd_ctime
= make_time(TIME_SHORT
, shadow
->sp_lstchg
);
714 user
->pwd_ctime
= xstrdup(status
[2]);
716 case COL_PWD_CTIME_MIN
:
718 if (shadow
->sp_min
<= 0)
719 user
->pwd_ctime_min
= xstrdup("unlimited");
721 xasprintf(&user
->pwd_ctime_min
, "%ld", shadow
->sp_min
);
724 user
->pwd_ctime_min
= xstrdup(status
[2]);
726 case COL_PWD_CTIME_MAX
:
728 if (shadow
->sp_max
<= 0)
729 user
->pwd_ctime_max
= xstrdup("unlimited");
731 xasprintf(&user
->pwd_ctime_max
, "%ld", shadow
->sp_max
);
734 user
->pwd_ctime_max
= xstrdup(status
[2]);
738 #ifdef HAVE_LIBSELINUX
739 /* typedefs and pointers are pure evil */
740 security_context_t con
= NULL
;
742 user
->context
= xstrdup(status
[2]);
749 /* something went very wrong here */
750 err(EXIT_FAILURE
, "fatal: unknown error");
753 /* check if we have the info needed to sort */
754 if (lslogins_flag
& F_SORT
) { /* sorting by username */
756 user
->login
= xstrdup(pwd
->pw_name
);
758 else /* sorting by UID */
759 user
->uid
= pwd
->pw_uid
;
763 /* some UNIX implementations set errno iff a passwd/grp/...
764 * entry was not found. The original UNIX logins(1) utility always
765 * ignores invalid login/group names, so we're going to as well.*/
766 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
767 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
770 static void *user_in_tree(void **rootp, struct lslogins_user *u)
773 rc = tfind(u, rootp, ctl->cmp_fn);
775 tdelete(u, rootp, ctl->cmp_fn);
780 /* get a definitive list of users we want info about... */
782 static int str_to_uint(char *s
, unsigned int *ul
)
787 *ul
= strtoul(s
, &end
, 0);
792 static int get_ulist(struct lslogins_control
*ctl
, char *logins
, char *groups
)
795 size_t i
= 0, n
= 0, *arsiz
;
805 /* an arbitrary starting value */
807 *ar
= xcalloc(1, sizeof(char *) * (*arsiz
));
809 while ((u
= strtok(logins
, ","))) {
812 /* user specified by UID? */
813 if (!str_to_uint(u
, &uid
)) {
819 (*ar
)[i
++] = xstrdup(u
);
822 *ar
= xrealloc(*ar
, sizeof(char *) * (*arsiz
+= 32));
824 /* FIXME: this might lead to duplicit entries, although not visible
825 * in output, crunching a user's info multiple times is very redundant */
826 while ((g
= strtok(groups
, ","))) {
829 /* user specified by GID? */
830 if (!str_to_uint(g
, &gid
))
838 while ((u
= grp
->gr_mem
[n
++])) {
839 (*ar
)[i
++] = xstrdup(u
);
842 *ar
= xrealloc(*ar
, sizeof(char *) * (*arsiz
+= 32));
849 static void free_ctl(struct lslogins_control
*ctl
)
856 while (n
< ctl
->ulsiz
)
857 free(ctl
->ulist
[n
++]);
863 static struct lslogins_user
*get_next_user(struct lslogins_control
*ctl
)
865 struct lslogins_user
*u
;
867 while (!(u
= get_user_info(ctl
, NULL
))) {
868 /* no "false" errno-s here, iff we're unable to
869 * get a valid user entry for any reason, quit */
877 static int get_user(struct lslogins_control
*ctl
, struct lslogins_user
**user
, const char *username
)
879 *user
= get_user_info(ctl
, username
);
881 if (IS_REAL_ERRNO(errno
))
885 static int create_usertree(struct lslogins_control
*ctl
)
887 struct lslogins_user
*user
= NULL
;
891 while (n
< ctl
->ulsiz
) {
892 if (get_user(ctl
, &user
, ctl
->ulist
[n
]))
894 if (user
) /* otherwise an invalid user name has probably been given */
895 tsearch(user
, &ctl
->usertree
, ctl
->cmp_fn
);
900 while ((user
= get_next_user(ctl
)))
901 tsearch(user
, &ctl
->usertree
, ctl
->cmp_fn
);
906 static int cmp_uname(const void *a
, const void *b
)
908 return strcmp(((struct lslogins_user
*)a
)->login
,
909 ((struct lslogins_user
*)b
)->login
);
912 static int cmp_uid(const void *a
, const void *b
)
914 uid_t x
= ((struct lslogins_user
*)a
)->uid
;
915 uid_t z
= ((struct lslogins_user
*)b
)->uid
;
916 return x
> z
? 1 : (x
< z
? -1 : 0);
919 static struct libscols_table
*setup_table(void)
921 struct libscols_table
*tb
= scols_new_table();
928 scols_table_enable_raw(tb
, 1);
929 scols_table_set_column_separator(tb
, ":");
932 scols_table_set_column_separator(tb
, "\n");
935 scols_table_enable_export(tb
, 1);
938 scols_table_set_line_separator(tb
, "\0");
941 scols_table_enable_raw(tb
, 1);
944 scols_table_enable_noheadings(tb
, 1);
949 while (n
< ncolumns
) {
950 if (!scols_table_new_column(tb
, coldescs
[columns
[n
]].name
,
951 coldescs
[columns
[n
]].whint
, coldescs
[columns
[n
]].flag
))
958 scols_unref_table(tb
);
962 static void fill_table(const void *u
, const VISIT which
, const int depth
__attribute__((unused
)))
964 struct libscols_line
*ln
;
965 struct lslogins_user
*user
= *(struct lslogins_user
**)u
;
968 if ((which
== preorder
) || (which
== endorder
))
971 ln
= scols_table_new_line(tb
, NULL
);
972 while (n
< ncolumns
) {
973 switch (columns
[n
]) {
975 if (scols_line_set_data(ln
, n
, user
->login
))
980 char *str_uid
= uidtostr(user
->uid
);
981 if (!str_uid
|| scols_line_set_data(ln
, n
, str_uid
))
987 if (scols_line_set_data(ln
, n
, status
[user
->nopasswd
]))
991 if (scols_line_set_data(ln
, n
, status
[user
->nologin
]))
995 if (scols_line_set_data(ln
, n
, status
[user
->locked
]))
999 if (scols_line_set_data(ln
, n
, user
->group
))
1004 char *str_gid
= gidtostr(user
->gid
);
1005 if (!str_gid
|| scols_line_set_data(ln
, n
, str_gid
))
1011 if (scols_line_set_data(ln
, n
, user
->sgroups
))
1015 if (scols_line_set_data(ln
, n
, user
->homedir
))
1019 if (scols_line_set_data(ln
, n
, user
->shell
))
1023 if (scols_line_set_data(ln
, n
, user
->gecos
))
1026 case COL_LAST_LOGIN
:
1027 if (scols_line_set_data(ln
, n
, user
->last_login
))
1031 if (scols_line_set_data(ln
, n
, user
->last_tty
))
1034 case COL_LAST_HOSTNAME
:
1035 if (scols_line_set_data(ln
, n
, user
->last_hostname
))
1038 case COL_FAILED_LOGIN
:
1039 if (scols_line_set_data(ln
, n
, user
->failed_login
))
1042 case COL_FAILED_TTY
:
1043 if (scols_line_set_data(ln
, n
, user
->failed_tty
))
1046 case COL_HUSH_STATUS
:
1047 if (scols_line_set_data(ln
, n
, status
[user
->hushed
]))
1051 if (scols_line_set_data(ln
, n
, user
->pwd_warn
))
1055 if (scols_line_set_data(ln
, n
, user
->pwd_expire
))
1059 if (scols_line_set_data(ln
, n
, user
->pwd_ctime
))
1062 case COL_PWD_CTIME_MIN
:
1063 if (scols_line_set_data(ln
, n
, user
->pwd_ctime_min
))
1066 case COL_PWD_CTIME_MAX
:
1067 if (scols_line_set_data(ln
, n
, user
->pwd_ctime_max
))
1070 #ifdef HAVE_LIBSELINUX
1072 if (scols_line_set_data(ln
, n
, user
->context
))
1077 /* something went very wrong here */
1078 err(EXIT_FAILURE
, "fatal: unknown error");
1086 static int print_pretty(struct libscols_table
*tb
)
1088 struct libscols_iter
*itr
= scols_new_iter(SCOLS_ITER_FORWARD
);
1089 struct libscols_column
*col
;
1090 struct libscols_cell
*data
;
1091 struct libscols_line
*ln
;
1092 const char *hstr
, *dstr
;
1095 ln
= scols_table_get_line(tb
, 0);
1096 while (!scols_table_next_column(tb
, itr
, &col
)) {
1098 data
= scols_line_get_cell(ln
, n
);
1100 hstr
= coldescs
[columns
[n
]].pretty_name
;
1101 dstr
= scols_cell_get_data(data
);
1103 printf("%s:%*c%-36s\n", hstr
, 26 - (int)strlen(hstr
), ' ', dstr
);
1107 scols_free_iter(itr
);
1111 static int print_user_table(struct lslogins_control
*ctl
)
1117 twalk(ctl
->usertree
, fill_table
);
1118 if (outmode
== out_pretty
)
1121 scols_print_table(tb
);
1125 static void free_user(void *f
)
1127 struct lslogins_user
*u
= f
;
1134 free(u
->pwd_ctime_min
);
1135 free(u
->pwd_ctime_max
);
1136 free(u
->last_login
);
1138 free(u
->last_hostname
);
1139 free(u
->failed_login
);
1140 free(u
->failed_tty
);
1143 free(u
->pwd_status
);
1144 #ifdef HAVE_LIBSELINUX
1145 freecon(u
->context
);
1149 struct lslogins_timefmt
{
1153 static struct lslogins_timefmt timefmts
[] = {
1154 { "short", TIME_SHORT_RELATIVE
},
1155 { "full", TIME_FULL
},
1156 { "iso", TIME_ISO
},
1159 int main(int argc
, char *argv
[])
1161 int c
, want_wtmp
= 0, want_btmp
= 0;
1162 char *logins
= NULL
, *groups
= NULL
;
1163 char *path_wtmp
= _PATH_WTMP
, *path_btmp
= _PATH_BTMP
;
1164 struct lslogins_control
*ctl
= xcalloc(1, sizeof(struct lslogins_control
));
1166 /* long only options. */
1168 OPT_LAST
= CHAR_MAX
+ 1,
1177 static const struct option longopts
[] = {
1178 { "acc-expiration", no_argument
, 0, 'a' },
1179 { "colon", no_argument
, 0, 'c' },
1180 { "export", no_argument
, 0, 'e' },
1181 { "failed", no_argument
, 0, 'f' },
1182 { "fulltimes", no_argument
, 0, OPT_FULLT
},
1183 { "groups", required_argument
, 0, 'g' },
1184 { "help", no_argument
, 0, 'h' },
1185 { "iso", no_argument
, 0, 'i' },
1186 { "logins", required_argument
, 0, 'l' },
1187 { "supp-groups", no_argument
, 0, 'm' },
1188 { "newline", no_argument
, 0, 'n' },
1189 { "notruncate", no_argument
, 0, OPT_NOTRUNC
},
1190 { "output", required_argument
, 0, 'o' },
1191 { "last", no_argument
, 0, OPT_LAST
},
1192 { "raw", no_argument
, 0, 'r' },
1193 { "system-accs", no_argument
, 0, 's' },
1194 { "sort-by-name", no_argument
, 0, 't' },
1195 { "time-format", required_argument
, 0, OPT_TIME_FMT
},
1196 { "user-accs", no_argument
, 0, 'u' },
1197 { "version", no_argument
, 0, OPT_VER
},
1198 { "extra", no_argument
, 0, 'x' },
1199 { "print0", no_argument
, 0, 'z' },
1200 /* TODO: find a reasonable way to do this for passwd/group/shadow,
1201 * as libc itself doesn't supply any way to get a specific
1202 * entry from a user-specified file */
1203 { "wtmp-file", required_argument
, 0, OPT_WTMP
},
1204 { "btmp-file", required_argument
, 0, OPT_BTMP
},
1205 #ifdef HAVE_LIBSELINUX
1206 { "context", no_argument
, 0, 'Z' },
1211 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
1212 { 'c','e','n','r','z' },
1213 { 'i', OPT_TIME_FMT
, OPT_FULLT
},
1216 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1218 setlocale(LC_ALL
, "");
1219 bindtextdomain(PACKAGE
, LOCALEDIR
);
1220 textdomain(PACKAGE
);
1221 atexit(close_stdout
);
1223 ctl
->cmp_fn
= cmp_uid
;
1224 ctl
->time_mode
= TIME_SHORT_RELATIVE
;
1226 while ((c
= getopt_long(argc
, argv
, "acefg:hil:mno:rstuxzZ",
1227 longopts
, NULL
)) != -1) {
1229 err_exclusive_options(c
, longopts
, excl
, excl_st
);
1233 lslogins_flag
|= F_EXPIR
;
1236 outmode
= out_colon
;
1239 outmode
= out_export
;
1242 lslogins_flag
|= F_FAIL
;
1250 ctl
->time_mode
= TIME_ISO
;
1256 lslogins_flag
|= F_MORE
;
1259 outmode
= out_newline
;
1265 ncolumns
= string_to_idarray(optarg
,
1266 columns
, ARRAY_SIZE(columns
),
1269 return EXIT_FAILURE
;
1276 lslogins_flag
|= F_LAST
;
1279 ctl
->SYS_UID_MIN
= getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN
);
1280 ctl
->SYS_UID_MAX
= getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX
);
1281 lslogins_flag
|= F_SYSAC
;
1284 ctl
->cmp_fn
= cmp_uname
;
1285 lslogins_flag
|= F_SORT
;
1288 ctl
->UID_MIN
= getlogindefs_num("UID_MIN", UL_UID_MIN
);
1289 ctl
->UID_MAX
= getlogindefs_num("UID_MAX", UL_UID_MAX
);
1290 lslogins_flag
|= F_USRAC
;
1293 printf(_("%s from %s\n"), program_invocation_short_name
,
1295 return EXIT_SUCCESS
;
1297 lslogins_flag
|= F_EXTRA
;
1309 coldescs
[COL_GECOS
].flag
= 0;
1312 ctl
->time_mode
= TIME_FULL
;
1318 for (i
= 0; i
< ARRAY_SIZE(timefmts
); i
++) {
1319 if (strcmp(timefmts
[i
].name
, optarg
) == 0) {
1320 ctl
->time_mode
= timefmts
[i
].val
;
1324 if (ctl
->time_mode
== TIME_INVALID
)
1329 #ifdef HAVE_LIBSELINUX
1330 lslogins_flag
|= F_SELINUX
;
1331 ctl
->sel_enabled
= is_selinux_enabled();
1332 if (ctl
->sel_enabled
== -1)
1341 if (argc
- optind
== 1) {
1342 if (strchr(argv
[optind
], ','))
1343 err(EXIT_FAILURE
, "%s", "Only one user may be specified. Use -l for multiple users");
1344 logins
= argv
[optind
];
1345 outmode
= out_pretty
;
1347 else if (argc
!= optind
)
1350 /* lslogins -u -s == lslogins */
1351 if (lslogins_flag
& F_USRAC
&& lslogins_flag
& F_SYSAC
)
1352 lslogins_flag
&= ~(F_USRAC
| F_SYSAC
);
1355 if (lslogins_flag
& F_SORT
) {
1356 columns
[ncolumns
++] = COL_LOGIN
;
1357 columns
[ncolumns
++] = COL_UID
;
1360 columns
[ncolumns
++] = COL_UID
;
1361 columns
[ncolumns
++] = COL_LOGIN
;
1363 columns
[ncolumns
++] = COL_PGRP
;
1364 columns
[ncolumns
++] = COL_PGID
;
1365 columns
[ncolumns
++] = COL_LAST_LOGIN
;
1369 if (lslogins_flag
& F_NOPWD
) {
1370 columns
[ncolumns
++] = COL_NOPASSWD
;
1372 if (lslogins_flag
& F_MORE
) {
1373 columns
[ncolumns
++] = COL_SGRPS
;
1375 if (lslogins_flag
& F_EXPIR
) {
1376 columns
[ncolumns
++] = COL_PWD_CTIME
;
1377 columns
[ncolumns
++] = COL_PWD_EXPIR
;
1379 if (lslogins_flag
& F_LAST
) {
1380 columns
[ncolumns
++] = COL_LAST_TTY
;
1381 columns
[ncolumns
++] = COL_LAST_HOSTNAME
;
1383 if (lslogins_flag
& F_FAIL
) {
1384 columns
[ncolumns
++] = COL_FAILED_LOGIN
;
1385 columns
[ncolumns
++] = COL_FAILED_TTY
;
1388 if (lslogins_flag
& F_EXTRA
) {
1389 columns
[ncolumns
++] = COL_HOME
;
1390 columns
[ncolumns
++] = COL_SHELL
;
1391 columns
[ncolumns
++] = COL_GECOS
;
1392 columns
[ncolumns
++] = COL_NOPASSWD
;
1393 columns
[ncolumns
++] = COL_NOLOGIN
;
1394 columns
[ncolumns
++] = COL_LOCKED
;
1395 columns
[ncolumns
++] = COL_HUSH_STATUS
;
1396 columns
[ncolumns
++] = COL_PWD_WARN
;
1397 columns
[ncolumns
++] = COL_PWD_CTIME_MIN
; /*?*/
1398 columns
[ncolumns
++] = COL_PWD_CTIME_MAX
; /*?*/
1400 if (lslogins_flag
& F_SELINUX
)
1401 columns
[ncolumns
++] = COL_SELINUX
;
1405 while (n
< ncolumns
) {
1407 if (i
<= COL_LAST_HOSTNAME
&& i
>= COL_LAST_LOGIN
)
1409 if (i
== COL_FAILED_TTY
&& i
>= COL_FAILED_LOGIN
)
1415 parse_wtmp(ctl
, path_wtmp
);
1417 parse_btmp(ctl
, path_btmp
);
1419 get_ulist(ctl
, logins
, groups
);
1421 if (create_usertree(ctl
))
1422 return EXIT_FAILURE
;
1424 print_user_table(ctl
);
1426 scols_unref_table(tb
);
1427 tdestroy(ctl
->usertree
, free_user
);
1431 return EXIT_SUCCESS
;