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