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