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