dcavalca / rpms / util-linux

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