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