]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/lslogins.c
misc: Fix various typos
[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 #include <search.h>
39
40 #include <libsmartcols.h>
41 #ifdef HAVE_LIBSELINUX
42 # include <selinux/selinux.h>
43 #endif
44
45 #ifdef HAVE_LIBSYSTEMD
46 # include <systemd/sd-journal.h>
47 #endif
48
49 #include "c.h"
50 #include "nls.h"
51 #include "closestream.h"
52 #include "xalloc.h"
53 #include "list.h"
54 #include "strutils.h"
55 #include "optutils.h"
56 #include "pathnames.h"
57 #include "logindefs.h"
58 #include "procutils.h"
59 #include "timeutils.h"
60
61 /*
62 * column description
63 */
64 struct lslogins_coldesc {
65 const char *name;
66 const char *help;
67 const char *pretty_name;
68
69 double whint; /* width hint */
70 long flag;
71 };
72
73 static int lslogins_flag;
74
75 #define UL_UID_MIN 1000
76 #define UL_UID_MAX 60000
77 #define UL_SYS_UID_MIN 201
78 #define UL_SYS_UID_MAX 999
79
80 /* we use the value of outmode to determine
81 * appropriate flags for the libsmartcols table
82 * (e.g., a value of out_newline would imply a raw
83 * table with the column separator set to '\n').
84 */
85 static int outmode;
86 /*
87 * output modes
88 */
89 enum {
90 OUT_COLON = 1,
91 OUT_EXPORT,
92 OUT_NEWLINE,
93 OUT_RAW,
94 OUT_NUL,
95 OUT_PRETTY
96 };
97
98 struct lslogins_user {
99 char *login;
100 uid_t uid;
101 char *group;
102 gid_t gid;
103 char *gecos;
104
105 int pwd_empty;
106 int nologin;
107 int pwd_lock;
108 int pwd_deny;
109
110 gid_t *sgroups;
111 size_t nsgroups;
112
113 char *pwd_ctime;
114 char *pwd_warn;
115 char *pwd_expire;
116 char *pwd_ctime_min;
117 char *pwd_ctime_max;
118
119 char *last_login;
120 char *last_tty;
121 char *last_hostname;
122
123 char *failed_login;
124 char *failed_tty;
125
126 #ifdef HAVE_LIBSELINUX
127 security_context_t context;
128 #endif
129 char *homedir;
130 char *shell;
131 char *pwd_status;
132 int hushed;
133 char *nprocs;
134
135 };
136
137 /*
138 * time modes
139 * */
140 enum {
141 TIME_INVALID = 0,
142 TIME_SHORT,
143 TIME_FULL,
144 TIME_ISO,
145 TIME_ISO_SHORT,
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 ulist_on : 1,
270 noheadings : 1,
271 notrunc : 1;
272 };
273
274 /* these have to remain global since there's no other reasonable way to pass
275 * them for each call of fill_table() via twalk() */
276 static struct libscols_table *tb;
277
278 /* columns[] array specifies all currently wanted output column. The columns
279 * are defined by coldescs[] array and you can specify (on command line) each
280 * column twice. That's enough, dynamically allocated array of the columns is
281 * unnecessary overkill and over-engineering in this case */
282 static int columns[ARRAY_SIZE(coldescs) * 2];
283 static int ncolumns;
284
285 static inline size_t err_columns_index(size_t arysz, size_t idx)
286 {
287 if (idx >= arysz)
288 errx(EXIT_FAILURE, _("too many columns specified, "
289 "the limit is %zu columns"),
290 arysz - 1);
291 return idx;
292 }
293
294 #define add_column(ary, n, id) \
295 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
296
297 static int column_name_to_id(const char *name, size_t namesz)
298 {
299 size_t i;
300
301 for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
302 const char *cn = coldescs[i].name;
303
304 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
305 return i;
306 }
307 warnx(_("unknown column: %s"), name);
308 return -1;
309 }
310
311 static struct timeval now;
312
313 static char *make_time(int mode, time_t time)
314 {
315 int rc = 0;
316 char buf[64] = {0};
317
318 switch(mode) {
319 case TIME_FULL:
320 {
321 char *s;
322 struct tm tm;
323 localtime_r(&time, &tm);
324
325 asctime_r(&tm, buf);
326 if (*(s = buf + strlen(buf) - 1) == '\n')
327 *s = '\0';
328 rc = 0;
329 break;
330 }
331 case TIME_SHORT:
332 rc = strtime_short(&time, &now, UL_SHORTTIME_THISYEAR_HHMM,
333 buf, sizeof(buf));
334 break;
335 case TIME_ISO:
336 rc = strtime_iso(&time, ISO_8601_DATE|ISO_8601_TIME|ISO_8601_TIMEZONE,
337 buf, sizeof(buf));
338 break;
339 case TIME_ISO_SHORT:
340 rc = strtime_iso(&time, ISO_8601_DATE, buf, sizeof(buf));
341 break;
342 default:
343 errx(EXIT_FAILURE, _("unsupported time type"));
344 }
345
346 if (rc)
347 errx(EXIT_FAILURE, _("failed to compose time string"));
348
349 return xstrdup(buf);
350 }
351
352
353 static char *uidtostr(uid_t uid)
354 {
355 char *str_uid = NULL;
356 xasprintf(&str_uid, "%u", uid);
357 return str_uid;
358 }
359
360 static char *gidtostr(gid_t gid)
361 {
362 char *str_gid = NULL;
363 xasprintf(&str_gid, "%u", gid);
364 return str_gid;
365 }
366
367 static char *build_sgroups_string(gid_t *sgroups, size_t nsgroups, int want_names)
368 {
369 size_t n = 0, maxlen, len;
370 char *res, *p;
371
372 if (!nsgroups)
373 return NULL;
374
375 len = maxlen = nsgroups * 10;
376 res = p = xmalloc(maxlen);
377
378 while (n < nsgroups) {
379 int x;
380 again:
381 if (!want_names)
382 x = snprintf(p, len, "%u,", sgroups[n]);
383 else {
384 struct group *grp = getgrgid(sgroups[n]);
385 if (!grp) {
386 free(res);
387 return NULL;
388 }
389 x = snprintf(p, len, "%s,", grp->gr_name);
390 }
391
392 if (x < 0 || (size_t) x >= len) {
393 size_t cur = p - res;
394
395 maxlen *= 2;
396 res = xrealloc(res, maxlen);
397 p = res + cur;
398 len = maxlen - cur;
399 goto again;
400 }
401
402 len -= x;
403 p += x;
404 ++n;
405 }
406
407 if (p > res)
408 *(p - 1) = '\0';
409
410 return res;
411 }
412
413 static struct utmp *get_last_wtmp(struct lslogins_control *ctl, const char *username)
414 {
415 size_t n = 0;
416 size_t len;
417
418 if (!username)
419 return NULL;
420
421 len = strlen(username);
422 n = ctl->wtmp_size - 1;
423 do {
424 if (!strncmp(username, ctl->wtmp[n].ut_user,
425 len < UT_NAMESIZE ? len : UT_NAMESIZE))
426 return ctl->wtmp + n;
427 } while (n--);
428 return NULL;
429
430 }
431
432 static int require_wtmp(void)
433 {
434 size_t i;
435 for (i = 0; i < (size_t) ncolumns; i++)
436 if (is_wtmp_col(columns[i]))
437 return 1;
438 return 0;
439 }
440
441 static int require_btmp(void)
442 {
443 size_t i;
444 for (i = 0; i < (size_t) ncolumns; i++)
445 if (is_btmp_col(columns[i]))
446 return 1;
447 return 0;
448 }
449
450 static struct utmp *get_last_btmp(struct lslogins_control *ctl, const char *username)
451 {
452 size_t n = 0;
453 size_t len;
454
455 if (!username)
456 return NULL;
457
458 len = strlen(username);
459 n = ctl->btmp_size - 1;
460 do {
461 if (!strncmp(username, ctl->btmp[n].ut_user,
462 len < UT_NAMESIZE ? len : UT_NAMESIZE))
463 return ctl->btmp + n;
464 }while (n--);
465 return NULL;
466
467 }
468
469 static int read_utmp(char const *file, size_t *nents, struct utmp **res)
470 {
471 size_t n_read = 0, n_alloc = 0;
472 struct utmp *utmp = NULL, *u;
473
474 if (utmpname(file) < 0)
475 return -errno;
476
477 setutent();
478 errno = 0;
479
480 while ((u = getutent()) != NULL) {
481 if (n_read == n_alloc) {
482 n_alloc += 32;
483 utmp = xrealloc(utmp, n_alloc * sizeof (struct utmp));
484 }
485 utmp[n_read++] = *u;
486 }
487 if (!u && errno) {
488 free(utmp);
489 return -errno;
490 }
491
492 endutent();
493
494 *nents = n_read;
495 *res = utmp;
496
497 return 0;
498 }
499
500 static int parse_wtmp(struct lslogins_control *ctl, char *path)
501 {
502 int rc = 0;
503
504 rc = read_utmp(path, &ctl->wtmp_size, &ctl->wtmp);
505 if (rc < 0 && errno != EACCES)
506 err(EXIT_FAILURE, "%s", path);
507 return rc;
508 }
509
510 static int parse_btmp(struct lslogins_control *ctl, char *path)
511 {
512 int rc = 0;
513
514 rc = read_utmp(path, &ctl->btmp_size, &ctl->btmp);
515 if (rc < 0 && errno != EACCES)
516 err(EXIT_FAILURE, "%s", path);
517 return rc;
518 }
519
520 static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
521 {
522 size_t n = 0;
523 int ngroups = 0;
524
525 *len = 0;
526 *list = NULL;
527
528 /* first let's get a supp. group count */
529 getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups);
530 if (!ngroups)
531 return -1;
532
533 *list = xcalloc(1, ngroups * sizeof(gid_t));
534
535 /* now for the actual list of GIDs */
536 if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups))
537 return -1;
538
539 *len = (size_t) ngroups;
540
541 /* getgroups also returns the user's primary GID - dispose of it */
542 while (n < *len) {
543 if ((*list)[n] == pwd->pw_gid)
544 break;
545 ++n;
546 }
547
548 if (*len)
549 (*list)[n] = (*list)[--(*len)];
550
551 return 0;
552 }
553
554 static int get_nprocs(const uid_t uid)
555 {
556 int nprocs = 0;
557 pid_t pid;
558 struct proc_processes *proc = proc_open_processes();
559
560 proc_processes_filter_by_uid(proc, uid);
561
562 while (!proc_next_pid(proc, &pid))
563 ++nprocs;
564
565 proc_close_processes(proc);
566 return nprocs;
567 }
568
569 static int valid_pwd(const char *str)
570 {
571 const char *p;
572
573 for (p = str; p && *p; p++)
574 if (!isalnum((unsigned int) *p))
575 return 0;
576 return p > str ? 1 : 0;
577 }
578
579 static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username)
580 {
581 struct lslogins_user *user;
582 struct passwd *pwd;
583 struct group *grp;
584 struct spwd *shadow;
585 struct utmp *user_wtmp = NULL, *user_btmp = NULL;
586 int n = 0;
587 time_t time;
588 uid_t uid;
589 errno = 0;
590
591 pwd = username ? getpwnam(username) : getpwent();
592 if (!pwd)
593 return NULL;
594
595 ctl->uid = uid = pwd->pw_uid;
596
597 /* nfsnobody is an exception to the UID_MAX limit. This is "nobody" on
598 * some systems; the decisive point is the UID - 65534 */
599 if ((lslogins_flag & F_USRAC) &&
600 strcmp("nfsnobody", pwd->pw_name) != 0 &&
601 uid != 0) {
602 if (uid < ctl->UID_MIN || uid > ctl->UID_MAX) {
603 errno = EAGAIN;
604 return NULL;
605 }
606
607 } else if ((lslogins_flag & F_SYSAC) &&
608 (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX)) {
609 errno = EAGAIN;
610 return NULL;
611 }
612
613 user = xcalloc(1, sizeof(struct lslogins_user));
614
615 grp = getgrgid(pwd->pw_gid);
616 if (!grp)
617 return NULL;
618
619 if (ctl->wtmp)
620 user_wtmp = get_last_wtmp(ctl, pwd->pw_name);
621 if (ctl->btmp)
622 user_btmp = get_last_btmp(ctl, pwd->pw_name);
623
624 lckpwdf();
625 shadow = getspnam(pwd->pw_name);
626 ulckpwdf();
627
628 /* required by tseach() stuff */
629 user->uid = pwd->pw_uid;
630
631 while (n < ncolumns) {
632 switch (columns[n++]) {
633 case COL_USER:
634 user->login = xstrdup(pwd->pw_name);
635 break;
636 case COL_UID:
637 user->uid = pwd->pw_uid;
638 break;
639 case COL_GROUP:
640 user->group = xstrdup(grp->gr_name);
641 break;
642 case COL_GID:
643 user->gid = pwd->pw_gid;
644 break;
645 case COL_SGROUPS:
646 case COL_SGIDS:
647 if (get_sgroups(&user->sgroups, &user->nsgroups, pwd))
648 err(EXIT_FAILURE, _("failed to get supplementary groups"));
649 break;
650 case COL_HOME:
651 user->homedir = xstrdup(pwd->pw_dir);
652 break;
653 case COL_SHELL:
654 user->shell = xstrdup(pwd->pw_shell);
655 break;
656 case COL_GECOS:
657 user->gecos = xstrdup(pwd->pw_gecos);
658 break;
659 case COL_LAST_LOGIN:
660 if (user_wtmp) {
661 time = user_wtmp->ut_tv.tv_sec;
662 user->last_login = make_time(ctl->time_mode, time);
663 }
664 break;
665 case COL_LAST_TTY:
666 if (user_wtmp)
667 user->last_tty = xstrdup(user_wtmp->ut_line);
668 break;
669 case COL_LAST_HOSTNAME:
670 if (user_wtmp)
671 user->last_hostname = xstrdup(user_wtmp->ut_host);
672 break;
673 case COL_FAILED_LOGIN:
674 if (user_btmp) {
675 time = user_btmp->ut_tv.tv_sec;
676 user->failed_login = make_time(ctl->time_mode, time);
677 }
678 break;
679 case COL_FAILED_TTY:
680 if (user_btmp)
681 user->failed_tty = xstrdup(user_btmp->ut_line);
682 break;
683 case COL_HUSH_STATUS:
684 user->hushed = get_hushlogin_status(pwd, 0);
685 if (user->hushed == -1)
686 user->hushed = STATUS_UNKNOWN;
687 break;
688 case COL_PWDEMPTY:
689 if (shadow) {
690 if (!*shadow->sp_pwdp) /* '\0' */
691 user->pwd_empty = STATUS_TRUE;
692 } else
693 user->pwd_empty = STATUS_UNKNOWN;
694 break;
695 case COL_PWDDENY:
696 if (shadow) {
697 if ((*shadow->sp_pwdp == '!' ||
698 *shadow->sp_pwdp == '*') &&
699 !valid_pwd(shadow->sp_pwdp + 1))
700 user->pwd_deny = STATUS_TRUE;
701 } else
702 user->pwd_deny = STATUS_UNKNOWN;
703 break;
704
705 case COL_PWDLOCK:
706 if (shadow) {
707 if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1))
708 user->pwd_lock = STATUS_TRUE;
709 } else
710 user->pwd_lock = STATUS_UNKNOWN;
711 break;
712 case COL_NOLOGIN:
713 if (strstr(pwd->pw_shell, "nologin"))
714 user->nologin = 1;
715 else if (pwd->pw_uid)
716 user->nologin = access(_PATH_NOLOGIN, F_OK) == 0 ||
717 access(_PATH_VAR_NOLOGIN, F_OK) == 0;
718 break;
719 case COL_PWD_WARN:
720 if (shadow && shadow->sp_warn >= 0)
721 xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
722 break;
723 case COL_PWD_EXPIR:
724 if (shadow && shadow->sp_expire >= 0)
725 user->pwd_expire = make_time(ctl->time_mode == TIME_ISO ?
726 TIME_ISO_SHORT : ctl->time_mode,
727 shadow->sp_expire * 86400);
728 break;
729 case COL_PWD_CTIME:
730 /* sp_lstchg is specified in days, showing hours
731 * (especially in non-GMT timezones) would only serve
732 * to confuse */
733 if (shadow)
734 user->pwd_ctime = make_time(ctl->time_mode == TIME_ISO ?
735 TIME_ISO_SHORT : ctl->time_mode,
736 shadow->sp_lstchg * 86400);
737 break;
738 case COL_PWD_CTIME_MIN:
739 if (shadow && shadow->sp_min > 0)
740 xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
741 break;
742 case COL_PWD_CTIME_MAX:
743 if (shadow && shadow->sp_max > 0)
744 xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
745 break;
746 case COL_SELINUX:
747 #ifdef HAVE_LIBSELINUX
748 if (ctl->selinux_enabled) {
749 /* typedefs and pointers are pure evil */
750 security_context_t con = NULL;
751 if (getcon(&con) == 0)
752 user->context = con;
753 }
754 #endif
755 break;
756 case COL_NPROCS:
757 xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
758 break;
759 default:
760 /* something went very wrong here */
761 err(EXIT_FAILURE, "fatal: unknown error");
762 break;
763 }
764 }
765
766 return user;
767 }
768
769 /* some UNIX implementations set errno iff a passwd/grp/...
770 * entry was not found. The original UNIX logins(1) utility always
771 * ignores invalid login/group names, so we're going to as well.*/
772 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
773 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
774
775 /* get a definitive list of users we want info about... */
776
777 static int str_to_uint(char *s, unsigned int *ul)
778 {
779 char *end;
780 if (!s || !*s)
781 return -1;
782 *ul = strtoul(s, &end, 0);
783 if (!*end)
784 return 0;
785 return 1;
786 }
787
788 static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
789 {
790 char *u, *g;
791 size_t i = 0, n = 0, *arsiz;
792 struct group *grp;
793 struct passwd *pwd;
794 char ***ar;
795 uid_t uid;
796 gid_t gid;
797
798 ar = &ctl->ulist;
799 arsiz = &ctl->ulsiz;
800
801 /* an arbitrary starting value */
802 *arsiz = 32;
803 *ar = xcalloc(1, sizeof(char *) * (*arsiz));
804
805 if (logins) {
806 while ((u = strtok(logins, ","))) {
807 logins = NULL;
808
809 /* user specified by UID? */
810 if (!str_to_uint(u, &uid)) {
811 pwd = getpwuid(uid);
812 if (!pwd)
813 continue;
814 u = pwd->pw_name;
815 }
816 (*ar)[i++] = xstrdup(u);
817
818 if (i == *arsiz)
819 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
820 }
821 ctl->ulist_on = 1;
822 }
823
824 if (groups) {
825 /* FIXME: this might lead to duplicate entries, although not visible
826 * in output, crunching a user's info multiple times is very redundant */
827 while ((g = strtok(groups, ","))) {
828 n = 0;
829 groups = NULL;
830
831 /* user specified by GID? */
832 if (!str_to_uint(g, &gid))
833 grp = getgrgid(gid);
834 else
835 grp = getgrnam(g);
836
837 if (!grp)
838 continue;
839
840 while ((u = grp->gr_mem[n++])) {
841 (*ar)[i++] = xstrdup(u);
842
843 if (i == *arsiz)
844 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
845 }
846 }
847 ctl->ulist_on = 1;
848 }
849 *arsiz = i;
850 return 0;
851 }
852
853 static void free_ctl(struct lslogins_control *ctl)
854 {
855 size_t n = 0;
856
857 free(ctl->wtmp);
858 free(ctl->btmp);
859
860 while (n < ctl->ulsiz)
861 free(ctl->ulist[n++]);
862
863 free(ctl->ulist);
864 free(ctl);
865 }
866
867 static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
868 {
869 struct lslogins_user *u;
870 errno = 0;
871 while (!(u = get_user_info(ctl, NULL))) {
872 /* no "false" errno-s here, iff we're unable to
873 * get a valid user entry for any reason, quit */
874 if (errno == EAGAIN)
875 continue;
876 return NULL;
877 }
878 return u;
879 }
880
881 static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
882 const char *username)
883 {
884 *user = get_user_info(ctl, username);
885 if (!*user)
886 if (IS_REAL_ERRNO(errno))
887 return -1;
888 return 0;
889 }
890
891 static int cmp_uid(const void *a, const void *b)
892 {
893 uid_t x = ((struct lslogins_user *)a)->uid;
894 uid_t z = ((struct lslogins_user *)b)->uid;
895 return x > z ? 1 : (x < z ? -1 : 0);
896 }
897
898 static int create_usertree(struct lslogins_control *ctl)
899 {
900 struct lslogins_user *user = NULL;
901 size_t n = 0;
902
903 if (ctl->ulist_on) {
904 while (n < ctl->ulsiz) {
905 if (get_user(ctl, &user, ctl->ulist[n]))
906 return -1;
907 if (user) /* otherwise an invalid user name has probably been given */
908 tsearch(user, &ctl->usertree, cmp_uid);
909 ++n;
910 }
911 } else {
912 while ((user = get_next_user(ctl)))
913 tsearch(user, &ctl->usertree, cmp_uid);
914 }
915 return 0;
916 }
917
918 static struct libscols_table *setup_table(struct lslogins_control *ctl)
919 {
920 struct libscols_table *table = scols_new_table();
921 int n = 0;
922
923 if (!table)
924 errx(EXIT_FAILURE, _("failed to initialize output table"));
925 if (ctl->noheadings)
926 scols_table_enable_noheadings(table, 1);
927
928 switch(outmode) {
929 case OUT_COLON:
930 scols_table_enable_raw(table, 1);
931 scols_table_set_column_separator(table, ":");
932 break;
933 case OUT_NEWLINE:
934 scols_table_set_column_separator(table, "\n");
935 /* fallthrough */
936 case OUT_EXPORT:
937 scols_table_enable_export(table, 1);
938 break;
939 case OUT_NUL:
940 scols_table_set_line_separator(table, "\0");
941 /* fallthrough */
942 case OUT_RAW:
943 scols_table_enable_raw(table, 1);
944 break;
945 case OUT_PRETTY:
946 scols_table_enable_noheadings(table, 1);
947 default:
948 break;
949 }
950
951 while (n < ncolumns) {
952 int flags = coldescs[columns[n]].flag;
953
954 if (ctl->notrunc)
955 flags &= ~SCOLS_FL_TRUNC;
956
957 if (!scols_table_new_column(table,
958 coldescs[columns[n]].name,
959 coldescs[columns[n]].whint,
960 flags))
961 goto fail;
962 ++n;
963 }
964
965 return table;
966 fail:
967 scols_unref_table(table);
968 return NULL;
969 }
970
971 static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
972 {
973 struct libscols_line *ln;
974 struct lslogins_user *user = *(struct lslogins_user **)u;
975 int n = 0;
976
977 if (which == preorder || which == endorder)
978 return;
979
980 ln = scols_table_new_line(tb, NULL);
981 while (n < ncolumns) {
982 int rc = 0;
983
984 switch (columns[n]) {
985 case COL_USER:
986 rc = scols_line_set_data(ln, n, user->login);
987 break;
988 case COL_UID:
989 rc = scols_line_refer_data(ln, n, uidtostr(user->uid));
990 break;
991 case COL_PWDEMPTY:
992 rc = scols_line_set_data(ln, n, get_status(user->pwd_empty));
993 break;
994 case COL_NOLOGIN:
995 rc = scols_line_set_data(ln, n, get_status(user->nologin));
996 break;
997 case COL_PWDLOCK:
998 rc = scols_line_set_data(ln, n, get_status(user->pwd_lock));
999 break;
1000 case COL_PWDDENY:
1001 rc = scols_line_set_data(ln, n, get_status(user->pwd_deny));
1002 break;
1003 case COL_GROUP:
1004 rc = scols_line_set_data(ln, n, user->group);
1005 break;
1006 case COL_GID:
1007 rc = scols_line_refer_data(ln, n, gidtostr(user->gid));
1008 break;
1009 case COL_SGROUPS:
1010 rc = scols_line_refer_data(ln, n,
1011 build_sgroups_string(user->sgroups,
1012 user->nsgroups,
1013 TRUE));
1014 break;
1015 case COL_SGIDS:
1016 rc = scols_line_refer_data(ln, n,
1017 build_sgroups_string(user->sgroups,
1018 user->nsgroups,
1019 FALSE));
1020 break;
1021 case COL_HOME:
1022 rc = scols_line_set_data(ln, n, user->homedir);
1023 break;
1024 case COL_SHELL:
1025 rc = scols_line_set_data(ln, n, user->shell);
1026 break;
1027 case COL_GECOS:
1028 rc = scols_line_set_data(ln, n, user->gecos);
1029 break;
1030 case COL_LAST_LOGIN:
1031 rc = scols_line_set_data(ln, n, user->last_login);
1032 break;
1033 case COL_LAST_TTY:
1034 rc = scols_line_set_data(ln, n, user->last_tty);
1035 break;
1036 case COL_LAST_HOSTNAME:
1037 rc = scols_line_set_data(ln, n, user->last_hostname);
1038 break;
1039 case COL_FAILED_LOGIN:
1040 rc = scols_line_set_data(ln, n, user->failed_login);
1041 break;
1042 case COL_FAILED_TTY:
1043 rc = scols_line_set_data(ln, n, user->failed_tty);
1044 break;
1045 case COL_HUSH_STATUS:
1046 rc = scols_line_set_data(ln, n, get_status(user->hushed));
1047 break;
1048 case COL_PWD_WARN:
1049 rc = scols_line_set_data(ln, n, user->pwd_warn);
1050 break;
1051 case COL_PWD_EXPIR:
1052 rc = scols_line_set_data(ln, n, user->pwd_expire);
1053 break;
1054 case COL_PWD_CTIME:
1055 rc = scols_line_set_data(ln, n, user->pwd_ctime);
1056 break;
1057 case COL_PWD_CTIME_MIN:
1058 rc = scols_line_set_data(ln, n, user->pwd_ctime_min);
1059 break;
1060 case COL_PWD_CTIME_MAX:
1061 rc = scols_line_set_data(ln, n, user->pwd_ctime_max);
1062 break;
1063 case COL_SELINUX:
1064 #ifdef HAVE_LIBSELINUX
1065 rc = scols_line_set_data(ln, n, user->context);
1066 #endif
1067 break;
1068 case COL_NPROCS:
1069 rc = scols_line_set_data(ln, n, user->nprocs);
1070 break;
1071 default:
1072 /* something went very wrong here */
1073 err(EXIT_FAILURE, _("internal error: unknown column"));
1074 }
1075
1076 if (rc != 0)
1077 err(EXIT_FAILURE, _("failed to set data"));
1078 ++n;
1079 }
1080 return;
1081 }
1082 #ifdef HAVE_LIBSYSTEMD
1083 static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
1084 {
1085 sd_journal *j;
1086 char *match, *timestamp;
1087 uint64_t x;
1088 time_t t;
1089 const char *identifier, *pid, *message;
1090 size_t identifier_len, pid_len, message_len;
1091
1092 if (journal_path)
1093 sd_journal_open_directory(&j, journal_path, 0);
1094 else
1095 sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
1096
1097 xasprintf(&match, "_UID=%d", uid);
1098
1099 sd_journal_add_match(j, match, 0);
1100 sd_journal_seek_tail(j);
1101 sd_journal_previous_skip(j, len);
1102
1103 do {
1104 if (0 > sd_journal_get_data(j, "SYSLOG_IDENTIFIER",
1105 (const void **) &identifier, &identifier_len))
1106 goto done;
1107 if (0 > sd_journal_get_data(j, "_PID",
1108 (const void **) &pid, &pid_len))
1109 goto done;
1110 if (0 > sd_journal_get_data(j, "MESSAGE",
1111 (const void **) &message, &message_len))
1112 goto done;
1113
1114 sd_journal_get_realtime_usec(j, &x);
1115 t = x / 1000000;
1116 timestamp = make_time(time_mode, t);
1117 /* Get rid of journal entry field identifiers */
1118 identifier = strchr(identifier, '=') + 1;
1119 pid = strchr(pid, '=') + 1;
1120 message = strchr(message, '=') + 1;
1121
1122 fprintf(stdout, "%s %s[%s]: %s\n", timestamp, identifier, pid,
1123 message);
1124 free(timestamp);
1125 } while (sd_journal_next(j));
1126
1127 done:
1128 free(match);
1129 sd_journal_flush_matches(j);
1130 sd_journal_close(j);
1131 }
1132 #endif
1133
1134 static int print_pretty(struct libscols_table *table)
1135 {
1136 struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
1137 struct libscols_column *col;
1138 struct libscols_cell *data;
1139 struct libscols_line *ln;
1140 const char *hstr, *dstr;
1141 int n = 0;
1142
1143 ln = scols_table_get_line(table, 0);
1144 while (!scols_table_next_column(table, itr, &col)) {
1145
1146 data = scols_line_get_cell(ln, n);
1147
1148 hstr = _(coldescs[columns[n]].pretty_name);
1149 dstr = scols_cell_get_data(data);
1150
1151 if (dstr)
1152 printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
1153 ++n;
1154 }
1155
1156 scols_free_iter(itr);
1157 return 0;
1158
1159 }
1160
1161 static int print_user_table(struct lslogins_control *ctl)
1162 {
1163 tb = setup_table(ctl);
1164 if (!tb)
1165 return -1;
1166
1167 twalk(ctl->usertree, fill_table);
1168 if (outmode == OUT_PRETTY) {
1169 print_pretty(tb);
1170 #ifdef HAVE_LIBSYSTEMD
1171 fprintf(stdout, _("\nLast logs:\n"));
1172 print_journal_tail(ctl->journal_path, ctl->uid, 3, ctl->time_mode);
1173 fputc('\n', stdout);
1174 #endif
1175 } else
1176 scols_print_table(tb);
1177 return 0;
1178 }
1179
1180 static void free_user(void *f)
1181 {
1182 struct lslogins_user *u = f;
1183 free(u->login);
1184 free(u->group);
1185 free(u->gecos);
1186 free(u->sgroups);
1187 free(u->pwd_ctime);
1188 free(u->pwd_warn);
1189 free(u->pwd_ctime_min);
1190 free(u->pwd_ctime_max);
1191 free(u->last_login);
1192 free(u->last_tty);
1193 free(u->last_hostname);
1194 free(u->failed_login);
1195 free(u->failed_tty);
1196 free(u->homedir);
1197 free(u->shell);
1198 free(u->pwd_status);
1199 #ifdef HAVE_LIBSELINUX
1200 freecon(u->context);
1201 #endif
1202 free(u);
1203 }
1204
1205 static int parse_time_mode(const char *optarg)
1206 {
1207 struct lslogins_timefmt {
1208 const char *name;
1209 const int val;
1210 };
1211 static const struct lslogins_timefmt timefmts[] = {
1212 {"iso", TIME_ISO},
1213 {"full", TIME_FULL},
1214 {"short", TIME_SHORT},
1215 };
1216 size_t i;
1217
1218 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
1219 if (strcmp(timefmts[i].name, optarg) == 0)
1220 return timefmts[i].val;
1221 }
1222 errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
1223 }
1224
1225 static void __attribute__((__noreturn__)) usage(FILE *out)
1226 {
1227 size_t i;
1228
1229 fputs(USAGE_HEADER, out);
1230 fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
1231
1232 fputs(USAGE_SEPARATOR, out);
1233 fputs(_("Display information about known users in the system.\n"), out);
1234
1235 fputs(USAGE_OPTIONS, out);
1236 fputs(_(" -a, --acc-expiration display info about passwords expiration\n"), out);
1237 fputs(_(" -c, --colon-separate display data in a format similar to /etc/passwd\n"), out);
1238 fputs(_(" -e, --export display in an export-able output format\n"), out);
1239 fputs(_(" -f, --failed display data about the users' last failed logins\n"), out);
1240 fputs(_(" -G, --supp-groups display information about groups\n"), out);
1241 fputs(_(" -g, --groups=<groups> display users belonging to a group in <groups>\n"), out);
1242 fputs(_(" -L, --last show info about the users' last login sessions\n"), out);
1243 fputs(_(" -l, --logins=<logins> display only users from <logins>\n"), out);
1244 fputs(_(" -n, --newline display each piece of information on a new line\n"), out);
1245 fputs(_(" --noheadings don't print headings\n"), out);
1246 fputs(_(" --notruncate don't truncate output\n"), out);
1247 fputs(_(" -o, --output[=<list>] define the columns to output\n"), out);
1248 fputs(_(" -p, --pwd display information related to login by password.\n"), out);
1249 fputs(_(" -r, --raw display in raw mode\n"), out);
1250 fputs(_(" -s, --system-accs display system accounts\n"), out);
1251 fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out);
1252 fputs(_(" -u, --user-accs display user accounts\n"), out);
1253 fputs(_(" -Z, --context display SELinux contexts\n"), out);
1254 fputs(_(" -z, --print0 delimit user entries with a nul character\n"), out);
1255 fputs(_(" --wtmp-file <path> set an alternate path for wtmp\n"), out);
1256 fputs(_(" --btmp-file <path> set an alternate path for btmp\n"), out);
1257 fputs(USAGE_SEPARATOR, out);
1258 fputs(USAGE_HELP, out);
1259 fputs(USAGE_VERSION, out);
1260
1261 fprintf(out, _("\nAvailable columns:\n"));
1262
1263 for (i = 0; i < ARRAY_SIZE(coldescs); i++)
1264 fprintf(out, " %14s %s\n", coldescs[i].name,
1265 _(coldescs[i].help));
1266
1267 fprintf(out, USAGE_MAN_TAIL("lslogins(1)"));
1268
1269 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
1270 }
1271
1272 int main(int argc, char *argv[])
1273 {
1274 int c, opt_o = 0;
1275 char *logins = NULL, *groups = NULL;
1276 char *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP;
1277 struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control));
1278 size_t i;
1279
1280 /* long only options. */
1281 enum {
1282 OPT_WTMP = CHAR_MAX + 1,
1283 OPT_BTMP,
1284 OPT_NOTRUNC,
1285 OPT_NOHEAD,
1286 OPT_TIME_FMT,
1287 };
1288
1289 static const struct option longopts[] = {
1290 { "acc-expiration", no_argument, 0, 'a' },
1291 { "colon-separate", no_argument, 0, 'c' },
1292 { "export", no_argument, 0, 'e' },
1293 { "failed", no_argument, 0, 'f' },
1294 { "groups", required_argument, 0, 'g' },
1295 { "help", no_argument, 0, 'h' },
1296 { "logins", required_argument, 0, 'l' },
1297 { "supp-groups", no_argument, 0, 'G' },
1298 { "newline", no_argument, 0, 'n' },
1299 { "notruncate", no_argument, 0, OPT_NOTRUNC },
1300 { "noheadings", no_argument, 0, OPT_NOHEAD },
1301 { "output", required_argument, 0, 'o' },
1302 { "last", no_argument, 0, 'L', },
1303 { "raw", no_argument, 0, 'r' },
1304 { "system-accs", no_argument, 0, 's' },
1305 { "time-format", required_argument, 0, OPT_TIME_FMT },
1306 { "user-accs", no_argument, 0, 'u' },
1307 { "version", no_argument, 0, 'V' },
1308 { "pwd", no_argument, 0, 'p' },
1309 { "print0", no_argument, 0, 'z' },
1310 { "wtmp-file", required_argument, 0, OPT_WTMP },
1311 { "btmp-file", required_argument, 0, OPT_BTMP },
1312 #ifdef HAVE_LIBSELINUX
1313 { "context", no_argument, 0, 'Z' },
1314 #endif
1315 { NULL, 0, 0, 0 }
1316 };
1317
1318 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1319 { 'G', 'o' },
1320 { 'L', 'o' },
1321 { 'Z', 'o' },
1322 { 'a', 'o' },
1323 { 'c','n','r','z' },
1324 { 'o', 'p' },
1325 { 0 }
1326 };
1327 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1328
1329 setlocale(LC_ALL, "");
1330 bindtextdomain(PACKAGE, LOCALEDIR);
1331 textdomain(PACKAGE);
1332 atexit(close_stdout);
1333
1334 ctl->time_mode = TIME_SHORT;
1335
1336 /* very basic default */
1337 add_column(columns, ncolumns++, COL_UID);
1338 add_column(columns, ncolumns++, COL_USER);
1339
1340 while ((c = getopt_long(argc, argv, "acefGg:hLl:no:prsuVzZ",
1341 longopts, NULL)) != -1) {
1342
1343 err_exclusive_options(c, longopts, excl, excl_st);
1344
1345 switch (c) {
1346 case 'a':
1347 add_column(columns, ncolumns++, COL_PWD_WARN);
1348 add_column(columns, ncolumns++, COL_PWD_CTIME_MIN);
1349 add_column(columns, ncolumns++, COL_PWD_CTIME_MAX);
1350 add_column(columns, ncolumns++, COL_PWD_CTIME);
1351 add_column(columns, ncolumns++, COL_PWD_EXPIR);
1352 break;
1353 case 'c':
1354 outmode = OUT_COLON;
1355 break;
1356 case 'e':
1357 outmode = OUT_EXPORT;
1358 break;
1359 case 'f':
1360 add_column(columns, ncolumns++, COL_FAILED_LOGIN);
1361 add_column(columns, ncolumns++, COL_FAILED_TTY);
1362 break;
1363 case 'G':
1364 add_column(columns, ncolumns++, COL_GID);
1365 add_column(columns, ncolumns++, COL_GROUP);
1366 add_column(columns, ncolumns++, COL_SGIDS);
1367 add_column(columns, ncolumns++, COL_SGROUPS);
1368 break;
1369 case 'g':
1370 groups = optarg;
1371 break;
1372 case 'h':
1373 usage(stdout);
1374 break;
1375 case 'L':
1376 add_column(columns, ncolumns++, COL_LAST_TTY);
1377 add_column(columns, ncolumns++, COL_LAST_HOSTNAME);
1378 add_column(columns, ncolumns++, COL_LAST_LOGIN);
1379 break;
1380 case 'l':
1381 logins = optarg;
1382 break;
1383 case 'n':
1384 outmode = OUT_NEWLINE;
1385 break;
1386 case 'o':
1387 if (optarg) {
1388 if (*optarg == '=')
1389 optarg++;
1390 ncolumns = string_to_idarray(optarg,
1391 columns, ARRAY_SIZE(columns),
1392 column_name_to_id);
1393 if (ncolumns < 0)
1394 return EXIT_FAILURE;
1395 }
1396 opt_o = 1;
1397 break;
1398 case 'r':
1399 outmode = OUT_RAW;
1400 break;
1401 case 's':
1402 ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN);
1403 ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX);
1404 lslogins_flag |= F_SYSAC;
1405 break;
1406 case 'u':
1407 ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN);
1408 ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX);
1409 lslogins_flag |= F_USRAC;
1410 break;
1411 case 'p':
1412 add_column(columns, ncolumns++, COL_PWDEMPTY);
1413 add_column(columns, ncolumns++, COL_PWDLOCK);
1414 add_column(columns, ncolumns++, COL_PWDDENY);
1415 add_column(columns, ncolumns++, COL_NOLOGIN);
1416 add_column(columns, ncolumns++, COL_HUSH_STATUS);
1417 break;
1418 case 'z':
1419 outmode = OUT_NUL;
1420 break;
1421 case OPT_WTMP:
1422 path_wtmp = optarg;
1423 break;
1424 case OPT_BTMP:
1425 path_btmp = optarg;
1426 break;
1427 case OPT_NOTRUNC:
1428 ctl->notrunc = 1;
1429 break;
1430 case OPT_NOHEAD:
1431 ctl->noheadings = 1;
1432 break;
1433 case OPT_TIME_FMT:
1434 ctl->time_mode = parse_time_mode(optarg);
1435 break;
1436 case 'V':
1437 printf(UTIL_LINUX_VERSION);
1438 return EXIT_SUCCESS;
1439 case 'Z':
1440 {
1441 #ifdef HAVE_LIBSELINUX
1442 int sl = is_selinux_enabled();
1443 if (sl < 0)
1444 warn(_("failed to request selinux state"));
1445 else
1446 ctl->selinux_enabled = sl == 1;
1447 #endif
1448 add_column(columns, ncolumns++, COL_SELINUX);
1449 break;
1450 }
1451 default:
1452 usage(stderr);
1453 }
1454 }
1455
1456 if (argc - optind == 1) {
1457 if (strchr(argv[optind], ','))
1458 errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
1459 logins = argv[optind];
1460 outmode = OUT_PRETTY;
1461 } else if (argc != optind)
1462 errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
1463
1464 scols_init_debug(0);
1465
1466 /* lslogins -u -s == lslogins */
1467 if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
1468 lslogins_flag &= ~(F_USRAC | F_SYSAC);
1469
1470 if (outmode == OUT_PRETTY && !opt_o) {
1471 /* all columns for lslogins <username> */
1472 for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
1473 columns[ncolumns++] = i;
1474
1475 } else if (ncolumns == 2 && !opt_o) {
1476 /* default colummns */
1477 add_column(columns, ncolumns++, COL_NPROCS);
1478 add_column(columns, ncolumns++, COL_PWDLOCK);
1479 add_column(columns, ncolumns++, COL_PWDDENY);
1480 add_column(columns, ncolumns++, COL_LAST_LOGIN);
1481 add_column(columns, ncolumns++, COL_GECOS);
1482 }
1483
1484 if (require_wtmp())
1485 parse_wtmp(ctl, path_wtmp);
1486 if (require_btmp())
1487 parse_btmp(ctl, path_btmp);
1488
1489 if (logins || groups)
1490 get_ulist(ctl, logins, groups);
1491
1492 if (create_usertree(ctl))
1493 return EXIT_FAILURE;
1494
1495 print_user_table(ctl);
1496
1497 scols_unref_table(tb);
1498 tdestroy(ctl->usertree, free_user);
1499 free_ctl(ctl);
1500
1501 return EXIT_SUCCESS;
1502 }