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