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