]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/lslogins.c
4503fe5d4679a564759190791795584f6171d241
[thirdparty/util-linux.git] / login-utils / lslogins.c
1 /*
2 * lslogins - List information about users on the system
3 *
4 * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
5 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
6 *
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.
11 *
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.
16 *
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.
20 */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/syslog.h>
29 #include <pwd.h>
30 #include <grp.h>
31 #include <shadow.h>
32 #include <paths.h>
33 #include <time.h>
34 #include <utmp.h>
35 #include <signal.h>
36 #include <err.h>
37 #include <limits.h>
38
39 #include <search.h>
40
41 #include <libsmartcols.h>
42 #ifdef HAVE_LIBSELINUX
43 # include <selinux/selinux.h>
44 #endif
45
46 #ifdef HAVE_LIBSYSTEMD
47 # include <systemd/sd-journal.h>
48 #endif
49
50 #include "c.h"
51 #include "nls.h"
52 #include "closestream.h"
53 #include "xalloc.h"
54 #include "list.h"
55 #include "strutils.h"
56 #include "optutils.h"
57 #include "pathnames.h"
58 #include "logindefs.h"
59 #include "readutmp.h"
60 #include "procutils.h"
61
62 /*
63 * column description
64 */
65 struct lslogins_coldesc {
66 const char *name;
67 const char *help;
68 const char *pretty_name;
69
70 double whint; /* width hint */
71 long flag;
72 };
73
74 static int lslogins_flag;
75
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
80
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').
85 */
86 static int outmode;
87 /*
88 * output modes
89 */
90 enum {
91 OUT_COLON = 1,
92 OUT_EXPORT,
93 OUT_NEWLINE,
94 OUT_RAW,
95 OUT_NUL,
96 OUT_PRETTY
97 };
98
99 struct lslogins_user {
100 char *login;
101 uid_t uid;
102 char *group;
103 gid_t gid;
104 char *gecos;
105
106 int pwd_empty;
107 int nologin;
108 int pwd_lock;
109 int pwd_deny;
110
111 gid_t *sgroups;
112 size_t nsgroups;
113
114 char *pwd_ctime;
115 char *pwd_warn;
116 char *pwd_expire;
117 char *pwd_ctime_min;
118 char *pwd_ctime_max;
119
120 char *last_login;
121 char *last_tty;
122 char *last_hostname;
123
124 char *failed_login;
125 char *failed_tty;
126
127 #ifdef HAVE_LIBSELINUX
128 security_context_t context;
129 #endif
130 char *homedir;
131 char *shell;
132 char *pwd_status;
133 int hushed;
134 char *nprocs;
135
136 };
137
138 /*
139 * time modes
140 * */
141 enum {
142 TIME_INVALID = 0,
143 TIME_SHORT,
144 TIME_FULL,
145 TIME_ISO,
146 };
147
148 /*
149 * flags
150 */
151 enum {
152 F_SYSAC = (1 << 3),
153 F_USRAC = (1 << 4),
154 };
155
156 /*
157 * IDs
158 */
159 enum {
160 COL_USER = 0,
161 COL_UID,
162 COL_GECOS,
163 COL_HOME,
164 COL_SHELL,
165 COL_NOLOGIN,
166 COL_PWDLOCK,
167 COL_PWDEMPTY,
168 COL_PWDDENY,
169 COL_GROUP,
170 COL_GID,
171 COL_SGROUPS,
172 COL_SGIDS,
173 COL_LAST_LOGIN,
174 COL_LAST_TTY,
175 COL_LAST_HOSTNAME,
176 COL_FAILED_LOGIN,
177 COL_FAILED_TTY,
178 COL_HUSH_STATUS,
179 COL_PWD_WARN,
180 COL_PWD_CTIME,
181 COL_PWD_CTIME_MIN,
182 COL_PWD_CTIME_MAX,
183 COL_PWD_EXPIR,
184 COL_SELINUX,
185 COL_NPROCS,
186 };
187
188 #define is_wtmp_col(x) ((x) == COL_LAST_LOGIN || \
189 (x) == COL_LAST_TTY || \
190 (x) == COL_LAST_HOSTNAME)
191
192 #define is_btmp_col(x) ((x) == COL_FAILED_LOGIN || \
193 (x) == COL_FAILED_TTY)
194
195 enum {
196 STATUS_FALSE = 0,
197 STATUS_TRUE,
198 STATUS_UNKNOWN
199 };
200
201 static const char *const status[] = {
202 [STATUS_FALSE] = "0",
203 [STATUS_TRUE] = "1",
204 [STATUS_UNKNOWN]= NULL
205 };
206
207 static const char *const pretty_status[] = {
208 [STATUS_FALSE] = N_("no"),
209 [STATUS_TRUE] = N_("yes"),
210 [STATUS_UNKNOWN]= NULL
211 };
212
213 #define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
214
215 static const struct lslogins_coldesc coldescs[] =
216 {
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 },
243 };
244
245 struct lslogins_control {
246 struct utmp *wtmp;
247 size_t wtmp_size;
248
249 struct utmp *btmp;
250 size_t btmp_size;
251
252 void *usertree;
253
254 uid_t uid;
255 uid_t UID_MIN;
256 uid_t UID_MAX;
257
258 uid_t SYS_UID_MIN;
259 uid_t SYS_UID_MAX;
260
261 char **ulist;
262 size_t ulsiz;
263
264 unsigned int time_mode;
265
266 const char *journal_path;
267
268 unsigned int selinux_enabled : 1,
269 noheadings : 1,
270 notrunc : 1;
271 };
272
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;
276
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];
282 static int ncolumns;
283
284 static inline size_t err_columns_index(size_t arysz, size_t idx)
285 {
286 if (idx >= arysz)
287 errx(EXIT_FAILURE, _("too many columns specified, "
288 "the limit is %zu columns"),
289 arysz - 1);
290 return idx;
291 }
292
293 #define add_column(ary, n, id) \
294 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
295
296 static struct timeval now;
297
298 static int date_is_today(time_t t)
299 {
300 if (now.tv_sec == 0)
301 gettimeofday(&now, NULL);
302 return t / (3600 * 24) == now.tv_sec / (3600 * 24);
303 }
304
305 static int date_is_thisyear(time_t t)
306 {
307 if (now.tv_sec == 0)
308 gettimeofday(&now, NULL);
309 return t / (3600 * 24 * 365) == now.tv_sec / (3600 * 24 * 365);
310 }
311
312 static int column_name_to_id(const char *name, size_t namesz)
313 {
314 size_t i;
315
316 for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
317 const char *cn = coldescs[i].name;
318
319 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
320 return i;
321 }
322 warnx(_("unknown column: %s"), name);
323 return -1;
324 }
325
326 static char *make_time(int mode, time_t time)
327 {
328 char *s;
329 struct tm tm;
330 char buf[64] = {0};
331
332 localtime_r(&time, &tm);
333
334 switch(mode) {
335 case TIME_FULL:
336 asctime_r(&tm, buf);
337 if (*(s = buf + strlen(buf) - 1) == '\n')
338 *s = '\0';
339 break;
340 case TIME_SHORT:
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);
345 else
346 strftime(buf, sizeof(buf), "%Y-%b%d", &tm);
347 break;
348 case TIME_ISO:
349 strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &tm);
350 break;
351 default:
352 errx(EXIT_FAILURE, _("unsupported time type"));
353 }
354 return xstrdup(buf);
355 }
356
357
358 static char *uidtostr(uid_t uid)
359 {
360 char *str_uid = NULL;
361 xasprintf(&str_uid, "%u", uid);
362 return str_uid;
363 }
364
365 static char *gidtostr(gid_t gid)
366 {
367 char *str_gid = NULL;
368 xasprintf(&str_gid, "%u", gid);
369 return str_gid;
370 }
371
372 static char *build_sgroups_string(gid_t *sgroups, size_t nsgroups, int want_names)
373 {
374 size_t n = 0, maxlen, len;
375 char *res, *p;
376
377 if (!nsgroups)
378 return NULL;
379
380 len = maxlen = nsgroups * 10;
381 res = p = xmalloc(maxlen);
382
383 while (n < nsgroups) {
384 int x;
385 again:
386 if (!want_names)
387 x = snprintf(p, len, "%u,", sgroups[n]);
388 else {
389 struct group *grp = getgrgid(sgroups[n]);
390 if (!grp) {
391 free(res);
392 return NULL;
393 }
394 x = snprintf(p, len, "%s,", grp->gr_name);
395 }
396
397 if (x < 0 || (size_t) x + 1 > len) {
398 size_t cur = p - res;
399
400 maxlen *= 2;
401 res = xrealloc(res, maxlen);
402 p = res + cur;
403 len = maxlen - cur;
404 goto again;
405 }
406
407 len -= x;
408 p += x;
409 ++n;
410 }
411
412 if (p > res)
413 *(p - 1) = '\0';
414
415 return res;
416 }
417
418 static struct utmp *get_last_wtmp(struct lslogins_control *ctl, const char *username)
419 {
420 size_t n = 0;
421 size_t len;
422
423 if (!username)
424 return NULL;
425
426 len = strlen(username);
427 n = ctl->wtmp_size - 1;
428 do {
429 if (!strncmp(username, ctl->wtmp[n].ut_user,
430 len < UT_NAMESIZE ? len : UT_NAMESIZE))
431 return ctl->wtmp + n;
432 } while (n--);
433 return NULL;
434
435 }
436
437 static int require_wtmp(void)
438 {
439 size_t i;
440 for (i = 0; i < (size_t) ncolumns; i++)
441 if (is_wtmp_col(columns[i]))
442 return 1;
443 return 0;
444 }
445
446 static int require_btmp(void)
447 {
448 size_t i;
449 for (i = 0; i < (size_t) ncolumns; i++)
450 if (is_btmp_col(columns[i]))
451 return 1;
452 return 0;
453 }
454
455 static struct utmp *get_last_btmp(struct lslogins_control *ctl, const char *username)
456 {
457 size_t n = 0;
458 size_t len;
459
460 if (!username)
461 return NULL;
462
463 len = strlen(username);
464 n = ctl->btmp_size - 1;
465 do {
466 if (!strncmp(username, ctl->btmp[n].ut_user,
467 len < UT_NAMESIZE ? len : UT_NAMESIZE))
468 return ctl->btmp + n;
469 }while (n--);
470 return NULL;
471
472 }
473
474 static int parse_wtmp(struct lslogins_control *ctl, char *path)
475 {
476 int rc = 0;
477
478 rc = read_utmp(path, &ctl->wtmp_size, &ctl->wtmp);
479 if (rc < 0 && errno != EACCES)
480 err(EXIT_FAILURE, "%s", path);
481 return rc;
482 }
483
484 static int parse_btmp(struct lslogins_control *ctl, char *path)
485 {
486 int rc = 0;
487
488 rc = read_utmp(path, &ctl->btmp_size, &ctl->btmp);
489 if (rc < 0 && errno != EACCES)
490 err(EXIT_FAILURE, "%s", path);
491 return rc;
492 }
493
494 static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
495 {
496 size_t n = 0;
497
498 *len = 0;
499 *list = NULL;
500
501 /* first let's get a supp. group count */
502 getgrouplist(pwd->pw_name, pwd->pw_gid, *list, (int *) len);
503 if (!*len)
504 return -1;
505
506 *list = xcalloc(1, *len * sizeof(gid_t));
507
508 /* now for the actual list of GIDs */
509 if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, (int *) len))
510 return -1;
511
512 /* getgroups also returns the user's primary GID - dispose of it */
513 while (n < *len) {
514 if ((*list)[n] == pwd->pw_gid)
515 break;
516 ++n;
517 }
518
519 if (*len)
520 (*list)[n] = (*list)[--(*len)];
521 return 0;
522 }
523
524 static int get_nprocs(const uid_t uid)
525 {
526 int nprocs = 0;
527 pid_t pid;
528 struct proc_processes *proc = proc_open_processes();
529
530 proc_processes_filter_by_uid(proc, uid);
531
532 while (!proc_next_pid(proc, &pid))
533 ++nprocs;
534
535 proc_close_processes(proc);
536 return nprocs;
537 }
538
539 static int valid_pwd(const char *str)
540 {
541 const char *p;
542
543 for (p = str; p && *p; p++)
544 if (!isalnum((unsigned int) *p))
545 return 0;
546 return p > str ? 1 : 0;
547 }
548
549 static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username)
550 {
551 struct lslogins_user *user;
552 struct passwd *pwd;
553 struct group *grp;
554 struct spwd *shadow;
555 struct utmp *user_wtmp = NULL, *user_btmp = NULL;
556 int n = 0;
557 time_t time;
558 uid_t uid;
559 errno = 0;
560
561 pwd = username ? getpwnam(username) : getpwent();
562 if (!pwd)
563 return NULL;
564
565 ctl->uid = uid = pwd->pw_uid;
566
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 &&
571 uid != 0) {
572 if (uid < ctl->UID_MIN || uid > ctl->UID_MAX) {
573 errno = EAGAIN;
574 return NULL;
575 }
576
577 } else if ((lslogins_flag & F_SYSAC) &&
578 (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX)) {
579 errno = EAGAIN;
580 return NULL;
581 }
582
583 user = xcalloc(1, sizeof(struct lslogins_user));
584
585 grp = getgrgid(pwd->pw_gid);
586 if (!grp)
587 return NULL;
588
589 if (ctl->wtmp)
590 user_wtmp = get_last_wtmp(ctl, pwd->pw_name);
591 if (ctl->btmp)
592 user_btmp = get_last_btmp(ctl, pwd->pw_name);
593
594 lckpwdf();
595 shadow = getspnam(pwd->pw_name);
596 ulckpwdf();
597
598 /* required by tseach() stuff */
599 user->uid = pwd->pw_uid;
600
601 while (n < ncolumns) {
602 switch (columns[n++]) {
603 case COL_USER:
604 user->login = xstrdup(pwd->pw_name);
605 break;
606 case COL_UID:
607 user->uid = pwd->pw_uid;
608 break;
609 case COL_GROUP:
610 user->group = xstrdup(grp->gr_name);
611 break;
612 case COL_GID:
613 user->gid = pwd->pw_gid;
614 break;
615 case COL_SGROUPS:
616 case COL_SGIDS:
617 if (get_sgroups(&user->sgroups, &user->nsgroups, pwd))
618 err(EXIT_FAILURE, _("failed to get supplementary groups"));
619 break;
620 case COL_HOME:
621 user->homedir = xstrdup(pwd->pw_dir);
622 break;
623 case COL_SHELL:
624 user->shell = xstrdup(pwd->pw_shell);
625 break;
626 case COL_GECOS:
627 user->gecos = xstrdup(pwd->pw_gecos);
628 break;
629 case COL_LAST_LOGIN:
630 if (user_wtmp) {
631 time = user_wtmp->ut_tv.tv_sec;
632 user->last_login = make_time(ctl->time_mode, time);
633 }
634 break;
635 case COL_LAST_TTY:
636 if (user_wtmp)
637 user->last_tty = xstrdup(user_wtmp->ut_line);
638 break;
639 case COL_LAST_HOSTNAME:
640 if (user_wtmp)
641 user->last_hostname = xstrdup(user_wtmp->ut_host);
642 break;
643 case COL_FAILED_LOGIN:
644 if (user_btmp) {
645 time = user_btmp->ut_tv.tv_sec;
646 user->failed_login = make_time(ctl->time_mode, time);
647 }
648 break;
649 case COL_FAILED_TTY:
650 if (user_btmp)
651 user->failed_tty = xstrdup(user_btmp->ut_line);
652 break;
653 case COL_HUSH_STATUS:
654 user->hushed = get_hushlogin_status(pwd, 0);
655 if (user->hushed == -1)
656 user->hushed = STATUS_UNKNOWN;
657 break;
658 case COL_PWDEMPTY:
659 if (shadow) {
660 if (!*shadow->sp_pwdp) /* '\0' */
661 user->pwd_empty = STATUS_TRUE;
662 } else
663 user->pwd_empty = STATUS_UNKNOWN;
664 break;
665 case COL_PWDDENY:
666 if (shadow) {
667 if ((*shadow->sp_pwdp == '!' ||
668 *shadow->sp_pwdp == '*') &&
669 !valid_pwd(shadow->sp_pwdp + 1))
670 user->pwd_deny = STATUS_TRUE;
671 } else
672 user->pwd_deny = STATUS_UNKNOWN;
673 break;
674
675 case COL_PWDLOCK:
676 if (shadow) {
677 if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1))
678 user->pwd_lock = STATUS_TRUE;
679 } else
680 user->pwd_lock = STATUS_UNKNOWN;
681 break;
682 case COL_NOLOGIN:
683 if (strstr(pwd->pw_shell, "nologin"))
684 user->nologin = 1;
685 else if (pwd->pw_uid)
686 user->nologin = access("/etc/nologin", F_OK) == 0 ||
687 access("/var/run/nologin", F_OK) == 0;
688 break;
689 case COL_PWD_WARN:
690 if (shadow && shadow->sp_warn >= 0)
691 xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
692 break;
693 case COL_PWD_EXPIR:
694 if (shadow && shadow->sp_expire >= 0)
695 user->pwd_expire = make_time(TIME_SHORT,
696 shadow->sp_expire * 86400);
697 break;
698 case COL_PWD_CTIME:
699 /* sp_lstchg is specified in days, showing hours
700 * (especially in non-GMT timezones) would only serve
701 * to confuse */
702 if (shadow)
703 user->pwd_ctime = make_time(TIME_SHORT,
704 shadow->sp_lstchg * 86400);
705 break;
706 case COL_PWD_CTIME_MIN:
707 if (shadow && shadow->sp_min > 0)
708 xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
709 break;
710 case COL_PWD_CTIME_MAX:
711 if (shadow && shadow->sp_max > 0)
712 xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
713 break;
714 case COL_SELINUX:
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)
720 user->context = con;
721 }
722 #endif
723 break;
724 case COL_NPROCS:
725 xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
726 break;
727 default:
728 /* something went very wrong here */
729 err(EXIT_FAILURE, "fatal: unknown error");
730 break;
731 }
732 }
733
734 return user;
735 }
736
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)
742
743 /* get a definitive list of users we want info about... */
744
745 static int str_to_uint(char *s, unsigned int *ul)
746 {
747 char *end;
748 if (!s || !*s)
749 return -1;
750 *ul = strtoul(s, &end, 0);
751 if (!*end)
752 return 0;
753 return 1;
754 }
755
756 static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
757 {
758 char *u, *g;
759 size_t i = 0, n = 0, *arsiz;
760 struct group *grp;
761 struct passwd *pwd;
762 char ***ar;
763 uid_t uid;
764 gid_t gid;
765
766 ar = &ctl->ulist;
767 arsiz = &ctl->ulsiz;
768
769 /* an arbitrary starting value */
770 *arsiz = 32;
771 *ar = xcalloc(1, sizeof(char *) * (*arsiz));
772
773 while ((u = strtok(logins, ","))) {
774 logins = NULL;
775
776 /* user specified by UID? */
777 if (!str_to_uint(u, &uid)) {
778 pwd = getpwuid(uid);
779 if (!pwd)
780 continue;
781 u = pwd->pw_name;
782 }
783 (*ar)[i++] = xstrdup(u);
784
785 if (i == *arsiz)
786 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
787 }
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, ","))) {
791 groups = NULL;
792
793 /* user specified by GID? */
794 if (!str_to_uint(g, &gid))
795 grp = getgrgid(gid);
796 else
797 grp = getgrnam(g);
798
799 if (!grp)
800 continue;
801
802 while ((u = grp->gr_mem[n++])) {
803 (*ar)[i++] = xstrdup(u);
804
805 if (i == *arsiz)
806 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
807 }
808 }
809 *arsiz = i;
810 return 0;
811 }
812
813 static void free_ctl(struct lslogins_control *ctl)
814 {
815 size_t n = 0;
816
817 free(ctl->wtmp);
818 free(ctl->btmp);
819
820 while (n < ctl->ulsiz)
821 free(ctl->ulist[n++]);
822
823 free(ctl->ulist);
824 free(ctl);
825 }
826
827 static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
828 {
829 struct lslogins_user *u;
830 errno = 0;
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 */
834 if (errno == EAGAIN)
835 continue;
836 return NULL;
837 }
838 return u;
839 }
840
841 static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
842 const char *username)
843 {
844 *user = get_user_info(ctl, username);
845 if (!*user && errno)
846 if (IS_REAL_ERRNO(errno))
847 return -1;
848 return 0;
849 }
850
851 static int cmp_uid(const void *a, const void *b)
852 {
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);
856 }
857
858 static int create_usertree(struct lslogins_control *ctl)
859 {
860 struct lslogins_user *user = NULL;
861 size_t n = 0;
862
863 if (*ctl->ulist) {
864 while (n < ctl->ulsiz) {
865 if (get_user(ctl, &user, ctl->ulist[n]))
866 return -1;
867 if (user) /* otherwise an invalid user name has probably been given */
868 tsearch(user, &ctl->usertree, cmp_uid);
869 ++n;
870 }
871 } else {
872 while ((user = get_next_user(ctl)))
873 tsearch(user, &ctl->usertree, cmp_uid);
874 }
875 return 0;
876 }
877
878 static struct libscols_table *setup_table(struct lslogins_control *ctl)
879 {
880 struct libscols_table *tb = scols_new_table();
881 int n = 0;
882
883 if (!tb)
884 errx(EXIT_FAILURE, _("failed to initialize output table"));
885 if (ctl->noheadings)
886 scols_table_enable_noheadings(tb, 1);
887
888 switch(outmode) {
889 case OUT_COLON:
890 scols_table_enable_raw(tb, 1);
891 scols_table_set_column_separator(tb, ":");
892 break;
893 case OUT_NEWLINE:
894 scols_table_set_column_separator(tb, "\n");
895 /* fallthrough */
896 case OUT_EXPORT:
897 scols_table_enable_export(tb, 1);
898 break;
899 case OUT_NUL:
900 scols_table_set_line_separator(tb, "\0");
901 /* fallthrough */
902 case OUT_RAW:
903 scols_table_enable_raw(tb, 1);
904 break;
905 case OUT_PRETTY:
906 scols_table_enable_noheadings(tb, 1);
907 default:
908 break;
909 }
910
911 while (n < ncolumns) {
912 int flags = coldescs[columns[n]].flag;
913
914 if (ctl->notrunc)
915 flags &= ~SCOLS_FL_TRUNC;
916
917 if (!scols_table_new_column(tb,
918 coldescs[columns[n]].name,
919 coldescs[columns[n]].whint,
920 flags))
921 goto fail;
922 ++n;
923 }
924
925 return tb;
926 fail:
927 scols_unref_table(tb);
928 return NULL;
929 }
930
931 static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
932 {
933 struct libscols_line *ln;
934 struct lslogins_user *user = *(struct lslogins_user **)u;
935 int n = 0;
936
937 if (which == preorder || which == endorder)
938 return;
939
940 ln = scols_table_new_line(tb, NULL);
941 while (n < ncolumns) {
942 int rc = 0;
943
944 switch (columns[n]) {
945 case COL_USER:
946 rc = scols_line_set_data(ln, n, user->login);
947 break;
948 case COL_UID:
949 rc = scols_line_refer_data(ln, n, uidtostr(user->uid));
950 break;
951 case COL_PWDEMPTY:
952 rc = scols_line_set_data(ln, n, get_status(user->pwd_empty));
953 break;
954 case COL_NOLOGIN:
955 rc = scols_line_set_data(ln, n, get_status(user->nologin));
956 break;
957 case COL_PWDLOCK:
958 rc = scols_line_set_data(ln, n, get_status(user->pwd_lock));
959 break;
960 case COL_PWDDENY:
961 rc = scols_line_set_data(ln, n, get_status(user->pwd_deny));
962 break;
963 case COL_GROUP:
964 rc = scols_line_set_data(ln, n, user->group);
965 break;
966 case COL_GID:
967 rc = scols_line_refer_data(ln, n, gidtostr(user->gid));
968 break;
969 case COL_SGROUPS:
970 rc = scols_line_refer_data(ln, n,
971 build_sgroups_string(user->sgroups,
972 user->nsgroups,
973 TRUE));
974 break;
975 case COL_SGIDS:
976 rc = scols_line_refer_data(ln, n,
977 build_sgroups_string(user->sgroups,
978 user->nsgroups,
979 FALSE));
980 break;
981 case COL_HOME:
982 rc = scols_line_set_data(ln, n, user->homedir);
983 break;
984 case COL_SHELL:
985 rc = scols_line_set_data(ln, n, user->shell);
986 break;
987 case COL_GECOS:
988 rc = scols_line_set_data(ln, n, user->gecos);
989 break;
990 case COL_LAST_LOGIN:
991 rc = scols_line_set_data(ln, n, user->last_login);
992 break;
993 case COL_LAST_TTY:
994 rc = scols_line_set_data(ln, n, user->last_tty);
995 break;
996 case COL_LAST_HOSTNAME:
997 rc = scols_line_set_data(ln, n, user->last_hostname);
998 break;
999 case COL_FAILED_LOGIN:
1000 rc = scols_line_set_data(ln, n, user->failed_login);
1001 break;
1002 case COL_FAILED_TTY:
1003 rc = scols_line_set_data(ln, n, user->failed_tty);
1004 break;
1005 case COL_HUSH_STATUS:
1006 rc = scols_line_set_data(ln, n, get_status(user->hushed));
1007 break;
1008 case COL_PWD_WARN:
1009 rc = scols_line_set_data(ln, n, user->pwd_warn);
1010 break;
1011 case COL_PWD_EXPIR:
1012 rc = scols_line_set_data(ln, n, user->pwd_expire);
1013 break;
1014 case COL_PWD_CTIME:
1015 rc = scols_line_set_data(ln, n, user->pwd_ctime);
1016 break;
1017 case COL_PWD_CTIME_MIN:
1018 rc = scols_line_set_data(ln, n, user->pwd_ctime_min);
1019 break;
1020 case COL_PWD_CTIME_MAX:
1021 rc = scols_line_set_data(ln, n, user->pwd_ctime_max);
1022 break;
1023 case COL_SELINUX:
1024 #ifdef HAVE_LIBSELINUX
1025 rc = scols_line_set_data(ln, n, user->context);
1026 #endif
1027 break;
1028 case COL_NPROCS:
1029 rc = scols_line_set_data(ln, n, user->nprocs);
1030 break;
1031 default:
1032 /* something went very wrong here */
1033 err(EXIT_FAILURE, _("internal error: unknown column"));
1034 }
1035
1036 if (rc != 0)
1037 err(EXIT_FAILURE, _("failed to set data"));
1038 ++n;
1039 }
1040 return;
1041 }
1042 #ifdef HAVE_LIBSYSTEMD
1043 static void print_journal_tail(const char *journal_path, uid_t uid, size_t len)
1044 {
1045 sd_journal *j;
1046 char *match, *buf;
1047 uint64_t x;
1048 time_t t;
1049 const char *identifier, *pid, *message;
1050 size_t identifier_len, pid_len, message_len;
1051
1052 if (journal_path)
1053 sd_journal_open_directory(&j, journal_path, 0);
1054 else
1055 sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
1056
1057 buf = xmalloc(sizeof(char) * 16);
1058 xasprintf(&match, "_UID=%d", uid);
1059
1060 sd_journal_add_match(j, match, 0);
1061 sd_journal_seek_tail(j);
1062 sd_journal_previous_skip(j, len);
1063
1064 do {
1065 if (0 > sd_journal_get_data(j, "SYSLOG_IDENTIFIER",
1066 (const void **) &identifier, &identifier_len))
1067 goto done;
1068 if (0 > sd_journal_get_data(j, "_PID",
1069 (const void **) &pid, &pid_len))
1070 goto done;
1071 if (0 > sd_journal_get_data(j, "MESSAGE",
1072 (const void **) &message, &message_len))
1073 goto done;
1074
1075 sd_journal_get_realtime_usec(j, &x);
1076 t = x / 1000000;
1077 strftime(buf, 16, "%b %d %H:%M:%S", localtime(&t));
1078
1079 fprintf(stdout, "%s", buf);
1080
1081 identifier = strchr(identifier, '=') + 1;
1082 pid = strchr(pid, '=') + 1 ;
1083 message = strchr(message, '=') + 1;
1084
1085 fprintf(stdout, " %s", identifier);
1086 fprintf(stdout, "[%s]:", pid);
1087 fprintf(stdout, "%s\n", message);
1088 } while (sd_journal_next(j));
1089
1090 done:
1091 free(buf);
1092 free(match);
1093 sd_journal_flush_matches(j);
1094 sd_journal_close(j);
1095 }
1096 #endif
1097
1098 static int print_pretty(struct libscols_table *tb)
1099 {
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;
1105 int n = 0;
1106
1107 ln = scols_table_get_line(tb, 0);
1108 while (!scols_table_next_column(tb, itr, &col)) {
1109
1110 data = scols_line_get_cell(ln, n);
1111
1112 hstr = _(coldescs[columns[n]].pretty_name);
1113 dstr = scols_cell_get_data(data);
1114
1115 if (dstr)
1116 printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
1117 ++n;
1118 }
1119
1120 scols_free_iter(itr);
1121 return 0;
1122
1123 }
1124
1125 static int print_user_table(struct lslogins_control *ctl)
1126 {
1127 tb = setup_table(ctl);
1128 if (!tb)
1129 return -1;
1130
1131 twalk(ctl->usertree, fill_table);
1132 if (outmode == OUT_PRETTY) {
1133 print_pretty(tb);
1134 #ifdef HAVE_LIBSYSTEMD
1135 fprintf(stdout, _("\nLast logs:\n"));
1136 print_journal_tail(ctl->journal_path, ctl->uid, 3);
1137 fputc('\n', stdout);
1138 #endif
1139 } else
1140 scols_print_table(tb);
1141 return 0;
1142 }
1143
1144 static void free_user(void *f)
1145 {
1146 struct lslogins_user *u = f;
1147 free(u->login);
1148 free(u->group);
1149 free(u->gecos);
1150 free(u->sgroups);
1151 free(u->pwd_ctime);
1152 free(u->pwd_warn);
1153 free(u->pwd_ctime_min);
1154 free(u->pwd_ctime_max);
1155 free(u->last_login);
1156 free(u->last_tty);
1157 free(u->last_hostname);
1158 free(u->failed_login);
1159 free(u->failed_tty);
1160 free(u->homedir);
1161 free(u->shell);
1162 free(u->pwd_status);
1163 #ifdef HAVE_LIBSELINUX
1164 freecon(u->context);
1165 #endif
1166 free(u);
1167 }
1168
1169 struct lslogins_timefmt {
1170 const char *name;
1171 int val;
1172 };
1173
1174 static struct lslogins_timefmt timefmts[] = {
1175 { "short", TIME_SHORT },
1176 { "full", TIME_FULL },
1177 { "iso", TIME_ISO },
1178 };
1179
1180 static void __attribute__((__noreturn__)) usage(FILE *out)
1181 {
1182 size_t i;
1183
1184 fputs(USAGE_HEADER, out);
1185 fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
1186
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);
1213
1214 fprintf(out, _("\nAvailable columns:\n"));
1215
1216 for (i = 0; i < ARRAY_SIZE(coldescs); i++)
1217 fprintf(out, " %14s %s\n", coldescs[i].name,
1218 _(coldescs[i].help));
1219
1220 fprintf(out, _("\nFor more details see lslogins(1).\n"));
1221
1222 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
1223 }
1224
1225 int main(int argc, char *argv[])
1226 {
1227 int c, opt_o = 0;
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));
1231 size_t i;
1232
1233 /* long only options. */
1234 enum {
1235 OPT_VER = CHAR_MAX + 1,
1236 OPT_WTMP,
1237 OPT_BTMP,
1238 OPT_NOTRUNC,
1239 OPT_NOHEAD,
1240 OPT_TIME_FMT,
1241 };
1242
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' },
1268 #endif
1269 { NULL, 0, 0, 0 }
1270 };
1271
1272 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1273 { 'G', 'o' },
1274 { 'L', 'o' },
1275 { 'Z', 'o' },
1276 { 'a', 'o' },
1277 { 'c','n','r','z' },
1278 { 'o', 'p' },
1279 { 0 }
1280 };
1281 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1282
1283 setlocale(LC_ALL, "");
1284 bindtextdomain(PACKAGE, LOCALEDIR);
1285 textdomain(PACKAGE);
1286 atexit(close_stdout);
1287
1288 ctl->time_mode = TIME_SHORT;
1289
1290 /* very basic default */
1291 add_column(columns, ncolumns++, COL_UID);
1292 add_column(columns, ncolumns++, COL_USER);
1293
1294 while ((c = getopt_long(argc, argv, "acfGg:hLl:no:prsuVxzZ",
1295 longopts, NULL)) != -1) {
1296
1297 err_exclusive_options(c, longopts, excl, excl_st);
1298
1299 switch (c) {
1300 case 'a':
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);
1306 break;
1307 case 'c':
1308 outmode = OUT_COLON;
1309 break;
1310 case 'e':
1311 outmode = OUT_EXPORT;
1312 break;
1313 case 'f':
1314 add_column(columns, ncolumns++, COL_FAILED_LOGIN);
1315 add_column(columns, ncolumns++, COL_FAILED_TTY);
1316 break;
1317 case 'G':
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);
1322 break;
1323 case 'g':
1324 groups = optarg;
1325 break;
1326 case 'h':
1327 usage(stdout);
1328 break;
1329 case 'L':
1330 add_column(columns, ncolumns++, COL_LAST_TTY);
1331 add_column(columns, ncolumns++, COL_LAST_HOSTNAME);
1332 add_column(columns, ncolumns++, COL_LAST_LOGIN);
1333 break;
1334 case 'l':
1335 logins = optarg;
1336 break;
1337 case 'n':
1338 outmode = OUT_NEWLINE;
1339 break;
1340 case 'o':
1341 if (optarg) {
1342 if (*optarg == '=')
1343 optarg++;
1344 ncolumns = string_to_idarray(optarg,
1345 columns, ARRAY_SIZE(columns),
1346 column_name_to_id);
1347 if (ncolumns < 0)
1348 return EXIT_FAILURE;
1349 }
1350 opt_o = 1;
1351 break;
1352 case 'r':
1353 outmode = OUT_RAW;
1354 break;
1355 case 's':
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;
1359 break;
1360 case 'u':
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;
1364 break;
1365 case 'p':
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);
1371 break;
1372 case 'z':
1373 outmode = OUT_NUL;
1374 break;
1375 case OPT_WTMP:
1376 path_wtmp = optarg;
1377 break;
1378 case OPT_BTMP:
1379 path_btmp = optarg;
1380 break;
1381 case OPT_NOTRUNC:
1382 ctl->notrunc = 1;
1383 break;
1384 case OPT_NOHEAD:
1385 ctl->noheadings = 1;
1386 break;
1387 case OPT_TIME_FMT:
1388 {
1389 size_t i;
1390
1391 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
1392 if (strcmp(timefmts[i].name, optarg) == 0) {
1393 ctl->time_mode = timefmts[i].val;
1394 break;
1395 }
1396 }
1397 if (ctl->time_mode == TIME_INVALID)
1398 usage(stderr);
1399 }
1400 break;
1401 case 'V':
1402 printf(UTIL_LINUX_VERSION);
1403 return EXIT_SUCCESS;
1404 case 'Z':
1405 {
1406 #ifdef HAVE_LIBSELINUX
1407 int sl = is_selinux_enabled();
1408 if (sl < 0)
1409 warn(_("failed to request selinux state"));
1410 else
1411 ctl->selinux_enabled = sl == 1;
1412 #endif
1413 add_column(columns, ncolumns++, COL_SELINUX);
1414 break;
1415 }
1416 default:
1417 usage(stderr);
1418 }
1419 }
1420
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)
1427 usage(stderr);
1428
1429 scols_init_debug(0);
1430
1431 /* lslogins -u -s == lslogins */
1432 if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
1433 lslogins_flag &= ~(F_USRAC | F_SYSAC);
1434
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;
1439
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);
1447 }
1448
1449 if (require_wtmp())
1450 parse_wtmp(ctl, path_wtmp);
1451 if (require_btmp())
1452 parse_btmp(ctl, path_btmp);
1453
1454 get_ulist(ctl, logins, groups);
1455
1456 if (create_usertree(ctl))
1457 return EXIT_FAILURE;
1458
1459 print_user_table(ctl);
1460
1461 scols_unref_table(tb);
1462 tdestroy(ctl->usertree, free_user);
1463 free_ctl(ctl);
1464
1465 return EXIT_SUCCESS;
1466 }