]>
Commit | Line | Data |
---|---|---|
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 | */ | |
65 | struct 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 | 74 | static 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 | */ | |
86 | static int outmode; | |
87 | /* | |
88 | * output modes | |
89 | */ | |
90 | enum { | |
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 | ||
99 | struct 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 | * */ | |
141 | enum { | |
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 | */ | |
151 | enum { | |
29cc2a55 OO |
152 | F_SYSAC = (1 << 3), |
153 | F_USRAC = (1 << 4), | |
ab1cfad5 OO |
154 | }; |
155 | ||
156 | /* | |
157 | * IDs | |
158 | */ | |
159 | enum { | |
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 |
195 | enum { |
196 | STATUS_FALSE = 0, | |
197 | STATUS_TRUE, | |
198 | STATUS_UNKNOWN | |
199 | }; | |
200 | ||
201 | static const char *const status[] = { | |
202 | [STATUS_FALSE] = "0", | |
203 | [STATUS_TRUE] = "1", | |
204 | [STATUS_UNKNOWN]= NULL | |
205 | }; | |
206 | ||
10c74524 KZ |
207 | static 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 | 215 | static 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 |
245 | struct 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 | 275 | static 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 */ | |
281 | static int columns[ARRAY_SIZE(coldescs) * 2]; | |
c6cf4d1c | 282 | static int ncolumns; |
dd7760a8 | 283 | |
87a144a5 KZ |
284 | static 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 |
296 | static struct timeval now; |
297 | ||
38496592 OO |
298 | static 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 | ||
305 | static 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 | 312 | static 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 | 326 | static 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 | |
358 | static 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 | ||
365 | static 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 | 372 | static 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; | |
385 | again: | |
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 | 418 | static 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 |
437 | static 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 | ||
446 | static 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 | 455 | static 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 | 474 | static 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 |
484 | static 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 | 494 | static 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 |
524 | static 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 |
539 | static 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 | 549 | static 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 | |
745 | static 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 |
756 | static 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 | ||
813 | static 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 | ||
827 | static 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 |
841 | static 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 |
851 | static 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 | 858 | static 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 | 878 | static 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; | |
926 | fail: | |
927 | scols_unref_table(tb); | |
928 | return NULL; | |
929 | } | |
c6cf4d1c | 930 | |
dd7760a8 | 931 | static 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 |
1043 | static 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 | 1090 | done: |
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 |
1098 | static 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 | 1125 | static 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 | 1144 | static 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 |
1169 | struct lslogins_timefmt { |
1170 | const char *name; | |
1171 | int val; | |
1172 | }; | |
8b5e2279 | 1173 | |
c20ffb8f | 1174 | static struct lslogins_timefmt timefmts[] = { |
ea24eacc | 1175 | { "short", TIME_SHORT }, |
c20ffb8f OO |
1176 | { "full", TIME_FULL }, |
1177 | { "iso", TIME_ISO }, | |
1178 | }; | |
c6cf4d1c | 1179 | |
512abd56 KZ |
1180 | static 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 |
1225 | int 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 | } |