]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/lslogins.c
lslogins: sync man page and usage()
[thirdparty/util-linux.git] / login-utils / lslogins.c
1 /*
2 * lslogins - List information about users on the system
3 *
4 * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
5 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it would be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/syslog.h>
29 #include <pwd.h>
30 #include <grp.h>
31 #include <shadow.h>
32 #include <paths.h>
33 #include <time.h>
34 #include <utmp.h>
35 #include <signal.h>
36 #include <err.h>
37 #include <limits.h>
38
39 #include <search.h>
40
41 #include <libsmartcols.h>
42 #ifdef HAVE_LIBSELINUX
43 # include <selinux/selinux.h>
44 #endif
45
46 #include "c.h"
47 #include "nls.h"
48 #include "closestream.h"
49 #include "xalloc.h"
50 #include "list.h"
51 #include "strutils.h"
52 #include "optutils.h"
53 #include "pathnames.h"
54 #include "logindefs.h"
55 #include "readutmp.h"
56
57 /*
58 * column description
59 */
60 struct lslogins_coldesc {
61 const char *name;
62 const char *help;
63 const char *pretty_name;
64
65 double whint; /* width hint */
66 long flag;
67 };
68
69 static int lslogins_flag;
70
71 #define UL_UID_MIN 1000
72 #define UL_UID_MAX 60000
73 #define UL_SYS_UID_MIN 201
74 #define UL_SYS_UID_MAX 999
75
76 /* we use the value of outmode to determine
77 * appropriate flags for the libsmartcols table
78 * (e.g., a value of out_newline would imply a raw
79 * table with the column separator set to '\n').
80 */
81 static int outmode;
82 /*
83 * output modes
84 */
85 enum {
86 OUT_COLON = 1,
87 OUT_EXPORT,
88 OUT_NEWLINE,
89 OUT_RAW,
90 OUT_NUL,
91 OUT_PRETTY
92 };
93
94 struct lslogins_user {
95 char *login;
96 uid_t uid;
97 char *group;
98 gid_t gid;
99 char *gecos;
100
101 int nopasswd;
102 int nologin;
103 int locked;
104
105 gid_t *sgroups;
106 size_t nsgroups;
107
108 char *pwd_ctime;
109 char *pwd_warn;
110 char *pwd_expire;
111 char *pwd_ctime_min;
112 char *pwd_ctime_max;
113
114 char *last_login;
115 char *last_tty;
116 char *last_hostname;
117
118 char *failed_login;
119 char *failed_tty;
120
121 #ifdef HAVE_LIBSELINUX
122 security_context_t context;
123 #endif
124 char *homedir;
125 char *shell;
126 char *pwd_status;
127 int hushed;
128
129 };
130
131 /*
132 * time modes
133 * */
134 enum {
135 TIME_INVALID = 0,
136 TIME_SHORT_RELATIVE,
137 TIME_SHORT,
138 TIME_FULL,
139 TIME_ISO,
140 };
141
142 /*
143 * flags
144 */
145 enum {
146 F_EXPIR = (1 << 0),
147 F_MORE = (1 << 1),
148 F_NOPWD = (1 << 2),
149 F_SYSAC = (1 << 3),
150 F_USRAC = (1 << 4),
151 F_SORT = (1 << 5),
152 F_EXTRA = (1 << 6),
153 F_FAIL = (1 << 7),
154 F_LAST = (1 << 8),
155 F_SELINUX = (1 << 9),
156 };
157
158 /*
159 * IDs
160 */
161 enum {
162 COL_LOGIN = 0,
163 COL_UID,
164 COL_GECOS,
165 COL_HOME,
166 COL_SHELL,
167 COL_PGRP,
168 COL_PGID,
169 COL_SGRPS,
170 COL_SGIDS,
171 COL_LAST_LOGIN,
172 COL_LAST_TTY,
173 COL_LAST_HOSTNAME,
174 COL_FAILED_LOGIN,
175 COL_FAILED_TTY,
176 COL_HUSH_STATUS,
177 COL_NOLOGIN,
178 COL_LOCKED,
179 COL_NOPASSWD,
180 COL_PWD_WARN,
181 COL_PWD_CTIME,
182 COL_PWD_CTIME_MIN,
183 COL_PWD_CTIME_MAX,
184 COL_PWD_EXPIR,
185 COL_SELINUX,
186 };
187
188 enum {
189 STATUS_FALSE = 0,
190 STATUS_TRUE,
191 STATUS_UNKNOWN
192 };
193
194 static const char *const status[] = {
195 [STATUS_FALSE] = "0",
196 [STATUS_TRUE] = "1",
197 [STATUS_UNKNOWN]= NULL
198 };
199
200 static const char *const pretty_status[] = {
201 [STATUS_FALSE] = N_("no"),
202 [STATUS_TRUE] = N_("yes"),
203 [STATUS_UNKNOWN]= NULL
204 };
205
206 #define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
207
208 static struct lslogins_coldesc coldescs[] =
209 {
210 [COL_LOGIN] = { "LOGIN", N_("user/system login"), N_("Login"), 0.2, SCOLS_FL_NOEXTREMES },
211 [COL_UID] = { "UID", N_("user ID"), "UID", 0.05, SCOLS_FL_RIGHT},
212 [COL_NOPASSWD] = { "NOPASSWD", N_("account has a password?"), N_("No password"), 1, SCOLS_FL_RIGHT },
213 [COL_NOLOGIN] = { "NOLOGIN", N_("account has a password?"), N_("No login"), 1, SCOLS_FL_RIGHT },
214 [COL_LOCKED] = { "LOCKED", N_("account has a password?"), N_("Locked"), 1, SCOLS_FL_RIGHT },
215 [COL_PGRP] = { "GROUP", N_("primary group name"), N_("Primary group"), 0.2 },
216 [COL_PGID] = { "GID", N_("primary group ID"), "GID", 0.05, SCOLS_FL_RIGHT },
217 [COL_SGRPS] = { "SUPP-GROUPS", N_("supplementary group names"), N_("Supplementary groups"), 0.1 },
218 [COL_SGIDS] = { "SUPP-GIDS", N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 },
219 [COL_HOME] = { "HOMEDIR", N_("home directory"), N_("Home directory"), 0.3 },
220 [COL_SHELL] = { "SHELL", N_("login shell"), N_("Shell"), 0.1 },
221 [COL_GECOS] = { "GECOS", N_("full user name"), N_("Gecos field"), 0.3, SCOLS_FL_TRUNC },
222 [COL_LAST_LOGIN] = { "LAST-LOGIN", N_("date of last login"), N_("Last login"), 24 },
223 [COL_LAST_TTY] = { "LAST-TTY", N_("last tty used"), N_("Last terminal"), 0.05 },
224 [COL_LAST_HOSTNAME] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"), 0.2},
225 [COL_FAILED_LOGIN] = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 24 },
226 [COL_FAILED_TTY] = { "FAILED-TTY", N_("where did the login fail?"), N_("Failed login terminal"), 0.05 },
227 [COL_HUSH_STATUS] = { "HUSHED", N_("User's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT },
228 [COL_PWD_WARN] = { "PWD-WARN", N_("password warn interval"), N_("Days to passwd warning"), 24 },
229 [COL_PWD_EXPIR] = { "PWD-EXPIR", N_("password expiration date"), N_("Password expiration"), 24 },
230 [COL_PWD_CTIME] = { "PWD-CHANGE", N_("date of last password change"), N_("Password changed"), 24 },
231 [COL_PWD_CTIME_MIN] = { "PWD-MIN", N_("number of days required between changes"), N_("Minimal change time"), 24 },
232 [COL_PWD_CTIME_MAX] = { "PWD-MAX", N_("max number of days a password may remain unchanged"), N_("Maximal change time"), 24 },
233 [COL_SELINUX] = { "CONTEXT", N_("the user's security context"), N_("Selinux context"), 0.4 },
234 };
235
236 struct lslogins_control {
237 struct utmp *wtmp;
238 size_t wtmp_size;
239
240 struct utmp *btmp;
241 size_t btmp_size;
242
243 void *usertree;
244
245 uid_t UID_MIN;
246 uid_t UID_MAX;
247
248 uid_t SYS_UID_MIN;
249 uid_t SYS_UID_MAX;
250
251 int (*cmp_fn) (const void *a, const void *b);
252
253 char **ulist;
254 size_t ulsiz;
255
256 int sel_enabled;
257 unsigned int time_mode;
258 };
259 /* these have to remain global since there's no other
260 * reasonable way to pass them for each call of fill_table()
261 * via twalk() */
262 static struct libscols_table *tb;
263 static int columns[ARRAY_SIZE(coldescs)];
264 static int ncolumns;
265
266 static int date_is_today(time_t t)
267 {
268 struct timeval tv;
269 gettimeofday(&tv, NULL);
270 return t / 86400 == tv.tv_sec / 86400;
271 }
272
273 static int
274 column_name_to_id(const char *name, size_t namesz)
275 {
276 size_t i;
277
278 for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
279 const char *cn = coldescs[i].name;
280
281 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) {
282 return i;
283 }
284 }
285 warnx(_("unknown column: %s"), name);
286 return -1;
287 }
288 static char *make_time(int mode, time_t time)
289 {
290 char *s;
291 struct tm tm;
292 char buf[32] = {0};
293
294 localtime_r(&time, &tm);
295
296 switch(mode) {
297 case TIME_FULL:
298 asctime_r(&tm, buf);
299 if (*(s = buf + strlen(buf) - 1) == '\n')
300 *s = '\0';
301 break;
302 case TIME_SHORT_RELATIVE:
303 if (date_is_today(time))
304 strftime(buf, 32, "%H:%M:%S", &tm);
305 else /*fallthrough*/
306 case TIME_SHORT:
307 strftime(buf, 32, "%a %b %d %Y", &tm);
308 break;
309 case TIME_ISO:
310 strftime(buf, 32, "%Y-%m-%dT%H:%M:%S%z", &tm);
311 break;
312 default:
313 exit(1);
314 }
315 return xstrdup(buf);
316 }
317
318
319 static char *uidtostr(uid_t uid)
320 {
321 char *str_uid = NULL;
322 xasprintf(&str_uid, "%u", uid);
323 return str_uid;
324 }
325
326 static char *gidtostr(gid_t gid)
327 {
328 char *str_gid = NULL;
329 xasprintf(&str_gid, "%u", gid);
330 return str_gid;
331 }
332
333 static char *build_sgroups_string(gid_t *sgroups, size_t nsgroups, int want_names)
334 {
335 size_t n = 0, maxlen, len;
336 char *res, *p;
337
338 len = maxlen = nsgroups * 10;
339 res = p = xmalloc(maxlen);
340
341 while (n < nsgroups) {
342 int x;
343 again:
344 if (!want_names)
345 x = snprintf(p, len, "%u,", sgroups[n]);
346 else {
347 struct group *grp = getgrgid(sgroups[n]);
348 if (!grp) {
349 free(res);
350 return NULL;
351 }
352 x = snprintf(p, len, "%s,", grp->gr_name);
353 }
354
355 if (x < 0 || (size_t) x + 1 > len) {
356 size_t cur = p - res;
357
358 maxlen *= 2;
359 res = xrealloc(res, maxlen);
360 p = res + cur;
361 len = maxlen - cur;
362 goto again;
363 }
364
365 len -= x;
366 p += x;
367 ++n;
368 }
369
370 if (p > res)
371 *(p - 1) = '\0';
372
373 return res;
374 }
375
376 static struct utmp *get_last_wtmp(struct lslogins_control *ctl, const char *username)
377 {
378 size_t n = 0;
379 size_t len;
380
381 if (!username)
382 return NULL;
383
384 len = strlen(username);
385 n = ctl->wtmp_size - 1;
386 do {
387 if (!strncmp(username, ctl->wtmp[n].ut_user,
388 len < UT_NAMESIZE ? len : UT_NAMESIZE))
389 return ctl->wtmp + n;
390 } while (n--);
391 return NULL;
392
393 }
394
395 static struct utmp *get_last_btmp(struct lslogins_control *ctl, const char *username)
396 {
397 size_t n = 0;
398 size_t len;
399
400 if (!username)
401 return NULL;
402
403 len = strlen(username);
404 n = ctl->btmp_size - 1;
405 do {
406 if (!strncmp(username, ctl->btmp[n].ut_user,
407 len < UT_NAMESIZE ? len : UT_NAMESIZE))
408 return ctl->btmp + n;
409 }while (n--);
410 return NULL;
411
412 }
413
414 static int parse_wtmp(struct lslogins_control *ctl, char *path)
415 {
416 int rc = 0;
417
418 rc = read_utmp(path, &ctl->wtmp_size, &ctl->wtmp);
419 if (rc < 0 && errno != EACCES)
420 err(EXIT_FAILURE, "%s", path);
421 return rc;
422 }
423
424 static int parse_btmp(struct lslogins_control *ctl, char *path)
425 {
426 int rc = 0;
427
428 rc = read_utmp(path, &ctl->btmp_size, &ctl->btmp);
429 if (rc < 0 && errno != EACCES)
430 err(EXIT_FAILURE, "%s", path);
431 return rc;
432 }
433
434 static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
435 {
436 size_t n = 0;
437
438 *len = 0;
439 *list = NULL;
440
441 /* first let's get a supp. group count */
442 getgrouplist(pwd->pw_name, pwd->pw_gid, *list, (int *) len);
443 if (!*len)
444 return -1;
445
446 *list = xcalloc(1, *len * sizeof(gid_t));
447
448 /* now for the actual list of GIDs */
449 if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, (int *) len))
450 return -1;
451
452 /* getgroups also returns the user's primary GID - dispose of it */
453 while (n < *len) {
454 if ((*list)[n] == pwd->pw_gid)
455 break;
456 ++n;
457 }
458
459 (*list)[n] = (*list)[--(*len)];
460 return 0;
461 }
462
463 static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username)
464 {
465 struct lslogins_user *user;
466 struct passwd *pwd;
467 struct group *grp;
468 struct spwd *shadow;
469 struct utmp *user_wtmp = NULL, *user_btmp = NULL;
470 int n = 0;
471 time_t time;
472 uid_t uid;
473 errno = 0;
474
475 if (username)
476 pwd = getpwnam(username);
477 else
478 pwd = getpwent();
479
480 if (!pwd)
481 return NULL;
482
483 uid = pwd->pw_uid;
484 /* nfsnobody is an exception to the UID_MAX limit.
485 * This is "nobody" on some systems; the decisive
486 * point is the UID - 65534 */
487 if ((lslogins_flag & F_USRAC) &&
488 strcmp("nfsnobody", pwd->pw_name)) {
489 if (uid < ctl->UID_MIN || uid > ctl->UID_MAX) {
490 errno = EAGAIN;
491 return NULL;
492 }
493
494 } else if (lslogins_flag & F_SYSAC) {
495 if (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX) {
496 errno = EAGAIN;
497 return NULL;
498 }
499 }
500
501 user = xcalloc(1, sizeof(struct lslogins_user));
502
503 grp = getgrgid(pwd->pw_gid);
504 if (!grp)
505 return NULL;
506
507 if (ctl->wtmp)
508 user_wtmp = get_last_wtmp(ctl, pwd->pw_name);
509 if (ctl->btmp)
510 user_btmp = get_last_btmp(ctl, pwd->pw_name);
511
512 /* sufficient permissions to get a shadow entry? */
513 errno = 0;
514 lckpwdf();
515 shadow = getspnam(pwd->pw_name);
516 ulckpwdf();
517
518 if (!shadow) {
519 if (errno != EACCES)
520 err(EXIT_FAILURE, "%s", strerror(errno));
521 } else {
522 /* we want these dates in seconds */
523 shadow->sp_lstchg *= 86400;
524 shadow->sp_expire *= 86400;
525 }
526
527 while (n < ncolumns) {
528 switch (columns[n++]) {
529 case COL_LOGIN:
530 user->login = xstrdup(pwd->pw_name);
531 break;
532 case COL_UID:
533 user->uid = pwd->pw_uid;
534 break;
535 case COL_PGRP:
536 user->group = xstrdup(grp->gr_name);
537 break;
538 case COL_PGID:
539 user->gid = pwd->pw_gid;
540 break;
541 case COL_SGRPS:
542 case COL_SGIDS:
543 if (get_sgroups(&user->sgroups, &user->nsgroups, pwd))
544 err(EXIT_FAILURE, _("failed to get supplementary groups"));
545 break;
546 case COL_HOME:
547 user->homedir = xstrdup(pwd->pw_dir);
548 break;
549 case COL_SHELL:
550 user->shell = xstrdup(pwd->pw_shell);
551 break;
552 case COL_GECOS:
553 user->gecos = xstrdup(pwd->pw_gecos);
554 break;
555 case COL_LAST_LOGIN:
556 if (user_wtmp) {
557 time = user_wtmp->ut_tv.tv_sec;
558 user->last_login = make_time(ctl->time_mode, time);
559 }
560 break;
561 case COL_LAST_TTY:
562 if (user_wtmp)
563 user->last_tty = xstrdup(user_wtmp->ut_line);
564 break;
565 case COL_LAST_HOSTNAME:
566 if (user_wtmp)
567 user->last_hostname = xstrdup(user_wtmp->ut_host);
568 break;
569 case COL_FAILED_LOGIN:
570 if (user_btmp) {
571 time = user_btmp->ut_tv.tv_sec;
572 user->failed_login = make_time(ctl->time_mode, time);
573 }
574 break;
575 case COL_FAILED_TTY:
576 if (user_btmp)
577 user->failed_tty = xstrdup(user_btmp->ut_line);
578 break;
579 case COL_HUSH_STATUS:
580 user->hushed = get_hushlogin_status(pwd, 0);
581 if (user->hushed == -1)
582 user->hushed = STATUS_UNKNOWN;
583 break;
584 case COL_NOPASSWD:
585 if (shadow) {
586 if (!*shadow->sp_pwdp) /* '\0' */
587 user->nopasswd = STATUS_TRUE;
588 } else
589 user->nopasswd = STATUS_UNKNOWN;
590 break;
591 case COL_NOLOGIN:
592 if ((pwd->pw_uid && !(close(open("/etc/nologin", O_RDONLY)))) ||
593 strstr(pwd->pw_shell, "nologin")) {
594 user->nologin = 1;
595 }
596 break;
597 case COL_LOCKED:
598 if (shadow) {
599 if (*shadow->sp_pwdp == '!')
600 user->locked = STATUS_TRUE;
601 } else
602 user->locked = STATUS_UNKNOWN;
603 break;
604 case COL_PWD_WARN:
605 if (shadow && shadow->sp_warn >= 0)
606 xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
607 break;
608 case COL_PWD_EXPIR:
609 if (shadow && shadow->sp_expire >= 0)
610 user->pwd_expire = make_time(TIME_SHORT, shadow->sp_expire);
611 break;
612 case COL_PWD_CTIME:
613 /* sp_lstchg is specified in days, showing hours (especially in non-GMT
614 * timezones) would only serve to confuse */
615 if (shadow)
616 user->pwd_ctime = make_time(TIME_SHORT, shadow->sp_lstchg);
617 break;
618 case COL_PWD_CTIME_MIN:
619 if (shadow) {
620 if (shadow->sp_min <= 0)
621 user->pwd_ctime_min = xstrdup("unlimited");
622 else
623 xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
624 }
625 break;
626 case COL_PWD_CTIME_MAX:
627 if (shadow) {
628 if (shadow->sp_max <= 0)
629 user->pwd_ctime_max = xstrdup("unlimited");
630 else
631 xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
632 }
633 break;
634 case COL_SELINUX:
635 {
636 #ifdef HAVE_LIBSELINUX
637 /* typedefs and pointers are pure evil */
638 security_context_t con = NULL;
639 if (getcon(&con) == 0)
640 user->context = con;
641 #endif
642 }
643 break;
644 default:
645 /* something went very wrong here */
646 err(EXIT_FAILURE, "fatal: unknown error");
647 }
648 }
649 /* check if we have the info needed to sort */
650 if (lslogins_flag & F_SORT) { /* sorting by username */
651 if (!user->login)
652 user->login = xstrdup(pwd->pw_name);
653 } else /* sorting by UID */
654 user->uid = pwd->pw_uid;
655
656 return user;
657 }
658 /* some UNIX implementations set errno iff a passwd/grp/...
659 * entry was not found. The original UNIX logins(1) utility always
660 * ignores invalid login/group names, so we're going to as well.*/
661 #define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
662 (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
663
664 /*
665 static void *user_in_tree(void **rootp, struct lslogins_user *u)
666 {
667 void *rc;
668 rc = tfind(u, rootp, ctl->cmp_fn);
669 if (!rc)
670 tdelete(u, rootp, ctl->cmp_fn);
671 return rc;
672 }
673 */
674
675 /* get a definitive list of users we want info about... */
676
677 static int str_to_uint(char *s, unsigned int *ul)
678 {
679 char *end;
680 if (!s || !*s)
681 return -1;
682 *ul = strtoul(s, &end, 0);
683 if (!*end)
684 return 0;
685 return 1;
686 }
687
688 static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
689 {
690 char *u, *g;
691 size_t i = 0, n = 0, *arsiz;
692 struct group *grp;
693 struct passwd *pwd;
694 char ***ar;
695 uid_t uid;
696 gid_t gid;
697
698 ar = &ctl->ulist;
699 arsiz = &ctl->ulsiz;
700
701 /* an arbitrary starting value */
702 *arsiz = 32;
703 *ar = xcalloc(1, sizeof(char *) * (*arsiz));
704
705 while ((u = strtok(logins, ","))) {
706 logins = NULL;
707
708 /* user specified by UID? */
709 if (!str_to_uint(u, &uid)) {
710 pwd = getpwuid(uid);
711 if (!pwd)
712 continue;
713 u = pwd->pw_name;
714 }
715 (*ar)[i++] = xstrdup(u);
716
717 if (i == *arsiz)
718 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
719 }
720 /* FIXME: this might lead to duplicit entries, although not visible
721 * in output, crunching a user's info multiple times is very redundant */
722 while ((g = strtok(groups, ","))) {
723 groups = NULL;
724
725 /* user specified by GID? */
726 if (!str_to_uint(g, &gid))
727 grp = getgrgid(gid);
728 else
729 grp = getgrnam(g);
730
731 if (!grp)
732 continue;
733
734 while ((u = grp->gr_mem[n++])) {
735 (*ar)[i++] = xstrdup(u);
736
737 if (i == *arsiz)
738 *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
739 }
740 }
741 *arsiz = i;
742 return 0;
743 }
744
745 static void free_ctl(struct lslogins_control *ctl)
746 {
747 size_t n = 0;
748
749 free(ctl->wtmp);
750 free(ctl->btmp);
751
752 while (n < ctl->ulsiz)
753 free(ctl->ulist[n++]);
754
755 free(ctl->ulist);
756 free(ctl);
757 }
758
759 static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
760 {
761 struct lslogins_user *u;
762 errno = 0;
763 while (!(u = get_user_info(ctl, NULL))) {
764 /* no "false" errno-s here, iff we're unable to
765 * get a valid user entry for any reason, quit */
766 if (errno == EAGAIN)
767 continue;
768 return NULL;
769 }
770 return u;
771 }
772
773 static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
774 const char *username)
775 {
776 *user = get_user_info(ctl, username);
777 if (!*user && errno)
778 if (IS_REAL_ERRNO(errno))
779 return -1;
780 return 0;
781 }
782
783 static int create_usertree(struct lslogins_control *ctl)
784 {
785 struct lslogins_user *user = NULL;
786 size_t n = 0;
787
788 if (*ctl->ulist) {
789 while (n < ctl->ulsiz) {
790 if (get_user(ctl, &user, ctl->ulist[n]))
791 return -1;
792 if (user) /* otherwise an invalid user name has probably been given */
793 tsearch(user, &ctl->usertree, ctl->cmp_fn);
794 ++n;
795 }
796 } else {
797 while ((user = get_next_user(ctl)))
798 tsearch(user, &ctl->usertree, ctl->cmp_fn);
799 }
800 return 0;
801 }
802
803 static int cmp_uname(const void *a, const void *b)
804 {
805 return strcmp(((struct lslogins_user *)a)->login,
806 ((struct lslogins_user *)b)->login);
807 }
808
809 static int cmp_uid(const void *a, const void *b)
810 {
811 uid_t x = ((struct lslogins_user *)a)->uid;
812 uid_t z = ((struct lslogins_user *)b)->uid;
813 return x > z ? 1 : (x < z ? -1 : 0);
814 }
815
816 static struct libscols_table *setup_table(void)
817 {
818 struct libscols_table *tb = scols_new_table();
819 int n = 0;
820 if (!tb)
821 return NULL;
822
823 switch(outmode) {
824 case OUT_COLON:
825 scols_table_enable_raw(tb, 1);
826 scols_table_set_column_separator(tb, ":");
827 break;
828 case OUT_NEWLINE:
829 scols_table_set_column_separator(tb, "\n");
830 /* fallthrough */
831 case OUT_EXPORT:
832 scols_table_enable_export(tb, 1);
833 break;
834 case OUT_NUL:
835 scols_table_set_line_separator(tb, "\0");
836 /* fallthrough */
837 case OUT_RAW:
838 scols_table_enable_raw(tb, 1);
839 break;
840 case OUT_PRETTY:
841 scols_table_enable_noheadings(tb, 1);
842 default:
843 break;
844 }
845
846 while (n < ncolumns) {
847 if (!scols_table_new_column(tb, coldescs[columns[n]].name,
848 coldescs[columns[n]].whint, coldescs[columns[n]].flag))
849 goto fail;
850 ++n;
851 }
852
853 return tb;
854 fail:
855 scols_unref_table(tb);
856 return NULL;
857 }
858
859 static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
860 {
861 struct libscols_line *ln;
862 struct lslogins_user *user = *(struct lslogins_user **)u;
863 int n = 0;
864
865 if (which == preorder || which == endorder)
866 return;
867
868 ln = scols_table_new_line(tb, NULL);
869 while (n < ncolumns) {
870 int rc = 0;
871
872 switch (columns[n]) {
873 case COL_LOGIN:
874 rc = scols_line_set_data(ln, n, user->login);
875 break;
876 case COL_UID:
877 rc = scols_line_refer_data(ln, n, uidtostr(user->uid));
878 break;
879 case COL_NOPASSWD:
880 rc = scols_line_set_data(ln, n, get_status(user->nopasswd));
881 break;
882 case COL_NOLOGIN:
883 rc = scols_line_set_data(ln, n, get_status(user->nologin));
884 break;
885 case COL_LOCKED:
886 rc = scols_line_set_data(ln, n, get_status(user->locked));
887 break;
888 case COL_PGRP:
889 rc = scols_line_set_data(ln, n, user->group);
890 break;
891 case COL_PGID:
892 rc = scols_line_refer_data(ln, n, gidtostr(user->gid));
893 break;
894 case COL_SGRPS:
895 rc = scols_line_refer_data(ln, n,
896 build_sgroups_string(user->sgroups,
897 user->nsgroups,
898 TRUE));
899 break;
900 case COL_SGIDS:
901 rc = scols_line_refer_data(ln, n,
902 build_sgroups_string(user->sgroups,
903 user->nsgroups,
904 FALSE));
905 break;
906 case COL_HOME:
907 rc = scols_line_set_data(ln, n, user->homedir);
908 break;
909 case COL_SHELL:
910 rc = scols_line_set_data(ln, n, user->shell);
911 break;
912 case COL_GECOS:
913 rc = scols_line_set_data(ln, n, user->gecos);
914 break;
915 case COL_LAST_LOGIN:
916 rc = scols_line_set_data(ln, n, user->last_login);
917 break;
918 case COL_LAST_TTY:
919 rc = scols_line_set_data(ln, n, user->last_tty);
920 break;
921 case COL_LAST_HOSTNAME:
922 rc = scols_line_set_data(ln, n, user->last_hostname);
923 break;
924 case COL_FAILED_LOGIN:
925 rc = scols_line_set_data(ln, n, user->failed_login);
926 break;
927 case COL_FAILED_TTY:
928 rc = scols_line_set_data(ln, n, user->failed_tty);
929 break;
930 case COL_HUSH_STATUS:
931 rc = scols_line_set_data(ln, n, get_status(user->hushed));
932 break;
933 case COL_PWD_WARN:
934 rc = scols_line_set_data(ln, n, user->pwd_warn);
935 break;
936 case COL_PWD_EXPIR:
937 rc = scols_line_set_data(ln, n, user->pwd_expire);
938 break;
939 case COL_PWD_CTIME:
940 rc = scols_line_set_data(ln, n, user->pwd_ctime);
941 break;
942 case COL_PWD_CTIME_MIN:
943 rc = scols_line_set_data(ln, n, user->pwd_ctime_min);
944 break;
945 case COL_PWD_CTIME_MAX:
946 rc = scols_line_set_data(ln, n, user->pwd_ctime_max);
947 break;
948 #ifdef HAVE_LIBSELINUX
949 case COL_SELINUX:
950 rc = scols_line_set_data(ln, n, user->context);
951 break;
952 #endif
953 default:
954 /* something went very wrong here */
955 err(EXIT_FAILURE, _("internal error: unknown column"));
956 }
957
958 if (rc != 0)
959 err(EXIT_FAILURE, _("failed to set data"));
960 ++n;
961 }
962 return;
963 }
964
965 static int print_pretty(struct libscols_table *tb)
966 {
967 struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
968 struct libscols_column *col;
969 struct libscols_cell *data;
970 struct libscols_line *ln;
971 const char *hstr, *dstr;
972 int n = 0;
973
974 ln = scols_table_get_line(tb, 0);
975 while (!scols_table_next_column(tb, itr, &col)) {
976
977 data = scols_line_get_cell(ln, n);
978
979 hstr = _(coldescs[columns[n]].pretty_name);
980 dstr = scols_cell_get_data(data);
981
982 if (dstr)
983 printf("%s:%*c%-36s\n", hstr, 26 - (int)strlen(hstr), ' ', dstr);
984 ++n;
985 }
986
987 scols_free_iter(itr);
988 return 0;
989
990 }
991
992 static int print_user_table(struct lslogins_control *ctl)
993 {
994 tb = setup_table();
995 if (!tb)
996 return -1;
997
998 twalk(ctl->usertree, fill_table);
999 if (outmode == OUT_PRETTY)
1000 print_pretty(tb);
1001 else
1002 scols_print_table(tb);
1003 return 0;
1004 }
1005
1006 static void free_user(void *f)
1007 {
1008 struct lslogins_user *u = f;
1009 free(u->login);
1010 free(u->group);
1011 free(u->gecos);
1012 free(u->sgroups);
1013 free(u->pwd_ctime);
1014 free(u->pwd_warn);
1015 free(u->pwd_ctime_min);
1016 free(u->pwd_ctime_max);
1017 free(u->last_login);
1018 free(u->last_tty);
1019 free(u->last_hostname);
1020 free(u->failed_login);
1021 free(u->failed_tty);
1022 free(u->homedir);
1023 free(u->shell);
1024 free(u->pwd_status);
1025 #ifdef HAVE_LIBSELINUX
1026 freecon(u->context);
1027 #endif
1028 free(u);
1029 }
1030
1031 struct lslogins_timefmt {
1032 const char *name;
1033 int val;
1034 };
1035
1036 static struct lslogins_timefmt timefmts[] = {
1037 { "short", TIME_SHORT_RELATIVE },
1038 { "full", TIME_FULL },
1039 { "iso", TIME_ISO },
1040 };
1041
1042 static void __attribute__((__noreturn__)) usage(FILE *out)
1043 {
1044 size_t i;
1045
1046 fputs(USAGE_HEADER, out);
1047 fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
1048
1049 fputs(USAGE_OPTIONS, out);
1050 fputs(_(" -a, --acc-expiration Display data\n"), out);
1051 fputs(_(" -c, --colon-separate Display data in a format similar to /etc/passwd\n"), out);
1052 fputs(_(" -e, --export Display in an export-able output format\n"), out);
1053 fputs(_(" -f, --failed Display data about the last users' failed logins\n"), out);
1054 fputs(_(" --fulltimes Show dates in a long format\n"), out);
1055 fputs(_(" -g, --groups=<groups> Display users belonging to a group in <groups>\n"), out);
1056 fputs(_(" -l, --logins=<logins> Display only users from <logins>\n"), out);
1057 fputs(_(" --last Show info about the users' last login sessions\n"), out);
1058 fputs(_(" -m, --supp-groups Display supplementary groups as well\n"), out);
1059 fputs(_(" -n, --newline Display each piece of information on a new line\n"), out);
1060 fputs(_(" --notruncate Don't truncate output\n"), out);
1061 fputs(_(" -o, --output[=<list>] Define the columns to output\n"), out);
1062 fputs(_(" -r, --raw Display the raw table\n"), out);
1063 fputs(_(" -s, --system-accs Display system accounts\n"), out);
1064 fputs(_(" -t, --sort-by-name Sort output by login instead of UID\n"), out);
1065 fputs(_(" --time-format=<type> Display dates in type <type>, where type is one of short|full|iso\n"), out);
1066 fputs(_(" -u, --user-accs Display user accounts\n"), out);
1067 fputs(_(" -x, --extra Display extra information\n"), out);
1068 fputs(_(" -z, --print0 Delimit user entries with a nul character\n"), out);
1069 fputs(_(" -Z, --context Display the users' security context\n"), out);
1070 fputs(_(" --wtmp-file <path> Set an alternate path for wtmp\n"), out);
1071 fputs(_(" --btmp-file <path> Set an alternate path for btmp\n"), out);
1072 fputs(USAGE_SEPARATOR, out);
1073 fputs(USAGE_HELP, out);
1074 fputs(USAGE_VERSION, out);
1075
1076 fprintf(out, _("\nAvailable columns:\n"));
1077
1078 for (i = 0; i < ARRAY_SIZE(coldescs); i++)
1079 fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
1080
1081 fprintf(out, _("\nFor more details see lslogins(1).\n"));
1082
1083 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
1084 }
1085
1086 int main(int argc, char *argv[])
1087 {
1088 int c, want_wtmp = 0, want_btmp = 0;
1089 char *logins = NULL, *groups = NULL;
1090 char *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP;
1091 struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control));
1092
1093 /* long only options. */
1094 enum {
1095 OPT_LAST = CHAR_MAX + 1,
1096 OPT_VER,
1097 OPT_WTMP,
1098 OPT_BTMP,
1099 OPT_NOTRUNC,
1100 OPT_FULLT,
1101 OPT_TIME_FMT,
1102 };
1103
1104 static const struct option longopts[] = {
1105 { "acc-expiration", no_argument, 0, 'a' },
1106 { "colon-separate", no_argument, 0, 'c' },
1107 { "export", no_argument, 0, 'e' },
1108 { "failed", no_argument, 0, 'f' },
1109 { "fulltimes", no_argument, 0, OPT_FULLT },
1110 { "groups", required_argument, 0, 'g' },
1111 { "help", no_argument, 0, 'h' },
1112 { "logins", required_argument, 0, 'l' },
1113 { "supp-groups", no_argument, 0, 'm' },
1114 { "newline", no_argument, 0, 'n' },
1115 { "notruncate", no_argument, 0, OPT_NOTRUNC },
1116 { "output", required_argument, 0, 'o' },
1117 { "last", no_argument, 0, OPT_LAST },
1118 { "raw", no_argument, 0, 'r' },
1119 { "system-accs", no_argument, 0, 's' },
1120 { "sort-by-name", no_argument, 0, 't' },
1121 { "time-format", required_argument, 0, OPT_TIME_FMT },
1122 { "user-accs", no_argument, 0, 'u' },
1123 { "version", no_argument, 0, OPT_VER },
1124 { "extra", no_argument, 0, 'x' },
1125 { "print0", no_argument, 0, 'z' },
1126 /* TODO: find a reasonable way to do this for passwd/group/shadow,
1127 * as libc itself doesn't supply any way to get a specific
1128 * entry from a user-specified file */
1129 { "wtmp-file", required_argument, 0, OPT_WTMP },
1130 { "btmp-file", required_argument, 0, OPT_BTMP },
1131 #ifdef HAVE_LIBSELINUX
1132 { "context", no_argument, 0, 'Z' },
1133 #endif
1134 { NULL, 0, 0, 0 }
1135 };
1136
1137 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1138 { 'c','e','n','r','z' },
1139 { 'i', OPT_FULLT, OPT_TIME_FMT },
1140 { 0 }
1141 };
1142 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1143
1144 setlocale(LC_ALL, "");
1145 bindtextdomain(PACKAGE, LOCALEDIR);
1146 textdomain(PACKAGE);
1147 atexit(close_stdout);
1148
1149 ctl->cmp_fn = cmp_uid;
1150 ctl->time_mode = TIME_SHORT_RELATIVE;
1151
1152 while ((c = getopt_long(argc, argv, "acefg:hl:mno:rstuxzZ",
1153 longopts, NULL)) != -1) {
1154
1155 err_exclusive_options(c, longopts, excl, excl_st);
1156
1157 switch (c) {
1158 case 'a':
1159 lslogins_flag |= F_EXPIR;
1160 break;
1161 case 'c':
1162 outmode = OUT_COLON;
1163 break;
1164 case 'e':
1165 outmode = OUT_EXPORT;
1166 break;
1167 case 'f':
1168 lslogins_flag |= F_FAIL;
1169 break;
1170 case 'g':
1171 groups = optarg;
1172 break;
1173 case 'h':
1174 usage(stdout);
1175 break;
1176 case 'l':
1177 logins = optarg;
1178 break;
1179 case 'm':
1180 lslogins_flag |= F_MORE;
1181 break;
1182 case 'n':
1183 outmode = OUT_NEWLINE;
1184 break;
1185 case 'o':
1186 if (optarg) {
1187 if (*optarg == '=')
1188 optarg++;
1189 ncolumns = string_to_idarray(optarg,
1190 columns, ARRAY_SIZE(columns),
1191 column_name_to_id);
1192 if (ncolumns < 0)
1193 return EXIT_FAILURE;
1194 }
1195 break;
1196 case 'r':
1197 outmode = OUT_RAW;
1198 break;
1199 case OPT_LAST:
1200 lslogins_flag |= F_LAST;
1201 break;
1202 case 's':
1203 ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN);
1204 ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX);
1205 lslogins_flag |= F_SYSAC;
1206 break;
1207 case 't':
1208 ctl->cmp_fn = cmp_uname;
1209 lslogins_flag |= F_SORT;
1210 break;
1211 case 'u':
1212 ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN);
1213 ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX);
1214 lslogins_flag |= F_USRAC;
1215 break;
1216 case OPT_VER:
1217 printf(_("%s from %s\n"), program_invocation_short_name,
1218 PACKAGE_STRING);
1219 return EXIT_SUCCESS;
1220 case 'x':
1221 lslogins_flag |= F_EXTRA;
1222 break;
1223 case 'z':
1224 outmode = OUT_NUL;
1225 break;
1226 case OPT_WTMP:
1227 path_wtmp = optarg;
1228 break;
1229 case OPT_BTMP:
1230 path_btmp = optarg;
1231 break;
1232 case OPT_NOTRUNC:
1233 coldescs[COL_GECOS].flag = 0;
1234 break;
1235 case OPT_FULLT:
1236 ctl->time_mode = TIME_FULL;
1237 break;
1238 case OPT_TIME_FMT:
1239 {
1240 size_t i;
1241
1242 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
1243 if (strcmp(timefmts[i].name, optarg) == 0) {
1244 ctl->time_mode = timefmts[i].val;
1245 break;
1246 }
1247 }
1248 if (ctl->time_mode == TIME_INVALID)
1249 usage(stderr);
1250 }
1251 break;
1252 case 'Z':
1253 #ifdef HAVE_LIBSELINUX
1254 lslogins_flag |= F_SELINUX;
1255 ctl->sel_enabled = is_selinux_enabled();
1256 if (ctl->sel_enabled == -1)
1257 exit(1);
1258 #endif
1259 break;
1260 default:
1261 usage(stderr);
1262 }
1263 }
1264
1265 if (argc - optind == 1) {
1266 if (strchr(argv[optind], ','))
1267 errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
1268 logins = argv[optind];
1269 outmode = OUT_PRETTY;
1270 } else if (argc != optind)
1271 usage(stderr);
1272
1273 /* lslogins -u -s == lslogins */
1274 if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
1275 lslogins_flag &= ~(F_USRAC | F_SYSAC);
1276
1277 if (!ncolumns && outmode == OUT_PRETTY) {
1278 size_t i;
1279 want_wtmp = 1;
1280
1281 for (i = 0; i < ARRAY_SIZE(coldescs); i++)
1282 columns[ncolumns++] = i;
1283
1284 } else if (!ncolumns) {
1285 if (lslogins_flag & F_SORT) {
1286 columns[ncolumns++] = COL_LOGIN;
1287 columns[ncolumns++] = COL_UID;
1288 } else {
1289 columns[ncolumns++] = COL_UID;
1290 columns[ncolumns++] = COL_LOGIN;
1291 }
1292 columns[ncolumns++] = COL_PGRP;
1293 columns[ncolumns++] = COL_PGID;
1294 columns[ncolumns++] = COL_LAST_LOGIN;
1295
1296 want_wtmp = 1;
1297
1298 if (lslogins_flag & F_NOPWD)
1299 columns[ncolumns++] = COL_NOPASSWD;
1300 if (lslogins_flag & F_MORE)
1301 columns[ncolumns++] = COL_SGRPS;
1302 if (lslogins_flag & F_EXPIR) {
1303 columns[ncolumns++] = COL_PWD_CTIME;
1304 columns[ncolumns++] = COL_PWD_EXPIR;
1305 }
1306 if (lslogins_flag & F_LAST) {
1307 columns[ncolumns++] = COL_LAST_TTY;
1308 columns[ncolumns++] = COL_LAST_HOSTNAME;
1309 }
1310 if (lslogins_flag & F_FAIL) {
1311 columns[ncolumns++] = COL_FAILED_LOGIN;
1312 columns[ncolumns++] = COL_FAILED_TTY;
1313 want_btmp = 1;
1314 }
1315 if (lslogins_flag & F_EXTRA) {
1316 columns[ncolumns++] = COL_HOME;
1317 columns[ncolumns++] = COL_SHELL;
1318 columns[ncolumns++] = COL_GECOS;
1319 columns[ncolumns++] = COL_NOPASSWD;
1320 columns[ncolumns++] = COL_NOLOGIN;
1321 columns[ncolumns++] = COL_LOCKED;
1322 columns[ncolumns++] = COL_HUSH_STATUS;
1323 columns[ncolumns++] = COL_PWD_WARN;
1324 columns[ncolumns++] = COL_PWD_CTIME_MIN; /*?*/
1325 columns[ncolumns++] = COL_PWD_CTIME_MAX; /*?*/
1326 }
1327 if (lslogins_flag & F_SELINUX)
1328 columns[ncolumns++] = COL_SELINUX;
1329 } else {
1330 int n = 0, i;
1331 while (n < ncolumns) {
1332 i = columns[n++];
1333 if (i <= COL_LAST_HOSTNAME && i >= COL_LAST_LOGIN)
1334 want_wtmp = 1;
1335 if (i == COL_FAILED_TTY && i >= COL_FAILED_LOGIN)
1336 want_btmp = 1;
1337 }
1338 }
1339
1340 if (want_wtmp)
1341 parse_wtmp(ctl, path_wtmp);
1342 if (want_btmp)
1343 parse_btmp(ctl, path_btmp);
1344
1345 get_ulist(ctl, logins, groups);
1346
1347 if (create_usertree(ctl))
1348 return EXIT_FAILURE;
1349
1350 print_user_table(ctl);
1351
1352 scols_unref_table(tb);
1353 tdestroy(ctl->usertree, free_user);
1354 free_ctl(ctl);
1355
1356
1357 return EXIT_SUCCESS;
1358 }