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