]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/lslogins.c
Merge branch 'rename-all' of https://github.com/utoddl/util-linux
[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 <utmpx.h>
35 #include <signal.h>
36 #include <err.h>
37 #include <limits.h>
38 #include <search.h>
39 #include <lastlog.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 "procutils.h"
60 #include "timeutils.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 101
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 enum {
100 LASTLOG_TIME,
101 LASTLOG_LINE,
102 LASTLOG_HOST
103 };
104
105 struct lslogins_user {
106 char *login;
107 uid_t uid;
108 char *group;
109 gid_t gid;
110 char *gecos;
111
112 int pwd_empty;
113 int nologin;
114 int pwd_lock;
115 int pwd_deny;
116
117 gid_t *sgroups;
118 size_t nsgroups;
119
120 char *pwd_ctime;
121 char *pwd_warn;
122 char *pwd_expire;
123 char *pwd_ctime_min;
124 char *pwd_ctime_max;
125 const char *pwd_method;
126
127 char *last_login;
128 char *last_tty;
129 char *last_hostname;
130
131 char *failed_login;
132 char *failed_tty;
133
134 #ifdef HAVE_LIBSELINUX
135 char *context;
136 #endif
137 char *homedir;
138 char *shell;
139 char *pwd_status;
140 int hushed;
141 char *nprocs;
142
143 };
144
145 /*
146 * time modes
147 * */
148 enum {
149 TIME_INVALID = 0,
150 TIME_SHORT,
151 TIME_FULL,
152 TIME_ISO,
153 TIME_ISO_SHORT,
154 };
155
156 /*
157 * flags
158 */
159 enum {
160 F_SYSAC = (1 << 3),
161 F_USRAC = (1 << 4),
162 };
163
164 /*
165 * IDs
166 */
167 enum {
168 COL_USER = 0,
169 COL_UID,
170 COL_GECOS,
171 COL_HOME,
172 COL_SHELL,
173 COL_NOLOGIN,
174 COL_PWDLOCK,
175 COL_PWDEMPTY,
176 COL_PWDDENY,
177 COL_PWDMETHOD,
178 COL_GROUP,
179 COL_GID,
180 COL_SGROUPS,
181 COL_SGIDS,
182 COL_LAST_LOGIN,
183 COL_LAST_TTY,
184 COL_LAST_HOSTNAME,
185 COL_FAILED_LOGIN,
186 COL_FAILED_TTY,
187 COL_HUSH_STATUS,
188 COL_PWD_WARN,
189 COL_PWD_CTIME,
190 COL_PWD_CTIME_MIN,
191 COL_PWD_CTIME_MAX,
192 COL_PWD_EXPIR,
193 COL_SELINUX,
194 COL_NPROCS,
195 };
196
197 #define is_wtmp_col(x) ((x) == COL_LAST_LOGIN || \
198 (x) == COL_LAST_TTY || \
199 (x) == COL_LAST_HOSTNAME)
200
201 #define is_btmp_col(x) ((x) == COL_FAILED_LOGIN || \
202 (x) == COL_FAILED_TTY)
203
204 enum {
205 STATUS_FALSE = 0,
206 STATUS_TRUE,
207 STATUS_UNKNOWN
208 };
209
210 static const char *const status[] = {
211 [STATUS_FALSE] = "0",
212 [STATUS_TRUE] = "1",
213 [STATUS_UNKNOWN]= NULL
214 };
215
216 static const char *const pretty_status[] = {
217 [STATUS_FALSE] = N_("no"),
218 [STATUS_TRUE] = N_("yes"),
219 [STATUS_UNKNOWN]= NULL
220 };
221
222 #define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
223
224 static const struct lslogins_coldesc coldescs[] =
225 {
226 [COL_USER] = { "USER", N_("user name"), N_("Username"), 0.1, SCOLS_FL_NOEXTREMES },
227 [COL_UID] = { "UID", N_("user ID"), "UID", 1, SCOLS_FL_RIGHT},
228 [COL_PWDEMPTY] = { "PWD-EMPTY", N_("password not required"), N_("Password not required"), 1, SCOLS_FL_RIGHT },
229 [COL_PWDDENY] = { "PWD-DENY", N_("login by password disabled"), N_("Login by password disabled"), 1, SCOLS_FL_RIGHT },
230 [COL_PWDLOCK] = { "PWD-LOCK", N_("password defined, but locked"), N_("Password is locked"), 1, SCOLS_FL_RIGHT },
231 [COL_PWDMETHOD] = { "PWD-METHOD", N_("password encryption method"), N_("Password encryption method"), 0.1 },
232 [COL_NOLOGIN] = { "NOLOGIN", N_("log in disabled by nologin(8) or pam_nologin(8)"), N_("No login"), 1, SCOLS_FL_RIGHT },
233 [COL_GROUP] = { "GROUP", N_("primary group name"), N_("Primary group"), 0.1 },
234 [COL_GID] = { "GID", N_("primary group ID"), "GID", 1, SCOLS_FL_RIGHT },
235 [COL_SGROUPS] = { "SUPP-GROUPS", N_("supplementary group names"), N_("Supplementary groups"), 0.1 },
236 [COL_SGIDS] = { "SUPP-GIDS", N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 },
237 [COL_HOME] = { "HOMEDIR", N_("home directory"), N_("Home directory"), 0.1 },
238 [COL_SHELL] = { "SHELL", N_("login shell"), N_("Shell"), 0.1 },
239 [COL_GECOS] = { "GECOS", N_("full user name"), N_("Gecos field"), 0.1, SCOLS_FL_TRUNC },
240 [COL_LAST_LOGIN] = { "LAST-LOGIN", N_("date of last login"), N_("Last login"), 0.1, SCOLS_FL_RIGHT },
241 [COL_LAST_TTY] = { "LAST-TTY", N_("last tty used"), N_("Last terminal"), 0.05 },
242 [COL_LAST_HOSTNAME] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"), 0.1},
243 [COL_FAILED_LOGIN] = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 0.1 },
244 [COL_FAILED_TTY] = { "FAILED-TTY", N_("where did the login fail?"), N_("Failed login terminal"), 0.05 },
245 [COL_HUSH_STATUS] = { "HUSHED", N_("user's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT },
246 [COL_PWD_WARN] = { "PWD-WARN", N_("days user is warned of password expiration"), N_("Password expiration warn interval"), 0.1, SCOLS_FL_RIGHT },
247 [COL_PWD_EXPIR] = { "PWD-EXPIR", N_("password expiration date"), N_("Password expiration"), 0.1, SCOLS_FL_RIGHT },
248 [COL_PWD_CTIME] = { "PWD-CHANGE", N_("date of last password change"), N_("Password changed"), 0.1, SCOLS_FL_RIGHT},
249 [COL_PWD_CTIME_MIN] = { "PWD-MIN", N_("number of days required between changes"), N_("Minimum change time"), 0.1, SCOLS_FL_RIGHT },
250 [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 },
251 [COL_SELINUX] = { "CONTEXT", N_("the user's security context"), N_("Selinux context"), 0.1 },
252 [COL_NPROCS] = { "PROC", N_("number of processes run by the user"), N_("Running processes"), 1, SCOLS_FL_RIGHT },
253 };
254
255 struct lslogins_control {
256 struct utmpx *wtmp;
257 size_t wtmp_size;
258
259 struct utmpx *btmp;
260 size_t btmp_size;
261
262 int lastlogin_fd;
263
264 void *usertree;
265
266 uid_t uid;
267 uid_t UID_MIN;
268 uid_t UID_MAX;
269
270 uid_t SYS_UID_MIN;
271 uid_t SYS_UID_MAX;
272
273 char **ulist;
274 size_t ulsiz;
275
276 unsigned int time_mode;
277
278 const char *journal_path;
279
280 unsigned int selinux_enabled : 1,
281 fail_on_unknown : 1, /* fail if user does not exist */
282 ulist_on : 1,
283 noheadings : 1,
284 notrunc : 1;
285 };
286
287 /* these have to remain global since there's no other reasonable way to pass
288 * them for each call of fill_table() via twalk() */
289 static struct libscols_table *tb;
290
291 /* columns[] array specifies all currently wanted output column. The columns
292 * are defined by coldescs[] array and you can specify (on command line) each
293 * column twice. That's enough, dynamically allocated array of the columns is
294 * unnecessary overkill and over-engineering in this case */
295 static int columns[ARRAY_SIZE(coldescs) * 2];
296 static size_t ncolumns;
297
298 static inline size_t err_columns_index(size_t arysz, size_t idx)
299 {
300 if (idx >= arysz)
301 errx(EXIT_FAILURE, _("too many columns specified, "
302 "the limit is %zu columns"),
303 arysz - 1);
304 return idx;
305 }
306
307 #define add_column(ary, n, id) \
308 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
309
310 static int column_name_to_id(const char *name, size_t namesz)
311 {
312 size_t i;
313
314 for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
315 const char *cn = coldescs[i].name;
316
317 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
318 return i;
319 }
320 warnx(_("unknown column: %s"), name);
321 return -1;
322 }
323
324 static struct timeval now;
325
326 static char *make_time(int mode, time_t time)
327 {
328 int rc = 0;
329 char buf[64] = {0};
330
331 switch(mode) {
332 case TIME_FULL:
333 {
334 char *s;
335 struct tm tm;
336 localtime_r(&time, &tm);
337
338 asctime_r(&tm, buf);
339 if (*(s = buf + strlen(buf) - 1) == '\n')
340 *s = '\0';
341 rc = 0;
342 break;
343 }
344 case TIME_SHORT:
345 rc = strtime_short(&time, &now, UL_SHORTTIME_THISYEAR_HHMM,
346 buf, sizeof(buf));
347 break;
348 case TIME_ISO:
349 rc = strtime_iso(&time, ISO_TIMESTAMP_T, buf, sizeof(buf));
350 break;
351 case TIME_ISO_SHORT:
352 rc = strtime_iso(&time, ISO_DATE, buf, sizeof(buf));
353 break;
354 default:
355 errx(EXIT_FAILURE, _("unsupported time type"));
356 }
357
358 if (rc)
359 errx(EXIT_FAILURE, _("failed to compose time string"));
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 utmpx *get_last_wtmp(struct lslogins_control *ctl, const char *username)
426 {
427 size_t n = 0;
428
429 if (!username)
430 return NULL;
431
432 n = ctl->wtmp_size - 1;
433 do {
434 if (!strncmp(username, ctl->wtmp[n].ut_user,
435 sizeof(ctl->wtmp[0].ut_user)))
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 < 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 < ncolumns; i++)
455 if (is_btmp_col(columns[i]))
456 return 1;
457 return 0;
458 }
459
460 static struct utmpx *get_last_btmp(struct lslogins_control *ctl, const char *username)
461 {
462 size_t n = 0;
463
464 if (!username)
465 return NULL;
466
467 n = ctl->btmp_size - 1;
468 do {
469 if (!strncmp(username, ctl->btmp[n].ut_user,
470 sizeof(ctl->wtmp[0].ut_user)))
471 return ctl->btmp + n;
472 }while (n--);
473 return NULL;
474
475 }
476
477 static int parse_utmpx(const char *path, size_t *nrecords, struct utmpx **records)
478 {
479 size_t i, imax = 0;
480 struct utmpx *ary = NULL;
481 struct stat st;
482
483 *nrecords = 0;
484 *records = NULL;
485
486 if (utmpxname(path) < 0)
487 return -errno;
488
489 /* optimize allocation according to file size, the realloc() below is
490 * just fallback only */
491 if (stat(path, &st) == 0 && (size_t) st.st_size > sizeof(struct utmpx)) {
492 imax = st.st_size / sizeof(struct utmpx);
493 ary = xmalloc(imax * sizeof(struct utmpx));
494 }
495
496 for (i = 0; ; i++) {
497 struct utmpx *u;
498 errno = 0;
499 u = getutxent();
500 if (!u) {
501 if (errno)
502 goto fail;
503 break;
504 }
505 if (i == imax)
506 ary = xrealloc(ary, (imax *= 2) * sizeof(struct utmpx));
507 ary[i] = *u;
508 }
509
510 *nrecords = i;
511 *records = ary;
512 endutxent();
513 return 0;
514 fail:
515 endutxent();
516 free(ary);
517 if (errno) {
518 if (errno != EACCES)
519 err(EXIT_FAILURE, "%s", path);
520 return -errno;
521 }
522 return -EINVAL;
523 }
524
525 static void get_lastlog(struct lslogins_control *ctl, uid_t uid, void *dst, int what)
526 {
527 struct lastlog ll;
528
529 if (ctl->lastlogin_fd < 0 ||
530 pread(ctl->lastlogin_fd, (void *)&ll, sizeof(ll), uid * sizeof(ll)) != sizeof(ll))
531 return;
532
533 switch (what) {
534 case LASTLOG_TIME: {
535 time_t *t = (time_t *)dst;
536 *t = ll.ll_time;
537 break;
538 }
539 case LASTLOG_LINE:
540 mem2strcpy(dst, ll.ll_line, sizeof(ll.ll_line), sizeof(ll.ll_line) + 1);
541 break;
542 case LASTLOG_HOST:
543 mem2strcpy(dst, ll.ll_host, sizeof(ll.ll_host), sizeof(ll.ll_host) + 1);
544 break;
545 default:
546 abort();
547 }
548 }
549
550 static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
551 {
552 size_t n = 0;
553 int ngroups = 0;
554
555 *len = 0;
556 *list = NULL;
557
558 /* first let's get a supp. group count */
559 getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups);
560 if (!ngroups)
561 return -1;
562
563 *list = xcalloc(1, ngroups * sizeof(gid_t));
564
565 fprintf(stderr, "KZAK>>> alloc '%p' for %s\n", *list, pwd->pw_name);
566
567
568 /* now for the actual list of GIDs */
569 if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups))
570 return -1;
571
572 *len = (size_t) ngroups;
573
574 /* getgroups also returns the user's primary GID - dispose of it */
575 while (n < *len) {
576 if ((*list)[n] == pwd->pw_gid)
577 break;
578 ++n;
579 }
580
581 if (*len)
582 (*list)[n] = (*list)[--(*len)];
583
584 return 0;
585 }
586
587 #ifdef __linux__
588 static int get_nprocs(const uid_t uid)
589 {
590 int nprocs = 0;
591 pid_t pid;
592 struct proc_processes *proc = proc_open_processes();
593
594 proc_processes_filter_by_uid(proc, uid);
595
596 while (!proc_next_pid(proc, &pid))
597 ++nprocs;
598
599 proc_close_processes(proc);
600 return nprocs;
601 }
602 #endif
603
604 static const char *get_pwd_method(const char *str, const char **next, unsigned int *sz)
605 {
606 const char *p = str;
607 const char *res = NULL;
608
609 if (!p || *p++ != '$')
610 return NULL;
611
612 if (sz)
613 *sz = 0;
614
615 switch (*p) {
616 case '1':
617 res = "MD5";
618 if (sz)
619 *sz = 22;
620 break;
621 case '2':
622 p++;
623 if (*p == 'a' || *p == 'y')
624 res = "Blowfish";
625 break;
626 case '5':
627 res = "SHA-256";
628 if (sz)
629 *sz = 43;
630 break;
631 case '6':
632 res = "SHA-512";
633 if (sz)
634 *sz = 86;
635 break;
636 default:
637 return NULL;
638 }
639 p++;
640
641 if (*p != '$')
642 return NULL;
643 if (next)
644 *next = ++p;
645 return res;
646 }
647
648 #define is_valid_pwd_char(x) (isalnum((unsigned char) (x)) || (x) == '.' || (x) == '/')
649
650 /*
651 * This function do not accept empty passwords or locked accouns.
652 */
653 static int valid_pwd(const char *str)
654 {
655 const char *p = str;
656 unsigned int sz = 0, n;
657
658 if (!str || !*str)
659 return 0;
660
661 /* $id$ */
662 if (get_pwd_method(str, &p, &sz) == NULL)
663 return 0;
664 if (!p || !*p)
665 return 0;
666
667 /* salt$ */
668 for (; *p; p++) {
669 if (*p == '$') {
670 p++;
671 break;
672 }
673 if (!is_valid_pwd_char(*p))
674 return 0;
675 }
676 if (!*p)
677 return 0;
678
679 /* encrypted */
680 for (n = 0; *p; p++, n++) {
681 if (!is_valid_pwd_char(*p))
682 return 0;
683 }
684
685 if (sz && n != sz)
686 return 0;
687 return 1;
688 }
689
690 static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username)
691 {
692 struct lslogins_user *user;
693 struct passwd *pwd;
694 struct group *grp;
695 struct spwd *shadow;
696 struct utmpx *user_wtmp = NULL, *user_btmp = NULL;
697 size_t n = 0;
698 time_t time;
699 uid_t uid;
700 errno = 0;
701
702 errno = 0;
703 pwd = username ? getpwnam(username) : getpwent();
704 if (!pwd)
705 return NULL;
706
707 ctl->uid = uid = pwd->pw_uid;
708
709 /* nfsnobody is an exception to the UID_MAX limit. This is "nobody" on
710 * some systems; the decisive point is the UID - 65534 */
711 if ((lslogins_flag & F_USRAC) &&
712 strcmp("nfsnobody", pwd->pw_name) != 0 &&
713 uid != 0) {
714 if (uid < ctl->UID_MIN || uid > ctl->UID_MAX) {
715 errno = EAGAIN;
716 return NULL;
717 }
718
719 } else if ((lslogins_flag & F_SYSAC) &&
720 (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX)) {
721 errno = EAGAIN;
722 return NULL;
723 }
724
725 errno = 0;
726 grp = getgrgid(pwd->pw_gid);
727 if (!grp)
728 return NULL;
729
730 user = xcalloc(1, sizeof(struct lslogins_user));
731
732 if (ctl->wtmp)
733 user_wtmp = get_last_wtmp(ctl, pwd->pw_name);
734 if (ctl->btmp)
735 user_btmp = get_last_btmp(ctl, pwd->pw_name);
736
737 lckpwdf();
738 shadow = getspnam(pwd->pw_name);
739 ulckpwdf();
740
741 /* required by tseach() stuff */
742 user->uid = pwd->pw_uid;
743
744 while (n < ncolumns) {
745 switch (columns[n++]) {
746 case COL_USER:
747 user->login = xstrdup(pwd->pw_name);
748 break;
749 case COL_UID:
750 user->uid = pwd->pw_uid;
751 break;
752 case COL_GROUP:
753 user->group = xstrdup(grp->gr_name);
754 break;
755 case COL_GID:
756 user->gid = pwd->pw_gid;
757 break;
758 case COL_SGROUPS:
759 case COL_SGIDS:
760 if (!user->nsgroups &&
761 get_sgroups(&user->sgroups, &user->nsgroups, pwd) < 0)
762 err(EXIT_FAILURE, _("failed to get supplementary groups"));
763 break;
764 case COL_HOME:
765 user->homedir = xstrdup(pwd->pw_dir);
766 break;
767 case COL_SHELL:
768 user->shell = xstrdup(pwd->pw_shell);
769 break;
770 case COL_GECOS:
771 user->gecos = xstrdup(pwd->pw_gecos);
772 break;
773 case COL_LAST_LOGIN:
774 if (user_wtmp) {
775 time = user_wtmp->ut_tv.tv_sec;
776 user->last_login = make_time(ctl->time_mode, time);
777 } else {
778 time = 0;
779 get_lastlog(ctl, pwd->pw_uid, &time, LASTLOG_TIME);
780 if (time)
781 user->last_login = make_time(ctl->time_mode, time);
782 }
783 break;
784 case COL_LAST_TTY:
785 user->last_tty = xcalloc(1, sizeof(user_wtmp->ut_line) + 1);
786 if (user_wtmp) {
787 mem2strcpy(user->last_tty, user_wtmp->ut_line,
788 sizeof(user_wtmp->ut_line),
789 sizeof(user_wtmp->ut_line) + 1);;
790 } else
791 get_lastlog(ctl, user->uid, user->last_tty, LASTLOG_LINE);
792 break;
793 case COL_LAST_HOSTNAME:
794 user->last_hostname = xcalloc(1, sizeof(user_wtmp->ut_host) + 1);
795 if (user_wtmp) {
796 mem2strcpy(user->last_hostname, user_wtmp->ut_host,
797 sizeof(user_wtmp->ut_host),
798 sizeof(user_wtmp->ut_host) + 1);;
799 } else
800 get_lastlog(ctl, user->uid, user->last_hostname, LASTLOG_HOST);
801 break;
802 case COL_FAILED_LOGIN:
803 if (user_btmp) {
804 time = user_btmp->ut_tv.tv_sec;
805 user->failed_login = make_time(ctl->time_mode, time);
806 }
807 break;
808 case COL_FAILED_TTY:
809 if (user_btmp) {
810 user->failed_tty = xmalloc(sizeof(user_btmp->ut_line) + 1);
811 mem2strcpy(user->failed_tty, user_btmp->ut_line,
812 sizeof(user_btmp->ut_line),
813 sizeof(user_btmp->ut_line) + 1);;
814 }
815 break;
816 case COL_HUSH_STATUS:
817 user->hushed = get_hushlogin_status(pwd, 0);
818 if (user->hushed == -1)
819 user->hushed = STATUS_UNKNOWN;
820 break;
821 case COL_PWDEMPTY:
822 if (shadow) {
823 if (!*shadow->sp_pwdp) /* '\0' */
824 user->pwd_empty = STATUS_TRUE;
825 } else
826 user->pwd_empty = STATUS_UNKNOWN;
827 break;
828 case COL_PWDDENY:
829 if (shadow) {
830 if ((*shadow->sp_pwdp == '!' ||
831 *shadow->sp_pwdp == '*') &&
832 !valid_pwd(shadow->sp_pwdp + 1))
833 user->pwd_deny = STATUS_TRUE;
834 } else
835 user->pwd_deny = STATUS_UNKNOWN;
836 break;
837 case COL_PWDLOCK:
838 if (shadow) {
839 if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1))
840 user->pwd_lock = STATUS_TRUE;
841 } else
842 user->pwd_lock = STATUS_UNKNOWN;
843 break;
844 case COL_PWDMETHOD:
845 if (shadow) {
846 const char *p = shadow->sp_pwdp;
847
848 if (*p == '!' || *p == '*')
849 p++;
850 user->pwd_method = get_pwd_method(p, NULL, NULL);
851 } else
852 user->pwd_method = NULL;
853 break;
854 case COL_NOLOGIN:
855 if (strstr(pwd->pw_shell, "nologin"))
856 user->nologin = 1;
857 else if (pwd->pw_uid)
858 user->nologin = access(_PATH_NOLOGIN, F_OK) == 0 ||
859 access(_PATH_VAR_NOLOGIN, F_OK) == 0;
860 break;
861 case COL_PWD_WARN:
862 if (shadow && shadow->sp_warn >= 0)
863 xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
864 break;
865 case COL_PWD_EXPIR:
866 if (shadow && shadow->sp_expire >= 0)
867 user->pwd_expire = make_time(ctl->time_mode == TIME_ISO ?
868 TIME_ISO_SHORT : ctl->time_mode,
869 shadow->sp_expire * 86400);
870 break;
871 case COL_PWD_CTIME:
872 /* sp_lstchg is specified in days, showing hours
873 * (especially in non-GMT timezones) would only serve
874 * to confuse */
875 if (shadow)
876 user->pwd_ctime = make_time(ctl->time_mode == TIME_ISO ?
877 TIME_ISO_SHORT : ctl->time_mode,
878 shadow->sp_lstchg * 86400);
879 break;
880 case COL_PWD_CTIME_MIN:
881 if (shadow && shadow->sp_min > 0)
882 xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
883 break;
884 case COL_PWD_CTIME_MAX:
885 if (shadow && shadow->sp_max > 0)
886 xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
887 break;
888 case COL_SELINUX:
889 #ifdef HAVE_LIBSELINUX
890 if (!ctl->selinux_enabled || getcon(&user->context) != 0)
891 user->context = NULL;
892 #endif
893 break;
894 case COL_NPROCS:
895 #ifdef __linux__
896
897 xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
898 #endif
899 break;
900 default:
901 /* something went very wrong here */
902 err(EXIT_FAILURE, "fatal: unknown error");
903 break;
904 }
905 }
906
907 return user;
908 }
909
910 static int str_to_uint(char *s, unsigned int *ul)
911 {
912 char *end = NULL;
913
914 if (!s || !*s)
915 return -1;
916
917 errno = 0;
918 *ul = strtoul(s, &end, 0);
919 if (errno == 0 && end && !*end)
920 return 0;
921 return 1;
922 }
923
924 /* get a definitive list of users we want info about... */
925 static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
926 {
927 char *u, *g;
928 size_t i = 0, n = 0, *arsiz;
929 struct group *grp;
930 struct passwd *pwd;
931 char ***ar;
932 uid_t uid;
933 gid_t gid;
934
935 ar = &ctl->ulist;
936 arsiz = &ctl->ulsiz;
937
938 /* an arbitrary starting value */
939 *arsiz = 32;
940 *ar = xcalloc(1, sizeof(char *) * (*arsiz));
941
942 if (logins) {
943 while ((u = strtok(logins, ","))) {
944 logins = NULL;
945
946 /* user specified by UID? */
947 if (!str_to_uint(u, &uid)) {
948 pwd = getpwuid(uid);
949 if (!pwd)
950 continue;
951 u = pwd->pw_name;
952 }
953 (*ar)[i++] = xstrdup(u);
954
955 if (i == *arsiz)
956 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
957 }
958 ctl->ulist_on = 1;
959 }
960
961 if (groups) {
962 /* FIXME: this might lead to duplicate entries, although not visible
963 * in output, crunching a user's info multiple times is very redundant */
964 while ((g = strtok(groups, ","))) {
965 n = 0;
966 groups = NULL;
967
968 /* user specified by GID? */
969 if (!str_to_uint(g, &gid))
970 grp = getgrgid(gid);
971 else
972 grp = getgrnam(g);
973
974 if (!grp)
975 continue;
976
977 while ((u = grp->gr_mem[n++])) {
978 (*ar)[i++] = xstrdup(u);
979
980 if (i == *arsiz)
981 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
982 }
983 }
984 ctl->ulist_on = 1;
985 }
986 *arsiz = i;
987 return 0;
988 }
989
990 static void free_ctl(struct lslogins_control *ctl)
991 {
992 size_t n = 0;
993
994 free(ctl->wtmp);
995 free(ctl->btmp);
996
997 while (n < ctl->ulsiz)
998 free(ctl->ulist[n++]);
999
1000 free(ctl->ulist);
1001 free(ctl);
1002 }
1003
1004 static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
1005 {
1006 struct lslogins_user *u;
1007 errno = 0;
1008 while (!(u = get_user_info(ctl, NULL))) {
1009 /* no "false" errno-s here, iff we're unable to
1010 * get a valid user entry for any reason, quit */
1011 if (errno == EAGAIN)
1012 continue;
1013 return NULL;
1014 }
1015 return u;
1016 }
1017
1018 /* some UNIX implementations set errno iff a passwd/grp/...
1019 * entry was not found. The original UNIX logins(1) utility always
1020 * ignores invalid login/group names, so we're going to as well.*/
1021 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
1022 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
1023
1024 static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
1025 const char *username)
1026 {
1027 *user = get_user_info(ctl, username);
1028 if (!*user && IS_REAL_ERRNO(errno))
1029 return -1;
1030 return 0;
1031 }
1032
1033 static int cmp_uid(const void *a, const void *b)
1034 {
1035 uid_t x = ((const struct lslogins_user *)a)->uid;
1036 uid_t z = ((const struct lslogins_user *)b)->uid;
1037 return x > z ? 1 : (x < z ? -1 : 0);
1038 }
1039
1040 static int create_usertree(struct lslogins_control *ctl)
1041 {
1042 struct lslogins_user *user = NULL;
1043 size_t n = 0;
1044
1045 if (ctl->ulist_on) {
1046 for (n = 0; n < ctl->ulsiz; n++) {
1047 int rc = get_user(ctl, &user, ctl->ulist[n]);
1048
1049 if (ctl->fail_on_unknown && !user) {
1050 warnx(_("cannot found '%s'"), ctl->ulist[n]);
1051 return -1;
1052 }
1053 if (rc || !user)
1054 continue;
1055 tsearch(user, &ctl->usertree, cmp_uid);
1056 }
1057 } else {
1058 while ((user = get_next_user(ctl)))
1059 tsearch(user, &ctl->usertree, cmp_uid);
1060 }
1061 return 0;
1062 }
1063
1064 static struct libscols_table *setup_table(struct lslogins_control *ctl)
1065 {
1066 struct libscols_table *table = scols_new_table();
1067 size_t n = 0;
1068
1069 if (!table)
1070 err(EXIT_FAILURE, _("failed to allocate output table"));
1071 if (ctl->noheadings)
1072 scols_table_enable_noheadings(table, 1);
1073
1074 switch(outmode) {
1075 case OUT_COLON:
1076 scols_table_enable_raw(table, 1);
1077 scols_table_set_column_separator(table, ":");
1078 break;
1079 case OUT_NEWLINE:
1080 scols_table_set_column_separator(table, "\n");
1081 /* fallthrough */
1082 case OUT_EXPORT:
1083 scols_table_enable_export(table, 1);
1084 break;
1085 case OUT_NUL:
1086 scols_table_set_line_separator(table, "\0");
1087 /* fallthrough */
1088 case OUT_RAW:
1089 scols_table_enable_raw(table, 1);
1090 break;
1091 case OUT_PRETTY:
1092 scols_table_enable_noheadings(table, 1);
1093 default:
1094 break;
1095 }
1096
1097 while (n < ncolumns) {
1098 int flags = coldescs[columns[n]].flag;
1099
1100 if (ctl->notrunc)
1101 flags &= ~SCOLS_FL_TRUNC;
1102
1103 if (!scols_table_new_column(table,
1104 coldescs[columns[n]].name,
1105 coldescs[columns[n]].whint,
1106 flags))
1107 goto fail;
1108 ++n;
1109 }
1110
1111 return table;
1112 fail:
1113 scols_unref_table(table);
1114 return NULL;
1115 }
1116
1117 static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
1118 {
1119 struct libscols_line *ln;
1120 const struct lslogins_user *user = *(struct lslogins_user * const *)u;
1121 size_t n = 0;
1122
1123 if (which == preorder || which == endorder)
1124 return;
1125
1126 ln = scols_table_new_line(tb, NULL);
1127 if (!ln)
1128 err(EXIT_FAILURE, _("failed to allocate output line"));
1129
1130 while (n < ncolumns) {
1131 int rc = 0;
1132
1133 switch (columns[n]) {
1134 case COL_USER:
1135 rc = scols_line_set_data(ln, n, user->login);
1136 break;
1137 case COL_UID:
1138 rc = scols_line_refer_data(ln, n, uidtostr(user->uid));
1139 break;
1140 case COL_PWDEMPTY:
1141 rc = scols_line_set_data(ln, n, get_status(user->pwd_empty));
1142 break;
1143 case COL_NOLOGIN:
1144 rc = scols_line_set_data(ln, n, get_status(user->nologin));
1145 break;
1146 case COL_PWDLOCK:
1147 rc = scols_line_set_data(ln, n, get_status(user->pwd_lock));
1148 break;
1149 case COL_PWDDENY:
1150 rc = scols_line_set_data(ln, n, get_status(user->pwd_deny));
1151 break;
1152 case COL_PWDMETHOD:
1153 rc = scols_line_set_data(ln, n, user->pwd_method);
1154 break;
1155 case COL_GROUP:
1156 rc = scols_line_set_data(ln, n, user->group);
1157 break;
1158 case COL_GID:
1159 rc = scols_line_refer_data(ln, n, gidtostr(user->gid));
1160 break;
1161 case COL_SGROUPS:
1162 rc = scols_line_refer_data(ln, n,
1163 build_sgroups_string(user->sgroups,
1164 user->nsgroups,
1165 TRUE));
1166 break;
1167 case COL_SGIDS:
1168 rc = scols_line_refer_data(ln, n,
1169 build_sgroups_string(user->sgroups,
1170 user->nsgroups,
1171 FALSE));
1172 break;
1173 case COL_HOME:
1174 rc = scols_line_set_data(ln, n, user->homedir);
1175 break;
1176 case COL_SHELL:
1177 rc = scols_line_set_data(ln, n, user->shell);
1178 break;
1179 case COL_GECOS:
1180 rc = scols_line_set_data(ln, n, user->gecos);
1181 break;
1182 case COL_LAST_LOGIN:
1183 rc = scols_line_set_data(ln, n, user->last_login);
1184 break;
1185 case COL_LAST_TTY:
1186 rc = scols_line_set_data(ln, n, user->last_tty);
1187 break;
1188 case COL_LAST_HOSTNAME:
1189 rc = scols_line_set_data(ln, n, user->last_hostname);
1190 break;
1191 case COL_FAILED_LOGIN:
1192 rc = scols_line_set_data(ln, n, user->failed_login);
1193 break;
1194 case COL_FAILED_TTY:
1195 rc = scols_line_set_data(ln, n, user->failed_tty);
1196 break;
1197 case COL_HUSH_STATUS:
1198 rc = scols_line_set_data(ln, n, get_status(user->hushed));
1199 break;
1200 case COL_PWD_WARN:
1201 rc = scols_line_set_data(ln, n, user->pwd_warn);
1202 break;
1203 case COL_PWD_EXPIR:
1204 rc = scols_line_set_data(ln, n, user->pwd_expire);
1205 break;
1206 case COL_PWD_CTIME:
1207 rc = scols_line_set_data(ln, n, user->pwd_ctime);
1208 break;
1209 case COL_PWD_CTIME_MIN:
1210 rc = scols_line_set_data(ln, n, user->pwd_ctime_min);
1211 break;
1212 case COL_PWD_CTIME_MAX:
1213 rc = scols_line_set_data(ln, n, user->pwd_ctime_max);
1214 break;
1215 case COL_SELINUX:
1216 #ifdef HAVE_LIBSELINUX
1217 rc = scols_line_set_data(ln, n, user->context);
1218 #endif
1219 break;
1220 case COL_NPROCS:
1221 #ifdef __linux__
1222
1223 rc = scols_line_set_data(ln, n, user->nprocs);
1224 #endif
1225 break;
1226 default:
1227 /* something went very wrong here */
1228 err(EXIT_FAILURE, _("internal error: unknown column"));
1229 }
1230
1231 if (rc)
1232 err(EXIT_FAILURE, _("failed to add output data"));
1233 ++n;
1234 }
1235 }
1236 #ifdef HAVE_LIBSYSTEMD
1237 static char *get_journal_data(sd_journal *j, const char *name)
1238 {
1239 const char *data = NULL, *p;
1240 size_t len = 0;
1241
1242 if (sd_journal_get_data(j, name, (const void **) &data, &len) < 0
1243 || !data || !len)
1244 return NULL;
1245
1246 /* Get rid of journal entry field identifiers */
1247 p = strnchr(data, len, '=');
1248 if (!p || !*(p + 1))
1249 return NULL;
1250 p++;
1251
1252 return xstrndup(p, len - (p - data));
1253 }
1254
1255 static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
1256 {
1257 sd_journal *j;
1258 char *match;
1259
1260 if (journal_path)
1261 sd_journal_open_directory(&j, journal_path, 0);
1262 else
1263 sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
1264
1265 xasprintf(&match, "_UID=%d", uid);
1266
1267 sd_journal_add_match(j, match, 0);
1268 sd_journal_seek_tail(j);
1269 sd_journal_previous_skip(j, len);
1270
1271 do {
1272 char *id, *pid, *msg, *ts;
1273 uint64_t x;
1274 time_t t;
1275
1276 sd_journal_get_realtime_usec(j, &x);
1277 t = x / 1000000;
1278 ts = make_time(time_mode, t);
1279
1280 id = get_journal_data(j, "SYSLOG_IDENTIFIER");
1281 pid = get_journal_data(j, "_PID");
1282 msg = get_journal_data(j, "MESSAGE");
1283
1284 if (ts && id && pid && msg)
1285 fprintf(stdout, "%s %s[%s]: %s\n", ts, id, pid, msg);
1286
1287 free(ts);
1288 free(id);
1289 free(pid);
1290 free(msg);
1291 } while (sd_journal_next(j));
1292
1293 free(match);
1294 sd_journal_flush_matches(j);
1295 sd_journal_close(j);
1296 }
1297 #endif
1298
1299 static int print_pretty(struct libscols_table *table)
1300 {
1301 struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
1302 struct libscols_column *col;
1303 struct libscols_cell *data;
1304 struct libscols_line *ln;
1305 const char *hstr, *dstr;
1306 int n = 0;
1307
1308 ln = scols_table_get_line(table, 0);
1309 while (!scols_table_next_column(table, itr, &col)) {
1310
1311 data = scols_line_get_cell(ln, n);
1312
1313 hstr = _(coldescs[columns[n]].pretty_name);
1314 dstr = scols_cell_get_data(data);
1315
1316 if (dstr)
1317 printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
1318 ++n;
1319 }
1320
1321 scols_free_iter(itr);
1322 return 0;
1323
1324 }
1325
1326 static int print_user_table(struct lslogins_control *ctl)
1327 {
1328 tb = setup_table(ctl);
1329 if (!tb)
1330 return -1;
1331
1332 twalk(ctl->usertree, fill_table);
1333 if (outmode == OUT_PRETTY) {
1334 print_pretty(tb);
1335 #ifdef HAVE_LIBSYSTEMD
1336 fprintf(stdout, _("\nLast logs:\n"));
1337 print_journal_tail(ctl->journal_path, ctl->uid, 3, ctl->time_mode);
1338 fputc('\n', stdout);
1339 #endif
1340 } else
1341 scols_print_table(tb);
1342 return 0;
1343 }
1344
1345 static void free_user(void *f)
1346 {
1347 struct lslogins_user *u = f;
1348 free(u->login);
1349 free(u->group);
1350 free(u->nprocs);
1351 free(u->gecos);
1352 free(u->sgroups);
1353 free(u->pwd_ctime);
1354 free(u->pwd_warn);
1355 free(u->pwd_ctime_min);
1356 free(u->pwd_ctime_max);
1357 free(u->last_login);
1358 free(u->last_tty);
1359 free(u->last_hostname);
1360 free(u->failed_login);
1361 free(u->failed_tty);
1362 free(u->homedir);
1363 free(u->shell);
1364 free(u->pwd_status);
1365 #ifdef HAVE_LIBSELINUX
1366 freecon(u->context);
1367 #endif
1368 free(u);
1369 }
1370
1371 static int parse_time_mode(const char *s)
1372 {
1373 struct lslogins_timefmt {
1374 const char *name;
1375 const int val;
1376 };
1377 static const struct lslogins_timefmt timefmts[] = {
1378 {"iso", TIME_ISO},
1379 {"full", TIME_FULL},
1380 {"short", TIME_SHORT},
1381 };
1382 size_t i;
1383
1384 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
1385 if (strcmp(timefmts[i].name, s) == 0)
1386 return timefmts[i].val;
1387 }
1388 errx(EXIT_FAILURE, _("unknown time format: %s"), s);
1389 }
1390
1391 static void __attribute__((__noreturn__)) usage(void)
1392 {
1393 FILE *out = stdout;
1394 size_t i;
1395
1396 fputs(USAGE_HEADER, out);
1397 fprintf(out, _(" %s [options] [<username>]\n"), program_invocation_short_name);
1398
1399 fputs(USAGE_SEPARATOR, out);
1400 fputs(_("Display information about known users in the system.\n"), out);
1401
1402 fputs(USAGE_OPTIONS, out);
1403 fputs(_(" -a, --acc-expiration display info about passwords expiration\n"), out);
1404 fputs(_(" -c, --colon-separate display data in a format similar to /etc/passwd\n"), out);
1405 fputs(_(" -e, --export display in an export-able output format\n"), out);
1406 fputs(_(" -f, --failed display data about the users' last failed logins\n"), out);
1407 fputs(_(" -G, --supp-groups display information about groups\n"), out);
1408 fputs(_(" -g, --groups=<groups> display users belonging to a group in <groups>\n"), out);
1409 fputs(_(" -L, --last show info about the users' last login sessions\n"), out);
1410 fputs(_(" -l, --logins=<logins> display only users from <logins>\n"), out);
1411 fputs(_(" -n, --newline display each piece of information on a new line\n"), out);
1412 fputs(_(" --noheadings don't print headings\n"), out);
1413 fputs(_(" --notruncate don't truncate output\n"), out);
1414 fputs(_(" -o, --output[=<list>] define the columns to output\n"), out);
1415 fputs(_(" --output-all output all columns\n"), out);
1416 fputs(_(" -p, --pwd display information related to login by password.\n"), out);
1417 fputs(_(" -r, --raw display in raw mode\n"), out);
1418 fputs(_(" -s, --system-accs display system accounts\n"), out);
1419 fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out);
1420 fputs(_(" -u, --user-accs display user accounts\n"), out);
1421 fputs(_(" -Z, --context display SELinux contexts\n"), out);
1422 fputs(_(" -z, --print0 delimit user entries with a nul character\n"), out);
1423 fputs(_(" --wtmp-file <path> set an alternate path for wtmp\n"), out);
1424 fputs(_(" --btmp-file <path> set an alternate path for btmp\n"), out);
1425 fputs(_(" --lastlog <path> set an alternate path for lastlog\n"), out);
1426 fputs(USAGE_SEPARATOR, out);
1427 printf(USAGE_HELP_OPTIONS(26));
1428
1429 fputs(USAGE_COLUMNS, out);
1430 for (i = 0; i < ARRAY_SIZE(coldescs); i++)
1431 fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
1432
1433 printf(USAGE_MAN_TAIL("lslogins(1)"));
1434
1435 exit(EXIT_SUCCESS);
1436 }
1437
1438 int main(int argc, char *argv[])
1439 {
1440 int c;
1441 char *logins = NULL, *groups = NULL, *outarg = NULL;
1442 char *path_lastlog = _PATH_LASTLOG, *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP;
1443 struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control));
1444 size_t i;
1445
1446 /* long only options. */
1447 enum {
1448 OPT_WTMP = CHAR_MAX + 1,
1449 OPT_BTMP,
1450 OPT_LASTLOG,
1451 OPT_NOTRUNC,
1452 OPT_NOHEAD,
1453 OPT_TIME_FMT,
1454 OPT_OUTPUT_ALL,
1455 };
1456
1457 static const struct option longopts[] = {
1458 { "acc-expiration", no_argument, 0, 'a' },
1459 { "colon-separate", no_argument, 0, 'c' },
1460 { "export", no_argument, 0, 'e' },
1461 { "failed", no_argument, 0, 'f' },
1462 { "groups", required_argument, 0, 'g' },
1463 { "help", no_argument, 0, 'h' },
1464 { "logins", required_argument, 0, 'l' },
1465 { "supp-groups", no_argument, 0, 'G' },
1466 { "newline", no_argument, 0, 'n' },
1467 { "notruncate", no_argument, 0, OPT_NOTRUNC },
1468 { "noheadings", no_argument, 0, OPT_NOHEAD },
1469 { "output", required_argument, 0, 'o' },
1470 { "output-all", no_argument, 0, OPT_OUTPUT_ALL },
1471 { "last", no_argument, 0, 'L', },
1472 { "raw", no_argument, 0, 'r' },
1473 { "system-accs", no_argument, 0, 's' },
1474 { "time-format", required_argument, 0, OPT_TIME_FMT },
1475 { "user-accs", no_argument, 0, 'u' },
1476 { "version", no_argument, 0, 'V' },
1477 { "pwd", no_argument, 0, 'p' },
1478 { "print0", no_argument, 0, 'z' },
1479 { "wtmp-file", required_argument, 0, OPT_WTMP },
1480 { "btmp-file", required_argument, 0, OPT_BTMP },
1481 { "lastlog-file", required_argument, 0, OPT_LASTLOG },
1482 #ifdef HAVE_LIBSELINUX
1483 { "context", no_argument, 0, 'Z' },
1484 #endif
1485 { NULL, 0, 0, 0 }
1486 };
1487
1488 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1489 { 'G', 'o' },
1490 { 'L', 'o' },
1491 { 'Z', 'o' },
1492 { 'a', 'o' },
1493 { 'c','n','r','z' },
1494 { 'o', 'p' },
1495 { 0 }
1496 };
1497 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1498
1499 setlocale(LC_ALL, "");
1500 bindtextdomain(PACKAGE, LOCALEDIR);
1501 textdomain(PACKAGE);
1502 close_stdout_atexit();
1503
1504 ctl->time_mode = TIME_SHORT;
1505
1506 /* very basic default */
1507 add_column(columns, ncolumns++, COL_UID);
1508 add_column(columns, ncolumns++, COL_USER);
1509
1510 while ((c = getopt_long(argc, argv, "acefGg:hLl:no:prsuVzZ",
1511 longopts, NULL)) != -1) {
1512
1513 err_exclusive_options(c, longopts, excl, excl_st);
1514
1515 switch (c) {
1516 case 'a':
1517 add_column(columns, ncolumns++, COL_PWD_WARN);
1518 add_column(columns, ncolumns++, COL_PWD_CTIME_MIN);
1519 add_column(columns, ncolumns++, COL_PWD_CTIME_MAX);
1520 add_column(columns, ncolumns++, COL_PWD_CTIME);
1521 add_column(columns, ncolumns++, COL_PWD_EXPIR);
1522 break;
1523 case 'c':
1524 outmode = OUT_COLON;
1525 break;
1526 case 'e':
1527 outmode = OUT_EXPORT;
1528 break;
1529 case 'f':
1530 add_column(columns, ncolumns++, COL_FAILED_LOGIN);
1531 add_column(columns, ncolumns++, COL_FAILED_TTY);
1532 break;
1533 case 'G':
1534 add_column(columns, ncolumns++, COL_GID);
1535 add_column(columns, ncolumns++, COL_GROUP);
1536 add_column(columns, ncolumns++, COL_SGIDS);
1537 add_column(columns, ncolumns++, COL_SGROUPS);
1538 break;
1539 case 'g':
1540 groups = optarg;
1541 break;
1542 case 'h':
1543 usage();
1544 break;
1545 case 'L':
1546 add_column(columns, ncolumns++, COL_LAST_TTY);
1547 add_column(columns, ncolumns++, COL_LAST_HOSTNAME);
1548 add_column(columns, ncolumns++, COL_LAST_LOGIN);
1549 break;
1550 case 'l':
1551 logins = optarg;
1552 break;
1553 case 'n':
1554 outmode = OUT_NEWLINE;
1555 break;
1556 case 'o':
1557 if (*optarg == '=')
1558 optarg++;
1559 outarg = optarg;
1560 break;
1561 case OPT_OUTPUT_ALL:
1562 for (ncolumns = 0; ncolumns < ARRAY_SIZE(coldescs); ncolumns++)
1563 columns[ncolumns] = ncolumns;
1564 break;
1565 case 'r':
1566 outmode = OUT_RAW;
1567 break;
1568 case 's':
1569 ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN);
1570 ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX);
1571 lslogins_flag |= F_SYSAC;
1572 break;
1573 case 'u':
1574 ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN);
1575 ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX);
1576 lslogins_flag |= F_USRAC;
1577 break;
1578 case 'p':
1579 add_column(columns, ncolumns++, COL_PWDEMPTY);
1580 add_column(columns, ncolumns++, COL_PWDLOCK);
1581 add_column(columns, ncolumns++, COL_PWDDENY);
1582 add_column(columns, ncolumns++, COL_NOLOGIN);
1583 add_column(columns, ncolumns++, COL_HUSH_STATUS);
1584 add_column(columns, ncolumns++, COL_PWDMETHOD);
1585 break;
1586 case 'z':
1587 outmode = OUT_NUL;
1588 break;
1589 case OPT_LASTLOG:
1590 path_lastlog = optarg;
1591 break;
1592 case OPT_WTMP:
1593 path_wtmp = optarg;
1594 break;
1595 case OPT_BTMP:
1596 path_btmp = optarg;
1597 break;
1598 case OPT_NOTRUNC:
1599 ctl->notrunc = 1;
1600 break;
1601 case OPT_NOHEAD:
1602 ctl->noheadings = 1;
1603 break;
1604 case OPT_TIME_FMT:
1605 ctl->time_mode = parse_time_mode(optarg);
1606 break;
1607 case 'V':
1608 print_version(EXIT_SUCCESS);
1609 case 'Z':
1610 {
1611 #ifdef HAVE_LIBSELINUX
1612 int sl = is_selinux_enabled();
1613 if (sl < 0)
1614 warn(_("failed to request selinux state"));
1615 else
1616 ctl->selinux_enabled = sl == 1;
1617 #endif
1618 add_column(columns, ncolumns++, COL_SELINUX);
1619 break;
1620 }
1621 default:
1622 errtryhelp(EXIT_FAILURE);
1623 }
1624 }
1625
1626 if (argc - optind == 1) {
1627 if (strchr(argv[optind], ','))
1628 errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
1629 logins = argv[optind];
1630 outmode = OUT_PRETTY;
1631 ctl->fail_on_unknown = 1;
1632 } else if (argc != optind)
1633 errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
1634
1635 scols_init_debug(0);
1636
1637 /* lslogins -u -s == lslogins */
1638 if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
1639 lslogins_flag &= ~(F_USRAC | F_SYSAC);
1640
1641 if (outmode == OUT_PRETTY) {
1642 /* all columns for lslogins <username> */
1643 for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
1644 columns[ncolumns++] = i;
1645
1646 } else if (ncolumns == 2) {
1647 /* default columns */
1648 #ifdef __linux__
1649 add_column(columns, ncolumns++, COL_NPROCS);
1650 #endif
1651 add_column(columns, ncolumns++, COL_PWDLOCK);
1652 add_column(columns, ncolumns++, COL_PWDDENY);
1653 add_column(columns, ncolumns++, COL_LAST_LOGIN);
1654 add_column(columns, ncolumns++, COL_GECOS);
1655 }
1656
1657 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
1658 &ncolumns, column_name_to_id) < 0)
1659 return EXIT_FAILURE;
1660
1661 if (require_wtmp()) {
1662 parse_utmpx(path_wtmp, &ctl->wtmp_size, &ctl->wtmp);
1663 ctl->lastlogin_fd = open(path_lastlog, O_RDONLY, 0);
1664 }
1665 if (require_btmp())
1666 parse_utmpx(path_btmp, &ctl->btmp_size, &ctl->btmp);
1667
1668 if (logins || groups)
1669 get_ulist(ctl, logins, groups);
1670
1671 if (create_usertree(ctl))
1672 return EXIT_FAILURE;
1673
1674 print_user_table(ctl);
1675
1676 scols_unref_table(tb);
1677 tdestroy(ctl->usertree, free_user);
1678
1679 if (ctl->lastlogin_fd >= 0)
1680 close(ctl->lastlogin_fd);
1681 free_ctl(ctl);
1682
1683 return EXIT_SUCCESS;
1684 }