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