You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2131 lines
57 KiB
2131 lines
57 KiB
diff -up util-linux-2.23.2/configure.ac.kzak util-linux-2.23.2/configure.ac |
|
--- util-linux-2.23.2/configure.ac.kzak 2014-12-12 15:27:43.505631342 +0100 |
|
+++ util-linux-2.23.2/configure.ac 2014-12-12 15:28:30.571177081 +0100 |
|
@@ -1027,6 +1027,11 @@ UL_REQUIRES_HAVE([lscpu], [cpu_set_t], [ |
|
AM_CONDITIONAL(BUILD_LSCPU, test "x$build_lscpu" = xyes) |
|
|
|
|
|
+UL_BUILD_INIT([lslogins], [check]) |
|
+UL_REQUIRES_BUILD([lslogins], [libsmartcols]) |
|
+AM_CONDITIONAL([BUILD_LSLOGINS], [test "x$build_lslogins" = xyes]) |
|
+ |
|
+ |
|
UL_BUILD_INIT([chcpu], [check]) |
|
UL_REQUIRES_LINUX([chcpu]) |
|
UL_REQUIRES_HAVE([chcpu], [cpu_set_t], [cpu_set_t type]) |
|
@@ -1404,6 +1409,37 @@ fi |
|
AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != "xno" ]) |
|
|
|
|
|
+# |
|
+# Backport from upstrem to RHEL7.1 |
|
+# |
|
+AC_ARG_WITH([systemd], |
|
+ AS_HELP_STRING([--with-systemd], [build with support for systemd]), |
|
+ [], [with_systemd=check] |
|
+) |
|
+ |
|
+have_systemd=no |
|
+AS_IF([test "x$with_systemd" != xno], [ |
|
+ # new version -- all libsystemd-* libs merged into libsystemd |
|
+ PKG_CHECK_MODULES([SYSTEMD], [libsystemd], [have_systemd=yes], [have_systemd=no]) |
|
+ # old versions |
|
+ AS_IF([test "x$have_systemd" != "xyes"], [ |
|
+ PKG_CHECK_MODULES([SYSTEMD_DAEMON], [libsystemd-daemon], |
|
+ [have_systemd_daemon=yes], [have_systemd_daemon=no]) |
|
+ PKG_CHECK_MODULES([SYSTEMD_JOURNAL], [libsystemd-journal], |
|
+ [have_systemd_journal=yes], [have_systemd_journal=no]) |
|
+ AS_IF([test "x$have_systemd_daemon" = "xyes" -a "x$have_systemd_journal" = "xyes" ],[ |
|
+ have_systemd=yes]) |
|
+ ]) |
|
+ AS_CASE([$with_systemd:$have_systemd], |
|
+ [yes:no], |
|
+ [AC_MSG_ERROR([systemd expected but libsystemd not found])], |
|
+ [*:yes], |
|
+ AC_DEFINE([HAVE_LIBSYSTEMD], [1], [Define if libsystemd is available]) |
|
+ ) |
|
+]) |
|
+ |
|
+ |
|
+ |
|
AC_ARG_WITH([bashcompletiondir], |
|
AS_HELP_STRING([--with-bashcompletiondir=DIR], [Bash completions directory]), |
|
[], |
|
diff -up util-linux-2.23.2/include/Makemodule.am.kzak util-linux-2.23.2/include/Makemodule.am |
|
--- util-linux-2.23.2/include/Makemodule.am.kzak 2013-07-15 10:25:46.277049008 +0200 |
|
+++ util-linux-2.23.2/include/Makemodule.am 2014-12-12 15:28:30.571177081 +0100 |
|
@@ -35,6 +35,7 @@ dist_noinst_HEADERS += \ |
|
include/procutils.h \ |
|
include/randutils.h \ |
|
include/rpmatch.h \ |
|
+ include/readutmp.h \ |
|
include/setproctitle.h \ |
|
include/strutils.h \ |
|
include/swapheader.h \ |
|
diff -up util-linux-2.23.2/include/readutmp.h.kzak util-linux-2.23.2/include/readutmp.h |
|
--- util-linux-2.23.2/include/readutmp.h.kzak 2014-12-12 15:28:30.571177081 +0100 |
|
+++ util-linux-2.23.2/include/readutmp.h 2014-12-12 15:28:30.571177081 +0100 |
|
@@ -0,0 +1,28 @@ |
|
+/* Declarations for GNU's read utmp module. |
|
+ |
|
+ Copyright (C) 1992-2007, 2009-2014 Free Software Foundation, Inc. |
|
+ |
|
+ This program is free software: you can redistribute it and/or modify |
|
+ it under the terms of the GNU General Public License as published by |
|
+ the Free Software Foundation; either version 3 of the License, or |
|
+ (at your option) any later version. |
|
+ |
|
+ This program is distributed in the hope that it will be useful, |
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ GNU General Public License for more details. |
|
+ |
|
+ You should have received a copy of the GNU General Public License |
|
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
|
+ |
|
+/* Written by jla; revised by djm */ |
|
+ |
|
+#ifndef READUTMP_H |
|
+#define READUTMP_H |
|
+ |
|
+#include <sys/types.h> |
|
+#include <utmp.h> |
|
+ |
|
+int read_utmp (char const *file, size_t *n_entries, struct utmp **utmp_buf); |
|
+ |
|
+#endif /* READUTMP_H */ |
|
diff -up util-linux-2.23.2/lib/Makemodule.am.kzak util-linux-2.23.2/lib/Makemodule.am |
|
--- util-linux-2.23.2/lib/Makemodule.am.kzak 2013-07-30 10:39:26.202738200 +0200 |
|
+++ util-linux-2.23.2/lib/Makemodule.am 2014-12-12 15:28:30.572177092 +0100 |
|
@@ -25,7 +25,8 @@ libcommon_la_SOURCES = \ |
|
lib/wholedisk.c \ |
|
lib/ttyutils.c \ |
|
lib/xgetpass.c \ |
|
- lib/exec_shell.c |
|
+ lib/exec_shell.c \ |
|
+ lib/readutmp.c |
|
|
|
if LINUX |
|
libcommon_la_SOURCES += \ |
|
diff -up util-linux-2.23.2/lib/readutmp.c.kzak util-linux-2.23.2/lib/readutmp.c |
|
--- util-linux-2.23.2/lib/readutmp.c.kzak 2014-12-12 15:28:30.572177092 +0100 |
|
+++ util-linux-2.23.2/lib/readutmp.c 2014-12-12 15:28:30.572177092 +0100 |
|
@@ -0,0 +1,76 @@ |
|
+/* GNU's read utmp module. |
|
+ |
|
+ Copyright (C) 1992-2001, 2003-2006, 2009-2014 Free Software Foundation, Inc. |
|
+ |
|
+ This program is free software: you can redistribute it and/or modify |
|
+ it under the terms of the GNU General Public License as published by |
|
+ the Free Software Foundation; either version 3 of the License, or |
|
+ (at your option) any later version. |
|
+ |
|
+ This program is distributed in the hope that it will be useful, |
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ GNU General Public License for more details. |
|
+ |
|
+ You should have received a copy of the GNU General Public License |
|
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
|
+ |
|
+/* Written by jla; revised by djm */ |
|
+/* extracted for util-linux by ooprala */ |
|
+ |
|
+#include <errno.h> |
|
+#include <stdio.h> |
|
+ |
|
+#include <sys/types.h> |
|
+#include <sys/stat.h> |
|
+#include <signal.h> |
|
+#include <stdbool.h> |
|
+#include <string.h> |
|
+#include <stdlib.h> |
|
+#include <stdint.h> |
|
+ |
|
+#include "xalloc.h" |
|
+#include "readutmp.h" |
|
+ |
|
+/* Read the utmp entries corresponding to file FILE into freshly- |
|
+ malloc'd storage, set *UTMP_BUF to that pointer, set *N_ENTRIES to |
|
+ the number of entries, and return zero. If there is any error, |
|
+ return -1, setting errno, and don't modify the parameters. |
|
+ If OPTIONS & READ_UTMP_CHECK_PIDS is nonzero, omit entries whose |
|
+ process-IDs do not currently exist. */ |
|
+int |
|
+read_utmp (char const *file, size_t *n_entries, struct utmp **utmp_buf) |
|
+{ |
|
+ size_t n_read = 0; |
|
+ size_t n_alloc = 0; |
|
+ struct utmp *utmp = NULL; |
|
+ struct utmp *u; |
|
+ |
|
+ /* Ignore the return value for now. |
|
+ Solaris' utmpname returns 1 upon success -- which is contrary |
|
+ to what the GNU libc version does. In addition, older GNU libc |
|
+ versions are actually void. */ |
|
+ utmpname(file); |
|
+ |
|
+ setutent(); |
|
+ |
|
+ errno = 0; |
|
+ while ((u = getutent()) != NULL) { |
|
+ if (n_read == n_alloc) { |
|
+ n_alloc += 32; |
|
+ utmp = xrealloc(utmp, n_alloc * sizeof (struct utmp)); |
|
+ if (!utmp) |
|
+ return -1; |
|
+ } |
|
+ utmp[n_read++] = *u; |
|
+ } |
|
+ if (!u && errno) |
|
+ return -1; |
|
+ |
|
+ endutent(); |
|
+ |
|
+ *n_entries = n_read; |
|
+ *utmp_buf = utmp; |
|
+ |
|
+ return 0; |
|
+} |
|
diff -up util-linux-2.23.2/login-utils/login.c.kzak util-linux-2.23.2/login-utils/login.c |
|
--- util-linux-2.23.2/login-utils/login.c.kzak 2014-12-12 15:27:43.436630542 +0100 |
|
+++ util-linux-2.23.2/login-utils/login.c 2014-12-12 15:28:30.573177104 +0100 |
|
@@ -923,124 +923,6 @@ static void loginpam_session(struct logi |
|
} |
|
|
|
/* |
|
- * We need to check effective UID/GID. For example $HOME could be on root |
|
- * squashed NFS or on NFS with UID mapping and access(2) uses real UID/GID. |
|
- * The open(2) seems as the surest solution. |
|
- * -- kzak@redhat.com (10-Apr-2009) |
|
- */ |
|
-static int effective_access(const char *path, int mode) |
|
-{ |
|
- int fd = open(path, mode); |
|
- if (fd != -1) |
|
- close(fd); |
|
- return fd == -1 ? -1 : 0; |
|
-} |
|
- |
|
-/* |
|
- * Check per accout or global hush-login setting. |
|
- * |
|
- * Hushed mode is enabled: |
|
- * |
|
- * a) if global (e.g. /etc/hushlogins) hush file exists: |
|
- * 1) for ALL ACCOUNTS if the file is empty |
|
- * 2) for the current user if the username or shell are found in the file |
|
- * |
|
- * b) if ~/.hushlogin file exists |
|
- * |
|
- * The ~/.hushlogin is ignored if the global hush file exists. |
|
- * |
|
- * The HUSHLOGIN_FILE login.def variable overwrites the default hush filename. |
|
- * |
|
- * Note that shadow-utils login(1) does not support "a1)". The "a1)" is |
|
- * necessary if you want to use PAM for "Last login" message. |
|
- * |
|
- * -- Karel Zak <kzak@redhat.com> (26-Aug-2011) |
|
- * |
|
- * |
|
- * Per-account check requires some explanation: As root we may not be able to |
|
- * read the directory of the user if it is on an NFS mounted filesystem. We |
|
- * temporarily set our effective uid to the user-uid making sure that we keep |
|
- * root privs. in the real uid. |
|
- * |
|
- * A portable solution would require a fork(), but we rely on Linux having the |
|
- * BSD setreuid() |
|
- */ |
|
-static int get_hushlogin_status(struct passwd *pwd) |
|
-{ |
|
- const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL }; |
|
- const char *file; |
|
- char buf[BUFSIZ]; |
|
- int i; |
|
- |
|
- file = getlogindefs_str("HUSHLOGIN_FILE", NULL); |
|
- if (file) { |
|
- if (!*file) |
|
- return 0; /* empty HUSHLOGIN_FILE defined */ |
|
- |
|
- files[0] = file; |
|
- files[1] = NULL; |
|
- } |
|
- |
|
- for (i = 0; files[i]; i++) { |
|
- int ok = 0; |
|
- |
|
- file = files[i]; |
|
- |
|
- /* Global hush-file*/ |
|
- if (*file == '/') { |
|
- struct stat st; |
|
- FILE *f; |
|
- |
|
- if (stat(file, &st) != 0) |
|
- continue; /* file does not exist */ |
|
- |
|
- if (st.st_size == 0) |
|
- return 1; /* for all accounts */ |
|
- |
|
- f = fopen(file, "r"); |
|
- if (!f) |
|
- continue; /* ignore errors... */ |
|
- |
|
- while (ok == 0 && fgets(buf, sizeof(buf), f)) { |
|
- buf[strlen(buf) - 1] = '\0'; |
|
- ok = !strcmp(buf, *buf == '/' ? pwd->pw_shell : |
|
- pwd->pw_name); |
|
- } |
|
- fclose(f); |
|
- if (ok) |
|
- return 1; /* found username/shell */ |
|
- |
|
- return 0; /* ignore per-account files */ |
|
- } |
|
- |
|
- /* Per-account setting */ |
|
- if (strlen(pwd->pw_dir) + sizeof(file) + 2 > sizeof(buf)) |
|
- continue; |
|
- else { |
|
- uid_t ruid = getuid(); |
|
- gid_t egid = getegid(); |
|
- |
|
- sprintf(buf, "%s/%s", pwd->pw_dir, file); |
|
- |
|
- if (setregid(-1, pwd->pw_gid) == 0 && |
|
- setreuid(0, pwd->pw_uid) == 0) |
|
- ok = effective_access(buf, O_RDONLY) == 0; |
|
- |
|
- if (setuid(0) != 0 || |
|
- setreuid(ruid, 0) != 0 || |
|
- setregid(-1, egid) != 0) { |
|
- syslog(LOG_ALERT, _("hush login status: restore original IDs failed")); |
|
- exit(EXIT_FAILURE); |
|
- } |
|
- if (ok) |
|
- return 1; /* enabled by user */ |
|
- } |
|
- } |
|
- |
|
- return 0; |
|
-} |
|
- |
|
-/* |
|
* Detach the controlling terminal, fork, restore syslog stuff and create a new |
|
* session. |
|
*/ |
|
@@ -1372,7 +1254,7 @@ int main(int argc, char **argv) |
|
|
|
endpwent(); |
|
|
|
- cxt.quiet = get_hushlogin_status(pwd); |
|
+ cxt.quiet = get_hushlogin_status(pwd, 1); |
|
|
|
log_utmp(&cxt); |
|
log_audit(&cxt, 1); |
|
diff -up util-linux-2.23.2/login-utils/logindefs.c.kzak util-linux-2.23.2/login-utils/logindefs.c |
|
--- util-linux-2.23.2/login-utils/logindefs.c.kzak 2013-06-13 09:46:10.442650810 +0200 |
|
+++ util-linux-2.23.2/login-utils/logindefs.c 2014-12-12 15:28:30.573177104 +0100 |
|
@@ -27,6 +27,9 @@ |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/syslog.h> |
|
+#include <sys/stat.h> |
|
+#include <sys/types.h> |
|
+#include <pwd.h> |
|
|
|
#include "c.h" |
|
#include "closestream.h" |
|
@@ -259,6 +262,135 @@ int logindefs_setenv(const char *name, c |
|
return val ? setenv(name, val, 1) : -1; |
|
} |
|
|
|
+/* |
|
+ * We need to check the effective UID/GID. For example, $HOME could be on a |
|
+ * root-squashed NFS or on an NFS with UID mapping, and access(2) uses the |
|
+ * real UID/GID. Then open(2) seems as the surest solution. |
|
+ * -- kzak@redhat.com (10-Apr-2009) |
|
+ */ |
|
+int effective_access(const char *path, int mode) |
|
+{ |
|
+ int fd = open(path, mode); |
|
+ if (fd != -1) |
|
+ close(fd); |
|
+ return fd == -1 ? -1 : 0; |
|
+} |
|
+ |
|
+ |
|
+/* |
|
+ * Check the per-account or the global hush-login setting. |
|
+ * |
|
+ * Hushed mode is enabled: |
|
+ * |
|
+ * a) if a global (e.g. /etc/hushlogins) hush file exists: |
|
+ * 1) for ALL ACCOUNTS if the file is empty |
|
+ * 2) for the current user if the username or shell is found in the file |
|
+ * |
|
+ * b) if a ~/.hushlogin file exists |
|
+ * |
|
+ * The ~/.hushlogin file is ignored if the global hush file exists. |
|
+ * |
|
+ * The HUSHLOGIN_FILE login.def variable overrides the default hush filename. |
|
+ * |
|
+ * Note that shadow-utils login(1) does not support "a1)". The "a1)" is |
|
+ * necessary if you want to use PAM for "Last login" message. |
|
+ * |
|
+ * -- Karel Zak <kzak@redhat.com> (26-Aug-2011) |
|
+ * |
|
+ * |
|
+ * The per-account check requires some explanation: As root we may not be able |
|
+ * to read the directory of the user if it is on an NFS-mounted filesystem. We |
|
+ * temporarily set our effective uid to the user-uid, making sure that we keep |
|
+ * root privileges in the real uid. |
|
+ * |
|
+ * A portable solution would require a fork(), but we rely on Linux having the |
|
+ * BSD setreuid(). |
|
+ */ |
|
+ |
|
+int get_hushlogin_status(struct passwd *pwd, int force_check) |
|
+{ |
|
+ const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL }; |
|
+ const char *file; |
|
+ char buf[BUFSIZ]; |
|
+ int i; |
|
+ |
|
+ file = getlogindefs_str("HUSHLOGIN_FILE", NULL); |
|
+ if (file) { |
|
+ if (!*file) |
|
+ return 0; /* empty HUSHLOGIN_FILE defined */ |
|
+ |
|
+ files[0] = file; |
|
+ files[1] = NULL; |
|
+ } |
|
+ |
|
+ for (i = 0; files[i]; i++) { |
|
+ int ok = 0; |
|
+ |
|
+ file = files[i]; |
|
+ |
|
+ /* global hush-file */ |
|
+ if (*file == '/') { |
|
+ struct stat st; |
|
+ FILE *f; |
|
+ |
|
+ if (stat(file, &st) != 0) |
|
+ continue; /* file does not exist */ |
|
+ |
|
+ if (st.st_size == 0) |
|
+ return 1; /* for all accounts */ |
|
+ |
|
+ f = fopen(file, "r"); |
|
+ if (!f) |
|
+ continue; /* ignore errors... */ |
|
+ |
|
+ while (ok == 0 && fgets(buf, sizeof(buf), f)) { |
|
+ buf[strlen(buf) - 1] = '\0'; |
|
+ ok = !strcmp(buf, *buf == '/' ? pwd->pw_shell : |
|
+ pwd->pw_name); |
|
+ } |
|
+ fclose(f); |
|
+ if (ok) |
|
+ return 1; /* found username/shell */ |
|
+ |
|
+ return 0; /* ignore per-account files */ |
|
+ } |
|
+ |
|
+ /* per-account setting */ |
|
+ if (strlen(pwd->pw_dir) + sizeof(file) + 2 > sizeof(buf)) |
|
+ continue; |
|
+ |
|
+ sprintf(buf, "%s/%s", pwd->pw_dir, file); |
|
+ |
|
+ if (force_check) { |
|
+ uid_t ruid = getuid(); |
|
+ gid_t egid = getegid(); |
|
+ |
|
+ if (setregid(-1, pwd->pw_gid) == 0 && |
|
+ setreuid(0, pwd->pw_uid) == 0) |
|
+ ok = effective_access(buf, O_RDONLY) == 0; |
|
+ |
|
+ if (setuid(0) != 0 || |
|
+ setreuid(ruid, 0) != 0 || |
|
+ setregid(-1, egid) != 0) { |
|
+ syslog(LOG_ALERT, _("hush login status: restore original IDs failed")); |
|
+ exit(EXIT_FAILURE); |
|
+ } |
|
+ if (ok) |
|
+ return 1; /* enabled by user */ |
|
+ } |
|
+ else { |
|
+ int rc; |
|
+ rc = effective_access(buf, O_RDONLY); |
|
+ if (rc == 0) |
|
+ return 1; |
|
+ else if (rc == -1 && errno == EACCES) |
|
+ return -1; |
|
+ } |
|
+ |
|
+ } |
|
+ |
|
+ return 0; |
|
+} |
|
#ifdef TEST_PROGRAM |
|
int main(int argc, char *argv[]) |
|
{ |
|
diff -up util-linux-2.23.2/login-utils/logindefs.h.kzak util-linux-2.23.2/login-utils/logindefs.h |
|
--- util-linux-2.23.2/login-utils/logindefs.h.kzak 2013-02-27 17:46:29.887020770 +0100 |
|
+++ util-linux-2.23.2/login-utils/logindefs.h 2014-12-12 15:28:30.573177104 +0100 |
|
@@ -8,5 +8,7 @@ extern unsigned long getlogindefs_num(co |
|
extern const char *getlogindefs_str(const char *name, const char *dflt); |
|
extern void free_getlogindefs_data(void); |
|
extern int logindefs_setenv(const char *name, const char *conf, const char *dflt); |
|
+extern int effective_access(const char *path, int mode); |
|
+extern int get_hushlogin_status(struct passwd *pwd, int force_check); |
|
|
|
#endif /* UTIL_LINUX_LOGINDEFS_H */ |
|
diff -up util-linux-2.23.2/login-utils/lslogins.1.kzak util-linux-2.23.2/login-utils/lslogins.1 |
|
--- util-linux-2.23.2/login-utils/lslogins.1.kzak 2014-12-12 15:28:30.574177115 +0100 |
|
+++ util-linux-2.23.2/login-utils/lslogins.1 2014-12-12 15:28:30.574177115 +0100 |
|
@@ -0,0 +1,132 @@ |
|
+.\" Copyright 2014 Ondrej Oprala (ondrej.oprala@gmail.com) |
|
+.\" May be distributed under the GNU General Public License |
|
+.TH LSLOGINS "1" "April 2014" "util-linux" "User Commands" |
|
+.SH NAME |
|
+lslogins \- display information about known users in the system |
|
+.SH SYNOPSIS |
|
+.B lslogins |
|
+[\fIoptions\fR] [\fB-s\fR|\fB-u\fR[=\fIUID\fR]] [\fB-g \fIgroups\fR] [\fB-l \fIlogins\fR] |
|
+.SH DESCRIPTION |
|
+.PP |
|
+Examine the wtmp and btmp logs, /etc/shadow (if necessary) and /etc/passwd |
|
+and output the desired data. |
|
+.PP |
|
+The default action is to list info about all the users in the system. |
|
+.SH OPTIONS |
|
+Mandatory arguments to long options are mandatory for short options too. |
|
+.TP |
|
+\fB\-a\fR, \fB\-\-acc\-expiration\fR |
|
+Display data about the date of last password change and the account expiration |
|
+date (see \fBshadow\fR(5) for more info). (Requires root priviliges.) |
|
+.TP |
|
+\fB\-\-btmp\-file \fIpath\fP |
|
+Alternate path for btmp. |
|
+.TP |
|
+\fB\-c\fR, \fB\-\-colon\-separate\fR |
|
+Separate info about each user with a colon instead of a newline. |
|
+.TP |
|
+\fB\-e\fR, \fB\-\-export\fR |
|
+Output data in the format of NAME=VALUE. |
|
+.TP |
|
+\fB\-f\fR, \fB\-\-failed\fR |
|
+Display data about the users' last failed login attempts. |
|
+.TP |
|
+\fB\-G\fR, \fB\-\-groups\-info\fR |
|
+Show information about groups. |
|
+.TP |
|
+\fB\-g\fR, \fB\-\-groups\fR=\fIgroups\fR |
|
+Only show data of users belonging to \fIgroups\fR. More than one group |
|
+may be specified; the list has to be comma-separated. |
|
+.TP |
|
+\fB\-h\fR, \fB\-\-help\fR |
|
+Display help information and exit. |
|
+.TP |
|
+\fB\-L\fR, \fB\-\-last\fR |
|
+Display data containing information about the users' last login sessions. |
|
+.TP |
|
+\fB\-l\fR, \fB\-\-logins\fR=\fIlogins\fR |
|
+Only show data of users with a login specified in \fIlogins\fR (user names or user |
|
+IDS). More than one login may be specified; the list has to be comma-separated. |
|
+.TP |
|
+\fB\-m\fR, \fB\-\-supp\-groups\fR |
|
+Show supplementary groups. |
|
+.TP |
|
+\fB\-n\fR, \fB\-\-newline\fR |
|
+Display each piece of information on a separate line. |
|
+.TP |
|
+\fB\-\-noheadings\fR |
|
+Do not print a header line. |
|
+.TP |
|
+\fB\-\-notruncate\fR |
|
+Don't truncate output. |
|
+.TP |
|
+\fB\-o\fR, \fB\-\-output \fIlist\fP |
|
+Specify which output columns to print. Use |
|
+.B \-\-help |
|
+to get a list of all supported columns. |
|
+.TP |
|
+\fB\-p\fR, \fB\-\-pwd\fR |
|
+Display information related to login by password (see also \fB\-afL). |
|
+.TP |
|
+\fB\-r\fR, \fB\-\-raw\fR |
|
+Raw output (no columnation). |
|
+.TP |
|
+\fB\-s\fR, \fB\-\-system\-accs\fR[=\fIthreshold\fR] |
|
+Show system accounts. These are by default all accounts with a UID below 1000 |
|
+(non-inclusive), with the exception of either nobody or nfsnobody (UID 65534). The UID |
|
+threshold can also be specified explicitly (necessary for some distributions that |
|
+allocate UIDs starting from 100, 500 - or an entirely different value - rather than 1000). |
|
+.TP |
|
+\fB\-\-time-format\fR \fItype\fP |
|
+Display dates in short, full or iso format. The default is short, this time |
|
+format is designed to be space efficient and human readable. |
|
+.TP |
|
+\fB\-u\fR, \fB\-\-user\-accs\fR[=\fIthreshold\fR] |
|
+Show user accounts. These are by default all accounts with UID above 1000 |
|
+(inclusive), with the exception of either nobody or nfsnobody (UID 65534). The UID |
|
+threshold can also be specified explicitly (necessary for some distributions that |
|
+allocate UIDs starting from 100, 500 - or an entirely different value - rather than 1000). |
|
+.TP |
|
+\fB\-V\fR, \fB\-\-version\fR |
|
+Display version information and exit. |
|
+.TP |
|
+\fB\-\-wtmp\-file \fIpath\fP |
|
+Alternate path for wtmp. |
|
+.TP |
|
+\fB\-Z\fR, \fB\-\-context\fR |
|
+Display the users' security context. |
|
+.TP |
|
+\fB\-z\fR, \fB\-\-print0\fR |
|
+Delimit user entries with a nul character, instead of a newline. |
|
+ |
|
+.SH NOTES |
|
+The default UID thresholds are read from /etc/login.defs. |
|
+ |
|
+.SH EXIT STATUS |
|
+.TP |
|
+0 |
|
+if OK, |
|
+.TP |
|
+1 |
|
+if incorrect arguments specified, |
|
+.TP |
|
+2 |
|
+if a serious error occurs (e.g. a corrupt log). |
|
+.SH SEE ALSO |
|
+\fBgroup\fP(5), \fBpasswd\fP(5), \fBshadow\fP(5), \fButmp\fP(5) |
|
+.SH HISTORY |
|
+The \fBlslogins\fP utility is inspired by the \fBlogins\fP utility, which first appeared in FreeBSD 4.10. |
|
+.SH AUTHORS |
|
+.MT ooprala@redhat.com |
|
+Ondrej Oprala |
|
+.ME |
|
+.br |
|
+.MT kzak@redhat.com |
|
+Karel Zak |
|
+.ME |
|
+ |
|
+.SH AVAILABILITY |
|
+The lslogins command is part of the util-linux package and is available from |
|
+.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ |
|
+Linux Kernel Archive |
|
+.UE . |
|
diff -up util-linux-2.23.2/login-utils/lslogins.c.kzak util-linux-2.23.2/login-utils/lslogins.c |
|
--- util-linux-2.23.2/login-utils/lslogins.c.kzak 2014-12-12 15:28:30.575177127 +0100 |
|
+++ util-linux-2.23.2/login-utils/lslogins.c 2014-12-12 15:29:19.084739609 +0100 |
|
@@ -0,0 +1,1476 @@ |
|
+/* |
|
+ * lslogins - List information about users on the system |
|
+ * |
|
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> |
|
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com> |
|
+ * |
|
+ * This program is free software; you can redistribute it and/or modify |
|
+ * it under the terms of the GNU General Public License as published by |
|
+ * the Free Software Foundation; either version 2 of the License, or |
|
+ * (at your option) any later version. |
|
+ * |
|
+ * This program is distributed in the hope that it would be useful, |
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
+ * GNU General Public License for more details. |
|
+ * |
|
+ * You should have received a copy of the GNU General Public License along |
|
+ * with this program; if not, write to the Free Software Foundation, Inc., |
|
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
+ */ |
|
+ |
|
+#include <stdio.h> |
|
+#include <stdlib.h> |
|
+#include <unistd.h> |
|
+#include <getopt.h> |
|
+#include <sys/types.h> |
|
+#include <sys/stat.h> |
|
+#include <sys/syslog.h> |
|
+#include <pwd.h> |
|
+#include <grp.h> |
|
+#include <shadow.h> |
|
+#include <paths.h> |
|
+#include <time.h> |
|
+#include <utmp.h> |
|
+#include <signal.h> |
|
+#include <err.h> |
|
+#include <limits.h> |
|
+#include <ctype.h> |
|
+ |
|
+#include <search.h> |
|
+ |
|
+#include <libsmartcols.h> |
|
+#ifdef HAVE_LIBSELINUX |
|
+# include <selinux/selinux.h> |
|
+#endif |
|
+ |
|
+#ifdef HAVE_LIBSYSTEMD |
|
+# include <systemd/sd-journal.h> |
|
+#endif |
|
+ |
|
+#include "c.h" |
|
+#include "nls.h" |
|
+#include "closestream.h" |
|
+#include "xalloc.h" |
|
+#include "list.h" |
|
+#include "strutils.h" |
|
+#include "optutils.h" |
|
+#include "pathnames.h" |
|
+#include "logindefs.h" |
|
+#include "readutmp.h" |
|
+#include "procutils.h" |
|
+ |
|
+/* |
|
+ * column description |
|
+ */ |
|
+struct lslogins_coldesc { |
|
+ const char *name; |
|
+ const char *help; |
|
+ const char *pretty_name; |
|
+ |
|
+ double whint; /* width hint */ |
|
+ long flag; |
|
+}; |
|
+ |
|
+static int lslogins_flag; |
|
+ |
|
+#define UL_UID_MIN 1000 |
|
+#define UL_UID_MAX 60000 |
|
+#define UL_SYS_UID_MIN 201 |
|
+#define UL_SYS_UID_MAX 999 |
|
+ |
|
+/* we use the value of outmode to determine |
|
+ * appropriate flags for the libsmartcols table |
|
+ * (e.g., a value of out_newline would imply a raw |
|
+ * table with the column separator set to '\n'). |
|
+ */ |
|
+static int outmode; |
|
+/* |
|
+ * output modes |
|
+ */ |
|
+enum { |
|
+ OUT_COLON = 1, |
|
+ OUT_EXPORT, |
|
+ OUT_NEWLINE, |
|
+ OUT_RAW, |
|
+ OUT_NUL, |
|
+ OUT_PRETTY |
|
+}; |
|
+ |
|
+struct lslogins_user { |
|
+ char *login; |
|
+ uid_t uid; |
|
+ char *group; |
|
+ gid_t gid; |
|
+ char *gecos; |
|
+ |
|
+ int pwd_empty; |
|
+ int nologin; |
|
+ int pwd_lock; |
|
+ int pwd_deny; |
|
+ |
|
+ gid_t *sgroups; |
|
+ size_t nsgroups; |
|
+ |
|
+ char *pwd_ctime; |
|
+ char *pwd_warn; |
|
+ char *pwd_expire; |
|
+ char *pwd_ctime_min; |
|
+ char *pwd_ctime_max; |
|
+ |
|
+ char *last_login; |
|
+ char *last_tty; |
|
+ char *last_hostname; |
|
+ |
|
+ char *failed_login; |
|
+ char *failed_tty; |
|
+ |
|
+#ifdef HAVE_LIBSELINUX |
|
+ security_context_t context; |
|
+#endif |
|
+ char *homedir; |
|
+ char *shell; |
|
+ char *pwd_status; |
|
+ int hushed; |
|
+ char *nprocs; |
|
+ |
|
+}; |
|
+ |
|
+/* |
|
+ * time modes |
|
+ * */ |
|
+enum { |
|
+ TIME_INVALID = 0, |
|
+ TIME_SHORT, |
|
+ TIME_FULL, |
|
+ TIME_ISO, |
|
+}; |
|
+ |
|
+/* |
|
+ * flags |
|
+ */ |
|
+enum { |
|
+ F_SYSAC = (1 << 3), |
|
+ F_USRAC = (1 << 4), |
|
+}; |
|
+ |
|
+/* |
|
+ * IDs |
|
+ */ |
|
+enum { |
|
+ COL_USER = 0, |
|
+ COL_UID, |
|
+ COL_GECOS, |
|
+ COL_HOME, |
|
+ COL_SHELL, |
|
+ COL_NOLOGIN, |
|
+ COL_PWDLOCK, |
|
+ COL_PWDEMPTY, |
|
+ COL_PWDDENY, |
|
+ COL_GROUP, |
|
+ COL_GID, |
|
+ COL_SGROUPS, |
|
+ COL_SGIDS, |
|
+ COL_LAST_LOGIN, |
|
+ COL_LAST_TTY, |
|
+ COL_LAST_HOSTNAME, |
|
+ COL_FAILED_LOGIN, |
|
+ COL_FAILED_TTY, |
|
+ COL_HUSH_STATUS, |
|
+ COL_PWD_WARN, |
|
+ COL_PWD_CTIME, |
|
+ COL_PWD_CTIME_MIN, |
|
+ COL_PWD_CTIME_MAX, |
|
+ COL_PWD_EXPIR, |
|
+ COL_SELINUX, |
|
+ COL_NPROCS, |
|
+}; |
|
+ |
|
+#define is_wtmp_col(x) ((x) == COL_LAST_LOGIN || \ |
|
+ (x) == COL_LAST_TTY || \ |
|
+ (x) == COL_LAST_HOSTNAME) |
|
+ |
|
+#define is_btmp_col(x) ((x) == COL_FAILED_LOGIN || \ |
|
+ (x) == COL_FAILED_TTY) |
|
+ |
|
+enum { |
|
+ STATUS_FALSE = 0, |
|
+ STATUS_TRUE, |
|
+ STATUS_UNKNOWN |
|
+}; |
|
+ |
|
+static const char *const status[] = { |
|
+ [STATUS_FALSE] = "0", |
|
+ [STATUS_TRUE] = "1", |
|
+ [STATUS_UNKNOWN]= NULL |
|
+}; |
|
+ |
|
+static const char *const pretty_status[] = { |
|
+ [STATUS_FALSE] = N_("no"), |
|
+ [STATUS_TRUE] = N_("yes"), |
|
+ [STATUS_UNKNOWN]= NULL |
|
+}; |
|
+ |
|
+#define get_status(x) (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)]) |
|
+ |
|
+static const struct lslogins_coldesc coldescs[] = |
|
+{ |
|
+ [COL_USER] = { "USER", N_("user name"), N_("Username"), 0.1, SCOLS_FL_NOEXTREMES }, |
|
+ [COL_UID] = { "UID", N_("user ID"), "UID", 1, SCOLS_FL_RIGHT}, |
|
+ [COL_PWDEMPTY] = { "PWD-EMPTY", N_("password not required"), N_("Password not required"), 1, SCOLS_FL_RIGHT }, |
|
+ [COL_PWDDENY] = { "PWD-DENY", N_("login by password disabled"), N_("Login by password disabled"), 1, SCOLS_FL_RIGHT }, |
|
+ [COL_PWDLOCK] = { "PWD-LOCK", N_("password defined, but locked"), N_("Password is locked"), 1, SCOLS_FL_RIGHT }, |
|
+ [COL_NOLOGIN] = { "NOLOGIN", N_("log in disabled by nologin(8) or pam_nologin(8)"), N_("No login"), 1, SCOLS_FL_RIGHT }, |
|
+ [COL_GROUP] = { "GROUP", N_("primary group name"), N_("Primary group"), 0.1 }, |
|
+ [COL_GID] = { "GID", N_("primary group ID"), "GID", 1, SCOLS_FL_RIGHT }, |
|
+ [COL_SGROUPS] = { "SUPP-GROUPS", N_("supplementary group names"), N_("Supplementary groups"), 0.1 }, |
|
+ [COL_SGIDS] = { "SUPP-GIDS", N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 }, |
|
+ [COL_HOME] = { "HOMEDIR", N_("home directory"), N_("Home directory"), 0.1 }, |
|
+ [COL_SHELL] = { "SHELL", N_("login shell"), N_("Shell"), 0.1 }, |
|
+ [COL_GECOS] = { "GECOS", N_("full user name"), N_("Gecos field"), 0.1, SCOLS_FL_TRUNC }, |
|
+ [COL_LAST_LOGIN] = { "LAST-LOGIN", N_("date of last login"), N_("Last login"), 0.1, SCOLS_FL_RIGHT }, |
|
+ [COL_LAST_TTY] = { "LAST-TTY", N_("last tty used"), N_("Last terminal"), 0.05 }, |
|
+ [COL_LAST_HOSTNAME] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"), 0.1}, |
|
+ [COL_FAILED_LOGIN] = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 0.1 }, |
|
+ [COL_FAILED_TTY] = { "FAILED-TTY", N_("where did the login fail?"), N_("Failed login terminal"), 0.05 }, |
|
+ [COL_HUSH_STATUS] = { "HUSHED", N_("user's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT }, |
|
+ [COL_PWD_WARN] = { "PWD-WARN", N_("days user is warned of password expiration"), N_("Password expiration warn interval"), 0.1, SCOLS_FL_RIGHT }, |
|
+ [COL_PWD_EXPIR] = { "PWD-EXPIR", N_("password expiration date"), N_("Password expiration"), 0.1, SCOLS_FL_RIGHT }, |
|
+ [COL_PWD_CTIME] = { "PWD-CHANGE", N_("date of last password change"), N_("Password changed"), 0.1, SCOLS_FL_RIGHT}, |
|
+ [COL_PWD_CTIME_MIN] = { "PWD-MIN", N_("number of days required between changes"), N_("Minimum change time"), 0.1, SCOLS_FL_RIGHT }, |
|
+ [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 }, |
|
+ [COL_SELINUX] = { "CONTEXT", N_("the user's security context"), N_("Selinux context"), 0.1 }, |
|
+ [COL_NPROCS] = { "PROC", N_("number of processes run by the user"), N_("Running processes"), 1, SCOLS_FL_RIGHT }, |
|
+}; |
|
+ |
|
+struct lslogins_control { |
|
+ struct utmp *wtmp; |
|
+ size_t wtmp_size; |
|
+ |
|
+ struct utmp *btmp; |
|
+ size_t btmp_size; |
|
+ |
|
+ void *usertree; |
|
+ |
|
+ uid_t uid; |
|
+ uid_t UID_MIN; |
|
+ uid_t UID_MAX; |
|
+ |
|
+ uid_t SYS_UID_MIN; |
|
+ uid_t SYS_UID_MAX; |
|
+ |
|
+ char **ulist; |
|
+ size_t ulsiz; |
|
+ |
|
+ unsigned int time_mode; |
|
+ |
|
+ const char *journal_path; |
|
+ |
|
+ unsigned int selinux_enabled : 1, |
|
+ ulist_on : 1, |
|
+ noheadings : 1, |
|
+ notrunc : 1; |
|
+}; |
|
+ |
|
+/* these have to remain global since there's no other reasonable way to pass |
|
+ * them for each call of fill_table() via twalk() */ |
|
+static struct libscols_table *tb; |
|
+ |
|
+/* columns[] array specifies all currently wanted output column. The columns |
|
+ * are defined by coldescs[] array and you can specify (on command line) each |
|
+ * column twice. That's enough, dynamically allocated array of the columns is |
|
+ * unnecessary overkill and over-engineering in this case */ |
|
+static int columns[ARRAY_SIZE(coldescs) * 2]; |
|
+static int ncolumns; |
|
+ |
|
+static inline size_t err_columns_index(size_t arysz, size_t idx) |
|
+{ |
|
+ if (idx >= arysz) |
|
+ errx(EXIT_FAILURE, _("too many columns specified, " |
|
+ "the limit is %zu columns"), |
|
+ arysz - 1); |
|
+ return idx; |
|
+} |
|
+ |
|
+#define add_column(ary, n, id) \ |
|
+ ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id)) |
|
+ |
|
+static struct timeval now; |
|
+ |
|
+static int date_is_today(time_t t) |
|
+{ |
|
+ if (now.tv_sec == 0) |
|
+ gettimeofday(&now, NULL); |
|
+ return t / (3600 * 24) == now.tv_sec / (3600 * 24); |
|
+} |
|
+ |
|
+static int date_is_thisyear(time_t t) |
|
+{ |
|
+ if (now.tv_sec == 0) |
|
+ gettimeofday(&now, NULL); |
|
+ return t / (3600 * 24 * 365) == now.tv_sec / (3600 * 24 * 365); |
|
+} |
|
+ |
|
+static int column_name_to_id(const char *name, size_t namesz) |
|
+{ |
|
+ size_t i; |
|
+ |
|
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++) { |
|
+ const char *cn = coldescs[i].name; |
|
+ |
|
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) |
|
+ return i; |
|
+ } |
|
+ warnx(_("unknown column: %s"), name); |
|
+ return -1; |
|
+} |
|
+ |
|
+static char *make_time(int mode, time_t time) |
|
+{ |
|
+ char *s; |
|
+ struct tm tm; |
|
+ char buf[64] = {0}; |
|
+ |
|
+ localtime_r(&time, &tm); |
|
+ |
|
+ switch(mode) { |
|
+ case TIME_FULL: |
|
+ asctime_r(&tm, buf); |
|
+ if (*(s = buf + strlen(buf) - 1) == '\n') |
|
+ *s = '\0'; |
|
+ break; |
|
+ case TIME_SHORT: |
|
+ if (date_is_today(time)) |
|
+ strftime(buf, sizeof(buf), "%H:%M:%S", &tm); |
|
+ else if (date_is_thisyear(time)) |
|
+ strftime(buf, sizeof(buf), "%b%d/%H:%M", &tm); |
|
+ else |
|
+ strftime(buf, sizeof(buf), "%Y-%b%d", &tm); |
|
+ break; |
|
+ case TIME_ISO: |
|
+ strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &tm); |
|
+ break; |
|
+ default: |
|
+ errx(EXIT_FAILURE, _("unsupported time type")); |
|
+ } |
|
+ return xstrdup(buf); |
|
+} |
|
+ |
|
+ |
|
+static char *uidtostr(uid_t uid) |
|
+{ |
|
+ char *str_uid = NULL; |
|
+ xasprintf(&str_uid, "%u", uid); |
|
+ return str_uid; |
|
+} |
|
+ |
|
+static char *gidtostr(gid_t gid) |
|
+{ |
|
+ char *str_gid = NULL; |
|
+ xasprintf(&str_gid, "%u", gid); |
|
+ return str_gid; |
|
+} |
|
+ |
|
+static char *build_sgroups_string(gid_t *sgroups, size_t nsgroups, int want_names) |
|
+{ |
|
+ size_t n = 0, maxlen, len; |
|
+ char *res, *p; |
|
+ |
|
+ if (!nsgroups) |
|
+ return NULL; |
|
+ |
|
+ len = maxlen = nsgroups * 10; |
|
+ res = p = xmalloc(maxlen); |
|
+ |
|
+ while (n < nsgroups) { |
|
+ int x; |
|
+again: |
|
+ if (!want_names) |
|
+ x = snprintf(p, len, "%u,", sgroups[n]); |
|
+ else { |
|
+ struct group *grp = getgrgid(sgroups[n]); |
|
+ if (!grp) { |
|
+ free(res); |
|
+ return NULL; |
|
+ } |
|
+ x = snprintf(p, len, "%s,", grp->gr_name); |
|
+ } |
|
+ |
|
+ if (x < 0 || (size_t) x + 1 > len) { |
|
+ size_t cur = p - res; |
|
+ |
|
+ maxlen *= 2; |
|
+ res = xrealloc(res, maxlen); |
|
+ p = res + cur; |
|
+ len = maxlen - cur; |
|
+ goto again; |
|
+ } |
|
+ |
|
+ len -= x; |
|
+ p += x; |
|
+ ++n; |
|
+ } |
|
+ |
|
+ if (p > res) |
|
+ *(p - 1) = '\0'; |
|
+ |
|
+ return res; |
|
+} |
|
+ |
|
+static struct utmp *get_last_wtmp(struct lslogins_control *ctl, const char *username) |
|
+{ |
|
+ size_t n = 0; |
|
+ size_t len; |
|
+ |
|
+ if (!username) |
|
+ return NULL; |
|
+ |
|
+ len = strlen(username); |
|
+ n = ctl->wtmp_size - 1; |
|
+ do { |
|
+ if (!strncmp(username, ctl->wtmp[n].ut_user, |
|
+ len < UT_NAMESIZE ? len : UT_NAMESIZE)) |
|
+ return ctl->wtmp + n; |
|
+ } while (n--); |
|
+ return NULL; |
|
+ |
|
+} |
|
+ |
|
+static int require_wtmp(void) |
|
+{ |
|
+ size_t i; |
|
+ for (i = 0; i < (size_t) ncolumns; i++) |
|
+ if (is_wtmp_col(columns[i])) |
|
+ return 1; |
|
+ return 0; |
|
+} |
|
+ |
|
+static int require_btmp(void) |
|
+{ |
|
+ size_t i; |
|
+ for (i = 0; i < (size_t) ncolumns; i++) |
|
+ if (is_btmp_col(columns[i])) |
|
+ return 1; |
|
+ return 0; |
|
+} |
|
+ |
|
+static struct utmp *get_last_btmp(struct lslogins_control *ctl, const char *username) |
|
+{ |
|
+ size_t n = 0; |
|
+ size_t len; |
|
+ |
|
+ if (!username) |
|
+ return NULL; |
|
+ |
|
+ len = strlen(username); |
|
+ n = ctl->btmp_size - 1; |
|
+ do { |
|
+ if (!strncmp(username, ctl->btmp[n].ut_user, |
|
+ len < UT_NAMESIZE ? len : UT_NAMESIZE)) |
|
+ return ctl->btmp + n; |
|
+ }while (n--); |
|
+ return NULL; |
|
+ |
|
+} |
|
+ |
|
+static int parse_wtmp(struct lslogins_control *ctl, char *path) |
|
+{ |
|
+ int rc = 0; |
|
+ |
|
+ rc = read_utmp(path, &ctl->wtmp_size, &ctl->wtmp); |
|
+ if (rc < 0 && errno != EACCES) |
|
+ err(EXIT_FAILURE, "%s", path); |
|
+ return rc; |
|
+} |
|
+ |
|
+static int parse_btmp(struct lslogins_control *ctl, char *path) |
|
+{ |
|
+ int rc = 0; |
|
+ |
|
+ rc = read_utmp(path, &ctl->btmp_size, &ctl->btmp); |
|
+ if (rc < 0 && errno != EACCES) |
|
+ err(EXIT_FAILURE, "%s", path); |
|
+ return rc; |
|
+} |
|
+ |
|
+static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd) |
|
+{ |
|
+ size_t n = 0; |
|
+ |
|
+ *len = 0; |
|
+ *list = NULL; |
|
+ |
|
+ /* first let's get a supp. group count */ |
|
+ getgrouplist(pwd->pw_name, pwd->pw_gid, *list, (int *) len); |
|
+ if (!*len) |
|
+ return -1; |
|
+ |
|
+ *list = xcalloc(1, *len * sizeof(gid_t)); |
|
+ |
|
+ /* now for the actual list of GIDs */ |
|
+ if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, (int *) len)) |
|
+ return -1; |
|
+ |
|
+ /* getgroups also returns the user's primary GID - dispose of it */ |
|
+ while (n < *len) { |
|
+ if ((*list)[n] == pwd->pw_gid) |
|
+ break; |
|
+ ++n; |
|
+ } |
|
+ |
|
+ if (*len) |
|
+ (*list)[n] = (*list)[--(*len)]; |
|
+ return 0; |
|
+} |
|
+ |
|
+static int get_nprocs(const uid_t uid) |
|
+{ |
|
+ int nprocs = 0; |
|
+ pid_t pid; |
|
+ struct proc_processes *proc = proc_open_processes(); |
|
+ |
|
+ proc_processes_filter_by_uid(proc, uid); |
|
+ |
|
+ while (!proc_next_pid(proc, &pid)) |
|
+ ++nprocs; |
|
+ |
|
+ proc_close_processes(proc); |
|
+ return nprocs; |
|
+} |
|
+ |
|
+static int valid_pwd(const char *str) |
|
+{ |
|
+ const char *p; |
|
+ |
|
+ for (p = str; p && *p; p++) |
|
+ if (!isalnum((unsigned int) *p)) |
|
+ return 0; |
|
+ return p > str ? 1 : 0; |
|
+} |
|
+ |
|
+static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username) |
|
+{ |
|
+ struct lslogins_user *user; |
|
+ struct passwd *pwd; |
|
+ struct group *grp; |
|
+ struct spwd *shadow; |
|
+ struct utmp *user_wtmp = NULL, *user_btmp = NULL; |
|
+ int n = 0; |
|
+ time_t time; |
|
+ uid_t uid; |
|
+ errno = 0; |
|
+ |
|
+ pwd = username ? getpwnam(username) : getpwent(); |
|
+ if (!pwd) |
|
+ return NULL; |
|
+ |
|
+ ctl->uid = uid = pwd->pw_uid; |
|
+ |
|
+ /* nfsnobody is an exception to the UID_MAX limit. This is "nobody" on |
|
+ * some systems; the decisive point is the UID - 65534 */ |
|
+ if ((lslogins_flag & F_USRAC) && |
|
+ strcmp("nfsnobody", pwd->pw_name) != 0 && |
|
+ uid != 0) { |
|
+ if (uid < ctl->UID_MIN || uid > ctl->UID_MAX) { |
|
+ errno = EAGAIN; |
|
+ return NULL; |
|
+ } |
|
+ |
|
+ } else if ((lslogins_flag & F_SYSAC) && |
|
+ (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX)) { |
|
+ errno = EAGAIN; |
|
+ return NULL; |
|
+ } |
|
+ |
|
+ user = xcalloc(1, sizeof(struct lslogins_user)); |
|
+ |
|
+ grp = getgrgid(pwd->pw_gid); |
|
+ if (!grp) |
|
+ return NULL; |
|
+ |
|
+ if (ctl->wtmp) |
|
+ user_wtmp = get_last_wtmp(ctl, pwd->pw_name); |
|
+ if (ctl->btmp) |
|
+ user_btmp = get_last_btmp(ctl, pwd->pw_name); |
|
+ |
|
+ lckpwdf(); |
|
+ shadow = getspnam(pwd->pw_name); |
|
+ ulckpwdf(); |
|
+ |
|
+ /* required by tseach() stuff */ |
|
+ user->uid = pwd->pw_uid; |
|
+ |
|
+ while (n < ncolumns) { |
|
+ switch (columns[n++]) { |
|
+ case COL_USER: |
|
+ user->login = xstrdup(pwd->pw_name); |
|
+ break; |
|
+ case COL_UID: |
|
+ user->uid = pwd->pw_uid; |
|
+ break; |
|
+ case COL_GROUP: |
|
+ user->group = xstrdup(grp->gr_name); |
|
+ break; |
|
+ case COL_GID: |
|
+ user->gid = pwd->pw_gid; |
|
+ break; |
|
+ case COL_SGROUPS: |
|
+ case COL_SGIDS: |
|
+ if (get_sgroups(&user->sgroups, &user->nsgroups, pwd)) |
|
+ err(EXIT_FAILURE, _("failed to get supplementary groups")); |
|
+ break; |
|
+ case COL_HOME: |
|
+ user->homedir = xstrdup(pwd->pw_dir); |
|
+ break; |
|
+ case COL_SHELL: |
|
+ user->shell = xstrdup(pwd->pw_shell); |
|
+ break; |
|
+ case COL_GECOS: |
|
+ user->gecos = xstrdup(pwd->pw_gecos); |
|
+ break; |
|
+ case COL_LAST_LOGIN: |
|
+ if (user_wtmp) { |
|
+ time = user_wtmp->ut_tv.tv_sec; |
|
+ user->last_login = make_time(ctl->time_mode, time); |
|
+ } |
|
+ break; |
|
+ case COL_LAST_TTY: |
|
+ if (user_wtmp) |
|
+ user->last_tty = xstrdup(user_wtmp->ut_line); |
|
+ break; |
|
+ case COL_LAST_HOSTNAME: |
|
+ if (user_wtmp) |
|
+ user->last_hostname = xstrdup(user_wtmp->ut_host); |
|
+ break; |
|
+ case COL_FAILED_LOGIN: |
|
+ if (user_btmp) { |
|
+ time = user_btmp->ut_tv.tv_sec; |
|
+ user->failed_login = make_time(ctl->time_mode, time); |
|
+ } |
|
+ break; |
|
+ case COL_FAILED_TTY: |
|
+ if (user_btmp) |
|
+ user->failed_tty = xstrdup(user_btmp->ut_line); |
|
+ break; |
|
+ case COL_HUSH_STATUS: |
|
+ user->hushed = get_hushlogin_status(pwd, 0); |
|
+ if (user->hushed == -1) |
|
+ user->hushed = STATUS_UNKNOWN; |
|
+ break; |
|
+ case COL_PWDEMPTY: |
|
+ if (shadow) { |
|
+ if (!*shadow->sp_pwdp) /* '\0' */ |
|
+ user->pwd_empty = STATUS_TRUE; |
|
+ } else |
|
+ user->pwd_empty = STATUS_UNKNOWN; |
|
+ break; |
|
+ case COL_PWDDENY: |
|
+ if (shadow) { |
|
+ if ((*shadow->sp_pwdp == '!' || |
|
+ *shadow->sp_pwdp == '*') && |
|
+ !valid_pwd(shadow->sp_pwdp + 1)) |
|
+ user->pwd_deny = STATUS_TRUE; |
|
+ } else |
|
+ user->pwd_deny = STATUS_UNKNOWN; |
|
+ break; |
|
+ |
|
+ case COL_PWDLOCK: |
|
+ if (shadow) { |
|
+ if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1)) |
|
+ user->pwd_lock = STATUS_TRUE; |
|
+ } else |
|
+ user->pwd_lock = STATUS_UNKNOWN; |
|
+ break; |
|
+ case COL_NOLOGIN: |
|
+ if (strstr(pwd->pw_shell, "nologin")) |
|
+ user->nologin = 1; |
|
+ else if (pwd->pw_uid) |
|
+ user->nologin = access("/etc/nologin", F_OK) == 0 || |
|
+ access("/var/run/nologin", F_OK) == 0; |
|
+ break; |
|
+ case COL_PWD_WARN: |
|
+ if (shadow && shadow->sp_warn >= 0) |
|
+ xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn); |
|
+ break; |
|
+ case COL_PWD_EXPIR: |
|
+ if (shadow && shadow->sp_expire >= 0) |
|
+ user->pwd_expire = make_time(TIME_SHORT, |
|
+ shadow->sp_expire * 86400); |
|
+ break; |
|
+ case COL_PWD_CTIME: |
|
+ /* sp_lstchg is specified in days, showing hours |
|
+ * (especially in non-GMT timezones) would only serve |
|
+ * to confuse */ |
|
+ if (shadow) |
|
+ user->pwd_ctime = make_time(TIME_SHORT, |
|
+ shadow->sp_lstchg * 86400); |
|
+ break; |
|
+ case COL_PWD_CTIME_MIN: |
|
+ if (shadow && shadow->sp_min > 0) |
|
+ xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min); |
|
+ break; |
|
+ case COL_PWD_CTIME_MAX: |
|
+ if (shadow && shadow->sp_max > 0) |
|
+ xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max); |
|
+ break; |
|
+ case COL_SELINUX: |
|
+#ifdef HAVE_LIBSELINUX |
|
+ if (ctl->selinux_enabled) { |
|
+ /* typedefs and pointers are pure evil */ |
|
+ security_context_t con = NULL; |
|
+ if (getcon(&con) == 0) |
|
+ user->context = con; |
|
+ } |
|
+#endif |
|
+ break; |
|
+ case COL_NPROCS: |
|
+ xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid)); |
|
+ break; |
|
+ default: |
|
+ /* something went very wrong here */ |
|
+ err(EXIT_FAILURE, "fatal: unknown error"); |
|
+ break; |
|
+ } |
|
+ } |
|
+ |
|
+ return user; |
|
+} |
|
+ |
|
+/* some UNIX implementations set errno iff a passwd/grp/... |
|
+ * entry was not found. The original UNIX logins(1) utility always |
|
+ * ignores invalid login/group names, so we're going to as well.*/ |
|
+#define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \ |
|
+ (e) == EBADF || (e) == EPERM || (e) == EAGAIN) |
|
+ |
|
+/* get a definitive list of users we want info about... */ |
|
+ |
|
+static int str_to_uint(char *s, unsigned int *ul) |
|
+{ |
|
+ char *end; |
|
+ if (!s || !*s) |
|
+ return -1; |
|
+ *ul = strtoul(s, &end, 0); |
|
+ if (!*end) |
|
+ return 0; |
|
+ return 1; |
|
+} |
|
+ |
|
+static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups) |
|
+{ |
|
+ char *u, *g; |
|
+ size_t i = 0, n = 0, *arsiz; |
|
+ struct group *grp; |
|
+ struct passwd *pwd; |
|
+ char ***ar; |
|
+ uid_t uid; |
|
+ gid_t gid; |
|
+ |
|
+ ar = &ctl->ulist; |
|
+ arsiz = &ctl->ulsiz; |
|
+ |
|
+ /* an arbitrary starting value */ |
|
+ *arsiz = 32; |
|
+ *ar = xcalloc(1, sizeof(char *) * (*arsiz)); |
|
+ |
|
+ if (logins) { |
|
+ while ((u = strtok(logins, ","))) { |
|
+ logins = NULL; |
|
+ |
|
+ /* user specified by UID? */ |
|
+ if (!str_to_uint(u, &uid)) { |
|
+ pwd = getpwuid(uid); |
|
+ if (!pwd) |
|
+ continue; |
|
+ u = pwd->pw_name; |
|
+ } |
|
+ (*ar)[i++] = xstrdup(u); |
|
+ |
|
+ if (i == *arsiz) |
|
+ *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32)); |
|
+ } |
|
+ ctl->ulist_on = 1; |
|
+ } |
|
+ |
|
+ if (groups) { |
|
+ /* FIXME: this might lead to duplicit entries, although not visible |
|
+ * in output, crunching a user's info multiple times is very redundant */ |
|
+ while ((g = strtok(groups, ","))) { |
|
+ n = 0; |
|
+ groups = NULL; |
|
+ |
|
+ /* user specified by GID? */ |
|
+ if (!str_to_uint(g, &gid)) |
|
+ grp = getgrgid(gid); |
|
+ else |
|
+ grp = getgrnam(g); |
|
+ |
|
+ if (!grp) |
|
+ continue; |
|
+ |
|
+ while ((u = grp->gr_mem[n++])) { |
|
+ (*ar)[i++] = xstrdup(u); |
|
+ |
|
+ if (i == *arsiz) |
|
+ *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32)); |
|
+ } |
|
+ } |
|
+ ctl->ulist_on = 1; |
|
+ } |
|
+ *arsiz = i; |
|
+ return 0; |
|
+} |
|
+ |
|
+static void free_ctl(struct lslogins_control *ctl) |
|
+{ |
|
+ size_t n = 0; |
|
+ |
|
+ free(ctl->wtmp); |
|
+ free(ctl->btmp); |
|
+ |
|
+ while (n < ctl->ulsiz) |
|
+ free(ctl->ulist[n++]); |
|
+ |
|
+ free(ctl->ulist); |
|
+ free(ctl); |
|
+} |
|
+ |
|
+static struct lslogins_user *get_next_user(struct lslogins_control *ctl) |
|
+{ |
|
+ struct lslogins_user *u; |
|
+ errno = 0; |
|
+ while (!(u = get_user_info(ctl, NULL))) { |
|
+ /* no "false" errno-s here, iff we're unable to |
|
+ * get a valid user entry for any reason, quit */ |
|
+ if (errno == EAGAIN) |
|
+ continue; |
|
+ return NULL; |
|
+ } |
|
+ return u; |
|
+} |
|
+ |
|
+static int get_user(struct lslogins_control *ctl, struct lslogins_user **user, |
|
+ const char *username) |
|
+{ |
|
+ *user = get_user_info(ctl, username); |
|
+ if (!*user && errno) |
|
+ if (IS_REAL_ERRNO(errno)) |
|
+ return -1; |
|
+ return 0; |
|
+} |
|
+ |
|
+static int cmp_uid(const void *a, const void *b) |
|
+{ |
|
+ uid_t x = ((struct lslogins_user *)a)->uid; |
|
+ uid_t z = ((struct lslogins_user *)b)->uid; |
|
+ return x > z ? 1 : (x < z ? -1 : 0); |
|
+} |
|
+ |
|
+static int create_usertree(struct lslogins_control *ctl) |
|
+{ |
|
+ struct lslogins_user *user = NULL; |
|
+ size_t n = 0; |
|
+ |
|
+ if (ctl->ulist_on) { |
|
+ while (n < ctl->ulsiz) { |
|
+ if (get_user(ctl, &user, ctl->ulist[n])) |
|
+ return -1; |
|
+ if (user) /* otherwise an invalid user name has probably been given */ |
|
+ tsearch(user, &ctl->usertree, cmp_uid); |
|
+ ++n; |
|
+ } |
|
+ } else { |
|
+ while ((user = get_next_user(ctl))) |
|
+ tsearch(user, &ctl->usertree, cmp_uid); |
|
+ } |
|
+ return 0; |
|
+} |
|
+ |
|
+static struct libscols_table *setup_table(struct lslogins_control *ctl) |
|
+{ |
|
+ struct libscols_table *tb = scols_new_table(); |
|
+ int n = 0; |
|
+ |
|
+ if (!tb) |
|
+ errx(EXIT_FAILURE, _("failed to initialize output table")); |
|
+ if (ctl->noheadings) |
|
+ scols_table_enable_noheadings(tb, 1); |
|
+ |
|
+ switch(outmode) { |
|
+ case OUT_COLON: |
|
+ scols_table_enable_raw(tb, 1); |
|
+ scols_table_set_column_separator(tb, ":"); |
|
+ break; |
|
+ case OUT_NEWLINE: |
|
+ scols_table_set_column_separator(tb, "\n"); |
|
+ /* fallthrough */ |
|
+ case OUT_EXPORT: |
|
+ scols_table_enable_export(tb, 1); |
|
+ break; |
|
+ case OUT_NUL: |
|
+ scols_table_set_line_separator(tb, "\0"); |
|
+ /* fallthrough */ |
|
+ case OUT_RAW: |
|
+ scols_table_enable_raw(tb, 1); |
|
+ break; |
|
+ case OUT_PRETTY: |
|
+ scols_table_enable_noheadings(tb, 1); |
|
+ default: |
|
+ break; |
|
+ } |
|
+ |
|
+ while (n < ncolumns) { |
|
+ int flags = coldescs[columns[n]].flag; |
|
+ |
|
+ if (ctl->notrunc) |
|
+ flags &= ~SCOLS_FL_TRUNC; |
|
+ |
|
+ if (!scols_table_new_column(tb, |
|
+ coldescs[columns[n]].name, |
|
+ coldescs[columns[n]].whint, |
|
+ flags)) |
|
+ goto fail; |
|
+ ++n; |
|
+ } |
|
+ |
|
+ return tb; |
|
+fail: |
|
+ scols_unref_table(tb); |
|
+ return NULL; |
|
+} |
|
+ |
|
+static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused))) |
|
+{ |
|
+ struct libscols_line *ln; |
|
+ struct lslogins_user *user = *(struct lslogins_user **)u; |
|
+ int n = 0; |
|
+ |
|
+ if (which == preorder || which == endorder) |
|
+ return; |
|
+ |
|
+ ln = scols_table_new_line(tb, NULL); |
|
+ while (n < ncolumns) { |
|
+ int rc = 0; |
|
+ |
|
+ switch (columns[n]) { |
|
+ case COL_USER: |
|
+ rc = scols_line_set_data(ln, n, user->login); |
|
+ break; |
|
+ case COL_UID: |
|
+ rc = scols_line_refer_data(ln, n, uidtostr(user->uid)); |
|
+ break; |
|
+ case COL_PWDEMPTY: |
|
+ rc = scols_line_set_data(ln, n, get_status(user->pwd_empty)); |
|
+ break; |
|
+ case COL_NOLOGIN: |
|
+ rc = scols_line_set_data(ln, n, get_status(user->nologin)); |
|
+ break; |
|
+ case COL_PWDLOCK: |
|
+ rc = scols_line_set_data(ln, n, get_status(user->pwd_lock)); |
|
+ break; |
|
+ case COL_PWDDENY: |
|
+ rc = scols_line_set_data(ln, n, get_status(user->pwd_deny)); |
|
+ break; |
|
+ case COL_GROUP: |
|
+ rc = scols_line_set_data(ln, n, user->group); |
|
+ break; |
|
+ case COL_GID: |
|
+ rc = scols_line_refer_data(ln, n, gidtostr(user->gid)); |
|
+ break; |
|
+ case COL_SGROUPS: |
|
+ rc = scols_line_refer_data(ln, n, |
|
+ build_sgroups_string(user->sgroups, |
|
+ user->nsgroups, |
|
+ TRUE)); |
|
+ break; |
|
+ case COL_SGIDS: |
|
+ rc = scols_line_refer_data(ln, n, |
|
+ build_sgroups_string(user->sgroups, |
|
+ user->nsgroups, |
|
+ FALSE)); |
|
+ break; |
|
+ case COL_HOME: |
|
+ rc = scols_line_set_data(ln, n, user->homedir); |
|
+ break; |
|
+ case COL_SHELL: |
|
+ rc = scols_line_set_data(ln, n, user->shell); |
|
+ break; |
|
+ case COL_GECOS: |
|
+ rc = scols_line_set_data(ln, n, user->gecos); |
|
+ break; |
|
+ case COL_LAST_LOGIN: |
|
+ rc = scols_line_set_data(ln, n, user->last_login); |
|
+ break; |
|
+ case COL_LAST_TTY: |
|
+ rc = scols_line_set_data(ln, n, user->last_tty); |
|
+ break; |
|
+ case COL_LAST_HOSTNAME: |
|
+ rc = scols_line_set_data(ln, n, user->last_hostname); |
|
+ break; |
|
+ case COL_FAILED_LOGIN: |
|
+ rc = scols_line_set_data(ln, n, user->failed_login); |
|
+ break; |
|
+ case COL_FAILED_TTY: |
|
+ rc = scols_line_set_data(ln, n, user->failed_tty); |
|
+ break; |
|
+ case COL_HUSH_STATUS: |
|
+ rc = scols_line_set_data(ln, n, get_status(user->hushed)); |
|
+ break; |
|
+ case COL_PWD_WARN: |
|
+ rc = scols_line_set_data(ln, n, user->pwd_warn); |
|
+ break; |
|
+ case COL_PWD_EXPIR: |
|
+ rc = scols_line_set_data(ln, n, user->pwd_expire); |
|
+ break; |
|
+ case COL_PWD_CTIME: |
|
+ rc = scols_line_set_data(ln, n, user->pwd_ctime); |
|
+ break; |
|
+ case COL_PWD_CTIME_MIN: |
|
+ rc = scols_line_set_data(ln, n, user->pwd_ctime_min); |
|
+ break; |
|
+ case COL_PWD_CTIME_MAX: |
|
+ rc = scols_line_set_data(ln, n, user->pwd_ctime_max); |
|
+ break; |
|
+ case COL_SELINUX: |
|
+#ifdef HAVE_LIBSELINUX |
|
+ rc = scols_line_set_data(ln, n, user->context); |
|
+#endif |
|
+ break; |
|
+ case COL_NPROCS: |
|
+ rc = scols_line_set_data(ln, n, user->nprocs); |
|
+ break; |
|
+ default: |
|
+ /* something went very wrong here */ |
|
+ err(EXIT_FAILURE, _("internal error: unknown column")); |
|
+ } |
|
+ |
|
+ if (rc != 0) |
|
+ err(EXIT_FAILURE, _("failed to set data")); |
|
+ ++n; |
|
+ } |
|
+ return; |
|
+} |
|
+#ifdef HAVE_LIBSYSTEMD |
|
+static void print_journal_tail(const char *journal_path, uid_t uid, size_t len) |
|
+{ |
|
+ sd_journal *j; |
|
+ char *match, *buf; |
|
+ uint64_t x; |
|
+ time_t t; |
|
+ const char *identifier, *pid, *message; |
|
+ size_t identifier_len, pid_len, message_len; |
|
+ |
|
+ if (journal_path) |
|
+ sd_journal_open_directory(&j, journal_path, 0); |
|
+ else |
|
+ sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); |
|
+ |
|
+ buf = xmalloc(sizeof(char) * 16); |
|
+ xasprintf(&match, "_UID=%d", uid); |
|
+ |
|
+ sd_journal_add_match(j, match, 0); |
|
+ sd_journal_seek_tail(j); |
|
+ sd_journal_previous_skip(j, len); |
|
+ |
|
+ do { |
|
+ if (0 > sd_journal_get_data(j, "SYSLOG_IDENTIFIER", |
|
+ (const void **) &identifier, &identifier_len)) |
|
+ return; |
|
+ if (0 > sd_journal_get_data(j, "_PID", |
|
+ (const void **) &pid, &pid_len)) |
|
+ return; |
|
+ if (0 > sd_journal_get_data(j, "MESSAGE", |
|
+ (const void **) &message, &message_len)) |
|
+ return; |
|
+ |
|
+ sd_journal_get_realtime_usec(j, &x); |
|
+ t = x / 1000000; |
|
+ strftime(buf, 16, "%b %d %H:%M:%S", localtime(&t)); |
|
+ |
|
+ fprintf(stdout, "%s", buf); |
|
+ |
|
+ identifier = strchr(identifier, '=') + 1; |
|
+ pid = strchr(pid, '=') + 1 ; |
|
+ message = strchr(message, '=') + 1; |
|
+ |
|
+ fprintf(stdout, " %s", identifier); |
|
+ fprintf(stdout, "[%s]:", pid); |
|
+ fprintf(stdout, "%s\n", message); |
|
+ } while (sd_journal_next(j)); |
|
+ |
|
+ free(buf); |
|
+ free(match); |
|
+ sd_journal_flush_matches(j); |
|
+ sd_journal_close(j); |
|
+} |
|
+#endif |
|
+ |
|
+static int print_pretty(struct libscols_table *tb) |
|
+{ |
|
+ struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD); |
|
+ struct libscols_column *col; |
|
+ struct libscols_cell *data; |
|
+ struct libscols_line *ln; |
|
+ const char *hstr, *dstr; |
|
+ int n = 0; |
|
+ |
|
+ ln = scols_table_get_line(tb, 0); |
|
+ while (!scols_table_next_column(tb, itr, &col)) { |
|
+ |
|
+ data = scols_line_get_cell(ln, n); |
|
+ |
|
+ hstr = _(coldescs[columns[n]].pretty_name); |
|
+ dstr = scols_cell_get_data(data); |
|
+ |
|
+ if (dstr) |
|
+ printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr); |
|
+ ++n; |
|
+ } |
|
+ |
|
+ scols_free_iter(itr); |
|
+ return 0; |
|
+ |
|
+} |
|
+ |
|
+static int print_user_table(struct lslogins_control *ctl) |
|
+{ |
|
+ tb = setup_table(ctl); |
|
+ if (!tb) |
|
+ return -1; |
|
+ |
|
+ twalk(ctl->usertree, fill_table); |
|
+ if (outmode == OUT_PRETTY) { |
|
+ print_pretty(tb); |
|
+#ifdef HAVE_LIBSYSTEMD |
|
+ fprintf(stdout, _("\nLast logs:\n")); |
|
+ print_journal_tail(ctl->journal_path, ctl->uid, 3); |
|
+ fputc('\n', stdout); |
|
+#endif |
|
+ } else |
|
+ scols_print_table(tb); |
|
+ return 0; |
|
+} |
|
+ |
|
+static void free_user(void *f) |
|
+{ |
|
+ struct lslogins_user *u = f; |
|
+ free(u->login); |
|
+ free(u->group); |
|
+ free(u->gecos); |
|
+ free(u->sgroups); |
|
+ free(u->pwd_ctime); |
|
+ free(u->pwd_warn); |
|
+ free(u->pwd_ctime_min); |
|
+ free(u->pwd_ctime_max); |
|
+ free(u->last_login); |
|
+ free(u->last_tty); |
|
+ free(u->last_hostname); |
|
+ free(u->failed_login); |
|
+ free(u->failed_tty); |
|
+ free(u->homedir); |
|
+ free(u->shell); |
|
+ free(u->pwd_status); |
|
+#ifdef HAVE_LIBSELINUX |
|
+ freecon(u->context); |
|
+#endif |
|
+ free(u); |
|
+} |
|
+ |
|
+struct lslogins_timefmt { |
|
+ const char *name; |
|
+ int val; |
|
+}; |
|
+ |
|
+static struct lslogins_timefmt timefmts[] = { |
|
+ { "short", TIME_SHORT }, |
|
+ { "full", TIME_FULL }, |
|
+ { "iso", TIME_ISO }, |
|
+}; |
|
+ |
|
+static void __attribute__((__noreturn__)) usage(FILE *out) |
|
+{ |
|
+ size_t i; |
|
+ |
|
+ fputs(USAGE_HEADER, out); |
|
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name); |
|
+ |
|
+ fputs(USAGE_OPTIONS, out); |
|
+ fputs(_(" -a, --acc-expiration display info about passwords expiration\n"), out); |
|
+ fputs(_(" -c, --colon-separate display data in a format similar to /etc/passwd\n"), out); |
|
+ fputs(_(" -e, --export display in an export-able output format\n"), out); |
|
+ fputs(_(" -f, --failed display data about the users' last failed logins\n"), out); |
|
+ fputs(_(" -G, --groups-info display information about groups\n"), out); |
|
+ fputs(_(" -g, --groups=<groups> display users belonging to a group in <groups>\n"), out); |
|
+ fputs(_(" -L, --last show info about the users' last login sessions\n"), out); |
|
+ fputs(_(" -l, --logins=<logins> display only users from <logins>\n"), out); |
|
+ fputs(_(" -m, --supp-groups display supplementary groups as well\n"), out); |
|
+ fputs(_(" -n, --newline display each piece of information on a new line\n"), out); |
|
+ fputs(_(" --noheadings don't print headings\n"), out); |
|
+ fputs(_(" --notruncate don't truncate output\n"), out); |
|
+ fputs(_(" -o, --output[=<list>] define the columns to output\n"), out); |
|
+ fputs(_(" -p, --pwd display information related to login by password.\n"), out); |
|
+ fputs(_(" -r, --raw display in raw mode\n"), out); |
|
+ fputs(_(" -s, --system-accs display system accounts\n"), out); |
|
+ fputs(_(" --time-format=<type> display dates in short, full or iso format\n"), out); |
|
+ fputs(_(" -u, --user-accs display user accounts\n"), out); |
|
+ fputs(_(" -Z, --context display SELinux contexts\n"), out); |
|
+ fputs(_(" -z, --print0 delimit user entries with a nul character\n"), out); |
|
+ fputs(_(" --wtmp-file <path> set an alternate path for wtmp\n"), out); |
|
+ fputs(_(" --btmp-file <path> set an alternate path for btmp\n"), out); |
|
+ fputs(USAGE_SEPARATOR, out); |
|
+ fputs(USAGE_HELP, out); |
|
+ fputs(USAGE_VERSION, out); |
|
+ |
|
+ fprintf(out, _("\nAvailable columns:\n")); |
|
+ |
|
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++) |
|
+ fprintf(out, " %14s %s\n", coldescs[i].name, |
|
+ _(coldescs[i].help)); |
|
+ |
|
+ fprintf(out, _("\nFor more details see lslogins(1).\n")); |
|
+ |
|
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); |
|
+} |
|
+ |
|
+int main(int argc, char *argv[]) |
|
+{ |
|
+ int c, opt_o = 0; |
|
+ char *logins = NULL, *groups = NULL; |
|
+ char *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP; |
|
+ struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control)); |
|
+ size_t i; |
|
+ |
|
+ /* long only options. */ |
|
+ enum { |
|
+ OPT_VER = CHAR_MAX + 1, |
|
+ OPT_WTMP, |
|
+ OPT_BTMP, |
|
+ OPT_NOTRUNC, |
|
+ OPT_NOHEAD, |
|
+ OPT_TIME_FMT, |
|
+ }; |
|
+ |
|
+ static const struct option longopts[] = { |
|
+ { "acc-expiration", no_argument, 0, 'a' }, |
|
+ { "colon-separate", no_argument, 0, 'c' }, |
|
+ { "export", no_argument, 0, 'e' }, |
|
+ { "failed", no_argument, 0, 'f' }, |
|
+ { "groups", required_argument, 0, 'g' }, |
|
+ { "help", no_argument, 0, 'h' }, |
|
+ { "logins", required_argument, 0, 'l' }, |
|
+ { "supp-groups", no_argument, 0, 'G' }, |
|
+ { "newline", no_argument, 0, 'n' }, |
|
+ { "notruncate", no_argument, 0, OPT_NOTRUNC }, |
|
+ { "noheadings", no_argument, 0, OPT_NOHEAD }, |
|
+ { "output", required_argument, 0, 'o' }, |
|
+ { "last", no_argument, 0, 'L', }, |
|
+ { "raw", no_argument, 0, 'r' }, |
|
+ { "system-accs", no_argument, 0, 's' }, |
|
+ { "time-format", required_argument, 0, OPT_TIME_FMT }, |
|
+ { "user-accs", no_argument, 0, 'u' }, |
|
+ { "version", no_argument, 0, 'V' }, |
|
+ { "pwd", no_argument, 0, 'p' }, |
|
+ { "print0", no_argument, 0, 'z' }, |
|
+ { "wtmp-file", required_argument, 0, OPT_WTMP }, |
|
+ { "btmp-file", required_argument, 0, OPT_BTMP }, |
|
+#ifdef HAVE_LIBSELINUX |
|
+ { "context", no_argument, 0, 'Z' }, |
|
+#endif |
|
+ { NULL, 0, 0, 0 } |
|
+ }; |
|
+ |
|
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
|
+ { 'G', 'o' }, |
|
+ { 'L', 'o' }, |
|
+ { 'Z', 'o' }, |
|
+ { 'a', 'o' }, |
|
+ { 'c','n','r','z' }, |
|
+ { 'o', 'p' }, |
|
+ { 0 } |
|
+ }; |
|
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; |
|
+ |
|
+ setlocale(LC_ALL, ""); |
|
+ bindtextdomain(PACKAGE, LOCALEDIR); |
|
+ textdomain(PACKAGE); |
|
+ atexit(close_stdout); |
|
+ |
|
+ ctl->time_mode = TIME_SHORT; |
|
+ |
|
+ /* very basic default */ |
|
+ add_column(columns, ncolumns++, COL_UID); |
|
+ add_column(columns, ncolumns++, COL_USER); |
|
+ |
|
+ while ((c = getopt_long(argc, argv, "acfGg:hLl:no:prsuVxzZ", |
|
+ longopts, NULL)) != -1) { |
|
+ |
|
+ err_exclusive_options(c, longopts, excl, excl_st); |
|
+ |
|
+ switch (c) { |
|
+ case 'a': |
|
+ add_column(columns, ncolumns++, COL_PWD_WARN); |
|
+ add_column(columns, ncolumns++, COL_PWD_CTIME_MIN); |
|
+ add_column(columns, ncolumns++, COL_PWD_CTIME_MAX); |
|
+ add_column(columns, ncolumns++, COL_PWD_CTIME); |
|
+ add_column(columns, ncolumns++, COL_PWD_EXPIR); |
|
+ break; |
|
+ case 'c': |
|
+ outmode = OUT_COLON; |
|
+ break; |
|
+ case 'e': |
|
+ outmode = OUT_EXPORT; |
|
+ break; |
|
+ case 'f': |
|
+ add_column(columns, ncolumns++, COL_FAILED_LOGIN); |
|
+ add_column(columns, ncolumns++, COL_FAILED_TTY); |
|
+ break; |
|
+ case 'G': |
|
+ add_column(columns, ncolumns++, COL_GID); |
|
+ add_column(columns, ncolumns++, COL_GROUP); |
|
+ add_column(columns, ncolumns++, COL_SGIDS); |
|
+ add_column(columns, ncolumns++, COL_SGROUPS); |
|
+ break; |
|
+ case 'g': |
|
+ groups = optarg; |
|
+ break; |
|
+ case 'h': |
|
+ usage(stdout); |
|
+ break; |
|
+ case 'L': |
|
+ add_column(columns, ncolumns++, COL_LAST_TTY); |
|
+ add_column(columns, ncolumns++, COL_LAST_HOSTNAME); |
|
+ add_column(columns, ncolumns++, COL_LAST_LOGIN); |
|
+ break; |
|
+ case 'l': |
|
+ logins = optarg; |
|
+ break; |
|
+ case 'n': |
|
+ outmode = OUT_NEWLINE; |
|
+ break; |
|
+ case 'o': |
|
+ if (optarg) { |
|
+ if (*optarg == '=') |
|
+ optarg++; |
|
+ ncolumns = string_to_idarray(optarg, |
|
+ columns, ARRAY_SIZE(columns), |
|
+ column_name_to_id); |
|
+ if (ncolumns < 0) |
|
+ return EXIT_FAILURE; |
|
+ } |
|
+ opt_o = 1; |
|
+ break; |
|
+ case 'r': |
|
+ outmode = OUT_RAW; |
|
+ break; |
|
+ case 's': |
|
+ ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN); |
|
+ ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX); |
|
+ lslogins_flag |= F_SYSAC; |
|
+ break; |
|
+ case 'u': |
|
+ ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN); |
|
+ ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX); |
|
+ lslogins_flag |= F_USRAC; |
|
+ break; |
|
+ case 'p': |
|
+ add_column(columns, ncolumns++, COL_PWDEMPTY); |
|
+ add_column(columns, ncolumns++, COL_PWDLOCK); |
|
+ add_column(columns, ncolumns++, COL_PWDDENY); |
|
+ add_column(columns, ncolumns++, COL_NOLOGIN); |
|
+ add_column(columns, ncolumns++, COL_HUSH_STATUS); |
|
+ break; |
|
+ case 'z': |
|
+ outmode = OUT_NUL; |
|
+ break; |
|
+ case OPT_WTMP: |
|
+ path_wtmp = optarg; |
|
+ break; |
|
+ case OPT_BTMP: |
|
+ path_btmp = optarg; |
|
+ break; |
|
+ case OPT_NOTRUNC: |
|
+ ctl->notrunc = 1; |
|
+ break; |
|
+ case OPT_NOHEAD: |
|
+ ctl->noheadings = 1; |
|
+ break; |
|
+ case OPT_TIME_FMT: |
|
+ { |
|
+ size_t i; |
|
+ |
|
+ for (i = 0; i < ARRAY_SIZE(timefmts); i++) { |
|
+ if (strcmp(timefmts[i].name, optarg) == 0) { |
|
+ ctl->time_mode = timefmts[i].val; |
|
+ break; |
|
+ } |
|
+ } |
|
+ if (ctl->time_mode == TIME_INVALID) |
|
+ usage(stderr); |
|
+ } |
|
+ break; |
|
+ case 'V': |
|
+ printf(UTIL_LINUX_VERSION); |
|
+ return EXIT_SUCCESS; |
|
+ case 'Z': |
|
+ { |
|
+#ifdef HAVE_LIBSELINUX |
|
+ int sl = is_selinux_enabled(); |
|
+ if (sl < 0) |
|
+ warn(_("failed to request selinux state")); |
|
+ else |
|
+ ctl->selinux_enabled = sl == 1; |
|
+#endif |
|
+ add_column(columns, ncolumns++, COL_SELINUX); |
|
+ break; |
|
+ } |
|
+ default: |
|
+ usage(stderr); |
|
+ } |
|
+ } |
|
+ |
|
+ if (argc - optind == 1) { |
|
+ if (strchr(argv[optind], ',')) |
|
+ errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users.")); |
|
+ logins = argv[optind]; |
|
+ outmode = OUT_PRETTY; |
|
+ } else if (argc != optind) |
|
+ usage(stderr); |
|
+ |
|
+ scols_init_debug(0); |
|
+ |
|
+ /* lslogins -u -s == lslogins */ |
|
+ if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC) |
|
+ lslogins_flag &= ~(F_USRAC | F_SYSAC); |
|
+ |
|
+ if (outmode == OUT_PRETTY && !opt_o) { |
|
+ /* all columns for lslogins <username> */ |
|
+ for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++) |
|
+ columns[ncolumns++] = i; |
|
+ |
|
+ } else if (ncolumns == 2 && !opt_o) { |
|
+ /* default colummns */ |
|
+ add_column(columns, ncolumns++, COL_NPROCS); |
|
+ add_column(columns, ncolumns++, COL_PWDLOCK); |
|
+ add_column(columns, ncolumns++, COL_PWDDENY); |
|
+ add_column(columns, ncolumns++, COL_LAST_LOGIN); |
|
+ add_column(columns, ncolumns++, COL_GECOS); |
|
+ } |
|
+ |
|
+ if (require_wtmp()) |
|
+ parse_wtmp(ctl, path_wtmp); |
|
+ if (require_btmp()) |
|
+ parse_btmp(ctl, path_btmp); |
|
+ |
|
+ if (logins || groups) |
|
+ get_ulist(ctl, logins, groups); |
|
+ |
|
+ if (create_usertree(ctl)) |
|
+ return EXIT_FAILURE; |
|
+ |
|
+ print_user_table(ctl); |
|
+ |
|
+ scols_unref_table(tb); |
|
+ tdestroy(ctl->usertree, free_user); |
|
+ free_ctl(ctl); |
|
+ |
|
+ return EXIT_SUCCESS; |
|
+} |
|
diff -up util-linux-2.23.2/login-utils/Makemodule.am.kzak util-linux-2.23.2/login-utils/Makemodule.am |
|
--- util-linux-2.23.2/login-utils/Makemodule.am.kzak 2013-06-13 09:46:10.441650801 +0200 |
|
+++ util-linux-2.23.2/login-utils/Makemodule.am 2014-12-12 15:28:30.576177139 +0100 |
|
@@ -145,6 +145,25 @@ endif |
|
endif # BUILD_NEWGRP |
|
|
|
|
|
+if BUILD_LSLOGINS |
|
+usrbin_exec_PROGRAMS += lslogins |
|
+dist_man_MANS += login-utils/lslogins.1 |
|
+lslogins_SOURCES = \ |
|
+ login-utils/lslogins.c \ |
|
+ login-utils/logindefs.c \ |
|
+ login-utils/logindefs.h |
|
+lslogins_LDADD = $(LDADD) libcommon.la libsmartcols.la |
|
+lslogins_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) |
|
+if HAVE_SELINUX |
|
+lslogins_LDADD += -lselinux |
|
+endif |
|
+if HAVE_SYSTEMD |
|
+lslogins_LDADD += $(SYSTEMD_LIBS) $(SYSTEMD_JOURNAL_LIBS) |
|
+lslogins_CFLAGS += $(SYSTEMD_CFLAGS) $(SYSTEMD_JOURNAL_CFLAGS) |
|
+endif |
|
+endif # BUILD_LSLOGINS |
|
+ |
|
+ |
|
if BUILD_VIPW |
|
usrsbin_exec_PROGRAMS += vipw |
|
dist_man_MANS += \
|
|
|