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