]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/lslogins.c
Merge branch 'libuuid-range' of https://github.com/UQ-RCC/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
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 = xmalloc(sizeof(user_wtmp->ut_line) + 1);
745 mem2strcpy(user->last_tty, user_wtmp->ut_line,
746 sizeof(user_wtmp->ut_line),
747 sizeof(user_wtmp->ut_line) + 1);;
748 }
749 break;
750 case COL_LAST_HOSTNAME:
751 if (user_wtmp) {
752 user->last_hostname = xmalloc(sizeof(user_wtmp->ut_host) + 1);
753 mem2strcpy(user->last_hostname, user_wtmp->ut_host,
754 sizeof(user_wtmp->ut_host),
755 sizeof(user_wtmp->ut_host) + 1);;
756 }
757 break;
758 case COL_FAILED_LOGIN:
759 if (user_btmp) {
760 time = user_btmp->ut_tv.tv_sec;
761 user->failed_login = make_time(ctl->time_mode, time);
762 }
763 break;
764 case COL_FAILED_TTY:
765 if (user_btmp) {
766 user->failed_tty = xmalloc(sizeof(user_btmp->ut_line) + 1);
767 mem2strcpy(user->failed_tty, user_btmp->ut_line,
768 sizeof(user_btmp->ut_line),
769 sizeof(user_btmp->ut_line) + 1);;
770 }
771 break;
772 case COL_HUSH_STATUS:
773 user->hushed = get_hushlogin_status(pwd, 0);
774 if (user->hushed == -1)
775 user->hushed = STATUS_UNKNOWN;
776 break;
777 case COL_PWDEMPTY:
778 if (shadow) {
779 if (!*shadow->sp_pwdp) /* '\0' */
780 user->pwd_empty = STATUS_TRUE;
781 } else
782 user->pwd_empty = STATUS_UNKNOWN;
783 break;
784 case COL_PWDDENY:
785 if (shadow) {
786 if ((*shadow->sp_pwdp == '!' ||
787 *shadow->sp_pwdp == '*') &&
788 !valid_pwd(shadow->sp_pwdp + 1))
789 user->pwd_deny = STATUS_TRUE;
790 } else
791 user->pwd_deny = STATUS_UNKNOWN;
792 break;
793 case COL_PWDLOCK:
794 if (shadow) {
795 if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1))
796 user->pwd_lock = STATUS_TRUE;
797 } else
798 user->pwd_lock = STATUS_UNKNOWN;
799 break;
800 case COL_PWDMETHOD:
801 if (shadow) {
802 const char *p = shadow->sp_pwdp;
803
804 if (*p == '!' || *p == '*')
805 p++;
806 user->pwd_method = get_pwd_method(p, NULL, NULL);
807 } else
808 user->pwd_method = NULL;
809 break;
810 case COL_NOLOGIN:
811 if (strstr(pwd->pw_shell, "nologin"))
812 user->nologin = 1;
813 else if (pwd->pw_uid)
814 user->nologin = access(_PATH_NOLOGIN, F_OK) == 0 ||
815 access(_PATH_VAR_NOLOGIN, F_OK) == 0;
816 break;
817 case COL_PWD_WARN:
818 if (shadow && shadow->sp_warn >= 0)
819 xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
820 break;
821 case COL_PWD_EXPIR:
822 if (shadow && shadow->sp_expire >= 0)
823 user->pwd_expire = make_time(ctl->time_mode == TIME_ISO ?
824 TIME_ISO_SHORT : ctl->time_mode,
825 shadow->sp_expire * 86400);
826 break;
827 case COL_PWD_CTIME:
828 /* sp_lstchg is specified in days, showing hours
829 * (especially in non-GMT timezones) would only serve
830 * to confuse */
831 if (shadow)
832 user->pwd_ctime = make_time(ctl->time_mode == TIME_ISO ?
833 TIME_ISO_SHORT : ctl->time_mode,
834 shadow->sp_lstchg * 86400);
835 break;
836 case COL_PWD_CTIME_MIN:
837 if (shadow && shadow->sp_min > 0)
838 xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
839 break;
840 case COL_PWD_CTIME_MAX:
841 if (shadow && shadow->sp_max > 0)
842 xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
843 break;
844 case COL_SELINUX:
845 #ifdef HAVE_LIBSELINUX
846 if (ctl->selinux_enabled) {
847 /* typedefs and pointers are pure evil */
848 security_context_t con = NULL;
849 if (getcon(&con) == 0)
850 user->context = con;
851 }
852 #endif
853 break;
854 case COL_NPROCS:
855 xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
856 break;
857 default:
858 /* something went very wrong here */
859 err(EXIT_FAILURE, "fatal: unknown error");
860 break;
861 }
862 }
863
864 return user;
865 }
866
867 static int str_to_uint(char *s, unsigned int *ul)
868 {
869 char *end;
870 if (!s || !*s)
871 return -1;
872 *ul = strtoul(s, &end, 0);
873 if (!*end)
874 return 0;
875 return 1;
876 }
877
878 /* get a definitive list of users we want info about... */
879 static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
880 {
881 char *u, *g;
882 size_t i = 0, n = 0, *arsiz;
883 struct group *grp;
884 struct passwd *pwd;
885 char ***ar;
886 uid_t uid;
887 gid_t gid;
888
889 ar = &ctl->ulist;
890 arsiz = &ctl->ulsiz;
891
892 /* an arbitrary starting value */
893 *arsiz = 32;
894 *ar = xcalloc(1, sizeof(char *) * (*arsiz));
895
896 if (logins) {
897 while ((u = strtok(logins, ","))) {
898 logins = NULL;
899
900 /* user specified by UID? */
901 if (!str_to_uint(u, &uid)) {
902 pwd = getpwuid(uid);
903 if (!pwd)
904 continue;
905 u = pwd->pw_name;
906 }
907 (*ar)[i++] = xstrdup(u);
908
909 if (i == *arsiz)
910 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
911 }
912 ctl->ulist_on = 1;
913 }
914
915 if (groups) {
916 /* FIXME: this might lead to duplicate entries, although not visible
917 * in output, crunching a user's info multiple times is very redundant */
918 while ((g = strtok(groups, ","))) {
919 n = 0;
920 groups = NULL;
921
922 /* user specified by GID? */
923 if (!str_to_uint(g, &gid))
924 grp = getgrgid(gid);
925 else
926 grp = getgrnam(g);
927
928 if (!grp)
929 continue;
930
931 while ((u = grp->gr_mem[n++])) {
932 (*ar)[i++] = xstrdup(u);
933
934 if (i == *arsiz)
935 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
936 }
937 }
938 ctl->ulist_on = 1;
939 }
940 *arsiz = i;
941 return 0;
942 }
943
944 static void free_ctl(struct lslogins_control *ctl)
945 {
946 size_t n = 0;
947
948 free(ctl->wtmp);
949 free(ctl->btmp);
950
951 while (n < ctl->ulsiz)
952 free(ctl->ulist[n++]);
953
954 free(ctl->ulist);
955 free(ctl);
956 }
957
958 static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
959 {
960 struct lslogins_user *u;
961 errno = 0;
962 while (!(u = get_user_info(ctl, NULL))) {
963 /* no "false" errno-s here, iff we're unable to
964 * get a valid user entry for any reason, quit */
965 if (errno == EAGAIN)
966 continue;
967 return NULL;
968 }
969 return u;
970 }
971
972 /* some UNIX implementations set errno iff a passwd/grp/...
973 * entry was not found. The original UNIX logins(1) utility always
974 * ignores invalid login/group names, so we're going to as well.*/
975 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
976 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
977
978 static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
979 const char *username)
980 {
981 *user = get_user_info(ctl, username);
982 if (!*user && IS_REAL_ERRNO(errno))
983 return -1;
984 return 0;
985 }
986
987 static int cmp_uid(const void *a, const void *b)
988 {
989 uid_t x = ((const struct lslogins_user *)a)->uid;
990 uid_t z = ((const struct lslogins_user *)b)->uid;
991 return x > z ? 1 : (x < z ? -1 : 0);
992 }
993
994 static int create_usertree(struct lslogins_control *ctl)
995 {
996 struct lslogins_user *user = NULL;
997 size_t n = 0;
998
999 if (ctl->ulist_on) {
1000 for (n = 0; n < ctl->ulsiz; n++) {
1001 int rc = get_user(ctl, &user, ctl->ulist[n]);
1002
1003 if (ctl->fail_on_unknown && !user) {
1004 warnx(_("cannot found '%s'"), ctl->ulist[n]);
1005 return -1;
1006 }
1007 if (rc || !user)
1008 continue;
1009
1010 tsearch(user, &ctl->usertree, cmp_uid);
1011 }
1012 } else {
1013 while ((user = get_next_user(ctl)))
1014 tsearch(user, &ctl->usertree, cmp_uid);
1015 }
1016 return 0;
1017 }
1018
1019 static struct libscols_table *setup_table(struct lslogins_control *ctl)
1020 {
1021 struct libscols_table *table = scols_new_table();
1022 size_t n = 0;
1023
1024 if (!table)
1025 err(EXIT_FAILURE, _("failed to allocate output table"));
1026 if (ctl->noheadings)
1027 scols_table_enable_noheadings(table, 1);
1028
1029 switch(outmode) {
1030 case OUT_COLON:
1031 scols_table_enable_raw(table, 1);
1032 scols_table_set_column_separator(table, ":");
1033 break;
1034 case OUT_NEWLINE:
1035 scols_table_set_column_separator(table, "\n");
1036 /* fallthrough */
1037 case OUT_EXPORT:
1038 scols_table_enable_export(table, 1);
1039 break;
1040 case OUT_NUL:
1041 scols_table_set_line_separator(table, "\0");
1042 /* fallthrough */
1043 case OUT_RAW:
1044 scols_table_enable_raw(table, 1);
1045 break;
1046 case OUT_PRETTY:
1047 scols_table_enable_noheadings(table, 1);
1048 default:
1049 break;
1050 }
1051
1052 while (n < ncolumns) {
1053 int flags = coldescs[columns[n]].flag;
1054
1055 if (ctl->notrunc)
1056 flags &= ~SCOLS_FL_TRUNC;
1057
1058 if (!scols_table_new_column(table,
1059 coldescs[columns[n]].name,
1060 coldescs[columns[n]].whint,
1061 flags))
1062 goto fail;
1063 ++n;
1064 }
1065
1066 return table;
1067 fail:
1068 scols_unref_table(table);
1069 return NULL;
1070 }
1071
1072 static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
1073 {
1074 struct libscols_line *ln;
1075 const struct lslogins_user *user = *(struct lslogins_user * const *)u;
1076 size_t n = 0;
1077
1078 if (which == preorder || which == endorder)
1079 return;
1080
1081 ln = scols_table_new_line(tb, NULL);
1082 if (!ln)
1083 err(EXIT_FAILURE, _("failed to allocate output line"));
1084
1085 while (n < ncolumns) {
1086 int rc = 0;
1087
1088 switch (columns[n]) {
1089 case COL_USER:
1090 rc = scols_line_set_data(ln, n, user->login);
1091 break;
1092 case COL_UID:
1093 rc = scols_line_refer_data(ln, n, uidtostr(user->uid));
1094 break;
1095 case COL_PWDEMPTY:
1096 rc = scols_line_set_data(ln, n, get_status(user->pwd_empty));
1097 break;
1098 case COL_NOLOGIN:
1099 rc = scols_line_set_data(ln, n, get_status(user->nologin));
1100 break;
1101 case COL_PWDLOCK:
1102 rc = scols_line_set_data(ln, n, get_status(user->pwd_lock));
1103 break;
1104 case COL_PWDDENY:
1105 rc = scols_line_set_data(ln, n, get_status(user->pwd_deny));
1106 break;
1107 case COL_PWDMETHOD:
1108 rc = scols_line_set_data(ln, n, user->pwd_method);
1109 break;
1110 case COL_GROUP:
1111 rc = scols_line_set_data(ln, n, user->group);
1112 break;
1113 case COL_GID:
1114 rc = scols_line_refer_data(ln, n, gidtostr(user->gid));
1115 break;
1116 case COL_SGROUPS:
1117 rc = scols_line_refer_data(ln, n,
1118 build_sgroups_string(user->sgroups,
1119 user->nsgroups,
1120 TRUE));
1121 break;
1122 case COL_SGIDS:
1123 rc = scols_line_refer_data(ln, n,
1124 build_sgroups_string(user->sgroups,
1125 user->nsgroups,
1126 FALSE));
1127 break;
1128 case COL_HOME:
1129 rc = scols_line_set_data(ln, n, user->homedir);
1130 break;
1131 case COL_SHELL:
1132 rc = scols_line_set_data(ln, n, user->shell);
1133 break;
1134 case COL_GECOS:
1135 rc = scols_line_set_data(ln, n, user->gecos);
1136 break;
1137 case COL_LAST_LOGIN:
1138 rc = scols_line_set_data(ln, n, user->last_login);
1139 break;
1140 case COL_LAST_TTY:
1141 rc = scols_line_set_data(ln, n, user->last_tty);
1142 break;
1143 case COL_LAST_HOSTNAME:
1144 rc = scols_line_set_data(ln, n, user->last_hostname);
1145 break;
1146 case COL_FAILED_LOGIN:
1147 rc = scols_line_set_data(ln, n, user->failed_login);
1148 break;
1149 case COL_FAILED_TTY:
1150 rc = scols_line_set_data(ln, n, user->failed_tty);
1151 break;
1152 case COL_HUSH_STATUS:
1153 rc = scols_line_set_data(ln, n, get_status(user->hushed));
1154 break;
1155 case COL_PWD_WARN:
1156 rc = scols_line_set_data(ln, n, user->pwd_warn);
1157 break;
1158 case COL_PWD_EXPIR:
1159 rc = scols_line_set_data(ln, n, user->pwd_expire);
1160 break;
1161 case COL_PWD_CTIME:
1162 rc = scols_line_set_data(ln, n, user->pwd_ctime);
1163 break;
1164 case COL_PWD_CTIME_MIN:
1165 rc = scols_line_set_data(ln, n, user->pwd_ctime_min);
1166 break;
1167 case COL_PWD_CTIME_MAX:
1168 rc = scols_line_set_data(ln, n, user->pwd_ctime_max);
1169 break;
1170 case COL_SELINUX:
1171 #ifdef HAVE_LIBSELINUX
1172 rc = scols_line_set_data(ln, n, user->context);
1173 #endif
1174 break;
1175 case COL_NPROCS:
1176 rc = scols_line_set_data(ln, n, user->nprocs);
1177 break;
1178 default:
1179 /* something went very wrong here */
1180 err(EXIT_FAILURE, _("internal error: unknown column"));
1181 }
1182
1183 if (rc)
1184 err(EXIT_FAILURE, _("failed to add output data"));
1185 ++n;
1186 }
1187 return;
1188 }
1189 #ifdef HAVE_LIBSYSTEMD
1190 static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
1191 {
1192 sd_journal *j;
1193 char *match, *timestamp;
1194 uint64_t x;
1195 time_t t;
1196 const char *identifier, *pid, *message;
1197 size_t identifier_len, pid_len, message_len;
1198
1199 if (journal_path)
1200 sd_journal_open_directory(&j, journal_path, 0);
1201 else
1202 sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
1203
1204 xasprintf(&match, "_UID=%d", uid);
1205
1206 sd_journal_add_match(j, match, 0);
1207 sd_journal_seek_tail(j);
1208 sd_journal_previous_skip(j, len);
1209
1210 do {
1211 if (0 > sd_journal_get_data(j, "SYSLOG_IDENTIFIER",
1212 (const void **) &identifier, &identifier_len))
1213 goto done;
1214 if (0 > sd_journal_get_data(j, "_PID",
1215 (const void **) &pid, &pid_len))
1216 goto done;
1217 if (0 > sd_journal_get_data(j, "MESSAGE",
1218 (const void **) &message, &message_len))
1219 goto done;
1220
1221 sd_journal_get_realtime_usec(j, &x);
1222 t = x / 1000000;
1223 timestamp = make_time(time_mode, t);
1224 /* Get rid of journal entry field identifiers */
1225 identifier = strchr(identifier, '=') + 1;
1226 pid = strchr(pid, '=') + 1;
1227 message = strchr(message, '=') + 1;
1228
1229 fprintf(stdout, "%s %s[%s]: %s\n", timestamp, identifier, pid,
1230 message);
1231 free(timestamp);
1232 } while (sd_journal_next(j));
1233
1234 done:
1235 free(match);
1236 sd_journal_flush_matches(j);
1237 sd_journal_close(j);
1238 }
1239 #endif
1240
1241 static int print_pretty(struct libscols_table *table)
1242 {
1243 struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
1244 struct libscols_column *col;
1245 struct libscols_cell *data;
1246 struct libscols_line *ln;
1247 const char *hstr, *dstr;
1248 int n = 0;
1249
1250 ln = scols_table_get_line(table, 0);
1251 while (!scols_table_next_column(table, itr, &col)) {
1252
1253 data = scols_line_get_cell(ln, n);
1254
1255 hstr = _(coldescs[columns[n]].pretty_name);
1256 dstr = scols_cell_get_data(data);
1257
1258 if (dstr)
1259 printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
1260 ++n;
1261 }
1262
1263 scols_free_iter(itr);
1264 return 0;
1265
1266 }
1267
1268 static int print_user_table(struct lslogins_control *ctl)
1269 {
1270 tb = setup_table(ctl);
1271 if (!tb)
1272 return -1;
1273
1274 twalk(ctl->usertree, fill_table);
1275 if (outmode == OUT_PRETTY) {
1276 print_pretty(tb);
1277 #ifdef HAVE_LIBSYSTEMD
1278 fprintf(stdout, _("\nLast logs:\n"));
1279 print_journal_tail(ctl->journal_path, ctl->uid, 3, ctl->time_mode);
1280 fputc('\n', stdout);
1281 #endif
1282 } else
1283 scols_print_table(tb);
1284 return 0;
1285 }
1286
1287 static void free_user(void *f)
1288 {
1289 struct lslogins_user *u = f;
1290 free(u->login);
1291 free(u->group);
1292 free(u->gecos);
1293 free(u->sgroups);
1294 free(u->pwd_ctime);
1295 free(u->pwd_warn);
1296 free(u->pwd_ctime_min);
1297 free(u->pwd_ctime_max);
1298 free(u->last_login);
1299 free(u->last_tty);
1300 free(u->last_hostname);
1301 free(u->failed_login);
1302 free(u->failed_tty);
1303 free(u->homedir);
1304 free(u->shell);
1305 free(u->pwd_status);
1306 #ifdef HAVE_LIBSELINUX
1307 freecon(u->context);
1308 #endif
1309 free(u);
1310 }
1311
1312 static int parse_time_mode(const char *s)
1313 {
1314 struct lslogins_timefmt {
1315 const char *name;
1316 const int val;
1317 };
1318 static const struct lslogins_timefmt timefmts[] = {
1319 {"iso", TIME_ISO},
1320 {"full", TIME_FULL},
1321 {"short", TIME_SHORT},
1322 };
1323 size_t i;
1324
1325 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
1326 if (strcmp(timefmts[i].name, s) == 0)
1327 return timefmts[i].val;
1328 }
1329 errx(EXIT_FAILURE, _("unknown time format: %s"), s);
1330 }
1331
1332 static void __attribute__((__noreturn__)) usage(void)
1333 {
1334 FILE *out = stdout;
1335 size_t i;
1336
1337 fputs(USAGE_HEADER, out);
1338 fprintf(out, _(" %s [options] [<username>]\n"), program_invocation_short_name);
1339
1340 fputs(USAGE_SEPARATOR, out);
1341 fputs(_("Display information about known users in the system.\n"), out);
1342
1343 fputs(USAGE_OPTIONS, out);
1344 fputs(_(" -a, --acc-expiration display info about passwords expiration\n"), out);
1345 fputs(_(" -c, --colon-separate display data in a format similar to /etc/passwd\n"), out);
1346 fputs(_(" -e, --export display in an export-able output format\n"), out);
1347 fputs(_(" -f, --failed display data about the users' last failed logins\n"), out);
1348 fputs(_(" -G, --supp-groups display information about groups\n"), out);
1349 fputs(_(" -g, --groups=<groups> display users belonging to a group in <groups>\n"), out);
1350 fputs(_(" -L, --last show info about the users' last login sessions\n"), out);
1351 fputs(_(" -l, --logins=<logins> display only users from <logins>\n"), out);
1352 fputs(_(" -n, --newline display each piece of information on a new line\n"), out);
1353 fputs(_(" --noheadings don't print headings\n"), out);
1354 fputs(_(" --notruncate don't truncate output\n"), out);
1355 fputs(_(" -o, --output[=<list>] define the columns to output\n"), out);
1356 fputs(_(" --output-all output all columns\n"), out);
1357 fputs(_(" -p, --pwd display information related to login by password.\n"), out);
1358 fputs(_(" -r, --raw display in raw mode\n"), out);
1359 fputs(_(" -s, --system-accs display system accounts\n"), out);
1360 fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out);
1361 fputs(_(" -u, --user-accs display user accounts\n"), out);
1362 fputs(_(" -Z, --context display SELinux contexts\n"), out);
1363 fputs(_(" -z, --print0 delimit user entries with a nul character\n"), out);
1364 fputs(_(" --wtmp-file <path> set an alternate path for wtmp\n"), out);
1365 fputs(_(" --btmp-file <path> set an alternate path for btmp\n"), out);
1366 fputs(USAGE_SEPARATOR, out);
1367 printf(USAGE_HELP_OPTIONS(26));
1368
1369 fputs(USAGE_COLUMNS, out);
1370 for (i = 0; i < ARRAY_SIZE(coldescs); i++)
1371 fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
1372
1373 printf(USAGE_MAN_TAIL("lslogins(1)"));
1374
1375 exit(EXIT_SUCCESS);
1376 }
1377
1378 int main(int argc, char *argv[])
1379 {
1380 int c;
1381 char *logins = NULL, *groups = NULL, *outarg = NULL;
1382 char *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP;
1383 struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control));
1384 size_t i;
1385
1386 /* long only options. */
1387 enum {
1388 OPT_WTMP = CHAR_MAX + 1,
1389 OPT_BTMP,
1390 OPT_NOTRUNC,
1391 OPT_NOHEAD,
1392 OPT_TIME_FMT,
1393 OPT_OUTPUT_ALL,
1394 };
1395
1396 static const struct option longopts[] = {
1397 { "acc-expiration", no_argument, 0, 'a' },
1398 { "colon-separate", no_argument, 0, 'c' },
1399 { "export", no_argument, 0, 'e' },
1400 { "failed", no_argument, 0, 'f' },
1401 { "groups", required_argument, 0, 'g' },
1402 { "help", no_argument, 0, 'h' },
1403 { "logins", required_argument, 0, 'l' },
1404 { "supp-groups", no_argument, 0, 'G' },
1405 { "newline", no_argument, 0, 'n' },
1406 { "notruncate", no_argument, 0, OPT_NOTRUNC },
1407 { "noheadings", no_argument, 0, OPT_NOHEAD },
1408 { "output", required_argument, 0, 'o' },
1409 { "output-all", no_argument, 0, OPT_OUTPUT_ALL },
1410 { "last", no_argument, 0, 'L', },
1411 { "raw", no_argument, 0, 'r' },
1412 { "system-accs", no_argument, 0, 's' },
1413 { "time-format", required_argument, 0, OPT_TIME_FMT },
1414 { "user-accs", no_argument, 0, 'u' },
1415 { "version", no_argument, 0, 'V' },
1416 { "pwd", no_argument, 0, 'p' },
1417 { "print0", no_argument, 0, 'z' },
1418 { "wtmp-file", required_argument, 0, OPT_WTMP },
1419 { "btmp-file", required_argument, 0, OPT_BTMP },
1420 #ifdef HAVE_LIBSELINUX
1421 { "context", no_argument, 0, 'Z' },
1422 #endif
1423 { NULL, 0, 0, 0 }
1424 };
1425
1426 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1427 { 'G', 'o' },
1428 { 'L', 'o' },
1429 { 'Z', 'o' },
1430 { 'a', 'o' },
1431 { 'c','n','r','z' },
1432 { 'o', 'p' },
1433 { 0 }
1434 };
1435 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1436
1437 setlocale(LC_ALL, "");
1438 bindtextdomain(PACKAGE, LOCALEDIR);
1439 textdomain(PACKAGE);
1440 close_stdout_atexit();
1441
1442 ctl->time_mode = TIME_SHORT;
1443
1444 /* very basic default */
1445 add_column(columns, ncolumns++, COL_UID);
1446 add_column(columns, ncolumns++, COL_USER);
1447
1448 while ((c = getopt_long(argc, argv, "acefGg:hLl:no:prsuVzZ",
1449 longopts, NULL)) != -1) {
1450
1451 err_exclusive_options(c, longopts, excl, excl_st);
1452
1453 switch (c) {
1454 case 'a':
1455 add_column(columns, ncolumns++, COL_PWD_WARN);
1456 add_column(columns, ncolumns++, COL_PWD_CTIME_MIN);
1457 add_column(columns, ncolumns++, COL_PWD_CTIME_MAX);
1458 add_column(columns, ncolumns++, COL_PWD_CTIME);
1459 add_column(columns, ncolumns++, COL_PWD_EXPIR);
1460 break;
1461 case 'c':
1462 outmode = OUT_COLON;
1463 break;
1464 case 'e':
1465 outmode = OUT_EXPORT;
1466 break;
1467 case 'f':
1468 add_column(columns, ncolumns++, COL_FAILED_LOGIN);
1469 add_column(columns, ncolumns++, COL_FAILED_TTY);
1470 break;
1471 case 'G':
1472 add_column(columns, ncolumns++, COL_GID);
1473 add_column(columns, ncolumns++, COL_GROUP);
1474 add_column(columns, ncolumns++, COL_SGIDS);
1475 add_column(columns, ncolumns++, COL_SGROUPS);
1476 break;
1477 case 'g':
1478 groups = optarg;
1479 break;
1480 case 'h':
1481 usage();
1482 break;
1483 case 'L':
1484 add_column(columns, ncolumns++, COL_LAST_TTY);
1485 add_column(columns, ncolumns++, COL_LAST_HOSTNAME);
1486 add_column(columns, ncolumns++, COL_LAST_LOGIN);
1487 break;
1488 case 'l':
1489 logins = optarg;
1490 break;
1491 case 'n':
1492 outmode = OUT_NEWLINE;
1493 break;
1494 case 'o':
1495 if (*optarg == '=')
1496 optarg++;
1497 outarg = optarg;
1498 break;
1499 case OPT_OUTPUT_ALL:
1500 for (ncolumns = 0; ncolumns < ARRAY_SIZE(coldescs); ncolumns++)
1501 columns[ncolumns] = ncolumns;
1502 break;
1503 case 'r':
1504 outmode = OUT_RAW;
1505 break;
1506 case 's':
1507 ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN);
1508 ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX);
1509 lslogins_flag |= F_SYSAC;
1510 break;
1511 case 'u':
1512 ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN);
1513 ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX);
1514 lslogins_flag |= F_USRAC;
1515 break;
1516 case 'p':
1517 add_column(columns, ncolumns++, COL_PWDEMPTY);
1518 add_column(columns, ncolumns++, COL_PWDLOCK);
1519 add_column(columns, ncolumns++, COL_PWDDENY);
1520 add_column(columns, ncolumns++, COL_NOLOGIN);
1521 add_column(columns, ncolumns++, COL_HUSH_STATUS);
1522 add_column(columns, ncolumns++, COL_PWDMETHOD);
1523 break;
1524 case 'z':
1525 outmode = OUT_NUL;
1526 break;
1527 case OPT_WTMP:
1528 path_wtmp = optarg;
1529 break;
1530 case OPT_BTMP:
1531 path_btmp = optarg;
1532 break;
1533 case OPT_NOTRUNC:
1534 ctl->notrunc = 1;
1535 break;
1536 case OPT_NOHEAD:
1537 ctl->noheadings = 1;
1538 break;
1539 case OPT_TIME_FMT:
1540 ctl->time_mode = parse_time_mode(optarg);
1541 break;
1542 case 'V':
1543 print_version(EXIT_SUCCESS);
1544 case 'Z':
1545 {
1546 #ifdef HAVE_LIBSELINUX
1547 int sl = is_selinux_enabled();
1548 if (sl < 0)
1549 warn(_("failed to request selinux state"));
1550 else
1551 ctl->selinux_enabled = sl == 1;
1552 #endif
1553 add_column(columns, ncolumns++, COL_SELINUX);
1554 break;
1555 }
1556 default:
1557 errtryhelp(EXIT_FAILURE);
1558 }
1559 }
1560
1561 if (argc - optind == 1) {
1562 if (strchr(argv[optind], ','))
1563 errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
1564 logins = argv[optind];
1565 outmode = OUT_PRETTY;
1566 ctl->fail_on_unknown = 1;
1567 } else if (argc != optind)
1568 errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
1569
1570 scols_init_debug(0);
1571
1572 /* lslogins -u -s == lslogins */
1573 if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
1574 lslogins_flag &= ~(F_USRAC | F_SYSAC);
1575
1576 if (outmode == OUT_PRETTY) {
1577 /* all columns for lslogins <username> */
1578 for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
1579 columns[ncolumns++] = i;
1580
1581 } else if (ncolumns == 2) {
1582 /* default columns */
1583 add_column(columns, ncolumns++, COL_NPROCS);
1584 add_column(columns, ncolumns++, COL_PWDLOCK);
1585 add_column(columns, ncolumns++, COL_PWDDENY);
1586 add_column(columns, ncolumns++, COL_LAST_LOGIN);
1587 add_column(columns, ncolumns++, COL_GECOS);
1588 }
1589
1590 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
1591 &ncolumns, column_name_to_id) < 0)
1592 return EXIT_FAILURE;
1593
1594 if (require_wtmp())
1595 parse_wtmp(ctl, path_wtmp);
1596 if (require_btmp())
1597 parse_btmp(ctl, path_btmp);
1598
1599 if (logins || groups)
1600 get_ulist(ctl, logins, groups);
1601
1602 if (create_usertree(ctl))
1603 return EXIT_FAILURE;
1604
1605 print_user_table(ctl);
1606
1607 scols_unref_table(tb);
1608 tdestroy(ctl->usertree, free_user);
1609 free_ctl(ctl);
1610
1611 return EXIT_SUCCESS;
1612 }