summaryrefslogtreecommitdiffstats
path: root/login-utils
diff options
context:
space:
mode:
Diffstat (limited to 'login-utils')
-rw-r--r--login-utils/Makemodule.am247
-rw-r--r--login-utils/auth.c65
-rw-r--r--login-utils/auth.h13
-rw-r--r--login-utils/ch-common.c34
-rw-r--r--login-utils/ch-common.h6
-rw-r--r--login-utils/chfn.1108
-rw-r--r--login-utils/chfn.c492
-rw-r--r--login-utils/chsh.165
-rw-r--r--login-utils/chsh.c365
-rw-r--r--login-utils/islocal.c112
-rw-r--r--login-utils/islocal.h1
-rw-r--r--login-utils/last.1198
-rw-r--r--login-utils/last.c1032
-rw-r--r--login-utils/lastb.11
-rw-r--r--login-utils/libuser.c72
-rw-r--r--login-utils/libuser.h14
-rw-r--r--login-utils/login.1350
-rw-r--r--login-utils/login.c1386
-rw-r--r--login-utils/logindefs.c437
-rw-r--r--login-utils/logindefs.h14
-rw-r--r--login-utils/lslogins.1152
-rw-r--r--login-utils/lslogins.c1595
-rw-r--r--login-utils/newgrp.134
-rw-r--r--login-utils/newgrp.c240
-rw-r--r--login-utils/nologin.852
-rw-r--r--login-utils/nologin.c89
-rw-r--r--login-utils/runuser.1254
-rw-r--r--login-utils/runuser.c7
-rw-r--r--login-utils/selinux_utils.c29
-rw-r--r--login-utils/selinux_utils.h7
-rw-r--r--login-utils/setpwnam.c218
-rw-r--r--login-utils/setpwnam.h33
-rw-r--r--login-utils/su-common.c1520
-rw-r--r--login-utils/su-common.h11
-rw-r--r--login-utils/su.1294
-rw-r--r--login-utils/su.c8
-rw-r--r--login-utils/sulogin-consoles.c828
-rw-r--r--login-utils/sulogin-consoles.h54
-rw-r--r--login-utils/sulogin.895
-rw-r--r--login-utils/sulogin.c1110
-rw-r--r--login-utils/utmpdump.193
-rw-r--r--login-utils/utmpdump.c397
-rw-r--r--login-utils/vigr.81
-rw-r--r--login-utils/vipw.889
-rw-r--r--login-utils/vipw.c373
45 files changed, 12595 insertions, 0 deletions
diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am
new file mode 100644
index 0000000..aafbea3
--- /dev/null
+++ b/login-utils/Makemodule.am
@@ -0,0 +1,247 @@
+
+if BUILD_LAST
+usrbin_exec_PROGRAMS += last
+dist_man_MANS += \
+ login-utils/last.1 \
+ login-utils/lastb.1
+last_SOURCES = login-utils/last.c lib/monotonic.c
+last_LDADD = $(LDADD) libcommon.la $(REALTIME_LIBS)
+
+install-exec-hook-last:
+ cd $(DESTDIR)$(usrbin_execdir) && ln -sf last lastb
+
+INSTALL_EXEC_HOOKS += install-exec-hook-last
+endif
+
+if BUILD_SULOGIN
+sbin_PROGRAMS += sulogin
+dist_man_MANS += login-utils/sulogin.8
+sulogin_SOURCES = \
+ login-utils/sulogin.c \
+ login-utils/sulogin-consoles.c \
+ login-utils/sulogin-consoles.h
+sulogin_LDADD = $(LDADD) libcommon.la
+
+if HAVE_LIBCRYPT
+sulogin_LDADD += -lcrypt
+endif
+if HAVE_SELINUX
+sulogin_LDADD += -lselinux
+endif
+
+check_PROGRAMS += test_consoles
+test_consoles_SOURCES = login-utils/sulogin-consoles.c
+test_consoles_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
+test_consoles_LDADD = $(LDADD) libcommon.la
+endif # BUILD_SULOGIN
+
+
+if BUILD_LOGIN
+bin_PROGRAMS += login
+dist_man_MANS += login-utils/login.1
+login_SOURCES = \
+ login-utils/login.c \
+ login-utils/logindefs.c \
+ login-utils/logindefs.h
+login_LDADD = $(LDADD) libcommon.la -lpam
+if HAVE_LINUXPAM
+login_LDADD += -lpam_misc
+endif
+if HAVE_AUDIT
+login_LDADD += -laudit
+endif
+if HAVE_SELINUX
+login_LDADD += -lselinux
+endif
+endif # BUILD_LOGIN
+
+
+if BUILD_NOLOGIN
+sbin_PROGRAMS += nologin
+dist_man_MANS += login-utils/nologin.8
+nologin_SOURCES = login-utils/nologin.c
+endif
+
+
+if BUILD_UTMPDUMP
+usrbin_exec_PROGRAMS += utmpdump
+dist_man_MANS += login-utils/utmpdump.1
+utmpdump_SOURCES = login-utils/utmpdump.c
+utmpdump_LDADD = $(LDADD) libcommon.la
+endif
+
+
+if BUILD_CHFN_CHSH
+usrbin_exec_PROGRAMS += chfn chsh
+dist_man_MANS += \
+ login-utils/chfn.1 \
+ login-utils/chsh.1
+
+chfn_chsh_sources = \
+ login-utils/ch-common.h \
+ login-utils/ch-common.c
+chfn_chsh_cflags = $(SUID_CFLAGS) $(AM_CFLAGS)
+chfn_chsh_ldflags = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+chfn_chsh_ldadd = libcommon.la $(READLINE_LIBS)
+
+if CHFN_CHSH_PASSWORD
+chfn_chsh_ldadd += -lpam
+if HAVE_LINUXPAM
+chfn_chsh_ldadd += -lpam_misc
+endif
+chfn_chsh_sources += \
+ login-utils/auth.c \
+ login-utils/auth.h
+endif # CHFN_CHSH_PASSWORD
+
+if HAVE_USER
+chfn_chsh_ldflags += $(LIBUSER_LIBS)
+chfn_chsh_cflags += $(LIBUSER_CFLAGS)
+chfn_chsh_sources+= \
+ login-utils/libuser.c \
+ login-utils/libuser.h
+else
+chfn_chsh_sources += \
+ login-utils/islocal.c \
+ login-utils/islocal.h \
+ login-utils/setpwnam.c \
+ login-utils/setpwnam.h
+endif
+
+if HAVE_SELINUX
+chfn_chsh_sources += \
+ login-utils/selinux_utils.c \
+ login-utils/selinux_utils.h
+chfn_chsh_ldadd += -lselinux
+endif
+
+chfn_SOURCES = \
+ login-utils/chfn.c \
+ login-utils/logindefs.c \
+ login-utils/logindefs.h \
+ $(chfn_chsh_sources)
+chfn_CFLAGS = $(chfn_chsh_cflags)
+chfn_LDFLAGS = $(chfn_chsh_ldflags)
+chfn_LDADD = $(LDADD) $(chfn_chsh_ldadd)
+
+chsh_SOURCES = login-utils/chsh.c $(chfn_chsh_sources)
+chsh_CFLAGS = $(chfn_chsh_cflags)
+chsh_LDFLAGS = $(chfn_chsh_ldflags)
+chsh_LDADD = $(LDADD) $(chfn_chsh_ldadd)
+endif # BUILD_CHFN_CHSH
+
+
+if BUILD_SU
+bin_PROGRAMS += su
+dist_man_MANS += login-utils/su.1
+su_SOURCES = \
+ login-utils/su.c \
+ login-utils/su-common.c \
+ login-utils/su-common.h \
+ login-utils/logindefs.c \
+ login-utils/logindefs.h
+su_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
+su_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+su_LDADD = $(LDADD) libcommon.la -lpam
+if HAVE_LINUXPAM
+su_LDADD += -lpam_misc
+endif
+if HAVE_UTIL
+su_LDADD += -lutil
+endif
+endif # BUILD_SU
+
+
+if BUILD_RUNUSER
+sbin_PROGRAMS += runuser
+dist_man_MANS += login-utils/runuser.1
+runuser_SOURCES = \
+ login-utils/runuser.c \
+ login-utils/su-common.c \
+ login-utils/su-common.h \
+ login-utils/logindefs.c \
+ login-utils/logindefs.h
+runuser_LDADD = $(LDADD) libcommon.la -lpam
+if HAVE_LINUXPAM
+runuser_LDADD += -lpam_misc
+endif
+if HAVE_UTIL
+runuser_LDADD += -lutil
+endif
+endif # BUILD_RUNUSER
+
+
+if BUILD_NEWGRP
+usrbin_exec_PROGRAMS += newgrp
+dist_man_MANS += login-utils/newgrp.1
+newgrp_SOURCES = login-utils/newgrp.c
+newgrp_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
+newgrp_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+newgrp_LDADD = $(LDADD)
+if HAVE_LIBCRYPT
+newgrp_LDADD += -lcrypt
+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 += \
+ login-utils/vigr.8 \
+ login-utils/vipw.8
+vipw_SOURCES = \
+ login-utils/vipw.c \
+ login-utils/setpwnam.h
+vipw_LDADD = $(LDADD) libcommon.la
+if HAVE_SELINUX
+vipw_LDADD += -lselinux
+endif
+install-exec-hook-vipw::
+ cd $(DESTDIR)$(usrsbin_execdir) && ln -sf vipw vigr
+
+INSTALL_EXEC_HOOKS += install-exec-hook-vipw
+endif # BUILD_VIPW
+
+
+check_PROGRAMS += \
+ test_islocal \
+ test_logindefs
+
+test_islocal_SOURCES = login-utils/islocal.c
+test_islocal_CPPFLAGS = -DTEST_PROGRAM $(AM_CPPFLAGS)
+
+test_logindefs_SOURCES = \
+ login-utils/logindefs.c \
+ login-utils/logindefs.h
+test_logindefs_CPPFLAGS = -DTEST_PROGRAM $(AM_CPPFLAGS)
+
+
+install-exec-hook:
+if BUILD_SU
+if MAKEINSTALL_DO_CHOWN
+ chown root:root $(DESTDIR)$(bindir)/su
+endif
+if MAKEINSTALL_DO_SETUID
+ chmod 4755 $(DESTDIR)$(bindir)/su
+endif
+endif
+if BUILD_VIPW
+ cd $(DESTDIR)$(usrsbin_execdir) && ln -sf vipw vigr
+endif
diff --git a/login-utils/auth.c b/login-utils/auth.c
new file mode 100644
index 0000000..fdeb12b
--- /dev/null
+++ b/login-utils/auth.c
@@ -0,0 +1,65 @@
+/*
+ * auth.c -- PAM authorization code, common between chsh and chfn
+ * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
+ *
+ * this program is free software. you can redistribute it and
+ * modify it under the terms of the gnu general public license.
+ * there is no warranty.
+ *
+ */
+
+#include <security/pam_appl.h>
+#ifdef HAVE_SECURITY_PAM_MISC_H
+# include <security/pam_misc.h>
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+# include <security/openpam.h>
+#endif
+
+#include "c.h"
+#include "auth.h"
+
+static int pam_fail_check(pam_handle_t *pamh, int retcode)
+{
+ if (retcode == PAM_SUCCESS)
+ return 0;
+ warnx("%s", pam_strerror(pamh, retcode));
+ pam_end(pamh, retcode);
+ return 1;
+}
+
+int auth_pam(const char *service_name, uid_t uid, const char *username)
+{
+ if (uid != 0) {
+ pam_handle_t *pamh = NULL;
+#ifdef HAVE_SECURITY_PAM_MISC_H
+ struct pam_conv conv = { misc_conv, NULL };
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+ struct pam_conv conv = { openpam_ttyconv, NULL };
+#endif
+ int retcode;
+
+ retcode = pam_start(service_name, username, &conv, &pamh);
+ if (pam_fail_check(pamh, retcode))
+ return FALSE;
+
+ retcode = pam_authenticate(pamh, 0);
+ if (pam_fail_check(pamh, retcode))
+ return FALSE;
+
+ retcode = pam_acct_mgmt(pamh, 0);
+ if (retcode == PAM_NEW_AUTHTOK_REQD)
+ retcode =
+ pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ if (pam_fail_check(pamh, retcode))
+ return FALSE;
+
+ retcode = pam_setcred(pamh, 0);
+ if (pam_fail_check(pamh, retcode))
+ return FALSE;
+
+ pam_end(pamh, 0);
+ /* no need to establish a session; this isn't a
+ * session-oriented activity... */
+ }
+ return TRUE;
+}
diff --git a/login-utils/auth.h b/login-utils/auth.h
new file mode 100644
index 0000000..bf7c369
--- /dev/null
+++ b/login-utils/auth.h
@@ -0,0 +1,13 @@
+/*
+ * auth.h -- PAM authorization code, common between chsh and chfn
+ * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
+ *
+ * this program is free software. you can redistribute it and
+ * modify it under the terms of the gnu general public license.
+ * there is no warranty.
+ *
+ */
+
+#include <sys/types.h>
+
+extern int auth_pam(const char *service_name, uid_t uid, const char *username);
diff --git a/login-utils/ch-common.c b/login-utils/ch-common.c
new file mode 100644
index 0000000..34b09f3
--- /dev/null
+++ b/login-utils/ch-common.c
@@ -0,0 +1,34 @@
+/*
+ * chfn and chsh shared functions
+ *
+ * this program is free software. you can redistribute it and
+ * modify it under the terms of the gnu general public license.
+ * there is no warranty.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#include "c.h"
+#include "nls.h"
+
+#include "ch-common.h"
+
+/*
+ * illegal_passwd_chars () -
+ * check whether a string contains illegal characters
+ */
+int illegal_passwd_chars(const char *str)
+{
+ const char illegal[] = ",:=\"\n";
+ const size_t len = strlen(str);
+ size_t i;
+
+ if (strpbrk(str, illegal))
+ return 1;
+ for (i = 0; i < len; i++) {
+ if (iscntrl(str[i]))
+ return 1;
+ }
+ return 0;
+}
diff --git a/login-utils/ch-common.h b/login-utils/ch-common.h
new file mode 100644
index 0000000..7f70e50
--- /dev/null
+++ b/login-utils/ch-common.h
@@ -0,0 +1,6 @@
+#ifndef UTIL_LINUX_CH_COMMON_H
+#define UTIL_LINUX_CH_COMMON_H
+
+extern int illegal_passwd_chars(const char *str);
+
+#endif /* UTIL_LINUX_CH_COMMON */
diff --git a/login-utils/chfn.1 b/login-utils/chfn.1
new file mode 100644
index 0000000..c7130c4
--- /dev/null
+++ b/login-utils/chfn.1
@@ -0,0 +1,108 @@
+.\"
+.\" chfn.1 -- change your finger information
+.\" (c) 1994 by salvatore valente <svalente@athena.mit.edu>
+.\"
+.\" This program is free software. You can redistribute it and
+.\" modify it under the terms of the GNU General Public License.
+.\" There is no warranty.
+.\"
+.TH CHFN 1 "November 2015" "util-linux" "User Commands"
+.SH NAME
+chfn \- change your finger information
+.SH SYNOPSIS
+.B chfn
+.RB [ \-f
+.IR full-name ]
+.RB [ \-o
+.IR office ]
+.RB [ \-p
+.IR office-phone ]
+.RB [ \-h
+.IR home-phone ]
+.RB [ \-u ]
+.RB [ \-v ]
+.RI [ username ]
+.SH DESCRIPTION
+.B chfn
+is used to change your finger information. This information is
+stored in the
+.I /etc/passwd
+file, and is displayed by the
+.B finger
+program. The Linux
+.B finger
+command will display four pieces of information that can be changed by
+.BR chfn :
+your real name, your work room and phone, and your home phone.
+.PP
+Any of the four pieces of information can be specified on the command
+line. If no information is given on the command line,
+.B chfn
+enters interactive mode.
+.PP
+In interactive mode,
+.B chfn
+will prompt for each field. At a prompt, you can enter the new information,
+or just press return to leave the field unchanged. Enter the keyword
+"none" to make the field blank.
+.PP
+.B chfn
+supports non-local entries (kerberos, LDAP, etc.\&) if linked with libuser,
+otherwise use \fBypchfn\fR, \fBlchfn\fR or any other implementation for
+non-local entries.
+.SH OPTIONS
+.TP
+.BR \-f , " \-\-full\-name " \fIfull-name
+Specify your real name.
+.TP
+.BR \-o , " \-\-office " \fIoffice
+Specify your office room number.
+.TP
+.BR \-p , " \-\-office\-phone " \fIoffice-phone
+Specify your office phone number.
+.TP
+.BR \-h , " \-\-home\-phone " \fIhome-phone
+Specify your home phone number.
+.TP
+.BR \-u , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-v , " \-\-version"
+Display version information and exit.
+.SH CONFIG FILE ITEMS
+.B chfn
+reads the
+.IR /etc\:/login.defs (5)
+configuration file. Note that the configuration file could be
+distributed with another package (e.g. shadow-utils). The following
+configuration items are relevant for
+.BR chfn (1):
+.PP
+.BI CHFN_RESTRICT " string"
+.RS 4
+Indicate which fields are changeable by \fBchfn\fR.
+
+The boolean setting \fB"yes"\fR means that only the Office, Office Phone and
+Home Phone fields are changeable, and boolean setting \fB"no"\fR means that
+also the Full Name is changeable.
+
+Another way to specify changeable fields is by abbreviations: f = Full Name,
+r = Office (room), w = Office (work) Phone, h = Home Phone. For example,
+\fBCHFN_RESTRICT "wh"\fR allows changing work and home phone numbers.
+
+If CHFN_RESTRICT is undefined, then all finger information is read-only.
+This is the default.
+.RE
+.PP
+.SH "EXIT STATUS"
+Returns 0 if operation was successful, 1 if operation failed or command syntax was not valid.
+.SH "SEE ALSO"
+.BR chsh (1),
+.BR finger (1),
+.BR login.defs (5),
+.BR passwd (5)
+.SH AUTHOR
+Salvatore Valente <svalente@mit.edu>
+.SH AVAILABILITY
+The chfn command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/login-utils/chfn.c b/login-utils/chfn.c
new file mode 100644
index 0000000..c5312fa
--- /dev/null
+++ b/login-utils/chfn.c
@@ -0,0 +1,492 @@
+/*
+ * chfn.c -- change your finger information
+ * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
+ * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
+ *
+ * this program is free software. you can redistribute it and
+ * modify it under the terms of the gnu general public license.
+ * there is no warranty.
+ *
+ * $Author: aebr $
+ * $Revision: 1.18 $
+ * $Date: 1998/06/11 22:30:11 $
+ *
+ * Updated Thu Oct 12 09:19:26 1995 by faith@cs.unc.edu with security
+ * patches from Zefram <A.Main@dcs.warwick.ac.uk>
+ *
+ * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de,
+ * to remove trailing empty fields. Oct 5, 96.
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "env.h"
+#include "closestream.h"
+#include "islocal.h"
+#include "nls.h"
+#include "setpwnam.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "logindefs.h"
+
+#include "ch-common.h"
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+# include "selinux_utils.h"
+#endif
+
+#ifdef HAVE_LIBUSER
+# include <libuser/user.h>
+# include "libuser.h"
+#elif CHFN_CHSH_PASSWORD
+# include "auth.h"
+#endif
+
+#ifdef HAVE_LIBREADLINE
+# define _FUNCTION_DEF
+# include <readline/readline.h>
+#endif
+
+struct finfo {
+ char *full_name;
+ char *office;
+ char *office_phone;
+ char *home_phone;
+ char *other;
+};
+
+struct chfn_control {
+ struct passwd *pw;
+ char *username;
+ /* "oldf" Contains the users original finger information.
+ * "newf" Contains the changed finger information, and contains
+ * NULL in fields that haven't been changed.
+ * In the end, "newf" is folded into "oldf". */
+ struct finfo oldf, newf;
+ unsigned int
+ allow_fullname:1, /* The login.defs restriction */
+ allow_room:1, /* see: man login.defs(5) */
+ allow_work:1, /* and look for CHFN_RESTRICT */
+ allow_home:1, /* keyword for these four. */
+ changed:1, /* is change requested */
+ interactive:1; /* whether to prompt for fields or not */
+};
+
+/* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
+#define MAX_FIELD_SIZE 256
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *fp = stdout;
+ fputs(USAGE_HEADER, fp);
+ fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, fp);
+ fputs(_("Change your finger information.\n"), fp);
+
+ fputs(USAGE_OPTIONS, fp);
+ fputs(_(" -f, --full-name <full-name> real name\n"), fp);
+ fputs(_(" -o, --office <office> office number\n"), fp);
+ fputs(_(" -p, --office-phone <phone> office phone number\n"), fp);
+ fputs(_(" -h, --home-phone <phone> home phone number\n"), fp);
+ fputs(USAGE_SEPARATOR, fp);
+ printf( " -u, --help %s\n", USAGE_OPTSTR_HELP);
+ printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION);
+ printf(USAGE_MAN_TAIL("chfn(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * check_gecos_string () --
+ * check that the given gecos string is legal. if it's not legal,
+ * output "msg" followed by a description of the problem, and return (-1).
+ */
+static int check_gecos_string(const char *msg, char *gecos)
+{
+ const size_t len = strlen(gecos);
+
+ if (MAX_FIELD_SIZE < len) {
+ warnx(_("field %s is too long"), msg);
+ return -1;
+ }
+ if (illegal_passwd_chars(gecos)) {
+ warnx(_("%s: has illegal characters"), gecos);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * parse_argv () --
+ * parse the command line arguments.
+ * returns true if no information beyond the username was given.
+ */
+static void parse_argv(struct chfn_control *ctl, int argc, char **argv)
+{
+ int index, c, status = 0;
+ static const struct option long_options[] = {
+ { "full-name", required_argument, NULL, 'f' },
+ { "office", required_argument, NULL, 'o' },
+ { "office-phone", required_argument, NULL, 'p' },
+ { "home-phone", required_argument, NULL, 'h' },
+ { "help", no_argument, NULL, 'u' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0 },
+ };
+
+ while ((c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
+ &index)) != -1) {
+ switch (c) {
+ case 'f':
+ if (!ctl->allow_fullname)
+ errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Name"));
+ ctl->newf.full_name = optarg;
+ status += check_gecos_string(_("Name"), optarg);
+ break;
+ case 'o':
+ if (!ctl->allow_room)
+ errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office"));
+ ctl->newf.office = optarg;
+ status += check_gecos_string(_("Office"), optarg);
+ break;
+ case 'p':
+ if (!ctl->allow_work)
+ errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office Phone"));
+ ctl->newf.office_phone = optarg;
+ status += check_gecos_string(_("Office Phone"), optarg);
+ break;
+ case 'h':
+ if (!ctl->allow_home)
+ errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Home Phone"));
+ ctl->newf.home_phone = optarg;
+ status += check_gecos_string(_("Home Phone"), optarg);
+ break;
+ case 'v':
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ case 'u':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ ctl->changed = 1;
+ ctl->interactive = 0;
+ }
+ if (status != 0)
+ exit(EXIT_FAILURE);
+ /* done parsing arguments. check for a username. */
+ if (optind < argc) {
+ if (optind + 1 < argc) {
+ warnx(_("cannot handle multiple usernames"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ ctl->username = argv[optind];
+ }
+ return;
+}
+
+/*
+ * parse_passwd () --
+ * take a struct password and fill in the fields of the struct finfo.
+ */
+static void parse_passwd(struct chfn_control *ctl)
+{
+ char *gecos;
+
+ if (!ctl->pw)
+ return;
+ /* use pw_gecos - we take a copy since PAM destroys the original */
+ gecos = xstrdup(ctl->pw->pw_gecos);
+ /* extract known fields */
+ ctl->oldf.full_name = strsep(&gecos, ",");
+ ctl->oldf.office = strsep(&gecos, ",");
+ ctl->oldf.office_phone = strsep(&gecos, ",");
+ ctl->oldf.home_phone = strsep(&gecos, ",");
+ /* extra fields contain site-specific information, and can
+ * not be changed by this version of chfn. */
+ ctl->oldf.other = strsep(&gecos, ",");
+}
+
+/*
+ * ask_new_field () --
+ * ask the user for a given field and check that the string is legal.
+ */
+static char *ask_new_field(struct chfn_control *ctl, const char *question,
+ char *def_val)
+{
+ int len;
+ char *buf;
+#ifndef HAVE_LIBREADLINE
+ size_t dummy = 0;
+#endif
+
+ if (!def_val)
+ def_val = "";
+ while (true) {
+ printf("%s [%s]: ", question, def_val);
+ __fpurge(stdin);
+#ifdef HAVE_LIBREADLINE
+ rl_bind_key('\t', rl_insert);
+ if ((buf = readline(NULL)) == NULL)
+#else
+ if (getline(&buf, &dummy, stdin) < 0)
+#endif
+ errx(EXIT_FAILURE, _("Aborted."));
+ /* remove white spaces from string end */
+ ltrim_whitespace((unsigned char *) buf);
+ len = rtrim_whitespace((unsigned char *) buf);
+ if (len == 0) {
+ free(buf);
+ return xstrdup(def_val);
+ }
+ if (!strcasecmp(buf, "none")) {
+ free(buf);
+ ctl->changed = 1;
+ return xstrdup("");
+ }
+ if (check_gecos_string(question, buf) >= 0)
+ break;
+ }
+ ctl->changed = 1;
+ return buf;
+}
+
+/*
+ * get_login_defs()
+ * find /etc/login.defs CHFN_RESTRICT and save restrictions to run time
+ */
+static void get_login_defs(struct chfn_control *ctl)
+{
+ const char *s;
+ size_t i;
+ int broken = 0;
+
+ /* real root does not have restrictions */
+ if (geteuid() == getuid() && getuid() == 0) {
+ ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
+ return;
+ }
+ s = getlogindefs_str("CHFN_RESTRICT", "");
+ if (!strcmp(s, "yes")) {
+ ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
+ return;
+ }
+ if (!strcmp(s, "no")) {
+ ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
+ return;
+ }
+ for (i = 0; s[i]; i++) {
+ switch (s[i]) {
+ case 'f':
+ ctl->allow_fullname = 1;
+ break;
+ case 'r':
+ ctl->allow_room = 1;
+ break;
+ case 'w':
+ ctl->allow_work = 1;
+ break;
+ case 'h':
+ ctl->allow_home = 1;
+ break;
+ default:
+ broken = 1;
+ }
+ }
+ if (broken)
+ warnx(_("%s: CHFN_RESTRICT has unexpected value: %s"), _PATH_LOGINDEFS, s);
+ if (!ctl->allow_fullname && !ctl->allow_room && !ctl->allow_work && !ctl->allow_home)
+ errx(EXIT_FAILURE, _("%s: CHFN_RESTRICT does not allow any changes"), _PATH_LOGINDEFS);
+ return;
+}
+
+/*
+ * ask_info () --
+ * prompt the user for the finger information and store it.
+ */
+static void ask_info(struct chfn_control *ctl)
+{
+ if (ctl->allow_fullname)
+ ctl->newf.full_name = ask_new_field(ctl, _("Name"), ctl->oldf.full_name);
+ if (ctl->allow_room)
+ ctl->newf.office = ask_new_field(ctl, _("Office"), ctl->oldf.office);
+ if (ctl->allow_work)
+ ctl->newf.office_phone = ask_new_field(ctl, _("Office Phone"), ctl->oldf.office_phone);
+ if (ctl->allow_home)
+ ctl->newf.home_phone = ask_new_field(ctl, _("Home Phone"), ctl->oldf.home_phone);
+ putchar('\n');
+}
+
+/*
+ * find_field () --
+ * find field value in uninteractive mode; can be new, old, or blank
+ */
+static char *find_field(char *nf, char *of)
+{
+ if (nf)
+ return nf;
+ if (of)
+ return of;
+ return xstrdup("");
+}
+
+/*
+ * add_missing () --
+ * add not supplied field values when in uninteractive mode
+ */
+static void add_missing(struct chfn_control *ctl)
+{
+ ctl->newf.full_name = find_field(ctl->newf.full_name, ctl->oldf.full_name);
+ ctl->newf.office = find_field(ctl->newf.office, ctl->oldf.office);
+ ctl->newf.office_phone = find_field(ctl->newf.office_phone, ctl->oldf.office_phone);
+ ctl->newf.home_phone = find_field(ctl->newf.home_phone, ctl->oldf.home_phone);
+ ctl->newf.other = find_field(ctl->newf.other, ctl->oldf.other);
+ printf("\n");
+}
+
+/*
+ * save_new_data () --
+ * save the given finger info in /etc/passwd.
+ * return zero on success.
+ */
+static int save_new_data(struct chfn_control *ctl)
+{
+ char *gecos;
+ int len;
+
+ /* create the new gecos string */
+ len = xasprintf(&gecos, "%s,%s,%s,%s,%s",
+ ctl->newf.full_name,
+ ctl->newf.office,
+ ctl->newf.office_phone,
+ ctl->newf.home_phone,
+ ctl->newf.other);
+
+ /* remove trailing empty fields (but not subfields of ctl->newf.other) */
+ if (!ctl->newf.other) {
+ while (len > 0 && gecos[len - 1] == ',')
+ len--;
+ gecos[len] = 0;
+ }
+
+#ifdef HAVE_LIBUSER
+ if (set_value_libuser("chfn", ctl->username, ctl->pw->pw_uid,
+ LU_GECOS, gecos) < 0) {
+#else /* HAVE_LIBUSER */
+ /* write the new struct passwd to the passwd file. */
+ ctl->pw->pw_gecos = gecos;
+ if (setpwnam(ctl->pw, ".chfn") < 0) {
+ warn("setpwnam failed");
+#endif
+ printf(_
+ ("Finger information *NOT* changed. Try again later.\n"));
+ return -1;
+ }
+ free(gecos);
+ printf(_("Finger information changed.\n"));
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ uid_t uid;
+ struct chfn_control ctl = {
+ .interactive = 1
+ };
+
+ sanitize_env();
+ setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+ uid = getuid();
+
+ /* check /etc/login.defs CHFN_RESTRICT */
+ get_login_defs(&ctl);
+
+ parse_argv(&ctl, argc, argv);
+ if (!ctl.username) {
+ ctl.pw = getpwuid(uid);
+ if (!ctl.pw)
+ errx(EXIT_FAILURE, _("you (user %d) don't exist."),
+ uid);
+ ctl.username = ctl.pw->pw_name;
+ } else {
+ ctl.pw = getpwnam(ctl.username);
+ if (!ctl.pw)
+ errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
+ ctl.username);
+ }
+ parse_passwd(&ctl);
+#ifndef HAVE_LIBUSER
+ if (!(is_local(ctl.username)))
+ errx(EXIT_FAILURE, _("can only change local entries"));
+#endif
+
+#ifdef HAVE_LIBSELINUX
+ if (is_selinux_enabled() > 0) {
+ if (uid == 0) {
+ access_vector_t av = get_access_vector("passwd", "chfn");
+
+ if (selinux_check_passwd_access(av) != 0) {
+ security_context_t user_context;
+ if (getprevcon(&user_context) < 0)
+ user_context = NULL;
+ errx(EXIT_FAILURE,
+ _("%s is not authorized to change "
+ "the finger info of %s"),
+ user_context ? : _("Unknown user context"),
+ ctl.username);
+ }
+ }
+ if (setupDefaultContext(_PATH_PASSWD))
+ errx(EXIT_FAILURE,
+ _("can't set default context for %s"), _PATH_PASSWD);
+ }
+#endif
+
+#ifdef HAVE_LIBUSER
+ /* If we're setuid and not really root, disallow the password change. */
+ if (geteuid() != getuid() && uid != ctl.pw->pw_uid) {
+#else
+ if (uid != 0 && uid != ctl.pw->pw_uid) {
+#endif
+ errno = EACCES;
+ err(EXIT_FAILURE, _("running UID doesn't match UID of user we're "
+ "altering, change denied"));
+ }
+
+ printf(_("Changing finger information for %s.\n"), ctl.username);
+
+#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
+ if (!auth_pam("chfn", uid, ctl.username)) {
+ return EXIT_FAILURE;
+ }
+#endif
+
+ if (ctl.interactive)
+ ask_info(&ctl);
+
+ add_missing(&ctl);
+
+ if (!ctl.changed) {
+ printf(_("Finger information not changed.\n"));
+ return EXIT_SUCCESS;
+ }
+
+ return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/login-utils/chsh.1 b/login-utils/chsh.1
new file mode 100644
index 0000000..c955b05
--- /dev/null
+++ b/login-utils/chsh.1
@@ -0,0 +1,65 @@
+.\"
+.\" chsh.1 -- change your login shell
+.\" (c) 1994 by salvatore valente <svalente@athena.mit.edu>
+.\"
+.\" This program is free software. You can redistribute it and
+.\" modify it under the terms of the GNU General Public License.
+.\" There is no warranty.
+.\"
+.TH CHSH 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+chsh \- change your login shell
+.SH SYNOPSIS
+.B chsh
+.RB [ \-s
+.IR shell ]
+.RB [ \-l ]
+.RB [ \-h ]
+.RB [ \-v ]
+.RI [ username ]
+.SH DESCRIPTION
+.B chsh
+is used to change your login shell.
+If a shell is not given on the command line,
+.B chsh
+prompts for one.
+
+.B chsh
+supports non-local entries (kerberos, LDAP, etc.\&) if linked with libuser,
+otherwise use \fBypchsh\fR, \fBlchsh\fR or any other implementation for
+non-local entries.
+.SH OPTIONS
+.TP
+.BR \-s , " \-\-shell " \fIshell
+Specify your login shell.
+.TP
+.BR \-l , " \-\-list\-shells"
+Print the list of shells listed in
+.I /etc/shells
+and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.TP
+.BR \-v , " \-\-version"
+Display version information and exit.
+.SH "VALID SHELLS"
+.B chsh
+will accept the full pathname of any executable file on the system.
+However, it will issue a warning if the shell is not listed in the
+.I /etc/shells
+file.
+On the other hand, it can also be configured such that it will
+only accept shells listed in this file, unless you are root.
+.SH "EXIT STATUS"
+Returns 0 if operation was successful, 1 if operation failed or command syntax was not valid.
+.SH "SEE ALSO"
+.BR login (1),
+.BR login.defs (5),
+.BR passwd (5),
+.BR shells (5)
+.SH AUTHOR
+Salvatore Valente <svalente@mit.edu>
+.SH AVAILABILITY
+The chsh command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/login-utils/chsh.c b/login-utils/chsh.c
new file mode 100644
index 0000000..c267c72
--- /dev/null
+++ b/login-utils/chsh.c
@@ -0,0 +1,365 @@
+/*
+ * chsh.c -- change your login shell
+ * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
+ * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
+ *
+ * this program is free software. you can redistribute it and
+ * modify it under the terms of the gnu general public license.
+ * there is no warranty.
+ *
+ * $Author: aebr $
+ * $Revision: 1.19 $
+ * $Date: 1998/06/11 22:30:14 $
+ *
+ * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
+ * patches from Zefram <A.Main@dcs.warwick.ac.uk>
+ *
+ * Updated Mon Jul 1 18:46:22 1996 by janl@math.uio.no with security
+ * suggestion from Zefram. Disallowing users with shells not in /etc/shells
+ * from changing their shell.
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "env.h"
+#include "closestream.h"
+#include "islocal.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "setpwnam.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#include "ch-common.h"
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+# include "selinux_utils.h"
+#endif
+
+
+#ifdef HAVE_LIBUSER
+# include <libuser/user.h>
+# include "libuser.h"
+#elif CHFN_CHSH_PASSWORD
+# include "auth.h"
+#endif
+
+#ifdef HAVE_LIBREADLINE
+# define _FUNCTION_DEF
+# include <readline/readline.h>
+#endif
+
+struct sinfo {
+ char *username;
+ char *shell;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *fp = stdout;
+ fputs(USAGE_HEADER, fp);
+ fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, fp);
+ fputs(_("Change your login shell.\n"), fp);
+
+ fputs(USAGE_OPTIONS, fp);
+ fputs(_(" -s, --shell <shell> specify login shell\n"), fp);
+ fputs(_(" -l, --list-shells print list of shells and exit\n"), fp);
+ fputs(USAGE_SEPARATOR, fp);
+ printf( " -u, --help %s\n", USAGE_OPTSTR_HELP);
+ printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION);
+ printf(USAGE_MAN_TAIL("chsh(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * is_known_shell() -- if the given shell appears in /etc/shells,
+ * return true. if not, return false.
+ */
+static int is_known_shell(const char *shell_name)
+{
+ char *s, ret = 0;
+
+ if (!shell_name)
+ return 0;
+
+ setusershell();
+ while ((s = getusershell())) {
+ if (strcmp(shell_name, s) == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ endusershell();
+ return ret;
+}
+
+/*
+ * print_shells () -- /etc/shells is outputted to stdout.
+ */
+static void print_shells(void)
+{
+ char *s;
+
+ while ((s = getusershell()))
+ printf("%s\n", s);
+ endusershell();
+}
+
+#ifdef HAVE_LIBREADLINE
+static char *shell_name_generator(const char *text, int state)
+{
+ static size_t len;
+ char *s;
+
+ if (!state) {
+ setusershell();
+ len = strlen(text);
+ }
+
+ while ((s = getusershell())) {
+ if (strncmp(s, text, len) == 0)
+ return xstrdup(s);
+ }
+ return NULL;
+}
+
+static char **shell_name_completion(const char *text,
+ int start __attribute__((__unused__)),
+ int end __attribute__((__unused__)))
+{
+ rl_attempted_completion_over = 1;
+ return rl_completion_matches(text, shell_name_generator);
+}
+#endif
+
+/*
+ * parse_argv () --
+ * parse the command line arguments, and fill in "pinfo" with any
+ * information from the command line.
+ */
+static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
+{
+ static const struct option long_options[] = {
+ {"shell", required_argument, NULL, 's'},
+ {"list-shells", no_argument, NULL, 'l'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'v'},
+ {NULL, 0, NULL, 0},
+ };
+ int c;
+
+ while ((c = getopt_long(argc, argv, "s:lhuv", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'v':
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ case 'u': /* deprecated */
+ case 'h':
+ usage();
+ case 'l':
+ print_shells();
+ exit(EXIT_SUCCESS);
+ case 's':
+ pinfo->shell = optarg;
+ break;
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ /* done parsing arguments. check for a username. */
+ if (optind < argc) {
+ if (optind + 1 < argc) {
+ errx(EXIT_FAILURE, _("cannot handle multiple usernames"));
+ }
+ pinfo->username = argv[optind];
+ }
+}
+
+/*
+ * ask_new_shell () --
+ * ask the user for a shell and return it.
+ */
+static char *ask_new_shell(char *question, char *oldshell)
+{
+ int len;
+ char *ans = NULL;
+#ifdef HAVE_LIBREADLINE
+ rl_attempted_completion_function = shell_name_completion;
+#else
+ size_t dummy = 0;
+#endif
+ if (!oldshell)
+ oldshell = "";
+ printf("%s [%s]\n", question, oldshell);
+#ifdef HAVE_LIBREADLINE
+ if ((ans = readline("> ")) == NULL)
+#else
+ if (getline(&ans, &dummy, stdin) < 0)
+#endif
+ return NULL;
+ /* remove the newline at the end of ans. */
+ ltrim_whitespace((unsigned char *) ans);
+ len = rtrim_whitespace((unsigned char *) ans);
+ if (len == 0)
+ return NULL;
+ return ans;
+}
+
+/*
+ * check_shell () -- if the shell is completely invalid, print
+ * an error and exit.
+ */
+static void check_shell(const char *shell)
+{
+ if (*shell != '/')
+ errx(EXIT_FAILURE, _("shell must be a full path name"));
+ if (access(shell, F_OK) < 0)
+ errx(EXIT_FAILURE, _("\"%s\" does not exist"), shell);
+ if (access(shell, X_OK) < 0)
+ errx(EXIT_FAILURE, _("\"%s\" is not executable"), shell);
+ if (illegal_passwd_chars(shell))
+ errx(EXIT_FAILURE, _("%s: has illegal characters"), shell);
+ if (!is_known_shell(shell)) {
+#ifdef ONLY_LISTED_SHELLS
+ if (!getuid())
+ warnx(_("Warning: \"%s\" is not listed in %s."), shell,
+ _PATH_SHELLS);
+ else
+ errx(EXIT_FAILURE,
+ _("\"%s\" is not listed in %s.\n"
+ "Use %s -l to see list."), shell, _PATH_SHELLS,
+ program_invocation_short_name);
+#else
+ warnx(_("\"%s\" is not listed in %s.\n"
+ "Use %s -l to see list."), shell, _PATH_SHELLS,
+ program_invocation_short_name);
+#endif
+ }
+}
+
+int main(int argc, char **argv)
+{
+ char *oldshell;
+ int nullshell = 0;
+ const uid_t uid = getuid();
+ struct sinfo info = { NULL };
+ struct passwd *pw;
+
+ sanitize_env();
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ parse_argv(argc, argv, &info);
+ if (!info.username) {
+ pw = getpwuid(uid);
+ if (!pw)
+ errx(EXIT_FAILURE, _("you (user %d) don't exist."),
+ uid);
+ } else {
+ pw = getpwnam(info.username);
+ if (!pw)
+ errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
+ info.username);
+ }
+
+#ifndef HAVE_LIBUSER
+ if (!(is_local(pw->pw_name)))
+ errx(EXIT_FAILURE, _("can only change local entries"));
+#endif
+
+#ifdef HAVE_LIBSELINUX
+ if (is_selinux_enabled() > 0) {
+ if (uid == 0) {
+ access_vector_t av = get_access_vector("passwd", "chsh");
+
+ if (selinux_check_passwd_access(av) != 0) {
+ security_context_t user_context;
+ if (getprevcon(&user_context) < 0)
+ user_context =
+ (security_context_t) NULL;
+
+ errx(EXIT_FAILURE,
+ _("%s is not authorized to change the shell of %s"),
+ user_context ? : _("Unknown user context"),
+ pw->pw_name);
+ }
+ }
+ if (setupDefaultContext(_PATH_PASSWD) != 0)
+ errx(EXIT_FAILURE,
+ _("can't set default context for %s"), _PATH_PASSWD);
+ }
+#endif
+
+ oldshell = pw->pw_shell;
+ if (oldshell == NULL || *oldshell == '\0') {
+ oldshell = _PATH_BSHELL; /* default */
+ nullshell = 1;
+ }
+
+ /* reality check */
+#ifdef HAVE_LIBUSER
+ /* If we're setuid and not really root, disallow the password change. */
+ if (geteuid() != getuid() && uid != pw->pw_uid) {
+#else
+ if (uid != 0 && uid != pw->pw_uid) {
+#endif
+ errno = EACCES;
+ err(EXIT_FAILURE,
+ _("running UID doesn't match UID of user we're "
+ "altering, shell change denied"));
+ }
+ if (uid != 0 && !is_known_shell(oldshell)) {
+ errno = EACCES;
+ err(EXIT_FAILURE, _("your shell is not in %s, "
+ "shell change denied"), _PATH_SHELLS);
+ }
+
+ printf(_("Changing shell for %s.\n"), pw->pw_name);
+
+#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
+ if (!auth_pam("chsh", uid, pw->pw_name)) {
+ return EXIT_FAILURE;
+ }
+#endif
+ if (!info.shell) {
+ info.shell = ask_new_shell(_("New shell"), oldshell);
+ if (!info.shell)
+ return EXIT_SUCCESS;
+ }
+
+ check_shell(info.shell);
+
+ if (!nullshell && strcmp(oldshell, info.shell) == 0)
+ errx(EXIT_SUCCESS, _("Shell not changed."));
+
+#ifdef HAVE_LIBUSER
+ if (set_value_libuser("chsh", pw->pw_name, uid,
+ LU_LOGINSHELL, info.shell) < 0)
+ errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later."));
+#else
+ pw->pw_shell = info.shell;
+ if (setpwnam(pw, ".chsh") < 0)
+ err(EXIT_FAILURE, _("setpwnam failed\n"
+ "Shell *NOT* changed. Try again later."));
+#endif
+
+ printf(_("Shell changed.\n"));
+ return EXIT_SUCCESS;
+}
diff --git a/login-utils/islocal.c b/login-utils/islocal.c
new file mode 100644
index 0000000..7c4e775
--- /dev/null
+++ b/login-utils/islocal.c
@@ -0,0 +1,112 @@
+/*
+ * islocal.c - returns true if user is registered in the local
+ * /etc/passwd file. Written by Álvaro Martínez Echevarria,
+ * alvaro@enano.etsit.upm.es, to allow peaceful coexistence with yp. Nov 94.
+ *
+ * Hacked a bit by poe@daimi.aau.dk
+ * See also ftp://ftp.daimi.aau.dk/pub/linux/poe/admutil*
+ *
+ * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de,
+ * to distinguish user names where one is a prefix of the other,
+ * and to use "pathnames.h". Oct 5, 96.
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2008-04-06 James Youngman, jay@gnu.org
+ * - Completely rewritten to remove assumption that /etc/passwd
+ * lines are < 1024 characters long. Also added unit tests.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "closestream.h"
+#include "islocal.h"
+#include "nls.h"
+#include "pathnames.h"
+
+static int is_local_in_file(const char *user, const char *filename)
+{
+ int local = 0;
+ size_t match;
+ int chin, skip;
+ FILE *f;
+
+ if (NULL == (f = fopen(filename, "r")))
+ return -1;
+
+ match = 0u;
+ skip = 0;
+ while ((chin = getc(f)) != EOF) {
+ if (skip) {
+ /* Looking for the start of the next line. */
+ if ('\n' == chin) {
+ /* Start matching username at the next char. */
+ skip = 0;
+ match = 0u;
+ }
+ } else {
+ if (':' == chin) {
+ if (0 == user[match]) {
+ /* Success. */
+ local = 1;
+ /* next line has no test coverage,
+ * but it is just an optimisation
+ * anyway. */
+ break;
+ } else {
+ /* we read a whole username, but it
+ * is the wrong user. Skip to the
+ * next line. */
+ skip = 1;
+ }
+ } else if ('\n' == chin) {
+ /* This line contains no colon; it's
+ * malformed. No skip since we are already
+ * at the start of the next line. */
+ match = 0u;
+ } else if (chin != user[match]) {
+ /* username does not match. */
+ skip = 1;
+ } else {
+ ++match;
+ }
+ }
+ }
+ fclose(f);
+ return local;
+}
+
+int is_local(const char *user)
+{
+ int rv;
+
+ if ((rv = is_local_in_file(user, _PATH_PASSWD)) < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), _PATH_PASSWD);
+ return rv;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ atexit(close_stdout);
+ if (argc <= 2) {
+ fprintf(stderr, _("Usage: %s <passwordfile> <username>...\n"),
+ argv[0]);
+ return 1;
+ } else {
+ int i;
+ for (i = 2; i < argc; i++) {
+ const int rv = is_local_in_file(argv[i], argv[1]);
+ if (rv < 0) {
+ perror(argv[1]);
+ return 2;
+ }
+ printf("%d:%s\n", rv, argv[i]);
+ }
+ return 0;
+ }
+}
+#endif
diff --git a/login-utils/islocal.h b/login-utils/islocal.h
new file mode 100644
index 0000000..2c58799
--- /dev/null
+++ b/login-utils/islocal.h
@@ -0,0 +1 @@
+extern int is_local(const char *user);
diff --git a/login-utils/last.1 b/login-utils/last.1
new file mode 100644
index 0000000..619be3f
--- /dev/null
+++ b/login-utils/last.1
@@ -0,0 +1,198 @@
+.\" Copyright (C) 1998-2004 Miquel van Smoorenburg.
+.\"
+.\" 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 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, write to the Free Software
+.\" Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+.\"
+.TH "LAST, LASTB" "1" "October 2013" "util-linux" "User Commands"
+.SH NAME
+last, lastb \- show a listing of last logged in users
+.SH SYNOPSIS
+.B last
+[options]
+.RI [ username "...] [" tty ...]
+.br
+.B lastb
+[options]
+.RI [ username "...] [" tty ...]
+.SH DESCRIPTION
+.B last
+searches back through the
+.I /var/log/wtmp
+file (or the file designated by the
+.B \-f
+option) and displays a list of all users logged in (and out) since that
+file was created. One or more
+.IR usernames " and/or " ttys
+can be given, in which case
+.B last
+will show only the entries matching those arguments. Names of
+.I ttys
+can be abbreviated, thus
+.B last 0
+is the same as
+.BR "last tty0" .
+.PP
+When catching a SIGINT signal (generated by the interrupt key, usually
+control-C) or a SIGQUIT signal,
+.B last
+will show how far it has searched through the file; in the case of the
+SIGINT signal
+.B last
+will then terminate.
+.PP
+The pseudo user
+.B reboot
+logs in each time the system is rebooted. Thus
+.B last reboot
+will show a log of all the reboots since the log file was created.
+.PP
+.B lastb
+is the same as
+.BR last ,
+except that by default it shows a log of the
+.I /var/log/btmp
+file, which contains all the bad login attempts.
+.SH OPTIONS
+.TP
+.BR \-a , " \-\-hostlast"
+Display the hostname in the last column. Useful in combination with the
+.B \-\-dns
+option.
+.TP
+.BR \-d , " \-\-dns"
+For non-local logins, Linux stores not only the host name of the remote
+host, but its IP number as well. This option translates the IP number
+back into a hostname.
+.TP
+.BR \-f , " \-\-file " \fIfile\fR
+Tell
+.B last
+to use a specific \fIfile\fR instead of
+.IR /var/log/wtmp .
+The
+.B \-\-file
+option can be given multiple times, and all of the specified files will be
+processed.
+.TP
+.BR \-F , " \-\-fulltimes"
+Print full login and logout times and dates.
+.TP
+.BR \-i , " \-\-ip"
+Like
+.B \-\-dns ,
+but displays the host's IP number instead of the name.
+.TP
+.BI \- number
+.TQ
+.BR \-n , " -\-limit " \fInumber\fR
+Tell
+.B last
+how many lines to show.
+.TP
+.BR \-p , " \-\-present " \fItime\fR
+Display the users who were present at the specified time. This is
+like using the options
+.BR \-\-since " and " \-\-until
+together with the same \fItime\fR.
+.TP
+.BR \-R , " \-\-nohostname"
+Suppresses the display of the hostname field.
+.TP
+.BR \-s , " \-\-since " \fItime\fR
+Display the state of logins since the specified
+.IR time .
+This is useful, e.g., to easily determine who was logged in at a
+particular time. The option is often combined with
+.BR \-\-until .
+.TP
+.BR \-t , " \-\-until " \fItime\fR
+Display the state of logins until the specified
+.IR time .
+.TP
+.BI \-\-time\-format " format"
+Define the output timestamp
+.I format
+to be one of
+.IR notime ,
+.IR short ,
+.IR full ,
+or
+.IR iso .
+The
+.I notime
+variant will not print any timestamps at all,
+.I short
+is the default, and
+.I full
+is the same as the
+.B \-\-fulltimes
+option. The
+.I iso
+variant will display the timestamp in ISO-8601 format. The ISO format
+contains timezone information, making it preferable when printouts are
+investigated outside of the system.
+.TP
+.BR \-w , " \-\-fullnames"
+Display full user names and domain names in the output.
+.TP
+.BR \-x , " \-\-system"
+Display the system shutdown entries and run level changes.
+.SH TIME FORMATS
+The options that take the
+.I time
+argument understand the following formats:
+.TS
+l2 l.
+YYYYMMDDhhmmss
+YYYY-MM-DD hh:mm:ss
+YYYY-MM-DD hh:mm (seconds will be set to 00)
+YYYY-MM-DD (time will be set to 00:00:00)
+hh:mm:ss (date will be set to today)
+hh:mm (date will be set to today, seconds to 00)
+now
+yesterday (time is set to 00:00:00)
+today (time is set to 00:00:00)
+tomorrow (time is set to 00:00:00)
++5min
+-5days
+.TE
+.SH NOTES
+The files
+.I wtmp
+and
+.I btmp
+might not be found. The system only logs information in these files if
+they are present. This is a local configuration issue. If you want the
+files to be used, they can be created with a simple
+.BR touch (1)
+command (for example,
+.IR "touch /var/log/wtmp" ).
+.SH FILES
+/var/log/wtmp
+.br
+/var/log/btmp
+.SH AUTHOR
+.MT miquels@cistron.nl
+Miquel van Smoorenburg
+.ME
+.SH AVAILABILITY
+The last command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
+.SH "SEE ALSO"
+.BR login (1),
+.BR wtmp (5),
+.BR init (8),
+.BR shutdown (8)
diff --git a/login-utils/last.c b/login-utils/last.c
new file mode 100644
index 0000000..6b25efd
--- /dev/null
+++ b/login-utils/last.c
@@ -0,0 +1,1032 @@
+/*
+ * last(1) from sysvinit project, merged into util-linux in Aug 2013.
+ *
+ * Copyright (C) 1991-2004 Miquel van Smoorenburg.
+ * Copyright (C) 2013 Ondrej Oprala <ooprala@redhat.com>
+ * Karel Zak <kzak@redhat.com>
+ *
+ * Re-implementation of the 'last' command, this time for Linux. Yes I know
+ * there is BSD last, but I just felt like writing this. No thanks :-). Also,
+ * this version gives lots more info (especially with -x)
+ *
+ *
+ * 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 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <utmpx.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <libgen.h>
+
+#include "c.h"
+#include "nls.h"
+#include "optutils.h"
+#include "pathnames.h"
+#include "xalloc.h"
+#include "closestream.h"
+#include "carefulputc.h"
+#include "strutils.h"
+#include "timeutils.h"
+#include "monotonic.h"
+
+#ifndef SHUTDOWN_TIME
+# define SHUTDOWN_TIME 254
+#endif
+
+#ifndef LAST_LOGIN_LEN
+# define LAST_LOGIN_LEN 8
+#endif
+
+#ifndef LAST_DOMAIN_LEN
+# define LAST_DOMAIN_LEN 16
+#endif
+
+#ifndef LAST_TIMESTAMP_LEN
+# define LAST_TIMESTAMP_LEN 32
+#endif
+
+#define UCHUNKSIZE 16384 /* How much we read at once. */
+
+struct last_control {
+ unsigned int lastb :1, /* Is this command 'lastb' */
+ extended :1, /* Lots of info */
+ showhost :1, /* Show hostname */
+ altlist :1, /* Hostname at the end */
+ usedns :1, /* Use DNS to lookup the hostname */
+ useip :1; /* Print IP address in number format */
+
+ unsigned int name_len; /* Number of login name characters to print */
+ unsigned int domain_len; /* Number of domain name characters to print */
+ unsigned int maxrecs; /* Maximum number of records to list */
+
+ char **show; /* Match search list */
+
+ struct timeval boot_time; /* system boot time */
+ time_t since; /* at what time to start displaying the file */
+ time_t until; /* at what time to stop displaying the file */
+ time_t present; /* who where present at time_t */
+ unsigned int time_fmt; /* time format */
+};
+
+/* Double linked list of struct utmp's */
+struct utmplist {
+ struct utmpx ut;
+ struct utmplist *next;
+ struct utmplist *prev;
+};
+
+/* Types of listing */
+enum {
+ R_CRASH = 1, /* No logout record, system boot in between */
+ R_DOWN, /* System brought down in decent way */
+ R_NORMAL, /* Normal */
+ R_NOW, /* Still logged in */
+ R_REBOOT, /* Reboot record. */
+ R_PHANTOM, /* No logout record but session is stale. */
+ R_TIMECHANGE /* NEW_TIME or OLD_TIME */
+};
+
+enum {
+ LAST_TIMEFTM_NONE = 0,
+ LAST_TIMEFTM_SHORT,
+ LAST_TIMEFTM_CTIME,
+ LAST_TIMEFTM_ISO8601,
+
+ LAST_TIMEFTM_HHMM, /* non-public */
+};
+
+struct last_timefmt {
+ const char *name;
+ int in_len; /* log-in */
+ int in_fmt;
+ int out_len; /* log-out */
+ int out_fmt;
+};
+
+static struct last_timefmt timefmts[] = {
+ [LAST_TIMEFTM_NONE] = { .name = "notime" },
+ [LAST_TIMEFTM_SHORT] = {
+ .name = "short",
+ .in_len = 16,
+ .out_len = 7,
+ .in_fmt = LAST_TIMEFTM_CTIME,
+ .out_fmt = LAST_TIMEFTM_HHMM
+ },
+ [LAST_TIMEFTM_CTIME] = {
+ .name = "full",
+ .in_len = 24,
+ .out_len = 26,
+ .in_fmt = LAST_TIMEFTM_CTIME,
+ .out_fmt = LAST_TIMEFTM_CTIME
+ },
+ [LAST_TIMEFTM_ISO8601] = {
+ .name = "iso",
+ .in_len = 25,
+ .out_len = 27,
+ .in_fmt = LAST_TIMEFTM_ISO8601,
+ .out_fmt = LAST_TIMEFTM_ISO8601
+ }
+};
+
+/* Global variables */
+static unsigned int recsdone; /* Number of records listed */
+static time_t lastdate; /* Last date we've seen */
+static time_t currentdate; /* date when we started processing the file */
+
+/* --time-format=option parser */
+static int which_time_format(const char *s)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
+ if (strcmp(timefmts[i].name, s) == 0)
+ return i;
+ }
+ errx(EXIT_FAILURE, _("unknown time format: %s"), s);
+}
+
+/*
+ * Read one utmp entry, return in new format.
+ * Automatically reposition file pointer.
+ */
+static int uread(FILE *fp, struct utmpx *u, int *quit, const char *filename)
+{
+ static int utsize;
+ static char buf[UCHUNKSIZE];
+ char tmp[1024];
+ static off_t fpos;
+ static int bpos;
+ off_t o;
+
+ if (quit == NULL && u != NULL) {
+ /*
+ * Normal read.
+ */
+ return fread(u, sizeof(struct utmpx), 1, fp);
+ }
+
+ if (u == NULL) {
+ /*
+ * Initialize and position.
+ */
+ utsize = sizeof(struct utmpx);
+ fseeko(fp, 0, SEEK_END);
+ fpos = ftello(fp);
+ if (fpos == 0)
+ return 0;
+ o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE;
+ if (fseeko(fp, o, SEEK_SET) < 0) {
+ warn(_("seek on %s failed"), filename);
+ return 0;
+ }
+ bpos = (int)(fpos - o);
+ if (fread(buf, bpos, 1, fp) != 1) {
+ warn(_("cannot read %s"), filename);
+ return 0;
+ }
+ fpos = o;
+ return 1;
+ }
+
+ /*
+ * Read one struct. From the buffer if possible.
+ */
+ bpos -= utsize;
+ if (bpos >= 0) {
+ memcpy(u, buf + bpos, sizeof(struct utmpx));
+ return 1;
+ }
+
+ /*
+ * Oops we went "below" the buffer. We should be able to
+ * seek back UCHUNKSIZE bytes.
+ */
+ fpos -= UCHUNKSIZE;
+ if (fpos < 0)
+ return 0;
+
+ /*
+ * Copy whatever is left in the buffer.
+ */
+ memcpy(tmp + (-bpos), buf, utsize + bpos);
+ if (fseeko(fp, fpos, SEEK_SET) < 0) {
+ warn(_("seek on %s failed"), filename);
+ return 0;
+ }
+
+ /*
+ * Read another UCHUNKSIZE bytes.
+ */
+ if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
+ warn(_("cannot read %s"), filename);
+ return 0;
+ }
+
+ /*
+ * The end of the UCHUNKSIZE byte buffer should be the first
+ * few bytes of the current struct utmp.
+ */
+ memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos);
+ bpos += UCHUNKSIZE;
+
+ memcpy(u, tmp, sizeof(struct utmpx));
+
+ return 1;
+}
+
+/*
+ * Print a short date.
+ */
+static char *showdate(void)
+{
+ char *s = ctime(&lastdate);
+ s[16] = 0;
+ return s;
+}
+
+/*
+ * SIGINT handler
+ */
+static void int_handler(int sig __attribute__((unused)))
+{
+ errx(EXIT_FAILURE, _("Interrupted %s"), showdate());
+}
+
+/*
+ * SIGQUIT handler
+ */
+static void quit_handler(int sig __attribute__((unused)))
+{
+ warnx(_("Interrupted %s"), showdate());
+ signal(SIGQUIT, quit_handler);
+}
+
+/*
+ * Lookup a host with DNS.
+ */
+static int dns_lookup(char *result, int size, int useip, int32_t *a)
+{
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr *sa;
+ int salen, flags;
+ int mapped = 0;
+
+ flags = useip ? NI_NUMERICHOST : 0;
+
+ /*
+ * IPv4 or IPv6 ?
+ * 1. If last 3 4bytes are 0, must be IPv4
+ * 2. If IPv6 in IPv4, handle as IPv4
+ * 3. Anything else is IPv6
+ *
+ * Ugly.
+ */
+ if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff))
+ mapped = 1;
+
+ if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) {
+ /* IPv4 */
+ sin.sin_family = AF_INET;
+ sin.sin_port = 0;
+ sin.sin_addr.s_addr = mapped ? a[3] : a[0];
+ sa = (struct sockaddr *)&sin;
+ salen = sizeof(sin);
+ } else {
+ /* IPv6 */
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = 0;
+ memcpy(sin6.sin6_addr.s6_addr, a, 16);
+ sa = (struct sockaddr *)&sin6;
+ salen = sizeof(sin6);
+ }
+
+ return getnameinfo(sa, salen, result, size, NULL, 0, flags);
+}
+
+static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when)
+{
+ int ret = 0;
+
+ switch (fmt) {
+ case LAST_TIMEFTM_NONE:
+ *dst = 0;
+ break;
+ case LAST_TIMEFTM_HHMM:
+ {
+ struct tm *tm = localtime(when);
+ if (!snprintf(dst, dlen, "%02d:%02d", tm->tm_hour, tm->tm_min))
+ ret = -1;
+ break;
+ }
+ case LAST_TIMEFTM_CTIME:
+ snprintf(dst, dlen, "%s", ctime(when));
+ ret = rtrim_whitespace((unsigned char *) dst);
+ break;
+ case LAST_TIMEFTM_ISO8601:
+ ret = strtime_iso(when, ISO_TIMESTAMP_T, dst, dlen);
+ break;
+ default:
+ abort();
+ }
+ return ret;
+}
+
+/*
+ * Remove trailing spaces from a string.
+ */
+static void trim_trailing_spaces(char *s)
+{
+ char *p;
+
+ for (p = s; *p; ++p)
+ continue;
+ while (p > s && isspace(*--p))
+ continue;
+ if (p > s)
+ ++p;
+ *p++ = '\n';
+ *p = '\0';
+}
+
+/*
+ * Show one line of information on screen
+ */
+static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_time, int what)
+{
+ time_t secs, utmp_time;
+ char logintime[LAST_TIMESTAMP_LEN];
+ char logouttime[LAST_TIMESTAMP_LEN];
+ char length[LAST_TIMESTAMP_LEN];
+ char final[512];
+ char utline[sizeof(p->ut_line) + 1];
+ char domain[256];
+ char *s;
+ int mins, hours, days;
+ int r, len;
+ struct last_timefmt *fmt;
+
+ /*
+ * uucp and ftp have special-type entries
+ */
+ utline[0] = 0;
+ strncat(utline, p->ut_line, sizeof(utline) - 1);
+ if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3]))
+ utline[3] = 0;
+ if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4]))
+ utline[4] = 0;
+
+ /*
+ * Is this something we want to show?
+ */
+ if (ctl->show) {
+ char **walk;
+ for (walk = ctl->show; *walk; walk++) {
+ if (strncmp(p->ut_user, *walk, sizeof(p->ut_user)) == 0 ||
+ strcmp(utline, *walk) == 0 ||
+ (strncmp(utline, "tty", 3) == 0 &&
+ strcmp(utline + 3, *walk) == 0)) break;
+ }
+ if (*walk == NULL) return 0;
+ }
+
+ /*
+ * Calculate times
+ */
+ fmt = &timefmts[ctl->time_fmt];
+
+ utmp_time = p->ut_tv.tv_sec;
+
+ if (ctl->present) {
+ if (ctl->present < utmp_time)
+ return 0;
+ if (0 < logout_time && logout_time < ctl->present)
+ return 0;
+ }
+
+ /* log-in time */
+ if (time_formatter(fmt->in_fmt, logintime,
+ sizeof(logintime), &utmp_time) < 0)
+ errx(EXIT_FAILURE, _("preallocation size exceeded"));
+
+ /* log-out time */
+ secs = logout_time - utmp_time; /* Under strange circumstances, secs < 0 can happen */
+ mins = (secs / 60) % 60;
+ hours = (secs / 3600) % 24;
+ days = secs / 86400;
+
+ strcpy(logouttime, "- ");
+ if (time_formatter(fmt->out_fmt, logouttime + 2,
+ sizeof(logouttime) - 2, &logout_time) < 0)
+ errx(EXIT_FAILURE, _("preallocation size exceeded"));
+
+ if (logout_time == currentdate) {
+ if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
+ sprintf(logouttime, " still running");
+ length[0] = 0;
+ } else {
+ sprintf(logouttime, " still");
+ sprintf(length, "running");
+ }
+ } else if (days) {
+ sprintf(length, "(%d+%02d:%02d)", days, abs(hours), abs(mins)); /* hours and mins always shown as positive (w/o minus sign!) even if secs < 0 */
+ } else if (hours) {
+ sprintf(length, " (%02d:%02d)", hours, abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
+ } else if (secs >= 0) {
+ sprintf(length, " (%02d:%02d)", hours, mins);
+ } else {
+ sprintf(length, " (-00:%02d)", abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
+ }
+
+ switch(what) {
+ case R_CRASH:
+ sprintf(logouttime, "- crash");
+ break;
+ case R_DOWN:
+ sprintf(logouttime, "- down ");
+ break;
+ case R_NOW:
+ if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
+ sprintf(logouttime, " still logged in");
+ length[0] = 0;
+ } else {
+ sprintf(logouttime, " still");
+ sprintf(length, "logged in");
+ }
+ break;
+ case R_PHANTOM:
+ if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
+ sprintf(logouttime, " gone - no logout");
+ length[0] = 0;
+ } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) {
+ sprintf(logouttime, " gone");
+ sprintf(length, "- no logout");
+ } else {
+ logouttime[0] = 0;
+ sprintf(length, "no logout");
+ }
+ break;
+ case R_TIMECHANGE:
+ logouttime[0] = 0;
+ length[0] = 0;
+ break;
+ case R_NORMAL:
+ case R_REBOOT:
+ break;
+ default:
+ abort();
+ }
+
+ /*
+ * Look up host with DNS if needed.
+ */
+ r = -1;
+ if (ctl->usedns || ctl->useip)
+ r = dns_lookup(domain, sizeof(domain), ctl->useip, (int32_t*)p->ut_addr_v6);
+ if (r < 0)
+ mem2strcpy(domain, p->ut_host, sizeof(p->ut_host), sizeof(domain));
+
+ if (ctl->showhost) {
+ if (!ctl->altlist) {
+ len = snprintf(final, sizeof(final),
+ "%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n",
+ ctl->name_len, p->ut_user, utline,
+ ctl->domain_len, domain,
+ fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
+ logouttime, length);
+ } else {
+ len = snprintf(final, sizeof(final),
+ "%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n",
+ ctl->name_len, p->ut_user, utline,
+ fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
+ logouttime, length, domain);
+ }
+ } else
+ len = snprintf(final, sizeof(final),
+ "%-8.*s %-12.12s %-*.*s %-*.*s %s\n",
+ ctl->name_len, p->ut_user, utline,
+ fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
+ logouttime, length);
+
+#if defined(__GLIBC__)
+# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
+ final[sizeof(final)-1] = '\0';
+# endif
+#endif
+
+ trim_trailing_spaces(final);
+ /*
+ * Print out "final" string safely.
+ */
+ for (s = final; *s; s++)
+ fputc_careful(*s, stdout, '*');
+
+ if (len < 0 || (size_t)len >= sizeof(final))
+ putchar('\n');
+
+ recsdone++;
+ if (ctl->maxrecs && ctl->maxrecs <= recsdone)
+ return 1;
+
+ return 0;
+}
+
+
+static void __attribute__((__noreturn__)) usage(const struct last_control *ctl)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(
+ " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Show a listing of last logged in users.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -<number> how many lines to show\n"), out);
+ fputs(_(" -a, --hostlast display hostnames in the last column\n"), out);
+ fputs(_(" -d, --dns translate the IP number back into a hostname\n"), out);
+ fprintf(out,
+ _(" -f, --file <file> use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP);
+ fputs(_(" -F, --fulltimes print full login and logout times and dates\n"), out);
+ fputs(_(" -i, --ip display IP numbers in numbers-and-dots notation\n"), out);
+ fputs(_(" -n, --limit <number> how many lines to show\n"), out);
+ fputs(_(" -R, --nohostname don't display the hostname field\n"), out);
+ fputs(_(" -s, --since <time> display the lines since the specified time\n"), out);
+ fputs(_(" -t, --until <time> display the lines until the specified time\n"), out);
+ fputs(_(" -p, --present <time> display who were present at the specified time\n"), out);
+ fputs(_(" -w, --fullnames display full user and domain names\n"), out);
+ fputs(_(" -x, --system display system shutdown entries and run level changes\n"), out);
+ fputs(_(" --time-format <format> show timestamps in the specified <format>:\n"
+ " notime|short|full|iso\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(22));
+ printf(USAGE_MAN_TAIL("last(1)"));
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static int is_phantom(const struct last_control *ctl, struct utmpx *ut)
+{
+ struct passwd *pw;
+ char path[sizeof(ut->ut_line) + 16];
+ int ret = 0;
+
+ if (ut->ut_tv.tv_sec < ctl->boot_time.tv_sec)
+ return 1;
+ pw = getpwnam(ut->ut_user);
+ if (!pw)
+ return 1;
+ sprintf(path, "/proc/%u/loginuid", ut->ut_pid);
+ if (access(path, R_OK) == 0) {
+ unsigned int loginuid;
+ FILE *f = NULL;
+
+ if (!(f = fopen(path, "r")))
+ return 1;
+ if (fscanf(f, "%u", &loginuid) != 1)
+ ret = 1;
+ fclose(f);
+ if (!ret && pw->pw_uid != loginuid)
+ return 1;
+ } else {
+ struct stat st;
+
+ sprintf(path, "/dev/%s", ut->ut_line);
+ if (stat(path, &st))
+ return 1;
+ if (pw->pw_uid != st.st_uid)
+ return 1;
+ }
+ return ret;
+}
+
+static void process_wtmp_file(const struct last_control *ctl,
+ const char *filename)
+{
+ FILE *fp; /* File pointer of wtmp file */
+
+ struct utmpx ut; /* Current utmp entry */
+ struct utmplist *ulist = NULL; /* All entries */
+ struct utmplist *p; /* Pointer into utmplist */
+ struct utmplist *next; /* Pointer into utmplist */
+
+ time_t lastboot = 0; /* Last boottime */
+ time_t lastrch = 0; /* Last run level change */
+ time_t lastdown; /* Last downtime */
+ time_t begintime; /* When wtmp begins */
+ int whydown = 0; /* Why we went down: crash or shutdown */
+
+ int c, x; /* Scratch */
+ struct stat st; /* To stat the [uw]tmp file */
+ int quit = 0; /* Flag */
+ int down = 0; /* Down flag */
+
+ time(&lastdown);
+ /*
+ * Fill in 'lastdate'
+ */
+ lastdate = currentdate = lastrch = lastdown;
+
+ /*
+ * Install signal handlers
+ */
+ signal(SIGINT, int_handler);
+ signal(SIGQUIT, quit_handler);
+
+ /*
+ * Open the utmp file
+ */
+ if ((fp = fopen(filename, "r")) == NULL)
+ err(EXIT_FAILURE, _("cannot open %s"), filename);
+
+ /*
+ * Optimize the buffer size.
+ */
+ setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
+
+ /*
+ * Read first structure to capture the time field
+ */
+ if (uread(fp, &ut, NULL, filename) == 1)
+ begintime = ut.ut_tv.tv_sec;
+ else {
+ if (fstat(fileno(fp), &st) != 0)
+ err(EXIT_FAILURE, _("stat of %s failed"), filename);
+ begintime = st.st_ctime;
+ quit = 1;
+ }
+
+ /*
+ * Go to end of file minus one structure
+ * and/or initialize utmp reading code.
+ */
+ uread(fp, NULL, NULL, filename);
+
+ /*
+ * Read struct after struct backwards from the file.
+ */
+ while (!quit) {
+
+ if (uread(fp, &ut, &quit, filename) != 1)
+ break;
+
+ if (ctl->since && ut.ut_tv.tv_sec < ctl->since)
+ continue;
+
+ if (ctl->until && ctl->until < ut.ut_tv.tv_sec)
+ continue;
+
+ lastdate = ut.ut_tv.tv_sec;
+
+ if (ctl->lastb) {
+ quit = list(ctl, &ut, ut.ut_tv.tv_sec, R_NORMAL);
+ continue;
+ }
+
+ /*
+ * Set ut_type to the correct type.
+ */
+ if (strncmp(ut.ut_line, "~", 1) == 0) {
+ if (strncmp(ut.ut_user, "shutdown", 8) == 0)
+ ut.ut_type = SHUTDOWN_TIME;
+ else if (strncmp(ut.ut_user, "reboot", 6) == 0)
+ ut.ut_type = BOOT_TIME;
+ else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
+ ut.ut_type = RUN_LVL;
+ }
+#if 1 /*def COMPAT*/
+ /*
+ * For stupid old applications that don't fill in
+ * ut_type correctly.
+ */
+ else {
+ if (ut.ut_type != DEAD_PROCESS &&
+ ut.ut_user[0] && ut.ut_line[0] &&
+ strcmp(ut.ut_user, "LOGIN") != 0)
+ ut.ut_type = USER_PROCESS;
+ /*
+ * Even worse, applications that write ghost
+ * entries: ut_type set to USER_PROCESS but
+ * empty ut_user...
+ */
+ if (ut.ut_user[0] == 0)
+ ut.ut_type = DEAD_PROCESS;
+
+ /*
+ * Clock changes.
+ */
+ if (strcmp(ut.ut_user, "date") == 0) {
+ if (ut.ut_line[0] == '|')
+ ut.ut_type = OLD_TIME;
+ if (ut.ut_line[0] == '{')
+ ut.ut_type = NEW_TIME;
+ }
+ }
+#endif
+ switch (ut.ut_type) {
+ case SHUTDOWN_TIME:
+ if (ctl->extended) {
+ strcpy(ut.ut_line, "system down");
+ quit = list(ctl, &ut, lastboot, R_NORMAL);
+ }
+ lastdown = lastrch = ut.ut_tv.tv_sec;
+ down = 1;
+ break;
+ case OLD_TIME:
+ case NEW_TIME:
+ if (ctl->extended) {
+ strcpy(ut.ut_line,
+ ut.ut_type == NEW_TIME ? "new time" :
+ "old time");
+ quit = list(ctl, &ut, lastdown, R_TIMECHANGE);
+ }
+ break;
+ case BOOT_TIME:
+ strcpy(ut.ut_line, "system boot");
+ quit = list(ctl, &ut, lastdown, R_REBOOT);
+ lastboot = ut.ut_tv.tv_sec;
+ down = 1;
+ break;
+ case RUN_LVL:
+ x = ut.ut_pid & 255;
+ if (ctl->extended) {
+ sprintf(ut.ut_line, "(to lvl %c)", x);
+ quit = list(ctl, &ut, lastrch, R_NORMAL);
+ }
+ if (x == '0' || x == '6') {
+ lastdown = ut.ut_tv.tv_sec;
+ down = 1;
+ ut.ut_type = SHUTDOWN_TIME;
+ }
+ lastrch = ut.ut_tv.tv_sec;
+ break;
+
+ case USER_PROCESS:
+ /*
+ * This was a login - show the first matching
+ * logout record and delete all records with
+ * the same ut_line.
+ */
+ c = 0;
+ for (p = ulist; p; p = next) {
+ next = p->next;
+ if (strncmp(p->ut.ut_line, ut.ut_line,
+ sizeof(ut.ut_line)) == 0) {
+ /* Show it */
+ if (c == 0) {
+ quit = list(ctl, &ut, p->ut.ut_tv.tv_sec, R_NORMAL);
+ c = 1;
+ }
+ if (p->next)
+ p->next->prev = p->prev;
+ if (p->prev)
+ p->prev->next = p->next;
+ else
+ ulist = p->next;
+ free(p);
+ }
+ }
+ /*
+ * Not found? Then crashed, down, still
+ * logged in, or missing logout record.
+ */
+ if (c == 0) {
+ if (!lastboot) {
+ c = R_NOW;
+ /* Is process still alive? */
+ if (is_phantom(ctl, &ut))
+ c = R_PHANTOM;
+ } else
+ c = whydown;
+ quit = list(ctl, &ut, lastboot, c);
+ }
+ /* fallthrough */
+
+ case DEAD_PROCESS:
+ /*
+ * Just store the data if it is
+ * interesting enough.
+ */
+ if (ut.ut_line[0] == 0)
+ break;
+ p = xmalloc(sizeof(struct utmplist));
+ memcpy(&p->ut, &ut, sizeof(struct utmpx));
+ p->next = ulist;
+ p->prev = NULL;
+ if (ulist)
+ ulist->prev = p;
+ ulist = p;
+ break;
+
+ case EMPTY:
+ case INIT_PROCESS:
+ case LOGIN_PROCESS:
+#ifdef ACCOUNTING
+ case ACCOUNTING:
+#endif
+ /* ignored ut_types */
+ break;
+
+ default:
+ warnx("unrecognized ut_type: %d", ut.ut_type);
+ }
+
+ /*
+ * If we saw a shutdown/reboot record we can remove
+ * the entire current ulist.
+ */
+ if (down) {
+ lastboot = ut.ut_tv.tv_sec;
+ whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
+ for (p = ulist; p; p = next) {
+ next = p->next;
+ free(p);
+ }
+ ulist = NULL;
+ down = 0;
+ }
+ }
+
+ if (ctl->time_fmt != LAST_TIMEFTM_NONE) {
+ struct last_timefmt *fmt;
+ char timestr[LAST_TIMESTAMP_LEN];
+ char *tmp = xstrdup(filename);
+
+ fmt = &timefmts[ctl->time_fmt];
+ if (time_formatter(fmt->in_fmt, timestr,
+ sizeof(timestr), &begintime) < 0)
+ errx(EXIT_FAILURE, _("preallocation size exceeded"));
+ printf(_("\n%s begins %s\n"), basename(tmp), timestr);
+ free(tmp);
+ }
+
+ fclose(fp);
+
+ for (p = ulist; p; p = next) {
+ next = p->next;
+ free(p);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct last_control ctl = {
+ .showhost = TRUE,
+ .name_len = LAST_LOGIN_LEN,
+ .time_fmt = LAST_TIMEFTM_SHORT,
+ .domain_len = LAST_DOMAIN_LEN
+ };
+ char **files = NULL;
+ size_t i, nfiles = 0;
+ int c;
+ usec_t p;
+
+ enum {
+ OPT_TIME_FORMAT = CHAR_MAX + 1
+ };
+ static const struct option long_opts[] = {
+ { "limit", required_argument, NULL, 'n' },
+ { "help", no_argument, NULL, 'h' },
+ { "file", required_argument, NULL, 'f' },
+ { "nohostname", no_argument, NULL, 'R' },
+ { "version", no_argument, NULL, 'V' },
+ { "hostlast", no_argument, NULL, 'a' },
+ { "since", required_argument, NULL, 's' },
+ { "until", required_argument, NULL, 't' },
+ { "present", required_argument, NULL, 'p' },
+ { "system", no_argument, NULL, 'x' },
+ { "dns", no_argument, NULL, 'd' },
+ { "ip", no_argument, NULL, 'i' },
+ { "fulltimes", no_argument, NULL, 'F' },
+ { "fullnames", no_argument, NULL, 'w' },
+ { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
+ { NULL, 0, NULL, 0 }
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+ /*
+ * Which file do we want to read?
+ */
+ ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
+ while ((c = getopt_long(argc, argv,
+ "hVf:n:RxadFit:p:s:0123456789w", long_opts, NULL)) != -1) {
+
+ err_exclusive_options(c, long_opts, excl, excl_st);
+
+ switch(c) {
+ case 'h':
+ usage(&ctl);
+ break;
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ case 'R':
+ ctl.showhost = 0;
+ break;
+ case 'x':
+ ctl.extended = 1;
+ break;
+ case 'n':
+ ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number"));
+ break;
+ case 'f':
+ if (!files)
+ files = xmalloc(sizeof(char *) * argc);
+ files[nfiles++] = xstrdup(optarg);
+ break;
+ case 'd':
+ ctl.usedns = 1;
+ break;
+ case 'i':
+ ctl.useip = 1;
+ break;
+ case 'a':
+ ctl.altlist = 1;
+ break;
+ case 'F':
+ ctl.time_fmt = LAST_TIMEFTM_CTIME;
+ break;
+ case 'p':
+ if (parse_timestamp(optarg, &p) < 0)
+ errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
+ ctl.present = (time_t) (p / 1000000);
+ break;
+ case 's':
+ if (parse_timestamp(optarg, &p) < 0)
+ errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
+ ctl.since = (time_t) (p / 1000000);
+ break;
+ case 't':
+ if (parse_timestamp(optarg, &p) < 0)
+ errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
+ ctl.until = (time_t) (p / 1000000);
+ break;
+ case 'w':
+ if (ctl.name_len < sizeof(((struct utmpx *) 0)->ut_user))
+ ctl.name_len = sizeof(((struct utmpx *) 0)->ut_user);
+ if (ctl.domain_len < sizeof(((struct utmpx *) 0)->ut_host))
+ ctl.domain_len = sizeof(((struct utmpx *) 0)->ut_host);
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ ctl.maxrecs = 10 * ctl.maxrecs + c - '0';
+ break;
+ case OPT_TIME_FORMAT:
+ ctl.time_fmt = which_time_format(optarg);
+ break;
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (optind < argc)
+ ctl.show = argv + optind;
+
+ if (!files) {
+ files = xmalloc(sizeof(char *));
+ files[nfiles++] = xstrdup(ctl.lastb ? _PATH_BTMP : _PATH_WTMP);
+ }
+
+ for (i = 0; i < nfiles; i++) {
+ get_boot_time(&ctl.boot_time);
+ process_wtmp_file(&ctl, files[i]);
+ free(files[i]);
+ }
+ free(files);
+ return EXIT_SUCCESS;
+}
diff --git a/login-utils/lastb.1 b/login-utils/lastb.1
new file mode 100644
index 0000000..f57c02a
--- /dev/null
+++ b/login-utils/lastb.1
@@ -0,0 +1 @@
+.so man1/last.1
diff --git a/login-utils/libuser.c b/login-utils/libuser.c
new file mode 100644
index 0000000..b11fadc
--- /dev/null
+++ b/login-utils/libuser.c
@@ -0,0 +1,72 @@
+/*
+ * libuser.c -- Utilize libuser to set a user attribute
+ * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
+ *
+ * this program is free software. you can redistribute it and
+ * modify it under the terms of the gnu general public license.
+ * there is no warranty.
+ *
+ */
+
+#include "libuser.h"
+
+#include <grp.h>
+#include <libuser/user.h>
+#include <unistd.h>
+
+#include "auth.h"
+#include "c.h"
+#include "nls.h"
+
+static int auth_lu(const char *service_name, struct lu_context *ctx, uid_t uid,
+ const char *username);
+
+static int auth_lu(const char *service_name, struct lu_context *ctx, uid_t uid,
+ const char *username) {
+ if (!lu_uses_elevated_privileges(ctx)) {
+ /* Drop privileges */
+ if (setegid(getgid()) == -1)
+ err(EXIT_FAILURE, _("Couldn't drop group privileges"));
+ if (seteuid(getuid()) == -1)
+ err(EXIT_FAILURE, _("Couldn't drop group privileges"));
+ return TRUE;
+ }
+
+ return auth_pam(service_name, uid, username);
+}
+
+int set_value_libuser(const char *service_name, const char *username, uid_t uid,
+ const char *attr, const char *val) {
+ struct lu_context *ctx;
+ struct lu_error *error = NULL;
+ struct lu_ent *ent;
+
+ ctx = lu_start(username, lu_user, NULL, NULL, lu_prompt_console_quiet,
+ NULL, &error);
+ if (ctx == NULL)
+ errx(EXIT_FAILURE, _("libuser initialization failed: %s."),
+ lu_strerror(error));
+
+ if (!auth_lu(service_name, ctx, uid, username)) {
+ errno = EACCES;
+ err(EXIT_FAILURE, _("changing user attribute failed"));
+ }
+
+ /* Look up the user's record. */
+ ent = lu_ent_new();
+ if (lu_user_lookup_name(ctx, username, ent, &error) == FALSE) {
+ lu_end(ctx);
+ errx(EXIT_FAILURE, _("user \"%s\" does not exist."), username);
+ }
+
+ lu_ent_set_string(ent, attr, val);
+ if (!lu_user_modify(ctx, ent, &error)) {
+ lu_ent_free(ent);
+ lu_end(ctx);
+ errx(EXIT_FAILURE, _("user attribute not changed: %s"), lu_strerror(error));
+ }
+ lu_ent_free(ent);
+ lu_end(ctx);
+
+ return 0;
+}
diff --git a/login-utils/libuser.h b/login-utils/libuser.h
new file mode 100644
index 0000000..7454b99
--- /dev/null
+++ b/login-utils/libuser.h
@@ -0,0 +1,14 @@
+/*
+ * libuser.h -- Utilize libuser to set a user attribute
+ * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
+ *
+ * this program is free software. you can redistribute it and
+ * modify it under the terms of the gnu general public license.
+ * there is no warranty.
+ *
+ */
+
+#include <sys/types.h>
+
+extern int set_value_libuser(const char *service_name, const char *username,
+ uid_t uid, const char *attr, const char *val);
diff --git a/login-utils/login.1 b/login-utils/login.1
new file mode 100644
index 0000000..cb8adde
--- /dev/null
+++ b/login-utils/login.1
@@ -0,0 +1,350 @@
+.\" Copyright 1993 Rickard E. Faith (faith@cs.unc.edu)
+.\" May be distributed under the GNU General Public License
+.TH LOGIN "1" "June 2012" "util-linux" "User Commands"
+.SH NAME
+login \- begin session on the system
+.SH SYNOPSIS
+.B login
+[
+.B \-p
+] [
+.B \-h
+.I host
+] [
+.B \-H
+] [
+.B \-f
+.I username
+|
+.I username
+]
+.SH DESCRIPTION
+.B login
+is used when signing onto a system. If no argument is given,
+.B login
+prompts for the username.
+.PP
+The user is then prompted for a password, where appropriate. Echoing
+is disabled to prevent revealing the password. Only a small number
+of password failures are permitted before
+.B login
+exits and the communications link is severed.
+.PP
+If password aging has been enabled for the account, the user may be
+prompted for a new password before proceeding. He will be forced to
+provide his old password and the new password before continuing.
+Please refer to
+.BR passwd (1)
+for more information.
+.PP
+The user and group ID will be set according to their values in the
+.I /etc/passwd
+file. There is one exception if the user ID is zero: in this case,
+only the primary group ID of the account is set. This should allow
+the system administrator to login even in case of network problems.
+The value for
+.BR $HOME ,
+.BR $USER ,
+.BR $SHELL ,
+.BR $PATH ,
+.BR $LOGNAME ,
+and
+.B $MAIL
+are set according to the appropriate fields in the password entry.
+.B $PATH
+defaults to
+.I /usr\:/local\:/bin:\:/bin:\:/usr\:/bin
+for normal users, and to
+.I /usr\:/local\:/sbin:\:/usr\:/local\:/bin:\:/sbin:\:/bin:\:/usr\:/sbin:\:/usr\:/bin
+for root, if not otherwise configured.
+.P
+The environment variable
+.B $TERM
+will be preserved, if it exists (other environment variables are
+preserved if the
+.B \-p
+option is given), else it will be initialized to the terminal type on your tty.
+.PP
+Then the user's shell is started. If no shell is specified for the
+user in
+.IR /etc\:/passwd ,
+then
+.I /bin\:/sh
+is used. If there is no directory specified in
+.IR /etc\:/passwd ,
+then
+.I /
+is used (the home directory is checked for the
+.I .hushlogin
+file described below).
+.PP
+If the file
+.I .hushlogin
+exists, then a "quiet" login is performed (this disables the checking
+of mail and the printing of the last login time and message of the
+day). Otherwise, if
+.I /var\:/log\:/lastlog
+exists, the last login time is printed (and the current login is
+recorded).
+.SH OPTIONS
+.TP
+.B \-p
+Used by
+.BR getty (8)
+to tell
+.B login
+not to destroy the environment.
+.TP
+.B \-f
+Used to skip a second login authentication. This specifically does
+.B not
+work for root, and does not appear to work well under Linux.
+.TP
+.B \-h
+Used by other servers (i.e.,
+.BR telnetd (8))
+to pass the name of the remote host to
+.B login
+so that it may be placed in utmp and wtmp. Only the superuser may
+use this option.
+.IP
+Note that the
+.B \-h
+option has impact on the
+.B PAM service
+.BR name .
+The standard service name is
+.IR login ,
+with the
+.B \-h
+option the name is
+.IR remote .
+It is necessary to create proper PAM config files (e.g.
+.I /etc\:/pam.d\:/login
+and
+.IR /etc\:/pam.d\:/remote ).
+.TP
+.B \-H
+Used by other servers (i.e.,
+.BR telnetd (8))
+to tell
+.B login
+that printing the hostname should be suppressed in the login: prompt.
+See also LOGIN_PLAIN_PROMPT below if your server does not allow to configure
+.B login
+command line.
+.TP
+\fB\-\-help\fR
+Display help text and exit.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.SH CONFIG FILE ITEMS
+.B login
+reads the
+.IR /etc\:/login.defs (5)
+configuration file. Note that the configuration file could be
+distributed with another package (e.g. shadow-utils). The following
+configuration items are relevant for
+.BR login (1):
+.PP
+.B MOTD_FILE
+(string)
+.RS 4
+If defined, a ":" delimited list of "message of the day" files to be
+displayed upon login. The default value is
+.IR /etc\:/motd .
+If the
+.B MOTD_FILE
+item is empty or a quiet login is enabled, then the message of the day
+is not displayed. Note that the same functionality is also provided
+by
+.BR pam_motd (8)
+PAM module.
+.RE
+.PP
+.B LOGIN_PLAIN_PROMPT
+(boolean)
+.RS 4
+Tell login that printing the hostname should be suppressed in the login:
+prompt. This is alternative to the \fB\-H\fR command line option. The default
+value is
+.IR no .
+.RE
+.PP
+.B LOGIN_TIMEOUT
+(number)
+.RS 4
+Max time in seconds for login. The default value is
+.IR 60 .
+.RE
+.PP
+.B LOGIN_RETRIES
+(number)
+.RS 4
+Maximum number of login retries in case of a bad password. The default
+value is
+.IR 3 .
+.RE
+.PP
+.B FAIL_DELAY
+(number)
+.RS 4
+Delay in seconds before being allowed another three tries after a
+login failure. The default value is
+.IR 5 .
+.RE
+.PP
+.B TTYPERM
+(string)
+.RS 4
+The terminal permissions. The default value is
+.I 0600
+or
+.I 0620
+if tty group is used.
+.RE
+.PP
+.B TTYGROUP
+(string)
+.RS 4
+The login tty will be owned by the
+.BR TTYGROUP .
+The default value is
+.IR tty .
+If the
+.B TTYGROUP
+does not exist, then the ownership of the terminal is set to the
+user\'s primary group.
+.PP
+The
+.B TTYGROUP
+can be either the name of a group or a numeric group identifier.
+.RE
+.PP
+.B HUSHLOGIN_FILE
+(string)
+.RS 4
+If defined, this file can inhibit all the usual chatter during the
+login sequence. If a full pathname (e.g.
+.IR /etc\:/hushlogins )
+is specified, then hushed mode will be enabled if the user\'s name or
+shell are found in the file. If this global hush login file is empty
+then the hushed mode will be enabled for all users.
+.PP
+If a full pathname is not specified, then hushed mode will be enabled
+if the file exists in the user\'s home directory.
+.PP
+The default is to check
+.I /etc\:/hushlogins
+and if it does not exist then
+.I ~/.hushlogin
+.PP
+If the
+.B HUSHLOGIN_FILE
+item is empty, then all the checks are disabled.
+.RE
+.PP
+.B DEFAULT_HOME
+(boolean)
+.RS 4
+Indicate if login is allowed if we cannot change directory to the
+home directory. If set to
+.IR yes ,
+the user will login in the root (/) directory if it is not possible
+to change directory to her home. The default value is
+.IR yes .
+.RE
+.PP
+.B LOG_UNKFAIL_ENAB
+(boolean)
+.RS 4
+Enable display of unknown usernames when login failures are recorded.
+The default value is
+.IR no .
+.PP
+Note that logging unknown usernames may be a security issue if a
+user enters her password instead of her login name.
+.RE
+.PP
+.B ENV_PATH
+(string)
+.RS 4
+If set, it will be used to define the PATH environment variable when
+a regular user logs in. The default value is
+.I /usr\:/local\:/bin:\:/bin:\:/usr\:/bin
+.RE
+.PP
+.B ENV_ROOTPATH
+(string)
+.br
+.B ENV_SUPATH
+(string)
+.RS 4
+If set, it will be used to define the PATH environment variable when
+the superuser logs in. The default value is
+.I /usr\:/local\:/sbin:\:/usr\:/local\:/bin:\:/sbin:\:/bin:\:/usr\:/sbin:\:/usr\:/bin
+.RE
+.SH FILES
+.nf
+.I /var/run/utmp
+.I /var/log/wtmp
+.I /var/log/lastlog
+.I /var/spool/mail/*
+.I /etc/motd
+.I /etc/passwd
+.I /etc/nologin
+.I /etc/pam.d/login
+.I /etc/pam.d/remote
+.I /etc/hushlogins
+.I .hushlogin
+.fi
+.SH "SEE ALSO"
+.BR mail (1),
+.BR passwd (1),
+.BR passwd (5),
+.BR environ (7),
+.BR getty (8),
+.BR init (8),
+.BR shutdown (8)
+.SH BUGS
+The undocumented BSD
+.B \-r
+option is not supported. This may be required by some
+.BR rlogind (8)
+programs.
+.PP
+A recursive login, as used to be possible in the good old days, no
+longer works; for most purposes
+.BR su (1)
+is a satisfactory substitute. Indeed, for security reasons, login
+does a vhangup() system call to remove any possible listening
+processes on the tty. This is to avoid password sniffing. If one
+uses the command
+.BR login ,
+then the surrounding shell gets killed by vhangup() because it's no
+longer the true owner of the tty. This can be avoided by using
+.B exec login
+in a top-level shell or xterm.
+.SH AUTHOR
+Derived from BSD login 5.40 (5/9/89) by
+.MT glad@\:daimi.\:dk
+Michael Glad
+.ME
+for HP-UX
+.br
+Ported to Linux 0.12:
+.MT poe@\:daimi.\:aau.\:dk
+Peter Orbaek
+.ME
+.br
+Rewritten to a PAM-only version by
+.MT kzak@\:redhat.\:com
+Karel Zak
+.ME
+.SH AVAILABILITY
+The login command is part of the util-linux package and is
+available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/login.c b/login-utils/login.c
new file mode 100644
index 0000000..23b62b8
--- /dev/null
+++ b/login-utils/login.c
@@ -0,0 +1,1386 @@
+/*
+ * login(1)
+ *
+ * This program is derived from 4.3 BSD software and is subject to the
+ * copyright notice below.
+ *
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ * Rewritten to PAM-only version.
+ *
+ * Michael Glad (glad@daimi.dk)
+ * Computer Science Department, Aarhus University, Denmark
+ * 1990-07-04
+ *
+ * Copyright (c) 1980, 1987, 1988 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley. The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+#include <sys/param.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <memory.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/file.h>
+#include <termios.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <utmpx.h>
+#ifdef HAVE_LASTLOG_H
+# include <lastlog.h>
+#endif
+#include <stdlib.h>
+#include <sys/syslog.h>
+#ifdef HAVE_LINUX_MAJOR_H
+# include <linux/major.h>
+#endif
+#include <netdb.h>
+#include <security/pam_appl.h>
+#ifdef HAVE_SECURITY_PAM_MISC_H
+# include <security/pam_misc.h>
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+# include <security/openpam.h>
+#endif
+#include <sys/sendfile.h>
+
+#ifdef HAVE_LIBAUDIT
+# include <libaudit.h>
+#endif
+
+#include "c.h"
+#include "setproctitle.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "nls.h"
+#include "env.h"
+#include "xalloc.h"
+#include "all-io.h"
+#include "fileutils.h"
+#include "ttyutils.h"
+#include "pwdutils.h"
+
+#include "logindefs.h"
+
+#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
+
+#define LOGIN_MAX_TRIES 3
+#define LOGIN_EXIT_TIMEOUT 5
+#define LOGIN_TIMEOUT 60
+
+#ifdef USE_TTY_GROUP
+# define TTY_MODE 0620
+#else
+# define TTY_MODE 0600
+#endif
+
+#define TTYGRPNAME "tty" /* name of group to own ttys */
+#define VCS_PATH_MAX 64
+
+/*
+ * Login control struct
+ */
+struct login_context {
+ const char *tty_path; /* ttyname() return value */
+ const char *tty_name; /* tty_path without /dev prefix */
+ const char *tty_number; /* end of the tty_path */
+ mode_t tty_mode; /* chmod() mode */
+
+ const char *username; /* points to PAM, pwd or cmd_username */
+ char *cmd_username; /* username specified on command line */
+
+
+ struct passwd *pwd; /* user info */
+ char *pwdbuf; /* pwd strings */
+
+ pam_handle_t *pamh; /* PAM handler */
+ struct pam_conv conv; /* PAM conversation */
+
+#ifdef LOGIN_CHOWN_VCS
+ char vcsn[VCS_PATH_MAX]; /* virtual console name */
+ char vcsan[VCS_PATH_MAX];
+#endif
+
+ char *thishost; /* this machine */
+ char *thisdomain; /* this machine's domain */
+ char *hostname; /* remote machine */
+ char hostaddress[16]; /* remote address */
+
+ pid_t pid;
+ int quiet; /* 1 if hush file exists */
+
+ unsigned int remote:1, /* login -h */
+ nohost:1, /* login -H */
+ noauth:1, /* login -f */
+ keep_env:1; /* login -p */
+};
+
+/*
+ * This bounds the time given to login. Not a define, so it can
+ * be patched on machines where it's too small.
+ */
+static unsigned int timeout = LOGIN_TIMEOUT;
+static int child_pid = 0;
+static volatile int got_sig = 0;
+static char timeout_msg[128];
+
+#ifdef LOGIN_CHOWN_VCS
+/* true if the filedescriptor fd is a console tty, very Linux specific */
+static int is_consoletty(int fd)
+{
+ struct stat stb;
+
+ if ((fstat(fd, &stb) >= 0)
+ && (major(stb.st_rdev) == TTY_MAJOR)
+ && (minor(stb.st_rdev) < 64)) {
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+
+/*
+ * Robert Ambrose writes:
+ * A couple of my users have a problem with login processes hanging around
+ * soaking up pts's. What they seem to hung up on is trying to write out the
+ * message 'Login timed out after %d seconds' when the connection has already
+ * been dropped.
+ * What I did was add a second timeout while trying to write the message, so
+ * the process just exits if the second timeout expires.
+ */
+static void __attribute__ ((__noreturn__))
+timedout2(int sig __attribute__ ((__unused__)))
+{
+ struct termios ti;
+
+ /* reset echo */
+ tcgetattr(0, &ti);
+ ti.c_lflag |= ECHO;
+ tcsetattr(0, TCSANOW, &ti);
+ _exit(EXIT_SUCCESS); /* %% */
+}
+
+static void timedout(int sig __attribute__ ((__unused__)))
+{
+ signal(SIGALRM, timedout2);
+ alarm(10);
+ ignore_result( write(STDERR_FILENO, timeout_msg, strlen(timeout_msg)) );
+ signal(SIGALRM, SIG_IGN);
+ alarm(0);
+ timedout2(0);
+}
+
+/*
+ * This handler allows to inform a shell about signals to login. If you have
+ * (root) permissions, you can kill all login children by one signal to the
+ * login process.
+ *
+ * Also, a parent who is session leader is able (before setsid() in the child)
+ * to inform the child when the controlling tty goes away (e.g. modem hangup).
+ */
+static void sig_handler(int signal)
+{
+ if (child_pid)
+ kill(-child_pid, signal);
+ else
+ got_sig = 1;
+ if (signal == SIGTERM)
+ kill(-child_pid, SIGHUP); /* because the shell often ignores SIGTERM */
+}
+
+/*
+ * Let us delay all exit() calls when the user is not authenticated
+ * or the session not fully initialized (loginpam_session()).
+ */
+static void __attribute__ ((__noreturn__)) sleepexit(int eval)
+{
+ sleep((unsigned int)getlogindefs_num("FAIL_DELAY", LOGIN_EXIT_TIMEOUT));
+ exit(eval);
+}
+
+static const char *get_thishost(struct login_context *cxt, const char **domain)
+{
+ if (!cxt->thishost) {
+ cxt->thishost = xgethostname();
+ if (!cxt->thishost) {
+ if (domain)
+ *domain = NULL;
+ return NULL;
+ }
+ cxt->thisdomain = strchr(cxt->thishost, '.');
+ if (cxt->thisdomain)
+ *cxt->thisdomain++ = '\0';
+ }
+
+ if (domain)
+ *domain = cxt->thisdomain;
+ return cxt->thishost;
+}
+
+/*
+ * Output the /etc/motd file.
+ *
+ * It determines the name of a login announcement file and outputs it to the
+ * user's terminal at login time. The MOTD_FILE configuration option is a
+ * colon-delimited list of filenames. An empty MOTD_FILE option disables
+ * message-of-the-day printing completely.
+ */
+static void motd(void)
+{
+ char *motdlist, *motdfile;
+ const char *mb;
+
+ mb = getlogindefs_str("MOTD_FILE", _PATH_MOTDFILE);
+ if (!mb || !*mb)
+ return;
+
+ motdlist = xstrdup(mb);
+
+ for (motdfile = strtok(motdlist, ":"); motdfile;
+ motdfile = strtok(NULL, ":")) {
+
+ struct stat st;
+ int fd;
+
+ fd = open(motdfile, O_RDONLY, 0);
+ if (fd < 0)
+ continue;
+ if (!fstat(fd, &st) && st.st_size)
+ sendfile(fileno(stdout), fd, NULL, st.st_size);
+ close(fd);
+ }
+
+ free(motdlist);
+}
+
+/*
+ * Nice and simple code provided by Linus Torvalds 16-Feb-93.
+ * Non-blocking stuff by Maciej W. Rozycki, macro@ds2.pg.gda.pl, 1999.
+ *
+ * He writes: "Login performs open() on a tty in a blocking mode.
+ * In some cases it may make login wait in open() for carrier infinitely,
+ * for example if the line is a simplistic case of a three-wire serial
+ * connection. I believe login should open the line in non-blocking mode,
+ * leaving the decision to make a connection to getty (where it actually
+ * belongs)."
+ */
+static void open_tty(const char *tty)
+{
+ int i, fd, flags;
+
+ fd = open(tty, O_RDWR | O_NONBLOCK);
+ if (fd == -1) {
+ syslog(LOG_ERR, _("FATAL: can't reopen tty: %m"));
+ sleepexit(EXIT_FAILURE);
+ }
+
+ if (!isatty(fd)) {
+ close(fd);
+ syslog(LOG_ERR, _("FATAL: %s is not a terminal"), tty);
+ sleepexit(EXIT_FAILURE);
+ }
+
+ flags = fcntl(fd, F_GETFL);
+ flags &= ~O_NONBLOCK;
+ fcntl(fd, F_SETFL, flags);
+
+ for (i = 0; i < fd; i++)
+ close(i);
+ for (i = 0; i < 3; i++)
+ if (fd != i)
+ dup2(fd, i);
+ if (fd >= 3)
+ close(fd);
+}
+
+#define chown_err(_what, _uid, _gid) \
+ syslog(LOG_ERR, _("chown (%s, %lu, %lu) failed: %m"), \
+ (_what), (unsigned long) (_uid), (unsigned long) (_gid))
+
+#define chmod_err(_what, _mode) \
+ syslog(LOG_ERR, _("chmod (%s, %u) failed: %m"), (_what), (_mode))
+
+static void chown_tty(struct login_context *cxt)
+{
+ const char *grname;
+ uid_t uid = cxt->pwd->pw_uid;
+ gid_t gid = cxt->pwd->pw_gid;
+
+ grname = getlogindefs_str("TTYGROUP", TTYGRPNAME);
+ if (grname && *grname) {
+ struct group *gr = getgrnam(grname);
+ if (gr) /* group by name */
+ gid = gr->gr_gid;
+ else /* group by ID */
+ gid = (gid_t) getlogindefs_num("TTYGROUP", gid);
+ }
+ if (fchown(0, uid, gid)) /* tty */
+ chown_err(cxt->tty_name, uid, gid);
+ if (fchmod(0, cxt->tty_mode))
+ chmod_err(cxt->tty_name, cxt->tty_mode);
+
+#ifdef LOGIN_CHOWN_VCS
+ if (is_consoletty(0)) {
+ if (chown(cxt->vcsn, uid, gid)) /* vcs */
+ chown_err(cxt->vcsn, uid, gid);
+ if (chmod(cxt->vcsn, cxt->tty_mode))
+ chmod_err(cxt->vcsn, cxt->tty_mode);
+
+ if (chown(cxt->vcsan, uid, gid)) /* vcsa */
+ chown_err(cxt->vcsan, uid, gid);
+ if (chmod(cxt->vcsan, cxt->tty_mode))
+ chmod_err(cxt->vcsan, cxt->tty_mode);
+ }
+#endif
+}
+
+/*
+ * Reads the current terminal path and initializes cxt->tty_* variables.
+ */
+static void init_tty(struct login_context *cxt)
+{
+ struct stat st;
+ struct termios tt, ttt;
+
+ cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE);
+
+ get_terminal_name(&cxt->tty_path, &cxt->tty_name, &cxt->tty_number);
+
+ /*
+ * In case login is suid it was possible to use a hardlink as stdin
+ * and exploit races for a local root exploit. (Wojciech Purczynski).
+ *
+ * More precisely, the problem is ttyn := ttyname(0); ...; chown(ttyn);
+ * here ttyname() might return "/tmp/x", a hardlink to a pseudotty.
+ * All of this is a problem only when login is suid, which it isn't.
+ */
+ if (!cxt->tty_path || !*cxt->tty_path ||
+ lstat(cxt->tty_path, &st) != 0 || !S_ISCHR(st.st_mode) ||
+ (st.st_nlink > 1 && strncmp(cxt->tty_path, "/dev/", 5)) ||
+ access(cxt->tty_path, R_OK | W_OK) != 0) {
+
+ syslog(LOG_ERR, _("FATAL: bad tty"));
+ sleepexit(EXIT_FAILURE);
+ }
+
+#ifdef LOGIN_CHOWN_VCS
+ if (cxt->tty_number) {
+ /* find names of Virtual Console devices, for later mode change */
+ snprintf(cxt->vcsn, sizeof(cxt->vcsn), "/dev/vcs%s", cxt->tty_number);
+ snprintf(cxt->vcsan, sizeof(cxt->vcsan), "/dev/vcsa%s", cxt->tty_number);
+ }
+#endif
+
+ tcgetattr(0, &tt);
+ ttt = tt;
+ ttt.c_cflag &= ~HUPCL;
+
+ if ((fchown(0, 0, 0) || fchmod(0, cxt->tty_mode)) && errno != EROFS) {
+
+ syslog(LOG_ERR, _("FATAL: %s: change permissions failed: %m"),
+ cxt->tty_path);
+ sleepexit(EXIT_FAILURE);
+ }
+
+ /* Kill processes left on this tty */
+ tcsetattr(0, TCSANOW, &ttt);
+
+ /*
+ * Let's close file descriptors before vhangup
+ * https://lkml.org/lkml/2012/6/5/145
+ */
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ signal(SIGHUP, SIG_IGN); /* so vhangup() won't kill us */
+ vhangup();
+ signal(SIGHUP, SIG_DFL);
+
+ /* open stdin,stdout,stderr to the tty */
+ open_tty(cxt->tty_path);
+
+ /* restore tty modes */
+ tcsetattr(0, TCSAFLUSH, &tt);
+}
+
+
+/*
+ * Logs failed login attempts in _PATH_BTMP, if it exists.
+ * Must be called only with username the name of an actual user.
+ * The most common login failure is to give password instead of username.
+ */
+static void log_btmp(struct login_context *cxt)
+{
+ struct utmpx ut;
+ struct timeval tv;
+
+ memset(&ut, 0, sizeof(ut));
+
+ str2memcpy(ut.ut_user,
+ cxt->username ? cxt->username : "(unknown)",
+ sizeof(ut.ut_user));
+
+ if (cxt->tty_number)
+ str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+ if (cxt->tty_name)
+ str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+
+ gettimeofday(&tv, NULL);
+ ut.ut_tv.tv_sec = tv.tv_sec;
+ ut.ut_tv.tv_usec = tv.tv_usec;
+
+ ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
+ ut.ut_pid = cxt->pid;
+
+ if (cxt->hostname) {
+ str2memcpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
+ if (*cxt->hostaddress)
+ memcpy(&ut.ut_addr_v6, cxt->hostaddress,
+ sizeof(ut.ut_addr_v6));
+ }
+
+ updwtmpx(_PATH_BTMP, &ut);
+}
+
+
+#ifdef HAVE_LIBAUDIT
+static void log_audit(struct login_context *cxt, int status)
+{
+ int audit_fd;
+ struct passwd *pwd = cxt->pwd;
+
+ audit_fd = audit_open();
+ if (audit_fd == -1)
+ return;
+ if (!pwd && cxt->username)
+ pwd = getpwnam(cxt->username);
+
+ audit_log_acct_message(audit_fd,
+ AUDIT_USER_LOGIN,
+ NULL,
+ "login",
+ cxt->username ? cxt->username : "(unknown)",
+ pwd ? pwd->pw_uid : (unsigned int) -1,
+ cxt->hostname,
+ NULL,
+ cxt->tty_name,
+ status);
+
+ close(audit_fd);
+}
+#else /* !HAVE_LIBAUDIT */
+# define log_audit(cxt, status)
+#endif /* HAVE_LIBAUDIT */
+
+static void log_lastlog(struct login_context *cxt)
+{
+ struct sigaction sa, oldsa_xfsz;
+ struct lastlog ll;
+ time_t t;
+ int fd;
+
+ if (!cxt->pwd)
+ return;
+
+ /* lastlog is huge on systems with large UIDs, ignore SIGXFSZ */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGXFSZ, &sa, &oldsa_xfsz);
+
+ fd = open(_PATH_LASTLOG, O_RDWR, 0);
+ if (fd < 0)
+ goto done;
+
+ if (lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET) == -1)
+ goto done;
+
+ /*
+ * Print last log message.
+ */
+ if (!cxt->quiet) {
+ if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) &&
+ ll.ll_time != 0) {
+ time_t ll_time = (time_t) ll.ll_time;
+
+ printf(_("Last login: %.*s "), 24 - 5, ctime(&ll_time));
+ if (*ll.ll_host != '\0')
+ printf(_("from %.*s\n"),
+ (int)sizeof(ll.ll_host), ll.ll_host);
+ else
+ printf(_("on %.*s\n"),
+ (int)sizeof(ll.ll_line), ll.ll_line);
+ }
+ if (lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET) == -1)
+ goto done;
+ }
+
+ memset((char *)&ll, 0, sizeof(ll));
+
+ time(&t);
+ ll.ll_time = t; /* ll_time is always 32bit */
+
+ if (cxt->tty_name)
+ str2memcpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line));
+ if (cxt->hostname)
+ str2memcpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host));
+
+ if (write_all(fd, (char *)&ll, sizeof(ll)))
+ warn(_("write lastlog failed"));
+done:
+ if (fd >= 0)
+ close(fd);
+
+ sigaction(SIGXFSZ, &oldsa_xfsz, NULL); /* restore original setting */
+}
+
+/*
+ * Update wtmp and utmp logs.
+ */
+static void log_utmp(struct login_context *cxt)
+{
+ struct utmpx ut;
+ struct utmpx *utp;
+ struct timeval tv;
+
+ utmpxname(_PATH_UTMP);
+ setutxent();
+
+ /* Find pid in utmp.
+ *
+ * login sometimes overwrites the runlevel entry in /var/run/utmp,
+ * confusing sysvinit. I added a test for the entry type, and the
+ * problem was gone. (In a runlevel entry, st_pid is not really a pid
+ * but some number calculated from the previous and current runlevel.)
+ * -- Michael Riepe <michael@stud.uni-hannover.de>
+ */
+ while ((utp = getutxent()))
+ if (utp->ut_pid == cxt->pid
+ && utp->ut_type >= INIT_PROCESS
+ && utp->ut_type <= DEAD_PROCESS)
+ break;
+
+ /* If we can't find a pre-existing entry by pid, try by line.
+ * BSD network daemons may rely on this. */
+ if (utp == NULL && cxt->tty_name) {
+ setutxent();
+ ut.ut_type = LOGIN_PROCESS;
+ str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+ utp = getutxline(&ut);
+ }
+
+ /* If we can't find a pre-existing entry by pid and line, try it by id.
+ * Very stupid telnetd daemons don't set up utmp at all. (kzak) */
+ if (utp == NULL && cxt->tty_number) {
+ setutxent();
+ ut.ut_type = DEAD_PROCESS;
+ str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+ utp = getutxid(&ut);
+ }
+
+ if (utp)
+ memcpy(&ut, utp, sizeof(ut));
+ else
+ /* some gettys/telnetds don't initialize utmp... */
+ memset(&ut, 0, sizeof(ut));
+
+ if (cxt->tty_number && ut.ut_id[0] == 0)
+ str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+ if (cxt->username)
+ str2memcpy(ut.ut_user, cxt->username, sizeof(ut.ut_user));
+ if (cxt->tty_name)
+ str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+
+ gettimeofday(&tv, NULL);
+ ut.ut_tv.tv_sec = tv.tv_sec;
+ ut.ut_tv.tv_usec = tv.tv_usec;
+ ut.ut_type = USER_PROCESS;
+ ut.ut_pid = cxt->pid;
+ if (cxt->hostname) {
+ str2memcpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
+ if (*cxt->hostaddress)
+ memcpy(&ut.ut_addr_v6, cxt->hostaddress,
+ sizeof(ut.ut_addr_v6));
+ }
+
+ pututxline(&ut);
+ endutxent();
+
+ updwtmpx(_PATH_WTMP, &ut);
+}
+
+static void log_syslog(struct login_context *cxt)
+{
+ struct passwd *pwd = cxt->pwd;
+
+ if (!cxt->tty_name)
+ return;
+
+ if (!strncmp(cxt->tty_name, "ttyS", 4))
+ syslog(LOG_INFO, _("DIALUP AT %s BY %s"),
+ cxt->tty_name, pwd->pw_name);
+
+ if (!pwd->pw_uid) {
+ if (cxt->hostname)
+ syslog(LOG_NOTICE, _("ROOT LOGIN ON %s FROM %s"),
+ cxt->tty_name, cxt->hostname);
+ else
+ syslog(LOG_NOTICE, _("ROOT LOGIN ON %s"), cxt->tty_name);
+ } else {
+ if (cxt->hostname)
+ syslog(LOG_INFO, _("LOGIN ON %s BY %s FROM %s"),
+ cxt->tty_name, pwd->pw_name, cxt->hostname);
+ else
+ syslog(LOG_INFO, _("LOGIN ON %s BY %s"), cxt->tty_name,
+ pwd->pw_name);
+ }
+}
+
+/* encapsulate stupid "void **" pam_get_item() API */
+static int loginpam_get_username(pam_handle_t *pamh, const char **name)
+{
+ const void *item = (const void *)*name;
+ int rc;
+ rc = pam_get_item(pamh, PAM_USER, &item);
+ *name = (const char *)item;
+ return rc;
+}
+
+static void loginpam_err(pam_handle_t *pamh, int retcode)
+{
+ const char *msg = pam_strerror(pamh, retcode);
+
+ if (msg) {
+ fprintf(stderr, "\n%s\n", msg);
+ syslog(LOG_ERR, "%s", msg);
+ }
+ pam_end(pamh, retcode);
+ sleepexit(EXIT_FAILURE);
+}
+
+/*
+ * Composes "<host> login: " string; or returns "login: " if -H is given or
+ * LOGIN_PLAIN_PROMPT=yes configured.
+ */
+static const char *loginpam_get_prompt(struct login_context *cxt)
+{
+ const char *host;
+ char *prompt, *dflt_prompt = _("login: ");
+ size_t sz;
+
+ if (cxt->nohost)
+ return dflt_prompt; /* -H on command line */
+
+ if (getlogindefs_bool("LOGIN_PLAIN_PROMPT", 0) == 1)
+ return dflt_prompt;
+
+ if (!(host = get_thishost(cxt, NULL)))
+ return dflt_prompt;
+
+ sz = strlen(host) + 1 + strlen(dflt_prompt) + 1;
+ prompt = xmalloc(sz);
+ snprintf(prompt, sz, "%s %s", host, dflt_prompt);
+
+ return prompt;
+}
+
+static pam_handle_t *init_loginpam(struct login_context *cxt)
+{
+ pam_handle_t *pamh = NULL;
+ int rc;
+
+ /*
+ * username is initialized to NULL and if specified on the command line
+ * it is set. Therefore, we are safe not setting it to anything.
+ */
+ rc = pam_start(cxt->remote ? "remote" : "login",
+ cxt->username, &cxt->conv, &pamh);
+ if (rc != PAM_SUCCESS) {
+ warnx(_("PAM failure, aborting: %s"), pam_strerror(pamh, rc));
+ syslog(LOG_ERR, _("Couldn't initialize PAM: %s"),
+ pam_strerror(pamh, rc));
+ sleepexit(EXIT_FAILURE);
+ }
+
+ /* hostname & tty are either set to NULL or their correct values,
+ * depending on how much we know. */
+ rc = pam_set_item(pamh, PAM_RHOST, cxt->hostname);
+ if (is_pam_failure(rc))
+ loginpam_err(pamh, rc);
+
+ rc = pam_set_item(pamh, PAM_TTY, cxt->tty_name);
+ if (is_pam_failure(rc))
+ loginpam_err(pamh, rc);
+
+ /*
+ * Andrew.Taylor@cal.montage.ca: Provide a user prompt to PAM so that
+ * the "login: " prompt gets localized. Unfortunately, PAM doesn't have
+ * an interface to specify the "Password: " string (yet).
+ */
+ rc = pam_set_item(pamh, PAM_USER_PROMPT, loginpam_get_prompt(cxt));
+ if (is_pam_failure(rc))
+ loginpam_err(pamh, rc);
+
+ /* We don't need the original username. We have to follow PAM. */
+ cxt->username = NULL;
+ cxt->pamh = pamh;
+
+ return pamh;
+}
+
+static void loginpam_auth(struct login_context *cxt)
+{
+ int rc, show_unknown;
+ unsigned int retries, failcount = 0;
+ const char *hostname = cxt->hostname ? cxt->hostname :
+ cxt->tty_name ? cxt->tty_name : "<unknown>";
+ pam_handle_t *pamh = cxt->pamh;
+
+ /* if we didn't get a user on the command line, set it to NULL */
+ loginpam_get_username(pamh, &cxt->username);
+
+ show_unknown = getlogindefs_bool("LOG_UNKFAIL_ENAB", 0);
+ retries = getlogindefs_num("LOGIN_RETRIES", LOGIN_MAX_TRIES);
+
+ /*
+ * There may be better ways to deal with some of these conditions, but
+ * at least this way I don't think we'll be giving away information...
+ *
+ * Perhaps someday we can trust that all PAM modules will pay attention
+ * to failure count and get rid of LOGIN_MAX_TRIES?
+ */
+ rc = pam_authenticate(pamh, 0);
+
+ while ((++failcount < retries) &&
+ ((rc == PAM_AUTH_ERR) ||
+ (rc == PAM_USER_UNKNOWN) ||
+ (rc == PAM_CRED_INSUFFICIENT) ||
+ (rc == PAM_AUTHINFO_UNAVAIL))) {
+
+ if (rc == PAM_USER_UNKNOWN && !show_unknown)
+ /*
+ * Logging unknown usernames may be a security issue if
+ * a user enters her password instead of her login name.
+ */
+ cxt->username = NULL;
+ else
+ loginpam_get_username(pamh, &cxt->username);
+
+ syslog(LOG_NOTICE,
+ _("FAILED LOGIN %u FROM %s FOR %s, %s"),
+ failcount, hostname,
+ cxt->username ? cxt->username : "(unknown)",
+ pam_strerror(pamh, rc));
+
+ log_btmp(cxt);
+ log_audit(cxt, 0);
+
+ fprintf(stderr, _("Login incorrect\n\n"));
+
+ pam_set_item(pamh, PAM_USER, NULL);
+ rc = pam_authenticate(pamh, 0);
+ }
+
+ if (is_pam_failure(rc)) {
+
+ if (rc == PAM_USER_UNKNOWN && !show_unknown)
+ cxt->username = NULL;
+ else
+ loginpam_get_username(pamh, &cxt->username);
+
+ if (rc == PAM_MAXTRIES)
+ syslog(LOG_NOTICE,
+ _("TOO MANY LOGIN TRIES (%u) FROM %s FOR %s, %s"),
+ failcount, hostname,
+ cxt->username ? cxt->username : "(unknown)",
+ pam_strerror(pamh, rc));
+ else
+ syslog(LOG_NOTICE,
+ _("FAILED LOGIN SESSION FROM %s FOR %s, %s"),
+ hostname,
+ cxt->username ? cxt->username : "(unknown)",
+ pam_strerror(pamh, rc));
+
+ log_btmp(cxt);
+ log_audit(cxt, 0);
+
+ fprintf(stderr, _("\nLogin incorrect\n"));
+ pam_end(pamh, rc);
+ sleepexit(EXIT_SUCCESS);
+ }
+}
+
+static void loginpam_acct(struct login_context *cxt)
+{
+ int rc;
+ pam_handle_t *pamh = cxt->pamh;
+
+ rc = pam_acct_mgmt(pamh, 0);
+
+ if (rc == PAM_NEW_AUTHTOK_REQD)
+ rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+
+ if (is_pam_failure(rc))
+ loginpam_err(pamh, rc);
+
+ /*
+ * Grab the user information out of the password file for future use.
+ * First get the username that we are actually using, though.
+ */
+ rc = loginpam_get_username(pamh, &cxt->username);
+ if (is_pam_failure(rc))
+ loginpam_err(pamh, rc);
+
+ if (!cxt->username || !*cxt->username) {
+ warnx(_("\nSession setup problem, abort."));
+ syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."),
+ __FUNCTION__, __LINE__);
+ pam_end(pamh, PAM_SYSTEM_ERR);
+ sleepexit(EXIT_FAILURE);
+ }
+}
+
+/*
+ * Note that the position of the pam_setcred() call is discussable:
+ *
+ * - the PAM docs recommend pam_setcred() before pam_open_session()
+ * - but the original RFC http://www.opengroup.org/rfc/mirror-rfc/rfc86.0.txt
+ * uses pam_setcred() after pam_open_session()
+ *
+ * The old login versions (before year 2011) followed the RFC. This is probably
+ * not optimal, because there could be a dependence between some session modules
+ * and the user's credentials.
+ *
+ * The best is probably to follow openssh and call pam_setcred() before and
+ * after pam_open_session(). -- kzak@redhat.com (18-Nov-2011)
+ *
+ */
+static void loginpam_session(struct login_context *cxt)
+{
+ int rc;
+ pam_handle_t *pamh = cxt->pamh;
+
+ rc = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+ if (is_pam_failure(rc))
+ loginpam_err(pamh, rc);
+
+ rc = pam_open_session(pamh, 0);
+ if (is_pam_failure(rc)) {
+ pam_setcred(cxt->pamh, PAM_DELETE_CRED);
+ loginpam_err(pamh, rc);
+ }
+
+ rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED);
+ if (is_pam_failure(rc)) {
+ pam_close_session(pamh, 0);
+ loginpam_err(pamh, rc);
+ }
+}
+
+/*
+ * Detach the controlling terminal, fork, restore syslog stuff, and create
+ * a new session.
+ */
+static void fork_session(struct login_context *cxt)
+{
+ struct sigaction sa, oldsa_hup, oldsa_term;
+
+ signal(SIGALRM, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTSTP, SIG_IGN);
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGINT, &sa, NULL);
+
+ sigaction(SIGHUP, &sa, &oldsa_hup); /* ignore when TIOCNOTTY */
+
+ /*
+ * Detach the controlling tty.
+ * We don't need the tty in a parent who only waits for a child.
+ * The child calls setsid() that detaches from the tty as well.
+ */
+ ioctl(0, TIOCNOTTY, NULL);
+
+ /*
+ * We have to beware of SIGTERM, because leaving a PAM session
+ * without pam_close_session() is a pretty bad thing.
+ */
+ sa.sa_handler = sig_handler;
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGTERM, &sa, &oldsa_term);
+
+ closelog();
+
+ /*
+ * We must fork before setuid(), because we need to call
+ * pam_close_session() as root.
+ */
+ child_pid = fork();
+ if (child_pid < 0) {
+ warn(_("fork failed"));
+
+ pam_setcred(cxt->pamh, PAM_DELETE_CRED);
+ pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
+ sleepexit(EXIT_FAILURE);
+ }
+
+ if (child_pid) {
+ /*
+ * parent - wait for child to finish, then clean up session
+ */
+ close(0);
+ close(1);
+ close(2);
+ free_getlogindefs_data();
+
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+
+ /* wait as long as any child is there */
+ while (wait(NULL) == -1 && errno == EINTR) ;
+ openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
+
+ pam_setcred(cxt->pamh, PAM_DELETE_CRED);
+ pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
+ exit(EXIT_SUCCESS);
+ }
+
+ /*
+ * child
+ */
+ sigaction(SIGHUP, &oldsa_hup, NULL); /* restore old state */
+ sigaction(SIGTERM, &oldsa_term, NULL);
+ if (got_sig)
+ exit(EXIT_FAILURE);
+
+ /*
+ * Problem: if the user's shell is a shell like ash that doesn't do
+ * setsid() or setpgrp(), then a ctrl-\, sending SIGQUIT to every
+ * process in the pgrp, will kill us.
+ */
+
+ /* start new session */
+ setsid();
+
+ /* make sure we have a controlling tty */
+ open_tty(cxt->tty_path);
+ openlog("login", LOG_ODELAY, LOG_AUTHPRIV); /* reopen */
+
+ /*
+ * TIOCSCTTY: steal tty from other process group.
+ */
+ if (ioctl(0, TIOCSCTTY, 1))
+ syslog(LOG_ERR, _("TIOCSCTTY failed: %m"));
+ signal(SIGINT, SIG_DFL);
+}
+
+/*
+ * Initialize $TERM, $HOME, ...
+ */
+static void init_environ(struct login_context *cxt)
+{
+ struct passwd *pwd = cxt->pwd;
+ char *termenv, **env;
+ char tmp[PATH_MAX];
+ int len, i;
+
+ termenv = getenv("TERM");
+ if (termenv)
+ termenv = xstrdup(termenv);
+
+ /* destroy environment unless user has requested preservation (-p) */
+ if (!cxt->keep_env) {
+ environ = xmalloc(sizeof(char *));
+ memset(environ, 0, sizeof(char *));
+ }
+
+ xsetenv("HOME", pwd->pw_dir, 0); /* legal to override */
+ xsetenv("USER", pwd->pw_name, 1);
+ xsetenv("SHELL", pwd->pw_shell, 1);
+ xsetenv("TERM", termenv ? termenv : "dumb", 1);
+ free(termenv);
+
+ if (pwd->pw_uid) {
+ if (logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH) != 0)
+ err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
+
+ } else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0 &&
+ logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT) != 0) {
+ err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH");
+ }
+
+ /* mailx will give a funny error msg if you forget this one */
+ len = snprintf(tmp, sizeof(tmp), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
+ if (len > 0 && (size_t) len < sizeof(tmp))
+ xsetenv("MAIL", tmp, 0);
+
+ /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll
+ * not allow modifying it.
+ */
+ xsetenv("LOGNAME", pwd->pw_name, 1);
+
+ env = pam_getenvlist(cxt->pamh);
+ for (i = 0; env && env[i]; i++)
+ putenv(env[i]);
+}
+
+/*
+ * This is called for the -h option, initializes cxt->{hostname,hostaddress}.
+ */
+static void init_remote_info(struct login_context *cxt, char *remotehost)
+{
+ const char *domain;
+ char *p;
+ struct addrinfo hints, *info = NULL;
+
+ cxt->remote = 1;
+
+ get_thishost(cxt, &domain);
+
+ if (domain && (p = strchr(remotehost, '.')) &&
+ strcasecmp(p + 1, domain) == 0)
+ *p = '\0';
+
+ cxt->hostname = xstrdup(remotehost);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG;
+ cxt->hostaddress[0] = 0;
+
+ if (getaddrinfo(cxt->hostname, NULL, &hints, &info) == 0 && info) {
+ if (info->ai_family == AF_INET) {
+ struct sockaddr_in *sa =
+ (struct sockaddr_in *) info->ai_addr;
+
+ memcpy(cxt->hostaddress, &(sa->sin_addr), sizeof(sa->sin_addr));
+
+ } else if (info->ai_family == AF_INET6) {
+ struct sockaddr_in6 *sa =
+ (struct sockaddr_in6 *) info->ai_addr;
+#ifdef IN6_IS_ADDR_V4MAPPED
+ if (IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr)) {
+ const uint8_t *bytes = sa->sin6_addr.s6_addr;
+ struct in_addr addr = { *(const in_addr_t *) (bytes + 12) };
+
+ memcpy(cxt->hostaddress, &addr, sizeof(struct in_addr));
+ } else
+#endif
+ memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr));
+ }
+ freeaddrinfo(info);
+ }
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ printf(_(" %s [-p] [-h <host>] [-H] [[-f] <username>]\n"), program_invocation_short_name);
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs(_("Begin a session on the system.\n"), stdout);
+
+ fputs(USAGE_OPTIONS, stdout);
+ puts(_(" -p do not destroy the environment"));
+ puts(_(" -f skip a second login authentication"));
+ puts(_(" -h <host> hostname to be used for utmp logging"));
+ puts(_(" -H suppress hostname in the login prompt"));
+ printf(" --help %s\n", USAGE_OPTSTR_HELP);
+ printf(" -V, --version %s\n", USAGE_OPTSTR_VERSION);
+ printf(USAGE_MAN_TAIL("login(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ int cnt;
+ char *childArgv[10];
+ char *buff;
+ int childArgc = 0;
+ int retcode;
+ struct sigaction act;
+ struct passwd *pwd;
+
+ struct login_context cxt = {
+ .tty_mode = TTY_MODE, /* tty chmod() */
+ .pid = getpid(), /* PID */
+#ifdef HAVE_SECURITY_PAM_MISC_H
+ .conv = { misc_conv, NULL } /* Linux-PAM conversation function */
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+ .conv = { openpam_ttyconv, NULL } /* OpenPAM conversation function */
+#endif
+
+ };
+
+ /* the only two longopts to satisfy UL standards */
+ enum { HELP_OPTION = CHAR_MAX + 1 };
+ static const struct option longopts[] = {
+ {"help", no_argument, NULL, HELP_OPTION},
+ {"version", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+
+ timeout = (unsigned int)getlogindefs_num("LOGIN_TIMEOUT", LOGIN_TIMEOUT);
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ /* TRANSLATORS: The standard value for %u is 60. */
+ snprintf(timeout_msg, sizeof(timeout_msg),
+ _("%s: timed out after %u seconds"),
+ program_invocation_short_name, timeout);
+
+ signal(SIGALRM, timedout);
+ (void) sigaction(SIGALRM, NULL, &act);
+ act.sa_flags &= ~SA_RESTART;
+ sigaction(SIGALRM, &act, NULL);
+ alarm(timeout);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+
+ setpriority(PRIO_PROCESS, 0, 0);
+ initproctitle(argc, argv);
+
+ /*
+ * -p is used by getty to tell login not to destroy the environment
+ * -f is used to skip a second login authentication
+ * -h is used by other servers to pass the name of the remote
+ * host to login so that it may be placed in utmp and wtmp
+ */
+ while ((c = getopt_long(argc, argv, "fHh:pV", longopts, NULL)) != -1)
+ switch (c) {
+ case 'f':
+ cxt.noauth = 1;
+ break;
+
+ case 'H':
+ cxt.nohost = 1;
+ break;
+
+ case 'h':
+ if (getuid()) {
+ fprintf(stderr,
+ _("login: -h is for superuser only\n"));
+ exit(EXIT_FAILURE);
+ }
+ init_remote_info(&cxt, optarg);
+ break;
+
+ case 'p':
+ cxt.keep_env = 1;
+ break;
+
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ case HELP_OPTION:
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv) {
+ char *p = *argv;
+
+ /* username from command line */
+ cxt.cmd_username = xstrdup(p);
+ /* used temporary, it'll be replaced by username from PAM or/and cxt->pwd */
+ cxt.username = cxt.cmd_username;
+
+ /* Wipe the name - some people mistype their password here. */
+ /* (Of course we are too late, but perhaps this helps a little...) */
+ while (*p)
+ *p++ = ' ';
+ }
+
+ for (cnt = get_fd_tabsize() - 1; cnt > 2; cnt--)
+ close(cnt);
+
+ setpgrp(); /* set pgid to pid this means that setsid() will fail */
+ init_tty(&cxt);
+
+ openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
+
+ init_loginpam(&cxt);
+
+ /* login -f, then the user has already been authenticated */
+ cxt.noauth = cxt.noauth && getuid() == 0 ? 1 : 0;
+
+ if (!cxt.noauth)
+ loginpam_auth(&cxt);
+
+ /*
+ * Authentication may be skipped (for example, during krlogin, rlogin,
+ * etc...), but it doesn't mean that we can skip other account checks.
+ * The account could be disabled or the password has expired (although
+ * the kerberos ticket is valid). -- kzak@redhat.com (22-Feb-2006)
+ */
+ loginpam_acct(&cxt);
+
+ cxt.pwd = xgetpwnam(cxt.username, &cxt.pwdbuf);
+ if (!cxt.pwd) {
+ warnx(_("\nSession setup problem, abort."));
+ syslog(LOG_ERR, _("Invalid user name \"%s\" in %s:%d. Abort."),
+ cxt.username, __FUNCTION__, __LINE__);
+ pam_end(cxt.pamh, PAM_SYSTEM_ERR);
+ sleepexit(EXIT_FAILURE);
+ }
+
+ pwd = cxt.pwd;
+ cxt.username = pwd->pw_name;
+
+ /*
+ * Initialize the supplementary group list. This should be done before
+ * pam_setcred, because PAM modules might add groups during that call.
+ *
+ * For root we don't call initgroups, instead we call setgroups with
+ * group 0. This avoids the need to step through the whole group file,
+ * which can cause problems if NIS, NIS+, LDAP or something similar
+ * is used and the machine has network problems.
+ */
+ retcode = pwd->pw_uid ? initgroups(cxt.username, pwd->pw_gid) : /* user */
+ setgroups(0, NULL); /* root */
+ if (retcode < 0) {
+ syslog(LOG_ERR, _("groups initialization failed: %m"));
+ warnx(_("\nSession setup problem, abort."));
+ pam_end(cxt.pamh, PAM_SYSTEM_ERR);
+ sleepexit(EXIT_FAILURE);
+ }
+
+ /*
+ * Open PAM session (after successful authentication and account check).
+ */
+ loginpam_session(&cxt);
+
+ /* committed to login -- turn off timeout */
+ alarm((unsigned int)0);
+
+ endpwent();
+
+ cxt.quiet = get_hushlogin_status(pwd, 1);
+
+ log_utmp(&cxt);
+ log_audit(&cxt, 1);
+ log_lastlog(&cxt);
+
+ chown_tty(&cxt);
+
+ if (setgid(pwd->pw_gid) < 0 && pwd->pw_gid) {
+ syslog(LOG_ALERT, _("setgid() failed"));
+ exit(EXIT_FAILURE);
+ }
+
+ if (pwd->pw_shell == NULL || *pwd->pw_shell == '\0')
+ pwd->pw_shell = _PATH_BSHELL;
+
+ init_environ(&cxt); /* init $HOME, $TERM ... */
+
+ setproctitle("login", cxt.username);
+
+ log_syslog(&cxt);
+
+ if (!cxt.quiet) {
+ motd();
+
+#ifdef LOGIN_STAT_MAIL
+ /*
+ * This turns out to be a bad idea: when the mail spool
+ * is NFS mounted, and the NFS connection hangs, the
+ * login hangs, even root cannot login.
+ * Checking for mail should be done from the shell.
+ */
+ {
+ struct stat st;
+ char *mail;
+
+ mail = getenv("MAIL");
+ if (mail && stat(mail, &st) == 0 && st.st_size != 0) {
+ if (st.st_mtime > st.st_atime)
+ printf(_("You have new mail.\n"));
+ else
+ printf(_("You have mail.\n"));
+ }
+ }
+#endif
+ }
+
+ /*
+ * Detach the controlling terminal, fork, and create a new session
+ * and reinitialize syslog stuff.
+ */
+ fork_session(&cxt);
+
+ /* discard permissions last so we can't get killed and drop core */
+ if (setuid(pwd->pw_uid) < 0 && pwd->pw_uid) {
+ syslog(LOG_ALERT, _("setuid() failed"));
+ exit(EXIT_FAILURE);
+ }
+
+ /* wait until here to change directory! */
+ if (chdir(pwd->pw_dir) < 0) {
+ warn(_("%s: change directory failed"), pwd->pw_dir);
+
+ if (!getlogindefs_bool("DEFAULT_HOME", 1))
+ exit(0);
+ if (chdir("/"))
+ exit(EXIT_FAILURE);
+ pwd->pw_dir = "/";
+ printf(_("Logging in with home = \"/\".\n"));
+ }
+
+ /* if the shell field has a space: treat it like a shell script */
+ if (strchr(pwd->pw_shell, ' ')) {
+ buff = xmalloc(strlen(pwd->pw_shell) + 6);
+
+ strcpy(buff, "exec ");
+ strcat(buff, pwd->pw_shell);
+ childArgv[childArgc++] = "/bin/sh";
+ childArgv[childArgc++] = "-sh";
+ childArgv[childArgc++] = "-c";
+ childArgv[childArgc++] = buff;
+ } else {
+ char tbuf[PATH_MAX + 2], *p;
+
+ tbuf[0] = '-';
+ xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ?
+ p + 1 : pwd->pw_shell), sizeof(tbuf) - 1);
+
+ childArgv[childArgc++] = pwd->pw_shell;
+ childArgv[childArgc++] = xstrdup(tbuf);
+ }
+
+ childArgv[childArgc++] = NULL;
+
+ execvp(childArgv[0], childArgv + 1);
+
+ if (!strcmp(childArgv[0], "/bin/sh"))
+ warn(_("couldn't exec shell script"));
+ else
+ warn(_("no shell"));
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/login-utils/logindefs.c b/login-utils/logindefs.c
new file mode 100644
index 0000000..ebf1a9f
--- /dev/null
+++ b/login-utils/logindefs.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2003, 2004, 2005 Thorsten Kukuk
+ * Author: Thorsten Kukuk <kukuk@suse.de>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain any existing copyright
+ * notice, and this entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ *
+ * 2. Redistributions in binary form must reproduce all prior and current
+ * copyright notices, this list of conditions, and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * 3. The name of any author may not be used to endorse or promote
+ * products derived from this software without their specific prior
+ * written permission.
+ */
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#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"
+#include "logindefs.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "xalloc.h"
+
+struct item {
+ char *name; /* name of the option. */
+ char *value; /* value of the option. */
+ char *path; /* name of config file for this option. */
+
+ struct item *next; /* pointer to next option. */
+};
+
+static struct item *list = NULL;
+
+static void (*logindefs_loader)(void *) = NULL;
+static void *logindefs_loader_data = NULL;
+
+void free_getlogindefs_data(void)
+{
+ struct item *ptr;
+
+ ptr = list;
+ while (ptr) {
+ struct item *tmp = ptr->next;
+
+ free(ptr->path);
+ free(ptr->name);
+ free(ptr->value);
+ free(ptr);
+ ptr = tmp;
+ }
+
+ list = NULL;
+}
+
+static void store(const char *name, const char *value, const char *path)
+{
+ struct item *new = xmalloc(sizeof(struct item));
+
+ if (!name)
+ abort();
+
+ new->name = xstrdup(name);
+ new->value = value && *value ? xstrdup(value) : NULL;
+ new->path = xstrdup(path);
+ new->next = list;
+ list = new;
+}
+
+void logindefs_load_file(const char *filename)
+{
+ FILE *f;
+ char buf[BUFSIZ];
+
+ f = fopen(filename, "r");
+ if (!f)
+ return;
+
+ while (fgets(buf, sizeof(buf), f)) {
+
+ char *p, *name, *data = NULL;
+
+ if (*buf == '#' || *buf == '\n')
+ continue; /* only comment or empty line */
+
+ p = strchr(buf, '#');
+ if (p)
+ *p = '\0';
+ else {
+ size_t n = strlen(buf);
+ if (n && *(buf + n - 1) == '\n')
+ *(buf + n - 1) = '\0';
+ }
+
+ if (!*buf)
+ continue; /* empty line */
+
+ /* ignore space at begin of the line */
+ name = buf;
+ while (*name && isspace((unsigned)*name))
+ name++;
+
+ /* go to the end of the name */
+ data = name;
+ while (*data && !(isspace((unsigned)*data) || *data == '='))
+ data++;
+ if (data > name && *data)
+ *data++ = '\0';
+
+ if (!*name || data == name)
+ continue;
+
+ /* go to the begin of the value */
+ while (*data
+ && (isspace((unsigned)*data) || *data == '='
+ || *data == '"'))
+ data++;
+
+ /* remove space at the end of the value */
+ p = data + strlen(data);
+ if (p > data)
+ p--;
+ while (p > data && (isspace((unsigned)*p) || *p == '"'))
+ *p-- = '\0';
+
+ store(name, data, filename);
+ }
+
+ fclose(f);
+}
+
+void logindefs_set_loader(void (*loader)(void *data), void *data)
+{
+ logindefs_loader = loader;
+ logindefs_loader_data = data;
+}
+
+static void load_defaults(void)
+{
+ if (logindefs_loader)
+ logindefs_loader(logindefs_loader_data);
+ else
+ logindefs_load_file(_PATH_LOGINDEFS);
+}
+
+static struct item *search(const char *name)
+{
+ struct item *ptr;
+
+ if (!list)
+ load_defaults();
+
+ ptr = list;
+ while (ptr != NULL) {
+ if (strcasecmp(name, ptr->name) == 0)
+ return ptr;
+ ptr = ptr->next;
+ }
+
+ return NULL;
+}
+
+static const char *search_config(const char *name)
+{
+ struct item *ptr;
+
+ ptr = list;
+ while (ptr != NULL) {
+ if (strcasecmp(name, ptr->name) == 0)
+ return ptr->path;
+ ptr = ptr->next;
+ }
+
+ return NULL;
+}
+
+int getlogindefs_bool(const char *name, int dflt)
+{
+ struct item *ptr = search(name);
+ return ptr && ptr->value ? (strcasecmp(ptr->value, "yes") == 0) : dflt;
+}
+
+unsigned long getlogindefs_num(const char *name, long dflt)
+{
+ struct item *ptr = search(name);
+ char *end = NULL;
+ unsigned long retval;
+
+ if (!ptr || !ptr->value)
+ return dflt;
+
+ errno = 0;
+ retval = strtoul(ptr->value, &end, 0);
+ if (end && *end == '\0' && !errno)
+ return retval;
+
+ syslog(LOG_NOTICE, _("%s: %s contains invalid numerical value: %s"),
+ search_config(name), name, ptr->value);
+ return dflt;
+}
+
+/*
+ * Returns:
+ * @dflt if @name not found
+ * "" (empty string) if found, but value not defined
+ * "string" if found
+ */
+const char *getlogindefs_str(const char *name, const char *dflt)
+{
+ struct item *ptr = search(name);
+
+ if (!ptr)
+ return dflt;
+ if (!ptr->value)
+ return "";
+ return ptr->value;
+}
+
+/*
+ * For compatibility with shadow-utils we have to support additional
+ * syntax for environment variables in login.defs(5) file. The standard
+ * syntax is:
+ *
+ * ENV_FOO data
+ *
+ * but shadow-utils supports also
+ *
+ * ENV_FOO FOO=data
+ *
+ * the FOO= prefix has to be remove before we call setenv().
+ */
+int logindefs_setenv(const char *name, const char *conf, const char *dflt)
+{
+ const char *val = getlogindefs_str(conf, dflt);
+ const char *p;
+
+ if (!val)
+ return -1;
+
+ p = strchr(val, '=');
+ if (p) {
+ size_t sz = strlen(name);
+
+ if (strncmp(val, name, sz) == 0 && *(p + 1)) {
+ val = p + 1;
+ if (*val == '"')
+ val++;
+ if (!*val)
+ val = dflt;
+ }
+ }
+
+ 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)) {
+ if (buf[0] != '\0')
+ 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[])
+{
+ char *name, *type;
+ atexit(close_stdout);
+
+ if (argc <= 1)
+ errx(EXIT_FAILURE, "usage: %s <filename> "
+ "[<str|num|bool> <valname>]", argv[0]);
+
+ logindefs_load_file(argv[1]);
+
+ if (argc != 4) { /* list all */
+ struct item *ptr;
+
+ for (ptr = list; ptr; ptr = ptr->next)
+ printf("%s: $%s: '%s'\n", ptr->path, ptr->name,
+ ptr->value);
+
+ return EXIT_SUCCESS;
+ }
+
+ type = argv[2];
+ name = argv[3];
+
+ if (strcmp(type, "str") == 0)
+ printf("$%s: '%s'\n", name, getlogindefs_str(name, "DEFAULT"));
+ else if (strcmp(type, "num") == 0)
+ printf("$%s: '%ld'\n", name, getlogindefs_num(name, 0));
+ else if (strcmp(type, "bool") == 0)
+ printf("$%s: '%s'\n", name,
+ getlogindefs_bool(name, 0) ? "Y" : "N");
+
+ return EXIT_SUCCESS;
+}
+#endif
diff --git a/login-utils/logindefs.h b/login-utils/logindefs.h
new file mode 100644
index 0000000..064737c
--- /dev/null
+++ b/login-utils/logindefs.h
@@ -0,0 +1,14 @@
+#ifndef UTIL_LINUX_LOGINDEFS_H
+#define UTIL_LINUX_LOGINDEFS_H
+
+extern void logindefs_load_file(const char *filename);
+extern void logindefs_set_loader(void (*loader)(void *data), void *data);
+extern int getlogindefs_bool(const char *name, int dflt);
+extern unsigned long getlogindefs_num(const char *name, long dflt);
+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 --git a/login-utils/lslogins.1 b/login-utils/lslogins.1
new file mode 100644
index 0000000..5aa14c7
--- /dev/null
+++ b/login-utils/lslogins.1
@@ -0,0 +1,152 @@
+.\" 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
+[options]
+.RB [ \-s | \-u [ =\fIUID ]]
+.RB [ \-g " \fIgroups\fR]"
+.RB [ \-l " \fIlogins\fR]"
+.RB [\fIusername\fR]
+.SH DESCRIPTION
+.PP
+Examine the wtmp and btmp logs, /etc/shadow (if necessary) and /etc/passwd
+and output the desired data.
+
+The optional argument \fIusername\fR forces
+.BR lslogins
+to print all available details about the specified user only. In this case the
+output format is different than in case of \fB\-l\fR or \fB\-g\fR and unknown
+is \fIusername\fR reported as an error.
+
+.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 privileges.)
+.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\-\-supp\-groups\fR
+Show information about supplementary 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. The unknown group
+names are ignored.
+
+Note that relation between user and group may be invisible for primary group if
+the user is not explicitly specify as group member (e.g. in /etc/group). If the
+command lslogins scans for groups than it uses groups database only, and user
+database with primary GID is not used at all.
+.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.
+The unknown login names are ignored.
+.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.
+The default list of columns may be extended if \fIlist\fP is
+specified in the format \fI+list\fP.
+.TP
+.B \-\-output\-all
+Output all available columns.
+.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
+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).
+This hardcoded default maybe overwritten by parameters SYS_UID_MIN and SYS_UID_MAX in
+the file /etc/login.defs.
+.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
+Show user accounts. These are by default all accounts with UID above 1000
+(inclusive), with the exception of either nobody or nfsnobody (UID 65534).
+This hardcoded default maybe overwritten by parameters UID_MIN and UID_MAX in
+the file /etc/login.defs.
+.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 https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c
new file mode 100644
index 0000000..e2a0d43
--- /dev/null
+++ b/login-utils/lslogins.c
@@ -0,0 +1,1595 @@
+/*
+ * 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 <utmpx.h>
+#include <signal.h>
+#include <err.h>
+#include <limits.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 "procutils.h"
+#include "timeutils.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;
+ const char *pwd_method;
+
+ 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,
+ TIME_ISO_SHORT,
+};
+
+/*
+ * 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_PWDMETHOD,
+ 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_PWDMETHOD] = { "PWD-METHOD", N_("password encryption method"), N_("Password encryption method"), 0.1 },
+ [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 utmpx *wtmp;
+ size_t wtmp_size;
+
+ struct utmpx *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,
+ fail_on_unknown : 1, /* fail if user does not exist */
+ 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 size_t 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 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 struct timeval now;
+
+static char *make_time(int mode, time_t time)
+{
+ int rc = 0;
+ char buf[64] = {0};
+
+ switch(mode) {
+ case TIME_FULL:
+ {
+ char *s;
+ struct tm tm;
+ localtime_r(&time, &tm);
+
+ asctime_r(&tm, buf);
+ if (*(s = buf + strlen(buf) - 1) == '\n')
+ *s = '\0';
+ rc = 0;
+ break;
+ }
+ case TIME_SHORT:
+ rc = strtime_short(&time, &now, UL_SHORTTIME_THISYEAR_HHMM,
+ buf, sizeof(buf));
+ break;
+ case TIME_ISO:
+ rc = strtime_iso(&time, ISO_TIMESTAMP_T, buf, sizeof(buf));
+ break;
+ case TIME_ISO_SHORT:
+ rc = strtime_iso(&time, ISO_DATE, buf, sizeof(buf));
+ break;
+ default:
+ errx(EXIT_FAILURE, _("unsupported time type"));
+ }
+
+ if (rc)
+ errx(EXIT_FAILURE, _("failed to compose time string"));
+
+ 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 >= 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 utmpx *get_last_wtmp(struct lslogins_control *ctl, const char *username)
+{
+ size_t n = 0;
+
+ if (!username)
+ return NULL;
+
+ n = ctl->wtmp_size - 1;
+ do {
+ if (!strncmp(username, ctl->wtmp[n].ut_user,
+ sizeof(ctl->wtmp[0].ut_user)))
+ return ctl->wtmp + n;
+ } while (n--);
+ return NULL;
+
+}
+
+static int require_wtmp(void)
+{
+ size_t i;
+ for (i = 0; i < ncolumns; i++)
+ if (is_wtmp_col(columns[i]))
+ return 1;
+ return 0;
+}
+
+static int require_btmp(void)
+{
+ size_t i;
+ for (i = 0; i < ncolumns; i++)
+ if (is_btmp_col(columns[i]))
+ return 1;
+ return 0;
+}
+
+static struct utmpx *get_last_btmp(struct lslogins_control *ctl, const char *username)
+{
+ size_t n = 0;
+
+ if (!username)
+ return NULL;
+
+ n = ctl->btmp_size - 1;
+ do {
+ if (!strncmp(username, ctl->btmp[n].ut_user,
+ sizeof(ctl->wtmp[0].ut_user)))
+ return ctl->btmp + n;
+ }while (n--);
+ return NULL;
+
+}
+
+static int read_utmp(char const *file, size_t *nents, struct utmpx **res)
+{
+ size_t n_read = 0, n_alloc = 0;
+ struct utmpx *utmp = NULL, *u;
+
+ if (utmpxname(file) < 0)
+ return -errno;
+
+ setutxent();
+ errno = 0;
+
+ while ((u = getutxent()) != NULL) {
+ if (n_read == n_alloc) {
+ n_alloc += 32;
+ utmp = xrealloc(utmp, n_alloc * sizeof (struct utmpx));
+ }
+ utmp[n_read++] = *u;
+ }
+ if (!u && errno) {
+ free(utmp);
+ return -errno;
+ }
+
+ endutxent();
+
+ *nents = n_read;
+ *res = utmp;
+
+ return 0;
+}
+
+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;
+ int ngroups = 0;
+
+ *len = 0;
+ *list = NULL;
+
+ /* first let's get a supp. group count */
+ getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups);
+ if (!ngroups)
+ return -1;
+
+ *list = xcalloc(1, ngroups * sizeof(gid_t));
+
+ /* now for the actual list of GIDs */
+ if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups))
+ return -1;
+
+ *len = (size_t) ngroups;
+
+ /* 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 const char *get_pwd_method(const char *str, const char **next, unsigned int *sz)
+{
+ const char *p = str;
+ const char *res = NULL;
+
+ if (!p || *p++ != '$')
+ return NULL;
+
+ if (sz)
+ *sz = 0;
+
+ switch (*p) {
+ case '1':
+ res = "MD5";
+ if (sz)
+ *sz = 22;
+ break;
+ case '2':
+ p++;
+ if (*p == 'a' || *p == 'y')
+ res = "Blowfish";
+ break;
+ case '5':
+ res = "SHA-256";
+ if (sz)
+ *sz = 43;
+ break;
+ case '6':
+ res = "SHA-512";
+ if (sz)
+ *sz = 86;
+ break;
+ default:
+ return NULL;
+ }
+ p++;
+
+ if (*p != '$')
+ return NULL;
+ if (next)
+ *next = ++p;
+ return res;
+}
+
+#define is_valid_pwd_char(x) (isalnum((unsigned char) (x)) || (x) == '.' || (x) == '/')
+
+static int valid_pwd(const char *str)
+{
+ const char *p = str;
+ unsigned int sz = 0, n;
+
+ /* $id$ */
+ if (get_pwd_method(str, &p, &sz) == NULL)
+ return 0;
+ if (!*p)
+ return 0;
+
+ /* salt$ */
+ for (; p && *p; p++) {
+ if (*p == '$') {
+ p++;
+ break;
+ }
+ if (!is_valid_pwd_char(*p))
+ return 0;
+ }
+ if (!*p)
+ return 0;
+
+ /* encrypted */
+ for (n = 0; p && *p; p++, n++) {
+ if (!is_valid_pwd_char(*p))
+ return 0;
+ }
+
+ if (sz && n != sz)
+ return 0;
+ return 1;
+}
+
+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 utmpx *user_wtmp = NULL, *user_btmp = NULL;
+ size_t n = 0;
+ time_t time;
+ uid_t uid;
+ errno = 0;
+
+ 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;
+ }
+
+ errno = 0;
+ grp = getgrgid(pwd->pw_gid);
+ if (!grp)
+ return NULL;
+
+ user = xcalloc(1, sizeof(struct lslogins_user));
+
+ 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_PWDMETHOD:
+ if (shadow) {
+ const char *p = shadow->sp_pwdp;
+
+ if (*p == '!' || *p == '*')
+ p++;
+ user->pwd_method = get_pwd_method(p, NULL, NULL);
+ } else
+ user->pwd_method = NULL;
+ break;
+ case COL_NOLOGIN:
+ if (strstr(pwd->pw_shell, "nologin"))
+ user->nologin = 1;
+ else if (pwd->pw_uid)
+ user->nologin = access(_PATH_NOLOGIN, F_OK) == 0 ||
+ access(_PATH_VAR_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(ctl->time_mode == TIME_ISO ?
+ TIME_ISO_SHORT : ctl->time_mode,
+ 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(ctl->time_mode == TIME_ISO ?
+ TIME_ISO_SHORT : ctl->time_mode,
+ 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;
+}
+
+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;
+}
+
+/* get a definitive list of users we want info about... */
+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 duplicate 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;
+}
+
+/* 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)
+
+static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
+ const char *username)
+{
+ *user = get_user_info(ctl, username);
+ if (!*user && IS_REAL_ERRNO(errno))
+ return -1;
+ return 0;
+}
+
+static int cmp_uid(const void *a, const void *b)
+{
+ uid_t x = ((const struct lslogins_user *)a)->uid;
+ uid_t z = ((const 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) {
+ for (n = 0; n < ctl->ulsiz; n++) {
+ int rc = get_user(ctl, &user, ctl->ulist[n]);
+
+ if (ctl->fail_on_unknown && !user) {
+ warnx(_("cannot found '%s'"), ctl->ulist[n]);
+ return -1;
+ }
+ if (rc || !user)
+ continue;
+
+ tsearch(user, &ctl->usertree, cmp_uid);
+ }
+ } 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 *table = scols_new_table();
+ size_t n = 0;
+
+ if (!table)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+ if (ctl->noheadings)
+ scols_table_enable_noheadings(table, 1);
+
+ switch(outmode) {
+ case OUT_COLON:
+ scols_table_enable_raw(table, 1);
+ scols_table_set_column_separator(table, ":");
+ break;
+ case OUT_NEWLINE:
+ scols_table_set_column_separator(table, "\n");
+ /* fallthrough */
+ case OUT_EXPORT:
+ scols_table_enable_export(table, 1);
+ break;
+ case OUT_NUL:
+ scols_table_set_line_separator(table, "\0");
+ /* fallthrough */
+ case OUT_RAW:
+ scols_table_enable_raw(table, 1);
+ break;
+ case OUT_PRETTY:
+ scols_table_enable_noheadings(table, 1);
+ default:
+ break;
+ }
+
+ while (n < ncolumns) {
+ int flags = coldescs[columns[n]].flag;
+
+ if (ctl->notrunc)
+ flags &= ~SCOLS_FL_TRUNC;
+
+ if (!scols_table_new_column(table,
+ coldescs[columns[n]].name,
+ coldescs[columns[n]].whint,
+ flags))
+ goto fail;
+ ++n;
+ }
+
+ return table;
+fail:
+ scols_unref_table(table);
+ return NULL;
+}
+
+static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
+{
+ struct libscols_line *ln;
+ const struct lslogins_user *user = *(struct lslogins_user * const *)u;
+ size_t n = 0;
+
+ if (which == preorder || which == endorder)
+ return;
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ 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_PWDMETHOD:
+ rc = scols_line_set_data(ln, n, user->pwd_method);
+ 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)
+ err(EXIT_FAILURE, _("failed to add output data"));
+ ++n;
+ }
+ return;
+}
+#ifdef HAVE_LIBSYSTEMD
+static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
+{
+ sd_journal *j;
+ char *match, *timestamp;
+ 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);
+
+ 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))
+ goto done;
+ if (0 > sd_journal_get_data(j, "_PID",
+ (const void **) &pid, &pid_len))
+ goto done;
+ if (0 > sd_journal_get_data(j, "MESSAGE",
+ (const void **) &message, &message_len))
+ goto done;
+
+ sd_journal_get_realtime_usec(j, &x);
+ t = x / 1000000;
+ timestamp = make_time(time_mode, t);
+ /* Get rid of journal entry field identifiers */
+ identifier = strchr(identifier, '=') + 1;
+ pid = strchr(pid, '=') + 1;
+ message = strchr(message, '=') + 1;
+
+ fprintf(stdout, "%s %s[%s]: %s\n", timestamp, identifier, pid,
+ message);
+ free(timestamp);
+ } while (sd_journal_next(j));
+
+done:
+ free(match);
+ sd_journal_flush_matches(j);
+ sd_journal_close(j);
+}
+#endif
+
+static int print_pretty(struct libscols_table *table)
+{
+ 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(table, 0);
+ while (!scols_table_next_column(table, 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, ctl->time_mode);
+ 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);
+}
+
+static int parse_time_mode(const char *s)
+{
+ struct lslogins_timefmt {
+ const char *name;
+ const int val;
+ };
+ static const struct lslogins_timefmt timefmts[] = {
+ {"iso", TIME_ISO},
+ {"full", TIME_FULL},
+ {"short", TIME_SHORT},
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
+ if (strcmp(timefmts[i].name, s) == 0)
+ return timefmts[i].val;
+ }
+ errx(EXIT_FAILURE, _("unknown time format: %s"), s);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<username>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Display information about known users in the system.\n"), out);
+
+ 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, --supp-groups 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(_(" -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(_(" --output-all output all columns\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);
+ printf(USAGE_HELP_OPTIONS(26));
+
+ fputs(USAGE_COLUMNS, out);
+ for (i = 0; i < ARRAY_SIZE(coldescs); i++)
+ fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help));
+
+ printf(USAGE_MAN_TAIL("lslogins(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ char *logins = NULL, *groups = NULL, *outarg = 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_WTMP = CHAR_MAX + 1,
+ OPT_BTMP,
+ OPT_NOTRUNC,
+ OPT_NOHEAD,
+ OPT_TIME_FMT,
+ OPT_OUTPUT_ALL,
+ };
+
+ 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' },
+ { "output-all", no_argument, 0, OPT_OUTPUT_ALL },
+ { "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, "acefGg:hLl:no:prsuVzZ",
+ 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();
+ 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 == '=')
+ optarg++;
+ outarg = optarg;
+ break;
+ case OPT_OUTPUT_ALL:
+ for (ncolumns = 0; ncolumns < ARRAY_SIZE(coldescs); ncolumns++)
+ columns[ncolumns] = ncolumns;
+ 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);
+ add_column(columns, ncolumns++, COL_PWDMETHOD);
+ 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:
+ ctl->time_mode = parse_time_mode(optarg);
+ 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:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ 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;
+ ctl->fail_on_unknown = 1;
+ } else if (argc != optind)
+ errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
+
+ 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) {
+ /* all columns for lslogins <username> */
+ for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
+ columns[ncolumns++] = i;
+
+ } else if (ncolumns == 2) {
+ /* 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 (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ 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 --git a/login-utils/newgrp.1 b/login-utils/newgrp.1
new file mode 100644
index 0000000..f85a8d3
--- /dev/null
+++ b/login-utils/newgrp.1
@@ -0,0 +1,34 @@
+.\" Original author unknown. This man page is in the public domain.
+.\" Modified Sat Oct 9 17:46:48 1993 by faith@cs.unc.edu
+.TH NEWGRP 1 "October 1993" "util-linux" "User Commands"
+.SH NAME
+newgrp \- log in to a new group
+.SH SYNOPSIS
+.B newgrp
+.RI [ group ]
+.SH DESCRIPTION
+.B newgrp
+changes the group identification of its caller, analogously to
+.BR login (1).
+The same person remains logged in, and the current directory
+is unchanged, but calculations of access permissions to files are performed
+with respect to the new group ID.
+.LP
+If no group is specified, the GID is changed to the login GID.
+.LP
+.SH FILES
+.I /etc/group
+.br
+.I /etc/passwd
+
+.SH "SEE ALSO"
+.BR login (1),
+.BR group (5)
+
+.SH AUTHOR
+Originally by Michael Haardt. Currently maintained by
+Peter Orbaek (poe@daimi.aau.dk).
+
+.SH AVAILABILITY
+The newgrp command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/login-utils/newgrp.c b/login-utils/newgrp.c
new file mode 100644
index 0000000..4538831
--- /dev/null
+++ b/login-utils/newgrp.c
@@ -0,0 +1,240 @@
+/* setgrp.c - by Michael Haardt. Set the gid if possible
+ * Added a bit more error recovery/reporting - poe
+ * Vesa Roukonen added code for asking password */
+
+/* 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#endif
+
+#ifdef HAVE_GETSGNAM
+# include <gshadow.h>
+#endif
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "xalloc.h"
+
+static char *xgetpass(FILE *input, const char *prompt)
+{
+ char *pass = NULL;
+ struct termios saved, no_echo;
+ const int fd = fileno(input);
+ size_t dummy = 0;
+ ssize_t len;
+
+ fputs(prompt, stdout);
+ if (isatty(fd)) {
+ /* disable echo */
+ tcgetattr(fd, &saved);
+ no_echo = saved;
+ no_echo.c_lflag &= ~ECHO;
+ no_echo.c_lflag |= ECHONL;
+ if (tcsetattr(fd, TCSANOW, &no_echo))
+ err(EXIT_FAILURE, _("could not set terminal attributes"));
+ }
+ len = getline(&pass, &dummy, input);
+ if (isatty(fd))
+ /* restore terminal */
+ if (tcsetattr(fd, TCSANOW, &saved))
+ err(EXIT_FAILURE, _("could not set terminal attributes"));
+ if (len < 0)
+ err(EXIT_FAILURE, _("getline() failed"));
+ if (0 < len && *(pass + len - 1) == '\n')
+ *(pass + len - 1) = '\0';
+ return pass;
+}
+
+#ifndef HAVE_EXPLICIT_BZERO
+/* Ensure memory is set to value c without compiler optimization getting
+ * into way that could happen with memset(3). */
+static int xmemset_s(void *v, size_t sz, const int c)
+{
+ volatile unsigned char *p = v;
+
+ if (v == NULL)
+ return EINVAL;
+ while (sz--)
+ *p++ = c;
+ return 0;
+}
+#endif
+
+/* try to read password from gshadow */
+static char *get_gshadow_pwd(const char *groupname)
+{
+#ifdef HAVE_GETSGNAM
+ struct sgrp *sgrp;
+
+ sgrp = getsgnam(groupname);
+ return sgrp ? xstrdup(sgrp->sg_passwd) : NULL;
+#else
+ char buf[BUFSIZ];
+ char *pwd = NULL;
+ FILE *f;
+
+ if (groupname == NULL || *groupname == '\0')
+ return NULL;
+
+ f = fopen(_PATH_GSHADOW, "r");
+ if (!f)
+ return NULL;
+
+ while (fgets(buf, sizeof buf, f)) {
+ char *cp = strchr(buf, ':');
+ if (!cp)
+ /* any junk in gshadow? */
+ continue;
+ *cp = '\0';
+ if (strcmp(buf, groupname) == 0) {
+ if (cp - buf >= BUFSIZ)
+ /* only group name on line */
+ break;
+ pwd = cp + 1;
+ if ((cp = strchr(pwd, ':')) && pwd == cp + 1)
+ /* empty password */
+ pwd = NULL;
+ else if (cp)
+ *cp = '\0';
+ break;
+ }
+ }
+ fclose(f);
+ return pwd ? xstrdup(pwd) : NULL;
+#endif /* HAVE_GETSGNAM */
+}
+
+static int allow_setgid(const struct passwd *pe, const struct group *ge)
+{
+ char **look;
+ int notfound = 1;
+ char *pwd, *xpwd;
+
+ if (getuid() == 0)
+ /* root may do anything */
+ return TRUE;
+ if (ge->gr_gid == pe->pw_gid)
+ /* You can switch back to your default group */
+ return TRUE;
+
+ look = ge->gr_mem;
+ while (*look && (notfound = strcmp(*look++, pe->pw_name))) ;
+
+ if (!notfound)
+ /* member of group => OK */
+ return TRUE;
+
+ /* Ask for password. Often there is no password in /etc/group, so
+ * contrary to login et al. we let an empty password mean the same
+ * as in /etc/passwd */
+
+ /* check /etc/gshadow */
+ if (!(pwd = get_gshadow_pwd(ge->gr_name)))
+ pwd = ge->gr_passwd;
+
+ if (pwd && *pwd && (xpwd = xgetpass(stdin, _("Password: ")))) {
+ char *cbuf = crypt(xpwd, pwd);
+
+#ifdef HAVE_EXPLICIT_BZERO
+ explicit_bzero(xpwd, strlen(xpwd));
+#else
+ xmemset_s(xpwd, strlen(xpwd), 0);
+#endif
+ free(xpwd);
+ if (!cbuf)
+ warn(_("crypt failed"));
+ else if (strcmp(pwd, cbuf) == 0)
+ return TRUE;
+ }
+
+ /* default to denial */
+ return FALSE;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s <group>\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Log in to a new group.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("newgrp(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct passwd *pw_entry;
+ struct group *gr_entry;
+ char *shell;
+ int ch;
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (ch) {
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (!(pw_entry = getpwuid(getuid())))
+ err(EXIT_FAILURE, _("who are you?"));
+
+ if (argc < 2) {
+ if (setgid(pw_entry->pw_gid) < 0)
+ err(EXIT_FAILURE, _("setgid failed"));
+ } else {
+ errno = 0;
+ if (!(gr_entry = getgrnam(argv[1]))) {
+ if (errno)
+ err(EXIT_FAILURE, _("no such group"));
+ else
+ errx(EXIT_FAILURE, _("no such group"));
+ }
+ if (!allow_setgid(pw_entry, gr_entry))
+ errx(EXIT_FAILURE, _("permission denied"));
+ if (setgid(gr_entry->gr_gid) < 0)
+ err(EXIT_FAILURE, _("setgid failed"));
+ }
+
+ if (setuid(getuid()) < 0)
+ err(EXIT_FAILURE, _("setuid failed"));
+
+ fflush(NULL);
+ shell = (pw_entry->pw_shell && *pw_entry->pw_shell ?
+ pw_entry->pw_shell : _PATH_BSHELL);
+ execl(shell, shell, (char *)0);
+ errexec(shell);
+}
diff --git a/login-utils/nologin.8 b/login-utils/nologin.8
new file mode 100644
index 0000000..ee59484
--- /dev/null
+++ b/login-utils/nologin.8
@@ -0,0 +1,52 @@
+.TH NOLOGIN 8 "September 2013" "util-linux" "System Administration"
+.SH NAME
+nologin \- politely refuse a login
+.SH SYNOPSIS
+.B nologin
+.RB [ \-V ]
+.RB [ \-h ]
+.SH DESCRIPTION
+.B nologin
+displays a message that an account is not available and exits non-zero. It is
+intended as a replacement shell field to deny login access to an account.
+.PP
+If the file /etc/nologin.txt exists, nologin displays its contents to the
+user instead of the default message.
+.PP
+The exit code returned by
+.B nologin
+is always 1.
+.PP
+.SH OPTIONS
+.IP "\fB\-h, \-\-help\fP"
+Display help text and exit.
+.IP "\fB-V, \-\-version"
+Display version information and exit.
+.SH NOTES
+.B nologin
+is a per-account way to disable login (usually used for system accounts like http or ftp).
+.BR nologin (8)
+uses /etc/nologin.txt as an optional source for a non-default message, the login
+access is always refused independently of the file.
+.PP
+.BR pam_nologin (8)
+PAM module usually prevents all non-root users from logging into the system.
+.BR pam_nologin (8)
+functionality is controlled by /var/run/nologin or the /etc/nologin file.
+.SH AUTHORS
+.UR kzak@redhat.com
+Karel Zak
+.UE
+.SH SEE ALSO
+.BR login (1),
+.BR passwd (5),
+.BR pam_nologin (8)
+.SH HISTORY
+The
+.B nologin
+command appeared in 4.4BSD.
+.SH AVAILABILITY
+The nologin command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/nologin.c b/login-utils/nologin.c
new file mode 100644
index 0000000..b0b6a72
--- /dev/null
+++ b/login-utils/nologin.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "nls.h"
+#include "pathnames.h"
+
+/*
+ * Always return EXIT_FAILURE (1), don't try to be smart!
+ */
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Politely refuse a login.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+
+ printf(USAGE_MAN_TAIL("nologin(8)"));
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ int c, fd = -1;
+ struct stat st;
+ static const struct option longopts[] = {
+ { "help", 0, NULL, 'h' },
+ { "version", 0, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ while ((c = getopt_long(argc, argv, "hV", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ usage();
+ break;
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_FAILURE;
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ fd = open(_PATH_NOLOGIN_TXT, O_RDONLY);
+ if (fd < 0)
+ goto dflt;
+
+ c = fstat(fd, &st);
+ if (c < 0 || !S_ISREG(st.st_mode))
+ goto dflt;
+ else {
+ char buf[BUFSIZ];
+ ssize_t rd;
+
+ while ((rd = read(fd, buf, sizeof(buf))) > 0)
+ ignore_result( write(STDOUT_FILENO, buf, rd) );
+
+ close(fd);
+ return EXIT_FAILURE;
+ }
+
+dflt:
+ if (fd >= 0)
+ close(fd);
+ fprintf(stdout, _("This account is currently not available.\n"));
+ return EXIT_FAILURE;
+}
diff --git a/login-utils/runuser.1 b/login-utils/runuser.1
new file mode 100644
index 0000000..bf0d024
--- /dev/null
+++ b/login-utils/runuser.1
@@ -0,0 +1,254 @@
+.TH RUNUSER 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+runuser \- run a command with substitute user and group ID
+.SH SYNOPSIS
+.BR runuser " [options] " \-u
+.I user
+.RI "[[\-\-] " command " ["argument "...]]"
+.LP
+.BR runuser " [options] [" \- ]
+.RI [ user " [" argument "...]]"
+.SH DESCRIPTION
+.B runuser
+allows to run commands with a substitute user and group ID.
+If the option \fB\-u\fR is not given, it falls back to
+.BR su -compatible
+semantics and a shell is executed.
+The difference between the commands
+.B runuser
+and
+.B su
+is that
+.B runuser
+does not ask for a password (because it may be executed by the root user only) and
+it uses a different PAM configuration.
+The command
+.B runuser
+does not have to be installed with set-user-ID permissions.
+.PP
+If the PAM session is not required then recommended solution is to use
+.BR setpriv (1)
+command.
+.PP
+When called without arguments,
+.B runuser
+defaults to running an interactive shell as
+.IR root .
+.PP
+For backward compatibility,
+.B runuser
+defaults to not change the current directory and to only set the
+environment variables
+.B HOME
+and
+.B SHELL
+(plus
+.B USER
+and
+.B LOGNAME
+if the target
+.I user
+is not root).
+This version of
+.B runuser
+uses PAM for session management.
+.SH OPTIONS
+.TP
+.BR \-c , " \-\-command" = \fIcommand
+Pass
+.I command
+to the shell with the
+.B \-c
+option.
+.TP
+.BR \-f , " \-\-fast"
+Pass
+.B \-f
+to the shell, which may or may not be useful depending on the
+shell.
+.TP
+.BR \-g , " \-\-group" = \fIgroup
+The primary group to be used. This option is allowed for the root user only.
+.TP
+.BR \-G , " \-\-supp\-group" = \fIgroup
+Specify a supplemental group. This option is available to the root user only. The first specified
+supplementary group is also used as a primary group if the option \fB\-\-group\fR is unspecified.
+.TP
+.BR \- , " \-l" , " \-\-login"
+Start the shell as a login shell with an environment similar to a real
+login:
+.RS 10
+.TP
+o
+clears all the environment variables except for
+.B TERM
+and variables specified by \fB\-\-whitelist\-environment\fR
+.TP
+o
+initializes the environment variables
+.BR HOME ,
+.BR SHELL ,
+.BR USER ,
+.BR LOGNAME ,
+.B PATH
+.TP
+o
+changes to the target user's home directory
+.TP
+o
+sets argv[0] of the shell to
+.RB ' \- '
+in order to make the shell a login shell
+.RE
+.TP
+.BR \-m , " \-p" , " \-\-preserve\-environment"
+Preserve the entire environment, i.e. it does not set
+.BR HOME ,
+.BR SHELL ,
+.B USER
+nor
+.BR LOGNAME .
+The option is ignored if the option \fB\-\-login\fR is specified.
+.TP
+.BR \-s , " \-\-shell" = \fIshell
+Run the specified \fIshell\fR instead of the default. The shell to run is
+selected according to the following rules, in order:
+.RS 10
+.TP
+o
+the shell specified with
+.B \-\-shell
+.TP
+o
+the shell specified in the environment variable
+.B SHELL
+if the
+.B \-\-preserve\-environment
+option is used
+.TP
+o
+the shell listed in the passwd entry of the target user
+.TP
+o
+/bin/sh
+.RE
+.IP
+If the target user has a restricted shell (i.e. not listed in
+/etc/shells) the
+.B \-\-shell
+option and the
+.B SHELL
+environment variables are ignored unless the calling user is root.
+.TP
+.BI \-\-session\-command= command
+Same as
+.B \-c ,
+but do not create a new session. (Discouraged.)
+.TP
+.BR \-w , " \-\-whitelist\-environment" = \fIlist
+Don't reset environment variables specified in comma separated \fIlist\fR when clears
+environment for \fB\-\-login\fR. The whitelist is ignored for the environment variables
+.BR HOME ,
+.BR SHELL ,
+.BR USER ,
+.BR LOGNAME ", and"
+.BR PATH "."
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH CONFIG FILES
+.B runuser
+reads the
+.I /etc/default/runuser
+and
+.I /etc/login.defs
+configuration files. The following configuration items are relevant
+for
+.BR runuser :
+.PP
+.B ENV_PATH
+(string)
+.RS 4
+Defines the PATH environment variable for a regular user. The
+default value is
+.IR /usr/local/bin:\:/bin:\:/usr/bin .
+.RE
+.PP
+.B ENV_ROOTPATH
+(string)
+.br
+.B ENV_SUPATH
+(string)
+.RS 4
+Defines the PATH environment variable for root. The default value is
+.IR /usr/local/sbin:\:/usr/local/bin:\:/sbin:\:/bin:\:/usr/sbin:\:/usr/bin .
+.RE
+.PP
+.B ALWAYS_SET_PATH
+(boolean)
+.RS 4
+If set to
+.I yes
+and \-\-login and \-\-preserve\-environment were not specified
+.B runuser
+initializes
+.BR PATH .
+.RE
+.sp
+The environment variable PATH may be different on systems where /bin and /sbin
+are merged into /usr.
+.SH EXIT STATUS
+.B runuser
+normally returns the exit status of the command it executed. If the
+command was killed by a signal,
+.B runuser
+returns the number of the signal plus 128.
+.PP
+Exit status generated by
+.B runuser
+itself:
+.RS 10
+.TP
+1
+Generic error before executing the requested command
+.TP
+126
+The requested command could not be executed
+.TP
+127
+The requested command was not found
+.RE
+.SH FILES
+.PD 0
+.TP 17
+/etc/pam.d/runuser
+default PAM configuration file
+.TP
+/etc/pam.d/runuser-l
+PAM configuration file if \-\-login is specified
+.TP
+/etc/default/runuser
+runuser specific logindef config file
+.TP
+/etc/login.defs
+global logindef config file
+.PD 1
+.SH "SEE ALSO"
+.BR setpriv (1),
+.BR su (1),
+.BR login.defs (5),
+.BR shells (5),
+.BR pam (8)
+.SH HISTORY
+This \fB runuser\fR command was
+derived from coreutils' \fBsu\fR, which was based on an implementation by
+David MacKenzie, and the Fedora \fBrunuser\fR command by Dan Walsh.
+.SH AVAILABILITY
+The runuser command is part of the util-linux package and is
+available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/runuser.c b/login-utils/runuser.c
new file mode 100644
index 0000000..d4f37f4
--- /dev/null
+++ b/login-utils/runuser.c
@@ -0,0 +1,7 @@
+
+#include "su-common.h"
+
+int main(int argc, char **argv)
+{
+ return su_main(argc, argv, RUNUSER_MODE);
+}
diff --git a/login-utils/selinux_utils.c b/login-utils/selinux_utils.c
new file mode 100644
index 0000000..dfd696f
--- /dev/null
+++ b/login-utils/selinux_utils.c
@@ -0,0 +1,29 @@
+#include <selinux/context.h>
+#include <selinux/selinux.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "selinux_utils.h"
+
+access_vector_t get_access_vector(const char *tclass, const char *op)
+{
+ security_class_t tc = string_to_security_class(tclass);
+
+ return tc ? string_to_av_perm(tc, op) : 0;
+}
+
+int setupDefaultContext(char *orig_file)
+{
+ if (is_selinux_enabled() > 0) {
+ security_context_t scontext;
+ if (getfilecon(orig_file, &scontext) < 0)
+ return 1;
+ if (setfscreatecon(scontext) < 0) {
+ freecon(scontext);
+ return 1;
+ }
+ freecon(scontext);
+ }
+ return 0;
+}
diff --git a/login-utils/selinux_utils.h b/login-utils/selinux_utils.h
new file mode 100644
index 0000000..cf0ed66
--- /dev/null
+++ b/login-utils/selinux_utils.h
@@ -0,0 +1,7 @@
+#ifndef UTIL_LINUX_SELINUX_UTILS_H
+#define UTIL_LINUX_SELINUX_UTILS_H
+
+extern access_vector_t get_access_vector(const char *tclass, const char *op);
+extern int setupDefaultContext(char *orig_file);
+
+#endif
diff --git a/login-utils/setpwnam.c b/login-utils/setpwnam.c
new file mode 100644
index 0000000..0616c79
--- /dev/null
+++ b/login-utils/setpwnam.c
@@ -0,0 +1,218 @@
+/*
+ * setpwnam.c -- edit an entry in a password database.
+ *
+ * (c) 1994 Salvatore Valente <svalente@mit.edu>
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Edited 11/10/96 (DD/MM/YY ;-) by Nicolai Langfeldt (janl@math.uio.no)
+ * to read /etc/passwd directly so that passwd, chsh and chfn can work on
+ * machines that run NIS (previously YP). Changes will not be made to
+ * usernames starting with +.
+ *
+ * This file is distributed with no warranty.
+ *
+ * Usage:
+ * 1) get a struct passwd * from getpwnam().
+ * You should assume a struct passwd has an infinite number of fields, so
+ * you should not try to create one from scratch.
+ * 2) edit the fields you want to edit.
+ * 3) call setpwnam() with the edited struct passwd.
+ *
+ * A _normal user_ program should never directly manipulate etc/passwd but
+ * /use getpwnam() and (family, as well as) setpwnam().
+ *
+ * But, setpwnam was made to _edit_ the password file. For use by chfn,
+ * chsh and passwd. _I_ _HAVE_ to read and write /etc/passwd directly. Let
+ * those who say nay be forever silent and think about how getpwnam (and
+ * family) works on a machine running YP.
+ *
+ * Added checks for failure of malloc() and removed error reporting to
+ * stderr, this is a library function and should not print on the screen,
+ * but return appropriate error codes.
+ * 27-Jan-97 - poe@daimi.aau.dk
+ *
+ * Thanks to "two guys named Ian".
+ *
+ * $Author: poer $
+ * $Revision: 1.13 $
+ * $Date: 1997/06/23 08:26:29 $
+ */
+
+#undef DEBUG
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "closestream.h"
+#include "setpwnam.h"
+
+static void pw_init(void);
+
+/*
+ * setpwnam () --
+ * takes a struct passwd in which every field is filled in and valid.
+ * If the given username exists in the passwd file, the entry is
+ * replaced with the given entry.
+ */
+int setpwnam(struct passwd *pwd, const char *prefix)
+{
+ FILE *fp = NULL, *pwf = NULL;
+ int save_errno;
+ int found;
+ int namelen;
+ int buflen = 256;
+ int contlen, rc;
+ char *linebuf = NULL;
+ char *tmpname = NULL;
+
+ pw_init();
+
+ if ((fp = xfmkstemp(&tmpname, "/etc", prefix)) == NULL)
+ return -1;
+
+ /* ptmp should be owned by root.root or root.wheel */
+ if (fchown(fileno(fp), (uid_t) 0, (gid_t) 0) < 0)
+ goto fail;
+
+ /* acquire exclusive lock */
+ if (lckpwdf() < 0)
+ goto fail;
+ pwf = fopen(PASSWD_FILE, "r");
+ if (!pwf)
+ goto fail;
+
+ namelen = strlen(pwd->pw_name);
+
+ linebuf = malloc(buflen);
+ if (!linebuf)
+ goto fail;
+
+ /* parse the passwd file */
+ found = false;
+
+ /* Do you wonder why I don't use getpwent? Read comments at top of
+ * file */
+ while (fgets(linebuf, buflen, pwf) != NULL) {
+ contlen = strlen(linebuf);
+ while (linebuf[contlen - 1] != '\n' && !feof(pwf)) {
+ char *tmp;
+ /* Extend input buffer if it failed getting the whole line,
+ * so now we double the buffer size */
+ buflen *= 2;
+ tmp = realloc(linebuf, buflen);
+ if (tmp == NULL)
+ goto fail;
+ linebuf = tmp;
+ /* And fill the rest of the buffer */
+ if (fgets(&linebuf[contlen], buflen / 2, pwf) == NULL)
+ break;
+ contlen = strlen(linebuf);
+ /* That was a lot of work for nothing. Gimme perl! */
+ }
+
+ /* Is this the username we were sent to change? */
+ if (!found && linebuf[namelen] == ':' &&
+ !strncmp(linebuf, pwd->pw_name, namelen)) {
+ /* Yes! So go forth in the name of the Lord and
+ * change it! */
+ if (putpwent(pwd, fp) < 0)
+ goto fail;
+ found = true;
+ continue;
+ }
+ /* Nothing in particular happened, copy input to output */
+ fputs(linebuf, fp);
+ }
+
+ /* xfmkstemp is too restrictive by default for passwd file */
+ if (fchmod(fileno(fp), 0644) < 0)
+ goto fail;
+ rc = close_stream(fp);
+ fp = NULL;
+ if (rc != 0)
+ goto fail;
+
+ fclose(pwf); /* I don't think I want to know if this failed */
+ pwf = NULL;
+
+ if (!found) {
+ errno = ENOENT; /* give me something better */
+ goto fail;
+ }
+
+ /* we don't care if we can't remove the backup file */
+ unlink(PASSWD_FILE ".OLD");
+ /* we don't care if we can't create the backup file */
+ ignore_result(link(PASSWD_FILE, PASSWD_FILE ".OLD"));
+ /* we DO care if we can't rename to the passwd file */
+ if (rename(tmpname, PASSWD_FILE) < 0)
+ goto fail;
+ /* finally: success */
+ ulckpwdf();
+ free(linebuf);
+ return 0;
+
+ fail:
+ save_errno = errno;
+ ulckpwdf();
+ if (fp != NULL)
+ fclose(fp);
+ if (tmpname != NULL)
+ unlink(tmpname);
+ free(tmpname);
+ if (pwf != NULL)
+ fclose(pwf);
+ free(linebuf);
+ errno = save_errno;
+ return -1;
+}
+
+/* Set up the limits so that we're not foiled */
+static void pw_init(void)
+{
+ struct rlimit rlim;
+
+ /* Unlimited resource limits. */
+ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
+ setrlimit(RLIMIT_CPU, &rlim);
+ setrlimit(RLIMIT_FSIZE, &rlim);
+ setrlimit(RLIMIT_STACK, &rlim);
+ setrlimit(RLIMIT_DATA, &rlim);
+ setrlimit(RLIMIT_RSS, &rlim);
+
+#ifndef DEBUG
+ /* Don't drop core (not really necessary, but GP's). */
+ rlim.rlim_cur = rlim.rlim_max = 0;
+ setrlimit(RLIMIT_CORE, &rlim);
+#endif
+
+ /* Turn off signals. */
+ signal(SIGALRM, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+ signal(SIGTTOU, SIG_IGN);
+
+ /* Create with exact permissions. */
+ umask(0);
+}
diff --git a/login-utils/setpwnam.h b/login-utils/setpwnam.h
new file mode 100644
index 0000000..9578592
--- /dev/null
+++ b/login-utils/setpwnam.h
@@ -0,0 +1,33 @@
+/*
+ * setpwnam.h --
+ * define several paths
+ *
+ * (c) 1994 Martin Schulze <joey@infodrom.north.de>
+ * This file is based on setpwnam.c which is
+ * (c) 1994 Salvatore Valente <svalente@mit.edu>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ */
+#ifndef UTIL_LINUX_SETPWNAM_H
+#define UTIL_LINUX_SETPWNAM_H
+
+#include "pathnames.h"
+
+#ifndef DEBUG
+# define PASSWD_FILE _PATH_PASSWD
+# define GROUP_FILE _PATH_GROUP
+# define SHADOW_FILE _PATH_SHADOW_PASSWD
+# define SGROUP_FILE _PATH_GSHADOW
+#else
+# define PASSWD_FILE "/tmp/passwd"
+# define GROUP_FILE "/tmp/group"
+# define SHADOW_FILE "/tmp/shadow"
+# define SGROUP_FILE "/tmp/gshadow"
+#endif
+
+extern int setpwnam (struct passwd *pwd, const char *prefix);
+
+#endif /* UTIL_LINUX_SETPWNAM_H */
diff --git a/login-utils/su-common.c b/login-utils/su-common.c
new file mode 100644
index 0000000..e0604e2
--- /dev/null
+++ b/login-utils/su-common.c
@@ -0,0 +1,1520 @@
+/*
+ * su(1) for Linux. Run a shell with substitute user and group IDs.
+ *
+ * Copyright (C) 1992-2006 Free Software Foundation, Inc.
+ * Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg
+ * Copyright (C) 2016-2017 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, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *
+ * Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>.
+ */
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <security/pam_appl.h>
+#ifdef HAVE_SECURITY_PAM_MISC_H
+# include <security/pam_misc.h>
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+# include <security/openpam.h>
+#endif
+#include <signal.h>
+#include <sys/wait.h>
+#include <syslog.h>
+#include <utmpx.h>
+
+#if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) && defined(HAVE_SYS_SIGNALFD_H)
+# include <pty.h>
+# include <poll.h>
+# include <sys/signalfd.h>
+# include "all-io.h"
+# define USE_PTY
+#endif
+
+#include "err.h"
+
+#include <stdbool.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "env.h"
+#include "closestream.h"
+#include "strv.h"
+#include "strutils.h"
+#include "ttyutils.h"
+#include "pwdutils.h"
+#include "optutils.h"
+
+#include "logindefs.h"
+#include "su-common.h"
+
+#include "debug.h"
+
+UL_DEBUG_DEFINE_MASK(su);
+UL_DEBUG_DEFINE_MASKNAMES(su) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define SU_DEBUG_INIT (1 << 1)
+#define SU_DEBUG_PAM (1 << 2)
+#define SU_DEBUG_PARENT (1 << 3)
+#define SU_DEBUG_TTY (1 << 4)
+#define SU_DEBUG_LOG (1 << 5)
+#define SU_DEBUG_MISC (1 << 6)
+#define SU_DEBUG_SIG (1 << 7)
+#define SU_DEBUG_PTY (1 << 8)
+#define SU_DEBUG_ALL 0xFFFF
+
+#define DBG(m, x) __UL_DBG(su, SU_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(su, SU_DEBUG_, m, x)
+
+
+/* name of the pam configuration files. separate configs for su and su - */
+#define PAM_SRVNAME_SU "su"
+#define PAM_SRVNAME_SU_L "su-l"
+
+#define PAM_SRVNAME_RUNUSER "runuser"
+#define PAM_SRVNAME_RUNUSER_L "runuser-l"
+
+#define _PATH_LOGINDEFS_SU "/etc/default/su"
+#define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
+
+#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
+
+/* The shell to run if none is given in the user's passwd entry. */
+#define DEFAULT_SHELL "/bin/sh"
+
+/* The user to become if none is specified. */
+#define DEFAULT_USER "root"
+
+#ifndef HAVE_ENVIRON_DECL
+extern char **environ;
+#endif
+
+enum {
+ SIGTERM_IDX = 0,
+ SIGINT_IDX,
+ SIGQUIT_IDX,
+
+ SIGNALS_IDX_COUNT
+};
+
+/*
+ * su/runuser control struct
+ */
+struct su_context {
+ pam_handle_t *pamh; /* PAM handler */
+ struct pam_conv conv; /* PAM conversation */
+
+ struct passwd *pwd; /* new user info */
+ char *pwdbuf; /* pwd strings */
+
+ const char *tty_name; /* tty_path without /dev prefix */
+ const char *tty_number; /* end of the tty_path */
+
+ char *new_user; /* wanted user */
+ char *old_user; /* original user */
+
+ pid_t child; /* fork() baby */
+ int childstatus; /* wait() status */
+
+ char **env_whitelist_names; /* environment whitelist */
+ char **env_whitelist_vals;
+
+ struct sigaction oldact[SIGNALS_IDX_COUNT]; /* original sigactions indexed by SIG*_IDX */
+
+#ifdef USE_PTY
+ struct termios stdin_attrs; /* stdin and slave terminal runtime attributes */
+ int pty_master;
+ int pty_slave;
+ int pty_sigfd; /* signalfd() */
+ int poll_timeout;
+ struct winsize win; /* terminal window size */
+ sigset_t oldsig; /* original signal mask */
+#endif
+ unsigned int runuser :1, /* flase=su, true=runuser */
+ runuser_uopt :1, /* runuser -u specified */
+ isterm :1, /* is stdin terminal? */
+ fast_startup :1, /* pass the `-f' option to the subshell. */
+ simulate_login :1, /* simulate a login instead of just starting a shell. */
+ change_environment :1, /* change some environment vars to indicate the user su'd to.*/
+ same_session :1, /* don't call setsid() with a command. */
+ suppress_pam_info:1, /* don't print PAM info messages (Last login, etc.). */
+ pam_has_session :1, /* PAM session opened */
+ pam_has_cred :1, /* PAM cred established */
+ pty :1, /* create pseudo-terminal */
+ restricted :1; /* false for root user */
+};
+
+
+static sig_atomic_t volatile caught_signal = false;
+
+/* Signal handler for parent process. */
+static void
+su_catch_sig(int sig)
+{
+ caught_signal = sig;
+}
+
+static void su_init_debug(void)
+{
+ __UL_INIT_DEBUG_FROM_ENV(su, SU_DEBUG_, 0, SU_DEBUG);
+}
+
+static void init_tty(struct su_context *su)
+{
+ su->isterm = isatty(STDIN_FILENO) ? 1 : 0;
+ DBG(TTY, ul_debug("initialize [is-term=%s]", su->isterm ? "true" : "false"));
+ if (su->isterm)
+ get_terminal_name(NULL, &su->tty_name, &su->tty_number);
+}
+
+/*
+ * Note, this function has to be possible call more than once. If the child is
+ * already dead than it returns saved result from the previous call.
+ */
+static int wait_for_child(struct su_context *su)
+{
+ pid_t pid = (pid_t) -1;;
+ int status = 0;
+
+ if (su->child == (pid_t) -1)
+ return su->childstatus;
+
+ if (su->child != (pid_t) -1) {
+ /*
+ * The "su" parent process spends all time here in waitpid(),
+ * but "su --pty" uses pty_proxy_master() and waitpid() is only
+ * called to pick up child status or to react to SIGSTOP.
+ */
+ DBG(SIG, ul_debug("waiting for child [%d]...", su->child));
+ for (;;) {
+ pid = waitpid(su->child, &status, WUNTRACED);
+
+ if (pid != (pid_t) - 1 && WIFSTOPPED(status)) {
+ DBG(SIG, ul_debug(" child got SIGSTOP -- stop all session"));
+ kill(getpid(), SIGSTOP);
+ /* once we get here, we must have resumed */
+ kill(pid, SIGCONT);
+ DBG(SIG, ul_debug(" session resumed -- continue"));
+#ifdef USE_PTY
+ /* Let's go back to pty_proxy_master() */
+ if (su->pty_sigfd != -1) {
+ DBG(SIG, ul_debug(" leaving on child SIGSTOP"));
+ return 0;
+ }
+#endif
+ } else
+ break;
+ }
+ }
+ if (pid != (pid_t) -1) {
+ if (WIFSIGNALED(status)) {
+ fprintf(stderr, "%s%s\n",
+ strsignal(WTERMSIG(status)),
+ WCOREDUMP(status) ? _(" (core dumped)")
+ : "");
+ status = WTERMSIG(status) + 128;
+ } else
+ status = WEXITSTATUS(status);
+
+ DBG(SIG, ul_debug("child %d is dead", su->child));
+ su->child = (pid_t) -1; /* Don't use the PID anymore! */
+ su->childstatus = status;
+ } else if (caught_signal)
+ status = caught_signal + 128;
+ else
+ status = 1;
+
+ DBG(SIG, ul_debug("child status=%d", status));
+ return status;
+}
+
+
+#ifdef USE_PTY
+static void pty_init_slave(struct su_context *su)
+{
+ DBG(PTY, ul_debug("initialize slave"));
+
+ ioctl(su->pty_slave, TIOCSCTTY, 1);
+ close(su->pty_master);
+
+ dup2(su->pty_slave, STDIN_FILENO);
+ dup2(su->pty_slave, STDOUT_FILENO);
+ dup2(su->pty_slave, STDERR_FILENO);
+
+ close(su->pty_slave);
+ close(su->pty_sigfd);
+
+ su->pty_slave = -1;
+ su->pty_master = -1;
+ su->pty_sigfd = -1;
+
+ sigprocmask(SIG_SETMASK, &su->oldsig, NULL);
+
+ DBG(PTY, ul_debug("... initialize slave done"));
+}
+
+static void pty_create(struct su_context *su)
+{
+ struct termios slave_attrs;
+ int rc;
+
+ if (su->isterm) {
+ DBG(PTY, ul_debug("create for terminal"));
+ struct winsize win;
+
+ /* original setting of the current terminal */
+ if (tcgetattr(STDIN_FILENO, &su->stdin_attrs) != 0)
+ err(EXIT_FAILURE, _("failed to get terminal attributes"));
+
+ /* reuse the current terminal setting for slave */
+ slave_attrs = su->stdin_attrs;
+ cfmakeraw(&slave_attrs);
+
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
+
+ /* create master+slave */
+ rc = openpty(&su->pty_master, &su->pty_slave, NULL, &slave_attrs, &win);
+
+ } else {
+ DBG(PTY, ul_debug("create for non-terminal"));
+ rc = openpty(&su->pty_master, &su->pty_slave, NULL, NULL, NULL);
+
+ /* set slave attributes */
+ if (!rc) {
+ tcgetattr(su->pty_slave, &slave_attrs);
+ cfmakeraw(&slave_attrs);
+ tcsetattr(su->pty_slave, TCSANOW, &slave_attrs);
+ }
+ }
+
+ if (rc < 0)
+ err(EXIT_FAILURE, _("failed to create pseudo-terminal"));
+
+ DBG(PTY, ul_debug("pty setup done [master=%d, slave=%d]", su->pty_master, su->pty_slave));
+}
+
+static void pty_cleanup(struct su_context *su)
+{
+ if (su->pty_master == -1)
+ return;
+
+ DBG(PTY, ul_debug("cleanup"));
+ if (su->isterm)
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &su->stdin_attrs);
+}
+
+static int write_output(char *obuf, ssize_t bytes)
+{
+ DBG(PTY, ul_debug(" writing output"));
+
+ if (write_all(STDOUT_FILENO, obuf, bytes)) {
+ DBG(PTY, ul_debug(" writing output *failed*"));
+ warn(_("write failed"));
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int write_to_child(struct su_context *su,
+ char *buf, size_t bufsz)
+{
+ return write_all(su->pty_master, buf, bufsz);
+}
+
+/*
+ * The su(1) is usually faster than shell, so it's a good idea to wait until
+ * the previous message has been already read by shell from slave before we
+ * write to master. This is necessary especially for EOF situation when we can
+ * send EOF to master before shell is fully initialized, to workaround this
+ * problem we wait until slave is empty. For example:
+ *
+ * echo "date" | su
+ *
+ * Unfortunately, the child (usually shell) can ignore stdin at all, so we
+ * don't wait forever to avoid dead locks...
+ *
+ * Note that su --pty is primarily designed for interactive sessions as it
+ * maintains master+slave tty stuff within the session. Use pipe to write to
+ * su(1) and assume non-interactive (tee-like) behavior is NOT well
+ * supported.
+ */
+static void write_eof_to_child(struct su_context *su)
+{
+ unsigned int tries = 0;
+ struct pollfd fds[] = {
+ { .fd = su->pty_slave, .events = POLLIN }
+ };
+ char c = DEF_EOF;
+
+ DBG(PTY, ul_debug(" waiting for empty slave"));
+ while (poll(fds, 1, 10) == 1 && tries < 8) {
+ DBG(PTY, ul_debug(" slave is not empty"));
+ xusleep(250000);
+ tries++;
+ }
+ if (tries < 8)
+ DBG(PTY, ul_debug(" slave is empty now"));
+
+ DBG(PTY, ul_debug(" sending EOF to master"));
+ write_to_child(su, &c, sizeof(char));
+}
+
+static int pty_handle_io(struct su_context *su, int fd, int *eof)
+{
+ char buf[BUFSIZ];
+ ssize_t bytes;
+
+ DBG(PTY, ul_debug("%d FD active", fd));
+ *eof = 0;
+
+ /* read from active FD */
+ bytes = read(fd, buf, sizeof(buf));
+ if (bytes < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+ return -errno;
+ }
+
+ if (bytes == 0) {
+ *eof = 1;
+ return 0;
+ }
+
+ /* from stdin (user) to command */
+ if (fd == STDIN_FILENO) {
+ DBG(PTY, ul_debug(" stdin --> master %zd bytes", bytes));
+
+ if (write_to_child(su, buf, bytes)) {
+ warn(_("write failed"));
+ return -errno;
+ }
+ /* without sync write_output() will write both input &
+ * shell output that looks like double echoing */
+ fdatasync(su->pty_master);
+
+ /* from command (master) to stdout */
+ } else if (fd == su->pty_master) {
+ DBG(PTY, ul_debug(" master --> stdout %zd bytes", bytes));
+ write_output(buf, bytes);
+ }
+
+ return 0;
+}
+
+static int pty_handle_signal(struct su_context *su, int fd)
+{
+ struct signalfd_siginfo info;
+ ssize_t bytes;
+
+ DBG(SIG, ul_debug("signal FD %d active", fd));
+
+ bytes = read(fd, &info, sizeof(info));
+ if (bytes != sizeof(info)) {
+ if (bytes < 0 && (errno == EAGAIN || errno == EINTR))
+ return 0;
+ return -errno;
+ }
+
+ switch (info.ssi_signo) {
+ case SIGCHLD:
+ DBG(SIG, ul_debug(" get signal SIGCHLD"));
+
+ /* The child terminated or stopped. Note that we ignore SIGCONT
+ * here, because stop/cont semantic is handled by wait_for_child() */
+ if (info.ssi_code == CLD_EXITED || info.ssi_status == SIGSTOP)
+ wait_for_child(su);
+ /* The child is dead, force poll() timeout. */
+ if (su->child == (pid_t) -1)
+ su->poll_timeout = 10;
+ return 0;
+ case SIGWINCH:
+ DBG(SIG, ul_debug(" get signal SIGWINCH"));
+ if (su->isterm) {
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
+ ioctl(su->pty_slave, TIOCSWINSZ, (char *)&su->win);
+ }
+ break;
+ case SIGTERM:
+ /* fallthrough */
+ case SIGINT:
+ /* fallthrough */
+ case SIGQUIT:
+ DBG(SIG, ul_debug(" get signal SIG{TERM,INT,QUIT}"));
+ caught_signal = info.ssi_signo;
+ /* Child termination is going to generate SIGCHILD (see above) */
+ kill(su->child, SIGTERM);
+ break;
+ default:
+ abort();
+ }
+
+ return 0;
+}
+
+static void pty_proxy_master(struct su_context *su)
+{
+ sigset_t ourset;
+ int rc = 0, ret, eof = 0;
+ enum {
+ POLLFD_SIGNAL = 0,
+ POLLFD_MASTER,
+ POLLFD_STDIN
+
+ };
+ struct pollfd pfd[] = {
+ [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP },
+ [POLLFD_MASTER] = { .fd = su->pty_master, .events = POLLIN | POLLERR | POLLHUP },
+ [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP }
+ };
+
+ /* for PTY mode we use signalfd
+ *
+ * TODO: script(1) initializes this FD before fork, good or bad idea?
+ */
+ sigfillset(&ourset);
+ if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
+ warn(_("cannot block signals"));
+ caught_signal = true;
+ return;
+ }
+
+ sigemptyset(&ourset);
+ sigaddset(&ourset, SIGCHLD);
+ sigaddset(&ourset, SIGWINCH);
+ sigaddset(&ourset, SIGALRM);
+ sigaddset(&ourset, SIGTERM);
+ sigaddset(&ourset, SIGINT);
+ sigaddset(&ourset, SIGQUIT);
+
+ if ((su->pty_sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) {
+ warn(("cannot create signal file descriptor"));
+ caught_signal = true;
+ return;
+ }
+
+ pfd[POLLFD_SIGNAL].fd = su->pty_sigfd;
+ su->poll_timeout = -1;
+
+ while (!caught_signal) {
+ size_t i;
+ int errsv;
+
+ DBG(PTY, ul_debug("calling poll()"));
+
+ /* wait for input or signal */
+ ret = poll(pfd, ARRAY_SIZE(pfd), su->poll_timeout);
+ errsv = errno;
+ DBG(PTY, ul_debug("poll() rc=%d", ret));
+
+ if (ret < 0) {
+ if (errsv == EAGAIN)
+ continue;
+ warn(_("poll failed"));
+ break;
+ }
+ if (ret == 0) {
+ DBG(PTY, ul_debug("leaving poll() loop [timeout=%d]", su->poll_timeout));
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pfd); i++) {
+ rc = 0;
+
+ if (pfd[i].revents == 0)
+ continue;
+
+ DBG(PTY, ul_debug(" active pfd[%s].fd=%d %s %s %s",
+ i == POLLFD_STDIN ? "stdin" :
+ i == POLLFD_MASTER ? "master" :
+ i == POLLFD_SIGNAL ? "signal" : "???",
+ pfd[i].fd,
+ pfd[i].revents & POLLIN ? "POLLIN" : "",
+ pfd[i].revents & POLLHUP ? "POLLHUP" : "",
+ pfd[i].revents & POLLERR ? "POLLERR" : ""));
+ switch (i) {
+ case POLLFD_STDIN:
+ case POLLFD_MASTER:
+ /* data */
+ if (pfd[i].revents & POLLIN)
+ rc = pty_handle_io(su, pfd[i].fd, &eof);
+ /* EOF maybe detected by two ways:
+ * A) poll() return POLLHUP event after close()
+ * B) read() returns 0 (no data) */
+ if ((pfd[i].revents & POLLHUP) || eof) {
+ DBG(PTY, ul_debug(" ignore FD"));
+ pfd[i].fd = -1;
+ if (i == POLLFD_STDIN) {
+ write_eof_to_child(su);
+ DBG(PTY, ul_debug(" ignore STDIN"));
+ }
+ }
+ continue;
+ case POLLFD_SIGNAL:
+ rc = pty_handle_signal(su, pfd[i].fd);
+ break;
+ }
+ if (rc)
+ break;
+ }
+ }
+
+ close(su->pty_sigfd);
+ su->pty_sigfd = -1;
+ DBG(PTY, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal, rc));
+}
+#endif /* USE_PTY */
+
+
+/* Log the fact that someone has run su to the user given by PW;
+ if SUCCESSFUL is true, they gave the correct password, etc. */
+
+static void log_syslog(struct su_context *su, bool successful)
+{
+ DBG(LOG, ul_debug("syslog logging"));
+
+ openlog(program_invocation_short_name, 0, LOG_AUTH);
+ syslog(LOG_NOTICE, "%s(to %s) %s on %s",
+ successful ? "" :
+ su->runuser ? "FAILED RUNUSER " : "FAILED SU ",
+ su->new_user, su->old_user ? : "",
+ su->tty_name ? : "none");
+ closelog();
+}
+
+/*
+ * Log failed login attempts in _PATH_BTMP if that exists.
+ */
+static void log_btmp(struct su_context *su)
+{
+ struct utmpx ut;
+ struct timeval tv;
+
+ DBG(LOG, ul_debug("btmp logging"));
+
+ memset(&ut, 0, sizeof(ut));
+ str2memcpy(ut.ut_user,
+ su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)",
+ sizeof(ut.ut_user));
+
+ if (su->tty_number)
+ str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id));
+ if (su->tty_name)
+ str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line));
+
+ gettimeofday(&tv, NULL);
+ ut.ut_tv.tv_sec = tv.tv_sec;
+ ut.ut_tv.tv_usec = tv.tv_usec;
+ ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
+ ut.ut_pid = getpid();
+
+ updwtmpx(_PATH_BTMP, &ut);
+}
+
+static int supam_conv( int num_msg,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *data)
+{
+ struct su_context *su = (struct su_context *) data;
+
+ if (su->suppress_pam_info
+ && num_msg == 1
+ && msg && msg[0]->msg_style == PAM_TEXT_INFO)
+ return PAM_SUCCESS;
+
+#ifdef HAVE_SECURITY_PAM_MISC_H
+ return misc_conv(num_msg, msg, resp, data);
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+ return openpam_ttyconv(num_msg, msg, resp, data);
+#endif
+}
+
+static void supam_cleanup(struct su_context *su, int retcode)
+{
+ const int errsv = errno;
+
+ DBG(PAM, ul_debug("cleanup"));
+
+ if (su->pam_has_session)
+ pam_close_session(su->pamh, 0);
+ if (su->pam_has_cred)
+ pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT);
+ pam_end(su->pamh, retcode);
+ errno = errsv;
+}
+
+
+static void supam_export_environment(struct su_context *su)
+{
+ char **env;
+
+ DBG(PAM, ul_debug("init environ[]"));
+
+ /* This is a copy but don't care to free as we exec later anyways. */
+ env = pam_getenvlist(su->pamh);
+
+ while (env && *env) {
+ if (putenv(*env) != 0)
+ err(EXIT_FAILURE, _("failed to modify environment"));
+ env++;
+ }
+}
+
+static void supam_authenticate(struct su_context *su)
+{
+ const char *srvname = NULL;
+ int rc;
+
+ srvname = su->runuser ?
+ (su->simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER) :
+ (su->simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU);
+
+ DBG(PAM, ul_debug("start [name: %s]", srvname));
+
+ rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh);
+ if (is_pam_failure(rc))
+ goto done;
+
+ if (su->tty_name) {
+ rc = pam_set_item(su->pamh, PAM_TTY, su->tty_name);
+ if (is_pam_failure(rc))
+ goto done;
+ }
+ if (su->old_user) {
+ rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user);
+ if (is_pam_failure(rc))
+ goto done;
+ }
+ if (su->runuser) {
+ /*
+ * This is the only difference between runuser(1) and su(1). The command
+ * runuser(1) does not required authentication, because user is root.
+ */
+ if (su->restricted)
+ errx(EXIT_FAILURE, _("may not be used by non-root users"));
+ return;
+ }
+
+ rc = pam_authenticate(su->pamh, 0);
+ if (is_pam_failure(rc))
+ goto done;
+
+ /* Check password expiration and offer option to change it. */
+ rc = pam_acct_mgmt(su->pamh, 0);
+ if (rc == PAM_NEW_AUTHTOK_REQD)
+ rc = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ done:
+ log_syslog(su, !is_pam_failure(rc));
+
+ if (is_pam_failure(rc)) {
+ const char *msg;
+
+ DBG(PAM, ul_debug("authentication failed"));
+ log_btmp(su);
+
+ msg = pam_strerror(su->pamh, rc);
+ pam_end(su->pamh, rc);
+ sleep(getlogindefs_num("FAIL_DELAY", 1));
+ errx(EXIT_FAILURE, "%s", msg ? msg : _("incorrect password"));
+ }
+}
+
+static void supam_open_session(struct su_context *su)
+{
+ int rc;
+
+ DBG(PAM, ul_debug("opening session"));
+
+ rc = pam_open_session(su->pamh, 0);
+ if (is_pam_failure(rc)) {
+ supam_cleanup(su, rc);
+ errx(EXIT_FAILURE, _("cannot open session: %s"),
+ pam_strerror(su->pamh, rc));
+ } else
+ su->pam_has_session = 1;
+}
+
+static void parent_setup_signals(struct su_context *su)
+{
+ sigset_t ourset;
+
+ /*
+ * Signals setup
+ *
+ * 1) block all signals
+ */
+ DBG(SIG, ul_debug("initialize signals"));
+
+ sigfillset(&ourset);
+ if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
+ warn(_("cannot block signals"));
+ caught_signal = true;
+ }
+
+ if (!caught_signal) {
+ struct sigaction action;
+ action.sa_handler = su_catch_sig;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+
+ sigemptyset(&ourset);
+
+ /* 2a) add wanted signals to the mask (for session) */
+ if (!su->same_session
+ && (sigaddset(&ourset, SIGINT)
+ || sigaddset(&ourset, SIGQUIT))) {
+
+ warn(_("cannot initialize signal mask for session"));
+ caught_signal = true;
+ }
+ /* 2b) add wanted generic signals to the mask */
+ if (!caught_signal
+ && (sigaddset(&ourset, SIGTERM)
+ || sigaddset(&ourset, SIGALRM))) {
+
+ warn(_("cannot initialize signal mask"));
+ caught_signal = true;
+ }
+
+ /* 3a) set signal handlers (for session) */
+ if (!caught_signal
+ && !su->same_session
+ && (sigaction(SIGINT, &action, &su->oldact[SIGINT_IDX])
+ || sigaction(SIGQUIT, &action, &su->oldact[SIGQUIT_IDX]))) {
+
+ warn(_("cannot set signal handler for session"));
+ caught_signal = true;
+ }
+
+ /* 3b) set signal handlers */
+ if (!caught_signal
+ && sigaction(SIGTERM, &action, &su->oldact[SIGTERM_IDX])) {
+
+ warn(_("cannot set signal handler"));
+ caught_signal = true;
+ }
+
+ /* 4) unblock wanted signals */
+ if (!caught_signal
+ && sigprocmask(SIG_UNBLOCK, &ourset, NULL)) {
+
+ warn(_("cannot set signal mask"));
+ caught_signal = true;
+ }
+ }
+}
+
+
+static void create_watching_parent(struct su_context *su)
+{
+ int status;
+
+ DBG(MISC, ul_debug("forking..."));
+#ifdef USE_PTY
+ /* no-op, just save original signal mask to oldsig */
+ sigprocmask(SIG_BLOCK, NULL, &su->oldsig);
+
+ if (su->pty)
+ pty_create(su);
+#endif
+ fflush(stdout); /* ??? */
+
+ switch ((int) (su->child = fork())) {
+ case -1: /* error */
+ supam_cleanup(su, PAM_ABORT);
+#ifdef USE_PTY
+ if (su->pty)
+ pty_cleanup(su);
+#endif
+ err(EXIT_FAILURE, _("cannot create child process"));
+ break;
+
+ case 0: /* child */
+ return;
+
+ default: /* parent */
+ DBG(MISC, ul_debug("child [pid=%d]", (int) su->child));
+ break;
+ }
+
+ /* free unnecessary stuff */
+ free_getlogindefs_data();
+
+ /* In the parent watch the child. */
+
+ /* su without pam support does not have a helper that keeps
+ sitting on any directory so let's go to /. */
+ if (chdir("/") != 0)
+ warn(_("cannot change directory to %s"), "/");
+#ifdef USE_PTY
+ if (su->pty)
+ pty_proxy_master(su);
+ else
+#endif
+ parent_setup_signals(su);
+
+ /*
+ * Wait for child
+ */
+ if (!caught_signal)
+ status = wait_for_child(su);
+ else
+ status = 1;
+
+ DBG(SIG, ul_debug("final child status=%d", status));
+
+ if (caught_signal && su->child != (pid_t)-1) {
+ fprintf(stderr, _("\nSession terminated, killing shell..."));
+ kill(su->child, SIGTERM);
+ }
+
+ supam_cleanup(su, PAM_SUCCESS);
+
+ if (caught_signal) {
+ if (su->child != (pid_t)-1) {
+ DBG(SIG, ul_debug("killing child"));
+ sleep(2);
+ kill(su->child, SIGKILL);
+ fprintf(stderr, _(" ...killed.\n"));
+ }
+
+ /* Let's terminate itself with the received signal.
+ *
+ * It seems that shells use WIFSIGNALED() rather than our exit status
+ * value to detect situations when is necessary to cleanup (reset)
+ * terminal settings (kzak -- Jun 2013).
+ */
+ DBG(SIG, ul_debug("restore signals setting"));
+ switch (caught_signal) {
+ case SIGTERM:
+ sigaction(SIGTERM, &su->oldact[SIGTERM_IDX], NULL);
+ break;
+ case SIGINT:
+ sigaction(SIGINT, &su->oldact[SIGINT_IDX], NULL);
+ break;
+ case SIGQUIT:
+ sigaction(SIGQUIT, &su->oldact[SIGQUIT_IDX], NULL);
+ break;
+ default:
+ /* just in case that signal stuff initialization failed and
+ * caught_signal = true */
+ caught_signal = SIGKILL;
+ break;
+ }
+ DBG(SIG, ul_debug("self-send %d signal", caught_signal));
+ kill(getpid(), caught_signal);
+ }
+
+#ifdef USE_PTY
+ if (su->pty)
+ pty_cleanup(su);
+#endif
+ DBG(MISC, ul_debug("exiting [rc=%d]", status));
+ exit(status);
+}
+
+/* Adds @name from the current environment to the whitelist. If @name is not
+ * set then nothing is added to the whitelist and returns 1.
+ */
+static int env_whitelist_add(struct su_context *su, const char *name)
+{
+ const char *env = getenv(name);
+
+ if (!env)
+ return 1;
+ if (strv_extend(&su->env_whitelist_names, name))
+ err_oom();
+ if (strv_extend(&su->env_whitelist_vals, env))
+ err_oom();
+ return 0;
+}
+
+static int env_whitelist_setenv(struct su_context *su, int overwrite)
+{
+ char **one;
+ size_t i = 0;
+ int rc;
+
+ STRV_FOREACH(one, su->env_whitelist_names) {
+ rc = setenv(*one, su->env_whitelist_vals[i], overwrite);
+ if (rc)
+ return rc;
+ i++;
+ }
+
+ return 0;
+}
+
+/* Creates (add to) whitelist from comma delimited string */
+static int env_whitelist_from_string(struct su_context *su, const char *str)
+{
+ char **all = strv_split(str, ",");
+ char **one;
+
+ if (!all) {
+ if (errno == ENOMEM)
+ err_oom();
+ return -EINVAL;
+ }
+
+ STRV_FOREACH(one, all)
+ env_whitelist_add(su, *one);
+ strv_free(all);
+ return 0;
+}
+
+static void setenv_path(const struct passwd *pw)
+{
+ int rc;
+
+ DBG(MISC, ul_debug("setting PATH"));
+
+ if (pw->pw_uid)
+ rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
+
+ else if ((rc = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
+ rc = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
+
+ if (rc)
+ err(EXIT_FAILURE, _("failed to set the PATH environment variable"));
+}
+
+static void modify_environment(struct su_context *su, const char *shell)
+{
+ const struct passwd *pw = su->pwd;
+
+
+ DBG(MISC, ul_debug("modify environ[]"));
+
+ /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
+ *
+ * Unset all other environment variables, but follow
+ * --whitelist-environment if specified.
+ */
+ if (su->simulate_login) {
+ /* leave TERM unchanged */
+ env_whitelist_add(su, "TERM");
+
+ /* Note that original su(1) has allocated environ[] by malloc
+ * to the number of expected variables. This seems unnecessary
+ * optimization as libc later realloc(current_size+2) and for
+ * empty environ[] the curren_size is zero. It seems better to
+ * keep all logic around environment in glibc's hands.
+ * --kzak [Aug 2018]
+ */
+#ifdef HAVE_CLEARENV
+ clearenv();
+#else
+ environ = NULL;
+#endif
+ /* always reset */
+ if (shell)
+ xsetenv("SHELL", shell, 1);
+
+ setenv_path(pw);
+
+ xsetenv("HOME", pw->pw_dir, 1);
+ xsetenv("USER", pw->pw_name, 1);
+ xsetenv("LOGNAME", pw->pw_name, 1);
+
+ /* apply all from whitelist, but no overwrite */
+ env_whitelist_setenv(su, 0);
+
+ /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
+ */
+ } else if (su->change_environment) {
+ xsetenv("HOME", pw->pw_dir, 1);
+ if (shell)
+ xsetenv("SHELL", shell, 1);
+
+ if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
+ setenv_path(pw);
+
+ if (pw->pw_uid) {
+ xsetenv("USER", pw->pw_name, 1);
+ xsetenv("LOGNAME", pw->pw_name, 1);
+ }
+ }
+
+ supam_export_environment(su);
+}
+
+static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups)
+{
+ int rc;
+
+ DBG(MISC, ul_debug("initialize groups"));
+
+ errno = 0;
+ if (ngroups)
+ rc = setgroups(ngroups, groups);
+ else
+ rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid);
+
+ if (rc == -1) {
+ supam_cleanup(su, PAM_ABORT);
+ err(EXIT_FAILURE, _("cannot set groups"));
+ }
+ endgrent();
+
+ rc = pam_setcred(su->pamh, PAM_ESTABLISH_CRED);
+ if (is_pam_failure(rc))
+ errx(EXIT_FAILURE, _("failed to user credentials: %s"),
+ pam_strerror(su->pamh, rc));
+ su->pam_has_cred = 1;
+}
+
+static void change_identity(const struct passwd *pw)
+{
+ DBG(MISC, ul_debug("changing identity [GID=%d, UID=%d]", pw->pw_gid, pw->pw_uid));
+
+ if (setgid(pw->pw_gid))
+ err(EXIT_FAILURE, _("cannot set group id"));
+ if (setuid(pw->pw_uid))
+ err(EXIT_FAILURE, _("cannot set user id"));
+}
+
+/* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
+ * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
+ * N_ADDITIONAL_ARGS extra arguments.
+ */
+static void run_shell(
+ struct su_context *su,
+ char const *shell, char const *command, char **additional_args,
+ size_t n_additional_args)
+{
+ size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
+ const char **args = xcalloc(n_args, sizeof *args);
+ size_t argno = 1;
+
+ DBG(MISC, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]",
+ shell, command,
+ su->simulate_login ? " login" : "",
+ su->fast_startup ? " fast-start" : ""));
+
+ if (su->simulate_login) {
+ char *arg0;
+ char *shell_basename;
+
+ shell_basename = basename(shell);
+ arg0 = xmalloc(strlen(shell_basename) + 2);
+ arg0[0] = '-';
+ strcpy(arg0 + 1, shell_basename);
+ args[0] = arg0;
+ } else
+ args[0] = basename(shell);
+
+ if (su->fast_startup)
+ args[argno++] = "-f";
+ if (command) {
+ args[argno++] = "-c";
+ args[argno++] = command;
+ }
+
+ memcpy(args + argno, additional_args, n_additional_args * sizeof *args);
+ args[argno + n_additional_args] = NULL;
+ execv(shell, (char **)args);
+ errexec(shell);
+}
+
+/* Return true if SHELL is a restricted shell (one not returned by
+ * getusershell), else false, meaning it is a standard shell.
+ */
+static bool is_restricted_shell(const char *shell)
+{
+ char *line;
+
+ setusershell();
+ while ((line = getusershell()) != NULL) {
+ if (*line != '#' && !strcmp(line, shell)) {
+ endusershell();
+ return false;
+ }
+ }
+ endusershell();
+
+ DBG(MISC, ul_debug("%s is restricted shell (not in /etc/shells)", shell));
+ return true;
+}
+
+static void usage_common(void)
+{
+ fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
+ fputs(_(" -w, --whitelist-environment <list> don't reset specified variables\n"), stdout);
+ fputs(USAGE_SEPARATOR, stdout);
+
+ fputs(_(" -g, --group <group> specify the primary group\n"), stdout);
+ fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout);
+ fputs(USAGE_SEPARATOR, stdout);
+
+ fputs(_(" -, -l, --login make the shell a login shell\n"), stdout);
+ fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
+ fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
+ " and do not create a new session\n"), stdout);
+ fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
+ fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
+ fputs(_(" -P, --pty create a new pseudo-terminal\n"), stdout);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ printf(USAGE_HELP_OPTIONS(33));
+}
+
+static void usage_runuser(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ fprintf(stdout,
+ _(" %1$s [options] -u <user> [[--] <command>]\n"
+ " %1$s [options] [-] [<user> [<argument>...]]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
+ "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
+ "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
+
+ fputs(USAGE_OPTIONS, stdout);
+ fputs(_(" -u, --user <user> username\n"), stdout);
+ usage_common();
+ fputs(USAGE_SEPARATOR, stdout);
+
+ fprintf(stdout, USAGE_MAN_TAIL("runuser(1)"));
+}
+
+static void usage_su(void)
+{
+ fputs(USAGE_HEADER, stdout);
+ fprintf(stdout,
+ _(" %s [options] [-] [<user> [<argument>...]]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs(_("Change the effective user ID and group ID to that of <user>.\n"
+ "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
+
+ fputs(USAGE_OPTIONS, stdout);
+ usage_common();
+
+ fprintf(stdout, USAGE_MAN_TAIL("su(1)"));
+}
+
+static void __attribute__((__noreturn__)) usage(int mode)
+{
+ if (mode == SU_MODE)
+ usage_su();
+ else
+ usage_runuser();
+
+ exit(EXIT_SUCCESS);
+}
+
+static void load_config(void *data)
+{
+ struct su_context *su = (struct su_context *) data;
+
+ DBG(MISC, ul_debug("loading logindefs"));
+ logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
+ logindefs_load_file(_PATH_LOGINDEFS);
+}
+
+/*
+ * Returns 1 if the current user is not root
+ */
+static int is_not_root(void)
+{
+ const uid_t ruid = getuid();
+ const uid_t euid = geteuid();
+
+ /* if we're really root and aren't running setuid */
+ return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
+}
+
+static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
+{
+ struct group *gr;
+
+ if (*ngroups >= NGROUPS_MAX)
+ errx(EXIT_FAILURE,
+ P_("specifying more than %d supplemental group is not possible",
+ "specifying more than %d supplemental groups is not possible",
+ NGROUPS_MAX - 1), NGROUPS_MAX - 1);
+
+ gr = getgrnam(name);
+ if (!gr)
+ errx(EXIT_FAILURE, _("group %s does not exist"), name);
+
+ DBG(MISC, ul_debug("add %s group [name=%s, GID=%d]", name, gr->gr_name, (int) gr->gr_gid));
+
+ *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
+ (*groups)[*ngroups] = gr->gr_gid;
+ (*ngroups)++;
+
+ return gr->gr_gid;
+}
+
+int su_main(int argc, char **argv, int mode)
+{
+ struct su_context _su = {
+ .conv = { supam_conv, NULL },
+ .runuser = (mode == RUNUSER_MODE ? 1 : 0),
+ .change_environment = 1,
+ .new_user = DEFAULT_USER,
+#ifdef USE_PTY
+ .pty_master = -1,
+ .pty_slave = -1,
+ .pty_sigfd = -1,
+#endif
+ }, *su = &_su;
+
+ int optc;
+ char *command = NULL;
+ int request_same_session = 0;
+ char *shell = NULL;
+
+ gid_t *groups = NULL;
+ size_t ngroups = 0;
+ bool use_supp = false;
+ bool use_gid = false;
+ gid_t gid = 0;
+
+ static const struct option longopts[] = {
+ {"command", required_argument, NULL, 'c'},
+ {"session-command", required_argument, NULL, 'C'},
+ {"fast", no_argument, NULL, 'f'},
+ {"login", no_argument, NULL, 'l'},
+ {"preserve-environment", no_argument, NULL, 'p'},
+ {"pty", no_argument, NULL, 'P'},
+ {"shell", required_argument, NULL, 's'},
+ {"group", required_argument, NULL, 'g'},
+ {"supp-group", required_argument, NULL, 'G'},
+ {"user", required_argument, NULL, 'u'}, /* runuser only */
+ {"whitelist-environment", required_argument, NULL, 'w'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'm', 'w' }, /* preserve-environment, whitelist-environment */
+ { 'p', 'w' }, /* preserve-environment, whitelist-environment */
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ su_init_debug();
+ su->conv.appdata_ptr = (void *) su;
+
+ while ((optc =
+ getopt_long(argc, argv, "c:fg:G:lmpPs:u:hVw:", longopts,
+ NULL)) != -1) {
+
+ err_exclusive_options(optc, longopts, excl, excl_st);
+
+ switch (optc) {
+ case 'c':
+ command = optarg;
+ break;
+
+ case 'C':
+ command = optarg;
+ request_same_session = 1;
+ break;
+
+ case 'f':
+ su->fast_startup = true;
+ break;
+
+ case 'g':
+ use_gid = true;
+ gid = add_supp_group(optarg, &groups, &ngroups);
+ break;
+
+ case 'G':
+ use_supp = true;
+ add_supp_group(optarg, &groups, &ngroups);
+ break;
+
+ case 'l':
+ su->simulate_login = true;
+ break;
+
+ case 'm':
+ case 'p':
+ su->change_environment = false;
+ break;
+
+ case 'w':
+ env_whitelist_from_string(su, optarg);
+ break;
+
+ case 'P':
+#ifdef USE_PTY
+ su->pty = 1;
+#else
+ errx(EXIT_FAILURE, _("--pty is not supported for your system"));
+#endif
+ break;
+
+ case 's':
+ shell = optarg;
+ break;
+
+ case 'u':
+ if (!su->runuser)
+ errtryhelp(EXIT_FAILURE);
+ su->runuser_uopt = 1;
+ su->new_user = optarg;
+ break;
+
+ case 'h':
+ usage(mode);
+
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ su->restricted = is_not_root();
+
+ if (optind < argc && !strcmp(argv[optind], "-")) {
+ su->simulate_login = true;
+ ++optind;
+ }
+
+ if (su->simulate_login && !su->change_environment) {
+ warnx(_
+ ("ignoring --preserve-environment, it's mutually exclusive with --login"));
+ su->change_environment = true;
+ }
+
+ switch (mode) {
+ case RUNUSER_MODE:
+ /* runuser -u <user> <command>
+ *
+ * If -u <user> is not specified, then follow traditional su(1) behavior and
+ * fallthrough
+ */
+ if (su->runuser_uopt) {
+ if (shell || su->fast_startup || command || su->simulate_login)
+ errx(EXIT_FAILURE,
+ _("options --{shell,fast,command,session-command,login} and "
+ "--user are mutually exclusive"));
+ if (optind == argc)
+ errx(EXIT_FAILURE, _("no command was specified"));
+ break;
+ }
+ /* fallthrough */
+ case SU_MODE:
+ if (optind < argc)
+ su->new_user = argv[optind++];
+ break;
+ }
+
+ if ((use_supp || use_gid) && su->restricted)
+ errx(EXIT_FAILURE,
+ _("only root can specify alternative groups"));
+
+ logindefs_set_loader(load_config, (void *) su);
+ init_tty(su);
+
+ su->pwd = xgetpwnam(su->new_user, &su->pwdbuf);
+ if (!su->pwd
+ || !su->pwd->pw_passwd
+ || !su->pwd->pw_name || !*su->pwd->pw_name
+ || !su->pwd->pw_dir || !*su->pwd->pw_dir)
+ errx(EXIT_FAILURE, _("user %s does not exist"), su->new_user);
+
+ su->new_user = su->pwd->pw_name;
+ su->old_user = xgetlogin();
+
+ if (!su->pwd->pw_shell || !*su->pwd->pw_shell)
+ su->pwd->pw_shell = DEFAULT_SHELL;
+
+ if (use_supp && !use_gid)
+ su->pwd->pw_gid = groups[0];
+ else if (use_gid)
+ su->pwd->pw_gid = gid;
+
+ supam_authenticate(su);
+
+ if (request_same_session || !command || !su->pwd->pw_uid)
+ su->same_session = 1;
+
+ /* initialize shell variable only if "-u <user>" not specified */
+ if (su->runuser_uopt) {
+ shell = NULL;
+ } else {
+ if (!shell && !su->change_environment)
+ shell = getenv("SHELL");
+
+ if (shell
+ && getuid() != 0
+ && is_restricted_shell(su->pwd->pw_shell)) {
+ /* The user being su'd to has a nonstandard shell, and
+ * so is probably a uucp account or has restricted
+ * access. Don't compromise the account by allowing
+ * access with a standard shell.
+ */
+ warnx(_("using restricted shell %s"), su->pwd->pw_shell);
+ shell = NULL;
+ }
+ shell = xstrdup(shell ? shell : su->pwd->pw_shell);
+ }
+
+ init_groups(su, groups, ngroups);
+
+ if (!su->simulate_login || command)
+ su->suppress_pam_info = 1; /* don't print PAM info messages */
+
+ supam_open_session(su);
+
+ create_watching_parent(su);
+ /* Now we're in the child. */
+
+ change_identity(su->pwd);
+ if (!su->same_session || su->pty) {
+ DBG(MISC, ul_debug("call setsid()"));
+ setsid();
+ }
+#ifdef USE_PTY
+ if (su->pty)
+ pty_init_slave(su);
+#endif
+ /* Set environment after pam_open_session, which may put KRB5CCNAME
+ into the pam_env, etc. */
+
+ modify_environment(su, shell);
+
+ if (su->simulate_login && chdir(su->pwd->pw_dir) != 0)
+ warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir);
+
+ if (shell)
+ run_shell(su, shell, command, argv + optind, max(0, argc - optind));
+
+ execvp(argv[optind], &argv[optind]);
+ err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
+}
diff --git a/login-utils/su-common.h b/login-utils/su-common.h
new file mode 100644
index 0000000..7cf3769
--- /dev/null
+++ b/login-utils/su-common.h
@@ -0,0 +1,11 @@
+#ifndef UTIL_LINUX_SU_COMMON_H
+#define UTIL_LINUX_SU_COMMON_H
+
+enum {
+ SU_MODE,
+ RUNUSER_MODE
+};
+
+extern int su_main(int argc, char **argv, int mode);
+
+#endif /* UTIL_LINUX_SU_COMMON */
diff --git a/login-utils/su.1 b/login-utils/su.1
new file mode 100644
index 0000000..d6a064f
--- /dev/null
+++ b/login-utils/su.1
@@ -0,0 +1,294 @@
+.TH SU 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+su \- run a command with substitute user and group ID
+.SH SYNOPSIS
+.BR su " [options] [" \- ]
+.RI [ user " [" argument ...]]
+.SH DESCRIPTION
+.B su
+allows to run commands with a substitute user and group ID.
+.PP
+When called without arguments,
+.B su
+defaults to running an interactive shell as
+.IR root .
+.PP
+For backward compatibility,
+.B su
+defaults to not change the current directory and to only set the
+environment variables
+.B HOME
+and
+.B SHELL
+(plus
+.B USER
+and
+.B LOGNAME
+if the target
+.I user
+is not root). It is recommended to always use the
+.B \-\-login
+option (instead of its shortcut
+.BR \- )
+to avoid side effects caused by mixing environments.
+.PP
+This version of
+.B su
+uses PAM for authentication, account and session management. Some
+configuration options found in other
+.B su
+implementations, such as support for a wheel group, have to be
+configured via PAM.
+.PP
+.B su
+is mostly designed for unprivileged users, the recommended solution for
+privileged users (e.g. scripts executed by root) is to use
+non-set-user-ID command
+.BR runuser (1)
+that does not require authentication and provide separate PAM configuration. If
+the PAM session is not required at all then the recommend solution is to use
+command
+.BR setpriv (1).
+
+.SH OPTIONS
+.TP
+.BR \-c , " \-\-command" = \fIcommand
+Pass
+.I command
+to the shell with the
+.B \-c
+option.
+.TP
+.BR \-f , " \-\-fast"
+Pass
+.B \-f
+to the shell, which may or may not be useful, depending on the shell.
+.TP
+.BR \-g , " \-\-group" = \fIgroup
+Specify the primary group. This option is available to the root user only.
+.TP
+.BR \-G , " \-\-supp\-group" = \fIgroup
+Specify a supplemental group. This option is available to the root user only. The first specified
+supplementary group is also used as a primary group if the option \fB\-\-group\fR is unspecified.
+.TP
+.BR \- , " \-l" , " \-\-login"
+Start the shell as a login shell with an environment similar to a real
+login:
+.RS 10
+.TP
+o
+clears all the environment variables except
+.B TERM
+and variables specified by \fB\-\-whitelist\-environment\fR
+.TP
+o
+initializes the environment variables
+.BR HOME ,
+.BR SHELL ,
+.BR USER ,
+.BR LOGNAME ", and"
+.B PATH
+.TP
+o
+changes to the target user's home directory
+.TP
+o
+sets argv[0] of the shell to
+.RB ' \- '
+in order to make the shell a login shell
+.RE
+.TP
+.BR \-m , " \-p" , " \-\-preserve\-environment"
+Preserve the entire environment, i.e. it does not set
+.BR HOME ,
+.BR SHELL ,
+.B USER
+nor
+.BR LOGNAME .
+This option is ignored if the option \fB\-\-login\fR is specified.
+.TP
+.BR \-P , " \-\-pty"
+Create pseudo-terminal for the session. The independent terminal provides
+better security as user does not share terminal with the original
+session. This allow to avoid TIOCSTI ioctl terminal injection and another
+security attacks against terminal file descriptors. The all session is also
+possible to move to background (e.g. "su --pty - username -c
+application &"). If the pseudo-terminal is enabled then su command works
+as a proxy between the sessions (copy stdin and stdout).
+
+This feature is EXPERIMENTAL for now and may be removed in the next releases.
+
+.TP
+.BR \-s , " \-\-shell" = \fIshell
+Run the specified \fIshell\fR instead of the default. The shell to run is
+selected according to the following rules, in order:
+.RS 10
+.TP
+o
+the shell specified with
+.B \-\-shell
+.TP
+o
+the shell specified in the environment variable
+.BR SHELL ,
+if the
+.B \-\-preserve\-environment
+option is used
+.TP
+o
+the shell listed in the passwd entry of the target user
+.TP
+o
+/bin/sh
+.RE
+.IP
+If the target user has a restricted shell (i.e. not listed in
+/etc/shells), the
+.B \-\-shell
+option and the
+.B SHELL
+environment variables are ignored unless the calling user is root.
+.TP
+.BI \-\-session\-command= command
+Same as
+.B \-c
+but do not create a new session. (Discouraged.)
+.TP
+.BR \-w , " \-\-whitelist\-environment" = \fIlist
+Don't reset environment variables specified in comma separated \fIlist\fR when clears
+environment for \fB\-\-login\fR. The whitelist is ignored for the environment variables
+.BR HOME ,
+.BR SHELL ,
+.BR USER ,
+.BR LOGNAME ", and"
+.BR PATH "."
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH SIGNALS
+Upon receiving either
+.BR SIGINT ,
+.B SIGQUIT
+or
+.BR SIGTERM ,
+.B su
+terminates its child and afterwards terminates itself with the received signal.
+The child is terminated by SIGTERM, after unsuccessful attempt and 2 seconds of
+delay the child is killed by SIGKILL.
+.SH CONFIG FILES
+.B su
+reads the
+.I /etc/default/su
+and
+.I /etc/login.defs
+configuration files. The following configuration items are relevant
+for
+.BR su (1):
+.PP
+.B FAIL_DELAY
+(number)
+.RS 4
+Delay in seconds in case of an authentication failure. The number must be
+a non-negative integer.
+.RE
+.PP
+.B ENV_PATH
+(string)
+.RS 4
+Defines the PATH environment variable for a regular user. The
+default value is
+.IR /usr/local/bin:\:/bin:\:/usr/bin .
+.RE
+.PP
+.B ENV_ROOTPATH
+(string)
+.br
+.B ENV_SUPATH
+(string)
+.RS 4
+Defines the PATH environment variable for root. The default value is
+.IR /usr/local/sbin:\:/usr/local/bin:\:/sbin:\:/bin:\:/usr/sbin:\:/usr/bin .
+.RE
+.PP
+.B ALWAYS_SET_PATH
+(boolean)
+.RS 4
+If set to
+.I yes
+and \-\-login and \-\-preserve\-environment were not specified
+.B su
+initializes
+.BR PATH .
+.RE
+.sp
+The environment variable PATH may be different on systems where /bin and /sbin
+are merged into /usr.
+.SH EXIT STATUS
+.B su
+normally returns the exit status of the command it executed. If the
+command was killed by a signal,
+.B su
+returns the number of the signal plus 128.
+.PP
+Exit status generated by
+.B su
+itself:
+.RS 10
+.TP
+1
+Generic error before executing the requested command
+.TP
+126
+The requested command could not be executed
+.TP
+127
+The requested command was not found
+.RE
+.SH FILES
+.PD 0
+.TP 17
+/etc/pam.d/su
+default PAM configuration file
+.TP
+/etc/pam.d/su-l
+PAM configuration file if \-\-login is specified
+.TP
+/etc/default/su
+command specific logindef config file
+.TP
+/etc/login.defs
+global logindef config file
+.PD 1
+.SH NOTES
+For security reasons
+.B su
+always logs failed log-in attempts to the btmp file, but it does not write to
+the lastlog file at all. This solution allows to control
+.B su
+behavior by PAM configuration. If you want to use the pam_lastlog module to
+print warning message about failed log-in attempts then the pam_lastlog has to
+be configured to update the lastlog file as well. For example by:
+
+.RS
+.br
+session required pam_lastlog.so nowtmp
+.RE
+.SH "SEE ALSO"
+.BR setpriv (1),
+.BR login.defs (5),
+.BR shells (5),
+.BR pam (8),
+.BR runuser (8)
+.SH HISTORY
+This \fBsu\fR command was
+derived from coreutils' \fBsu\fR, which was based on an implementation by
+David MacKenzie. The util-linux has been refactored by Karel Zak.
+.SH AVAILABILITY
+The su command is part of the util-linux package and is
+available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/su.c b/login-utils/su.c
new file mode 100644
index 0000000..d9c67f3
--- /dev/null
+++ b/login-utils/su.c
@@ -0,0 +1,8 @@
+
+#include "su-common.h"
+
+int main(int argc, char **argv)
+{
+ return su_main(argc, argv, SU_MODE);
+}
+
diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c
new file mode 100644
index 0000000..2c0eed3
--- /dev/null
+++ b/login-utils/sulogin-consoles.c
@@ -0,0 +1,828 @@
+/*
+ * consoles.c Routines to detect the system consoles
+ *
+ * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved.
+ * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012 Werner Fink <werner@suse.de>
+ *
+ * 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, 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 (see the file COPYING); if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Author: Werner Fink <werner@suse.de>
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#ifdef __linux__
+# include <sys/vt.h>
+# include <sys/kd.h>
+# include <linux/serial.h>
+# include <linux/major.h>
+#endif
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef USE_SULOGIN_EMERGENCY_MOUNT
+# include <sys/mount.h>
+# include <linux/fs.h>
+# include <linux/magic.h>
+# ifndef MNT_DETACH
+# define MNT_DETACH 2
+# endif
+#endif
+
+#include "c.h"
+#include "canonicalize.h"
+#include "sulogin-consoles.h"
+
+#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
+# ifndef typeof
+# define typeof __typeof__
+# endif
+# ifndef restrict
+# define restrict __restrict__
+# endif
+#endif
+
+#define alignof(type) ((sizeof(type)+(sizeof(void*)-1)) & ~(sizeof(void*)-1))
+#define strsize(string) (strlen((string))+1)
+
+static int consoles_debug;
+#define DBG(x) do { \
+ if (consoles_debug) { \
+ fputs("consoles debug: ", stderr); \
+ x; \
+ } \
+ } while (0)
+
+static inline void __attribute__ ((__format__ (__printf__, 1, 2)))
+dbgprint(const char * const mesg, ...)
+{
+ va_list ap;
+ va_start(ap, mesg);
+ vfprintf(stderr, mesg, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+#ifdef USE_SULOGIN_EMERGENCY_MOUNT
+/*
+ * Make C library standard calls such like ttyname(3) work
+ * even if the system does not show any of the standard
+ * directories.
+ */
+
+static uint32_t emergency_flags;
+# define MNT_PROCFS 0x0001
+# define MNT_DEVTMPFS 0x0002
+
+void emergency_do_umounts(void)
+{
+ if (emergency_flags & MNT_DEVTMPFS)
+ umount2("/dev", MNT_DETACH);
+ if (emergency_flags & MNT_PROCFS)
+ umount2("/proc", MNT_DETACH);
+}
+
+void emergency_do_mounts(void)
+{
+ struct stat rt, xt;
+
+ if (emergency_flags) {
+ emergency_flags = 0;
+ return;
+ }
+
+ if (stat("/", &rt) != 0) {
+ warn("cannot get file status of root file system\n");
+ return;
+ }
+
+ if (stat("/proc", &xt) == 0
+ && rt.st_dev == xt.st_dev
+ && mount("proc", "/proc", "proc", MS_RELATIME, NULL) == 0)
+ emergency_flags |= MNT_PROCFS;
+
+ if (stat("/dev", &xt) == 0
+ && rt.st_dev == xt.st_dev
+ && mount("devtmpfs", "/dev", "devtmpfs",
+ MS_RELATIME, "mode=0755,nr_inodes=0") == 0) {
+
+ emergency_flags |= MNT_DEVTMPFS;
+ mknod("/dev/console", S_IFCHR|S_IRUSR|S_IWUSR,
+ makedev(TTYAUX_MAJOR, 1));
+
+ if (symlink("/proc/self/fd", "/dev/fd") == 0) {
+ ignore_result( symlink("fd/0", "/dev/stdin") );
+ ignore_result( symlink("fd/1", "/dev/stdout") );
+ ignore_result( symlink("fd/2", "/dev/stderr") );
+ }
+ }
+}
+
+#else /* !USE_SULOGIN_EMERGENCY_MOUNT */
+
+void emergency_do_umounts(void) { }
+void emergency_do_mounts(void) { }
+
+#endif /* USE_SULOGIN_EMERGENCY_MOUNT */
+
+/*
+ * Read and allocate one line from file,
+ * the caller has to free the result
+ */
+static __attribute__((__nonnull__))
+char *oneline(const char * const file)
+{
+ FILE *fp;
+ char *ret = NULL;
+ size_t dummy = 0;
+ ssize_t len;
+
+ DBG(dbgprint("reading %s", file));
+
+ if (!(fp = fopen(file, "r" UL_CLOEXECSTR)))
+ return NULL;
+ len = getline(&ret, &dummy, fp);
+ if (len >= 0) {
+ char *nl;
+
+ if (len)
+ ret[len-1] = '\0';
+ if ((nl = strchr(ret, '\n')))
+ *nl = '\0';
+ }
+
+ fclose(fp);
+ return ret;
+}
+
+#ifdef __linux__
+/*
+ * Read and determine active attribute for tty below
+ * /sys/class/tty, the caller has to free the result.
+ */
+static __attribute__((__malloc__))
+char *actattr(const char * const tty)
+{
+ char *ret, *path;
+
+ if (!tty || !*tty)
+ return NULL;
+ if (asprintf(&path, "/sys/class/tty/%s/active", tty) < 0)
+ return NULL;
+
+ ret = oneline(path);
+ free(path);
+ return ret;
+}
+
+/*
+ * Read and determine device attribute for tty below
+ * /sys/class/tty.
+ */
+static
+dev_t devattr(const char * const tty)
+{
+ dev_t dev = 0;
+ char *path, *value;
+
+ if (!tty || !*tty)
+ return 0;
+ if (asprintf(&path, "/sys/class/tty/%s/dev", tty) < 0)
+ return 0;
+
+ value = oneline(path);
+ if (value) {
+ unsigned int maj, min;
+
+ if (sscanf(value, "%u:%u", &maj, &min) == 2)
+ dev = makedev(maj, min);
+ free(value);
+ }
+
+ free(path);
+ return dev;
+}
+#endif /* __linux__ */
+
+/*
+ * Search below /dev for the character device in `dev_t comparedev' variable.
+ * Note that realpath(3) is used here to avoid not existent devices due the
+ * strdup(3) used in our canonicalize_path()!
+ */
+static
+#ifdef __GNUC__
+__attribute__((__nonnull__,__malloc__,__hot__))
+#endif
+char* scandev(DIR *dir, const dev_t comparedev)
+{
+ char path[PATH_MAX];
+ char *name = NULL;
+ const struct dirent *dent;
+ int len, fd;
+
+ DBG(dbgprint("scanning /dev for %u:%u", major(comparedev), minor(comparedev)));
+
+ /*
+ * Try udev links on character devices first.
+ */
+ if ((len = snprintf(path, sizeof(path),
+ "/dev/char/%u:%u", major(comparedev), minor(comparedev))) > 0 &&
+ (size_t)len < sizeof(path)) {
+
+ name = realpath(path, NULL);
+ if (name)
+ goto out;
+ }
+
+ fd = dirfd(dir);
+ rewinddir(dir);
+ while ((dent = readdir(dir))) {
+ struct stat st;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (dent->d_type != DT_UNKNOWN && dent->d_type != DT_CHR)
+ continue;
+#endif
+ if (fstatat(fd, dent->d_name, &st, 0) < 0)
+ continue;
+ if (!S_ISCHR(st.st_mode))
+ continue;
+ if (comparedev != st.st_rdev)
+ continue;
+ if ((len = snprintf(path, sizeof(path), "/dev/%s", dent->d_name)) < 0 ||
+ (size_t)len >= sizeof(path))
+ continue;
+
+ name = realpath(path, NULL);
+ if (name)
+ goto out;
+ }
+
+#ifdef USE_SULOGIN_EMERGENCY_MOUNT
+ /*
+ * There was no /dev mounted hence and no device was found hence we create our own.
+ */
+ if (!name && (emergency_flags & MNT_DEVTMPFS)) {
+
+ if ((len = snprintf(path, sizeof(path),
+ "/dev/tmp-%u:%u", major(comparedev), minor(comparedev))) < 0 ||
+ (size_t)len >= sizeof(path))
+ goto out;
+
+ if (mknod(path, S_IFCHR|S_IRUSR|S_IWUSR, comparedev) < 0 && errno != EEXIST)
+ goto out;
+
+ name = realpath(path, NULL);
+ }
+#endif
+out:
+ return name;
+}
+
+/*
+ * Default control characters for an unknown terminal line.
+ */
+
+/*
+ * Allocate an aligned `struct console' memory area,
+ * initialize its default values, and append it to
+ * the global linked list.
+ */
+static
+#ifdef __GNUC__
+__attribute__((__hot__))
+#endif
+int append_console(struct list_head *consoles, const char * const name)
+{
+ struct console *restrict tail;
+ const struct console *last = NULL;
+
+ DBG(dbgprint("appenging %s", name));
+
+ if (!list_empty(consoles))
+ last = list_last_entry(consoles, struct console, entry);
+
+ if (posix_memalign((void *) &tail, sizeof(void *),
+ alignof(struct console) + strsize(name)) != 0)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&tail->entry);
+ INIT_CHARDATA(&tail->cp);
+
+ list_add_tail(&tail->entry, consoles);
+ tail->tty = ((char *) tail) + alignof(struct console);
+ strcpy(tail->tty, name);
+
+ tail->file = (FILE*)0;
+ tail->flags = 0;
+ tail->fd = -1;
+ tail->id = last ? last->id + 1 : 0;
+ tail->pid = -1;
+ memset(&tail->tio, 0, sizeof(tail->tio));
+
+ return 0;
+}
+
+#ifdef __linux__
+/*
+ * return codes:
+ * < 0 - fatal error (no mem or so... )
+ * 0 - success
+ * 1 - recoverable error
+ * 2 - detection not available
+ */
+static int detect_consoles_from_proc(struct list_head *consoles)
+{
+ char fbuf[16 + 1];
+ DIR *dir = NULL;
+ FILE *fc = NULL;
+ int maj, min, rc = 1, matches;
+
+ DBG(dbgprint("trying /proc"));
+
+ fc = fopen("/proc/consoles", "r" UL_CLOEXECSTR);
+ if (!fc) {
+ rc = 2;
+ goto done;
+ }
+ dir = opendir("/dev");
+ if (!dir)
+ goto done;
+
+ while ((matches = fscanf(fc, "%*s %*s (%16[^)]) %d:%d", fbuf, &maj, &min)) >= 1) {
+ char *name;
+ dev_t comparedev;
+
+ if (matches != 3)
+ continue;
+ if (!strchr(fbuf, 'E'))
+ continue;
+ comparedev = makedev(maj, min);
+ name = scandev(dir, comparedev);
+ if (!name)
+ continue;
+ rc = append_console(consoles, name);
+ free(name);
+ if (rc < 0)
+ goto done;
+ }
+
+ rc = list_empty(consoles) ? 1 : 0;
+done:
+ if (dir)
+ closedir(dir);
+ if (fc)
+ fclose(fc);
+ DBG(dbgprint("[/proc rc=%d]", rc));
+ return rc;
+}
+
+/*
+ * return codes:
+ * < 0 - fatal error (no mem or so... )
+ * 0 - success
+ * 1 - recoverable error
+ * 2 - detection not available
+ */
+static int detect_consoles_from_sysfs(struct list_head *consoles)
+{
+ char *attrib = NULL, *words, *token;
+ DIR *dir = NULL;
+ int rc = 1;
+
+ DBG(dbgprint("trying /sys"));
+
+ attrib = actattr("console");
+ if (!attrib) {
+ rc = 2;
+ goto done;
+ }
+
+ words = attrib;
+
+ dir = opendir("/dev");
+ if (!dir)
+ goto done;
+
+ while ((token = strsep(&words, " \t\r\n"))) {
+ char *name;
+ dev_t comparedev;
+
+ if (*token == '\0')
+ continue;
+
+ comparedev = devattr(token);
+ if (comparedev == makedev(TTY_MAJOR, 0)) {
+ char *tmp = actattr(token);
+ if (!tmp)
+ continue;
+ comparedev = devattr(tmp);
+ free(tmp);
+ }
+
+ name = scandev(dir, comparedev);
+ if (!name)
+ continue;
+ rc = append_console(consoles, name);
+ free(name);
+ if (rc < 0)
+ goto done;
+ }
+
+ rc = list_empty(consoles) ? 1 : 0;
+done:
+ free(attrib);
+ if (dir)
+ closedir(dir);
+ DBG(dbgprint("[/sys rc=%d]", rc));
+ return rc;
+}
+
+
+static int detect_consoles_from_cmdline(struct list_head *consoles)
+{
+ char *cmdline, *words, *token;
+ dev_t comparedev;
+ DIR *dir = NULL;
+ int rc = 1, fd;
+
+ DBG(dbgprint("trying kernel cmdline"));
+
+ cmdline = oneline("/proc/cmdline");
+ if (!cmdline) {
+ rc = 2;
+ goto done;
+ }
+
+ words= cmdline;
+ dir = opendir("/dev");
+ if (!dir)
+ goto done;
+
+ while ((token = strsep(&words, " \t\r\n"))) {
+#ifdef TIOCGDEV
+ unsigned int devnum;
+#else
+ struct vt_stat vt;
+ struct stat st;
+#endif
+ char *colon, *name;
+
+ if (*token != 'c')
+ continue;
+ if (strncmp(token, "console=", 8) != 0)
+ continue;
+ token += 8;
+
+ if (strcmp(token, "brl") == 0)
+ token += 4;
+ if ((colon = strchr(token, ',')))
+ *colon = '\0';
+
+ if (asprintf(&name, "/dev/%s", token) < 0)
+ continue;
+ if ((fd = open(name, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC)) < 0) {
+ free(name);
+ continue;
+ }
+ free(name);
+#ifdef TIOCGDEV
+ if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
+ close(fd);
+ continue;
+ }
+ comparedev = (dev_t) devnum;
+#else
+ if (fstat(fd, &st) < 0) {
+ close(fd);
+ continue;
+ }
+ comparedev = st.st_rdev;
+ if (comparedev == makedev(TTY_MAJOR, 0)) {
+ if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
+ close(fd);
+ continue;
+ }
+ comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
+ }
+#endif
+ close(fd);
+
+ name = scandev(dir, comparedev);
+ if (!name)
+ continue;
+ rc = append_console(consoles, name);
+ free(name);
+ if (rc < 0)
+ goto done;
+ }
+
+ rc = list_empty(consoles) ? 1 : 0;
+done:
+ if (dir)
+ closedir(dir);
+ free(cmdline);
+ DBG(dbgprint("[kernel cmdline rc=%d]", rc));
+ return rc;
+}
+
+#ifdef TIOCGDEV
+static int detect_consoles_from_tiocgdev(struct list_head *consoles,
+ const int fallback,
+ const char *device)
+{
+ unsigned int devnum;
+ char *name;
+ int rc = 1, fd = -1;
+ dev_t comparedev;
+ DIR *dir = NULL;
+ struct console *console;
+
+ DBG(dbgprint("trying tiocgdev"));
+
+ if (!device || !*device)
+ fd = dup(fallback);
+ else
+ fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
+
+ if (fd < 0)
+ goto done;
+ if (ioctl (fd, TIOCGDEV, &devnum) < 0)
+ goto done;
+
+ comparedev = (dev_t) devnum;
+ dir = opendir("/dev");
+ if (!dir)
+ goto done;
+
+ name = scandev(dir, comparedev);
+ closedir(dir);
+
+ if (!name) {
+ name = (char *) (device && *device ? device : ttyname(fallback));
+ if (!name)
+ name = "/dev/tty1";
+
+ name = strdup(name);
+ if (!name) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ }
+ rc = append_console(consoles, name);
+ free(name);
+ if (rc < 0)
+ goto done;
+ if (list_empty(consoles)) {
+ rc = 1;
+ goto done;
+ }
+ console = list_last_entry(consoles, struct console, entry);
+ if (console && (!device || !*device))
+ console->fd = fallback;
+done:
+ if (fd >= 0)
+ close(fd);
+ DBG(dbgprint("[tiocgdev rc=%d]", rc));
+ return rc;
+}
+#endif /* TIOCGDEV */
+#endif /* __linux__ */
+
+/*
+ * Try to detect the real device(s) used for the system console
+ * /dev/console if but only if /dev/console is used. On Linux
+ * this can be more than one device, e.g. a serial line as well
+ * as a virtual console as well as a simple printer.
+ *
+ * Returns 1 if stdout and stderr should be reconnected and 0
+ * otherwise or less than zero on error.
+ */
+int detect_consoles(const char *device, const int fallback, struct list_head *consoles)
+{
+ int fd, reconnect = 0, rc;
+ dev_t comparedev = 0;
+
+ consoles_debug = getenv("CONSOLES_DEBUG") ? 1 : 0;
+
+ if (!device || !*device)
+ fd = fallback >= 0 ? dup(fallback) : - 1;
+ else {
+ fd = open(device, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
+ reconnect = 1;
+ }
+
+ DBG(dbgprint("detection started [device=%s, fallback=%d]",
+ device, fallback));
+
+ if (fd >= 0) {
+ DIR *dir;
+ char *name;
+ struct stat st;
+#ifdef TIOCGDEV
+ unsigned int devnum;
+#endif
+#ifdef __GNU__
+ /*
+ * The Hurd always gives st_rdev as 0, which causes this
+ * method to select the first terminal it finds.
+ */
+ close(fd);
+ goto fallback;
+#endif
+ DBG(dbgprint("trying device/fallback file descriptor"));
+
+ if (fstat(fd, &st) < 0) {
+ close(fd);
+ goto fallback;
+ }
+ comparedev = st.st_rdev;
+
+ if (reconnect &&
+ (fstat(fallback, &st) < 0 || comparedev != st.st_rdev))
+ dup2(fd, fallback);
+#ifdef __linux__
+ /*
+ * Check if the device detection for Linux system console should be used.
+ */
+ if (comparedev == makedev(TTYAUX_MAJOR, 0)) { /* /dev/tty */
+ close(fd);
+ device = "/dev/tty";
+ goto fallback;
+ }
+ if (comparedev == makedev(TTYAUX_MAJOR, 1)) { /* /dev/console */
+ close(fd);
+ goto console;
+ }
+ if (comparedev == makedev(TTYAUX_MAJOR, 2)) { /* /dev/ptmx */
+ close(fd);
+ device = "/dev/tty";
+ goto fallback;
+ }
+ if (comparedev == makedev(TTY_MAJOR, 0)) { /* /dev/tty0 */
+ struct vt_stat vt;
+ if (ioctl(fd, VT_GETSTATE, &vt) < 0) {
+ close(fd);
+ goto fallback;
+ }
+ comparedev = makedev(TTY_MAJOR, (int)vt.v_active);
+ }
+#endif
+#ifdef TIOCGDEV
+ if (ioctl (fd, TIOCGDEV, &devnum) < 0) {
+ close(fd);
+ goto fallback;
+ }
+ comparedev = (dev_t)devnum;
+#endif
+ close(fd);
+ dir = opendir("/dev");
+ if (!dir)
+ goto fallback;
+ name = scandev(dir, comparedev);
+ closedir(dir);
+
+ if (name) {
+ rc = append_console(consoles, name);
+ free(name);
+ if (rc < 0)
+ return rc;
+ }
+ if (list_empty(consoles))
+ goto fallback;
+
+ DBG(dbgprint("detection success [rc=%d]", reconnect));
+ return reconnect;
+ }
+#ifdef __linux__
+console:
+ /*
+ * Detection of devices used for Linux system console using
+ * the /proc/consoles API with kernel 2.6.38 and higher.
+ */
+ rc = detect_consoles_from_proc(consoles);
+ if (rc == 0)
+ return reconnect; /* success */
+ if (rc < 0)
+ return rc; /* fatal error */
+
+ /*
+ * Detection of devices used for Linux system console using
+ * the sysfs /sys/class/tty/ API with kernel 2.6.37 and higher.
+ */
+ rc = detect_consoles_from_sysfs(consoles);
+ if (rc == 0)
+ return reconnect; /* success */
+ if (rc < 0)
+ return rc; /* fatal error */
+
+ /*
+ * Detection of devices used for Linux system console using
+ * kernel parameter on the kernels command line.
+ */
+ rc = detect_consoles_from_cmdline(consoles);
+ if (rc == 0)
+ return reconnect; /* success */
+ if (rc < 0)
+ return rc; /* fatal error */
+
+ /*
+ * Detection of the device used for Linux system console using
+ * the ioctl TIOCGDEV if available (e.g. official 2.6.38).
+ */
+#ifdef TIOCGDEV
+ rc = detect_consoles_from_tiocgdev(consoles, fallback, device);
+ if (rc == 0)
+ return reconnect; /* success */
+ if (rc < 0)
+ return rc; /* fatal error */
+#endif
+ if (!list_empty(consoles)) {
+ DBG(dbgprint("detection success [rc=%d]", reconnect));
+ return reconnect;
+ }
+
+#endif /* __linux __ */
+
+fallback:
+ if (fallback >= 0) {
+ const char *name;
+ char *n;
+ struct console *console;
+
+ if (device && *device != '\0')
+ name = device;
+ else name = ttyname(fallback);
+
+ if (!name)
+ name = "/dev/tty";
+
+ n = strdup(name);
+ if (!n)
+ return -ENOMEM;
+ rc = append_console(consoles, n);
+ free(n);
+ if (rc < 0)
+ return rc;
+ if (list_empty(consoles))
+ return 1;
+ console = list_last_entry(consoles, struct console, entry);
+ if (console)
+ console->fd = fallback;
+ }
+
+ DBG(dbgprint("detection done by fallback [rc=%d]", reconnect));
+ return reconnect;
+}
+
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ char *name = NULL;
+ int fd, re;
+ struct list_head *p, consoles;
+
+ if (argc == 2) {
+ name = argv[1];
+ fd = open(name, O_RDWR);
+ } else {
+ name = ttyname(STDIN_FILENO);
+ fd = STDIN_FILENO;
+ }
+
+ if (!name)
+ errx(EXIT_FAILURE, "usage: %s [<tty>]\n", program_invocation_short_name);
+
+ INIT_LIST_HEAD(&consoles);
+ re = detect_consoles(name, fd, &consoles);
+
+ list_for_each(p, &consoles) {
+ struct console *c = list_entry(p, struct console, entry);
+ printf("%s: id=%d %s\n", c->tty, c->id, re ? "(reconnect) " : "");
+ }
+
+ return 0;
+}
+#endif
diff --git a/login-utils/sulogin-consoles.h b/login-utils/sulogin-consoles.h
new file mode 100644
index 0000000..f762e87
--- /dev/null
+++ b/login-utils/sulogin-consoles.h
@@ -0,0 +1,54 @@
+/*
+ * consoles.h Header file for routines to detect the system consoles
+ *
+ * Copyright (c) 2011 SuSE LINUX Products GmbH, All rights reserved.
+ * Copyright (c) 2012 Werner Fink <werner@suse.de>
+ *
+ * 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, 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 (see the file COPYING); if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Author: Werner Fink <werner@suse.de>
+ */
+#ifndef UTIL_LINUX_SULOGIN_CONSOLES_H
+#define UTIL_LINUX_SULOGIN_CONSOLES_H
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <termios.h>
+
+#include "list.h"
+#include "ttyutils.h"
+
+struct console {
+ struct list_head entry;
+ char *tty;
+ FILE *file;
+ uint32_t flags;
+ int fd, id;
+#define CON_SERIAL 0x0001
+#define CON_NOTTY 0x0002
+ pid_t pid;
+ struct chardata cp;
+ struct termios tio;
+};
+
+extern int detect_consoles(const char *device, int fallback,
+ struct list_head *consoles);
+
+extern void emergency_do_umounts(void);
+extern void emergency_do_mounts(void);
+
+#endif /* UTIL_LINUX_SULOGIN_CONSOLES_H */
diff --git a/login-utils/sulogin.8 b/login-utils/sulogin.8
new file mode 100644
index 0000000..7998b4a
--- /dev/null
+++ b/login-utils/sulogin.8
@@ -0,0 +1,95 @@
+.\" Copyright (C) 1998-2006 Miquel van Smoorenburg.
+.\" Copyright (C) 2012 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 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, write to the Free Software
+.\" Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+.\"
+.TH SULOGIN "8" "July 2014" "util-linux" "System Administration"
+.SH NAME
+sulogin \- single-user login
+.SH SYNOPSIS
+.BR sulogin " [options]"
+.RI [ tty ]
+.SH DESCRIPTION
+.B sulogin
+is invoked by
+.B init
+when the system goes into single-user mode.
+.PP
+The user is prompted:
+.IP "" .5i
+Give root password for system maintenance
+.br
+(or type Control\-D for normal startup):
+.PP
+If the root account is locked and --force is specified, no password is required.
+.PP
+.B sulogin
+will be connected to the current terminal, or to the optional \fItty\fR device that
+can be specified on the command line (typically
+.IR /dev/console ).
+.PP
+When the user exits from the single-user shell, or presses control\-D at the
+prompt, the system will continue to boot.
+.SH OPTIONS
+.IP "\fB\-e\fR, \fB\-\-force\fP"
+If the default method of obtaining the root password from the system via
+.BR getpwnam (3)
+fails, then examine
+.I /etc/passwd
+and
+.I /etc/shadow
+to get the password. If these files are damaged or nonexistent, or when
+root account is locked by '!' or '*' at the begin of the password then
+.B sulogin
+will \fBstart a root shell without asking for a password\fP.
+.PP
+.IP
+Only use the
+.B \-e
+option if you are sure the console is physically protected against
+unauthorized access.
+.IP "\fB\-p\fR, \fB\-\-login\-shell\fP"
+Specifying this option causes
+.B sulogin
+to start the shell process as a login shell.
+.IP "\fB\-t\fR, \fB\-\-timeout \fIseconds\fP"
+Specify the maximum amount of time to wait for user input. By default,
+.B sulogin
+will wait forever.
+.IP "\fB\-h\fR, \fB\-\-help\fP"
+Display help text and exit.
+.IP "\fB\-V\fR, \fB\-\-version\fP"
+Display version information and exit.
+.SH ENVIRONMENT VARIABLES
+.B sulogin
+looks for the environment variable
+.B SUSHELL
+or
+.B sushell
+to determine what shell to start. If the environment variable is not set, it
+will try to execute root's shell from
+.IR /etc/passwd .
+If that fails, it
+will fall back to
+.IR /bin/sh .
+.SH AUTHOR
+.B sulogin
+was written by Miquel van Smoorenburg for sysvinit and later ported
+to util-linux by Dave Reisner and Karel Zak.
+.SH AVAILABILITY
+The sulogin command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c
new file mode 100644
index 0000000..5f09bd4
--- /dev/null
+++ b/login-utils/sulogin.c
@@ -0,0 +1,1110 @@
+/*
+ * sulogin
+ *
+ * This program gives Linux machines a reasonable secure way to boot single
+ * user. It forces the user to supply the root password before a shell is
+ * started. If there is a shadow password file and the encrypted root password
+ * is "x" the shadow password will be used.
+ *
+ * Copyright (C) 1998-2003 Miquel van Smoorenburg.
+ * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012 Werner Fink <werner@suse.de>
+ *
+ * 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 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <termios.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#endif
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+# include <selinux/get_context_list.h>
+#endif
+
+#ifdef __linux__
+# include <sys/kd.h>
+# include <sys/param.h>
+#endif
+
+#include "c.h"
+#include "closestream.h"
+#include "env.h"
+#include "nls.h"
+#include "pathnames.h"
+#ifdef USE_PLYMOUTH_SUPPORT
+# include "plymouth-ctrl.h"
+#endif
+#include "strutils.h"
+#include "ttyutils.h"
+#include "sulogin-consoles.h"
+#define CONMAX 16
+
+static unsigned int timeout;
+static int profile;
+static volatile uint32_t openfd; /* Remember higher file descriptors */
+
+static struct sigaction saved_sigint;
+static struct sigaction saved_sigtstp;
+static struct sigaction saved_sigquit;
+static struct sigaction saved_sighup;
+static struct sigaction saved_sigchld;
+
+static volatile sig_atomic_t alarm_rised;
+static volatile sig_atomic_t sigchild;
+
+#ifndef IUCLC
+# define IUCLC 0
+#endif
+
+#ifndef WEXITED
+# warning "WEXITED is missing, sulogin may not work as expected"
+# define WEXITED 0
+#endif
+
+static int locked_account_password(const char * const passwd)
+{
+ if (passwd && (*passwd == '*' || *passwd == '!'))
+ return 1;
+ return 0;
+}
+
+/*
+ * Fix the tty modes and set reasonable defaults.
+ */
+static void tcinit(struct console *con)
+{
+ int flags = 0, mode = 0;
+ struct termios *tio = &con->tio;
+ const int fd = con->fd;
+#ifdef USE_PLYMOUTH_SUPPORT
+ struct termios lock;
+ int i = (plymouth_command(MAGIC_PING)) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0;
+ if (i)
+ plymouth_command(MAGIC_QUIT);
+ while (i-- > 0) {
+ /*
+ * With plymouth the termios flags become changed after this
+ * function had changed the termios.
+ */
+ memset(&lock, 0, sizeof(struct termios));
+ if (ioctl(fd, TIOCGLCKTRMIOS, &lock) < 0)
+ break;
+ if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag)
+ break;
+ sleep(1);
+ }
+ memset(&lock, 0, sizeof(struct termios));
+ ioctl(fd, TIOCSLCKTRMIOS, &lock);
+#endif
+ errno = 0;
+
+ if (tcgetattr(fd, tio) < 0) {
+ warn(_("tcgetattr failed"));
+ con->flags |= CON_NOTTY;
+ return;
+ }
+
+ /* Handle lines other than virtual consoles here */
+#if defined(KDGKBMODE)
+ if (ioctl(fd, KDGKBMODE, &mode) < 0)
+#endif
+ {
+ speed_t ispeed, ospeed;
+ struct winsize ws;
+ errno = 0;
+
+ /* this is a modem line */
+ con->flags |= CON_SERIAL;
+
+ /* Flush input and output queues on modem lines */
+ tcflush(fd, TCIOFLUSH);
+
+ ispeed = cfgetispeed(tio);
+ ospeed = cfgetospeed(tio);
+
+ if (!ispeed) ispeed = TTYDEF_SPEED;
+ if (!ospeed) ospeed = TTYDEF_SPEED;
+
+ tio->c_cflag = CREAD | CS8 | HUPCL | (tio->c_cflag & CLOCAL);
+ tio->c_iflag = 0;
+ tio->c_lflag = 0;
+ tio->c_oflag &= OPOST | ONLCR;
+
+ cfsetispeed(tio, ispeed);
+ cfsetospeed(tio, ospeed);
+
+#ifdef HAVE_STRUCT_TERMIOS_C_LINE
+ tio->c_line = 0;
+#endif
+ tio->c_cc[VTIME] = 0;
+ tio->c_cc[VMIN] = 1;
+
+ if (ioctl(fd, TIOCGWINSZ, &ws) == 0) {
+ int set = 0;
+ if (ws.ws_row == 0) {
+ ws.ws_row = 24;
+ set++;
+ }
+ if (ws.ws_col == 0) {
+ ws.ws_col = 80;
+ set++;
+ }
+ if (set)
+ ignore_result( ioctl(fd, TIOCSWINSZ, &ws) );
+ }
+
+ setlocale(LC_CTYPE, "POSIX");
+ goto setattr;
+ }
+#if defined(IUTF8) && defined(KDGKBMODE)
+ /* Handle mode of current keyboard setup, e.g. for UTF-8 */
+ switch(mode) {
+ case K_UNICODE:
+ setlocale(LC_CTYPE, "C.UTF-8");
+ flags |= UL_TTY_UTF8;
+ break;
+ case K_RAW:
+ case K_MEDIUMRAW:
+ case K_XLATE:
+ default:
+ setlocale(LC_CTYPE, "POSIX");
+ break;
+ }
+#else
+ setlocale(LC_CTYPE, "POSIX");
+#endif
+ reset_virtual_console(tio, flags);
+setattr:
+ if (tcsetattr(fd, TCSANOW, tio))
+ warn(_("tcsetattr failed"));
+
+ /* Enable blocking mode for read and write */
+ if ((flags = fcntl(fd, F_GETFL, 0)) != -1)
+ ignore_result( fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) );
+}
+
+/*
+ * Finalize the tty modes on modem lines.
+ */
+static void tcfinal(struct console *con)
+{
+ struct termios *tio = &con->tio;
+ const int fd = con->fd;
+
+ if ((con->flags & CON_SERIAL) == 0) {
+ xsetenv("TERM", "linux", 1);
+ return;
+ }
+ if (con->flags & CON_NOTTY) {
+ xsetenv("TERM", "dumb", 1);
+ return;
+ }
+
+#if defined (__s390__) || defined (__s390x__)
+ xsetenv("TERM", "dumb", 1);
+#else
+ xsetenv("TERM", "vt102", 1);
+#endif
+ tio->c_iflag |= (IXON | IXOFF);
+ tio->c_lflag |= (ICANON | ISIG | ECHO|ECHOE|ECHOK|ECHOKE);
+ tio->c_oflag |= OPOST;
+
+ tio->c_cc[VINTR] = CINTR;
+ tio->c_cc[VQUIT] = CQUIT;
+ tio->c_cc[VERASE] = con->cp.erase;
+ tio->c_cc[VKILL] = con->cp.kill;
+ tio->c_cc[VEOF] = CEOF;
+#ifdef VSWTC
+ tio->c_cc[VSWTC] = _POSIX_VDISABLE;
+#elif defined(VSWTCH)
+ tio->c_cc[VSWTCH] = _POSIX_VDISABLE;
+#endif
+ tio->c_cc[VSTART] = CSTART;
+ tio->c_cc[VSTOP] = CSTOP;
+ tio->c_cc[VSUSP] = CSUSP;
+ tio->c_cc[VEOL] = _POSIX_VDISABLE;
+
+ if (con->cp.eol == CR) {
+ tio->c_iflag |= ICRNL;
+ tio->c_iflag &= ~(INLCR|IGNCR);
+ tio->c_oflag |= ONLCR;
+ tio->c_oflag &= ~(OCRNL|ONLRET);
+ }
+
+ switch (con->cp.parity) {
+ default:
+ case 0:
+ tio->c_cflag &= ~(PARODD | PARENB);
+ tio->c_iflag &= ~(INPCK | ISTRIP);
+ break;
+ case 1: /* odd parity */
+ tio->c_cflag |= PARODD;
+ /* fallthrough */
+ case 2: /* even parity */
+ tio->c_cflag |= PARENB;
+ tio->c_iflag |= (INPCK | ISTRIP);
+ /* fallthrough */
+ case (1 | 2): /* no parity bit */
+ tio->c_cflag &= ~CSIZE;
+ tio->c_cflag |= CS7;
+ break;
+ }
+
+ /* Set line attributes */
+ tcsetattr(fd, TCSANOW, tio);
+}
+
+/*
+ * Called at timeout.
+ */
+static void alrm_handler(int sig __attribute__((unused)))
+{
+ /* Timeout expired */
+ alarm_rised++;
+}
+
+static void chld_handler(int sig __attribute__((unused)))
+{
+ sigchild++;
+}
+
+static void mask_signal(int signal, void (*handler)(int),
+ struct sigaction *origaction)
+{
+ struct sigaction newaction;
+
+ newaction.sa_handler = handler;
+ sigemptyset(&newaction.sa_mask);
+ newaction.sa_flags = 0;
+
+ sigaction(signal, &newaction, origaction);
+}
+
+static void unmask_signal(int signal, struct sigaction *sa)
+{
+ sigaction(signal, sa, NULL);
+}
+
+/*
+ * See if an encrypted password is valid. The encrypted password is checked for
+ * traditional-style DES and FreeBSD-style MD5 encryption.
+ */
+static int valid(const char *pass)
+{
+ const char *s;
+ char id[5];
+ size_t len;
+ off_t off;
+
+ if (pass[0] == 0)
+ return 1;
+ if (pass[0] != '$')
+ goto check_des;
+
+ /*
+ * up to 4 bytes for the signature e.g. $1$
+ */
+ for (s = pass+1; *s && *s != '$'; s++);
+
+ if (*s++ != '$')
+ return 0;
+
+ if ((off = (off_t)(s-pass)) > 4 || off < 3)
+ return 0;
+
+ memset(id, '\0', sizeof(id));
+ strncpy(id, pass, off);
+
+ /*
+ * up to 16 bytes for the salt
+ */
+ for (; *s && *s != '$'; s++);
+
+ if (*s++ != '$')
+ return 0;
+
+ if ((off_t)(s-pass) > 16)
+ return 0;
+
+ len = strlen(s);
+
+ /*
+ * the MD5 hash (128 bits or 16 bytes) encoded in base64 = 22 bytes
+ */
+ if ((strcmp(id, "$1$") == 0) && (len < 22 || len > 24))
+ return 0;
+
+ /*
+ * the SHA-256 hash 43 bytes
+ */
+ if ((strcmp(id, "$5$") == 0) && (len < 42 || len > 44))
+ return 0;
+
+ /*
+ * the SHA-512 hash 86 bytes
+ */
+ if ((strcmp(id, "$6$") == 0) && (len < 85 || len > 87))
+ return 0;
+
+ /*
+ * e.g. Blowfish hash
+ */
+ return 1;
+check_des:
+ if (strlen(pass) != 13)
+ return 0;
+
+ for (s = pass; *s; s++) {
+ if ((*s < '0' || *s > '9') &&
+ (*s < 'a' || *s > 'z') &&
+ (*s < 'A' || *s > 'Z') &&
+ *s != '.' && *s != '/')
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Set a variable if the value is not NULL.
+ */
+static inline void set(char **var, char *val)
+{
+ if (val)
+ *var = val;
+}
+
+/*
+ * Get the root password entry.
+ */
+static struct passwd *getrootpwent(int try_manually)
+{
+ static struct passwd pwd;
+ struct passwd *pw;
+ struct spwd *spw;
+ FILE *fp;
+ static char line[2 * BUFSIZ];
+ static char sline[2 * BUFSIZ];
+ char *p;
+
+ /*
+ * First, we try to get the password the standard way using normal
+ * library calls.
+ */
+ if ((pw = getpwnam("root")) &&
+ !strcmp(pw->pw_passwd, "x") &&
+ (spw = getspnam("root")))
+ pw->pw_passwd = spw->sp_pwdp;
+
+ if (pw || !try_manually)
+ return pw;
+
+ /*
+ * If we come here, we could not retrieve the root password through
+ * library calls and we try to read the password and shadow files
+ * manually.
+ */
+ pwd.pw_name = "root";
+ pwd.pw_passwd = "";
+ pwd.pw_gecos = "Super User";
+ pwd.pw_dir = "/";
+ pwd.pw_shell = "";
+ pwd.pw_uid = 0;
+ pwd.pw_gid = 0;
+
+ if ((fp = fopen(_PATH_PASSWD, "r")) == NULL) {
+ warn(_("cannot open %s"), _PATH_PASSWD);
+ return &pwd;
+ }
+
+ /*
+ * Find root in the password file.
+ */
+ while ((p = fgets(line, sizeof(line), fp)) != NULL) {
+ if (strncmp(line, "root:", 5) != 0)
+ continue;
+ p += 5;
+ set(&pwd.pw_passwd, strsep(&p, ":"));
+ strsep(&p, ":");
+ strsep(&p, ":");
+ set(&pwd.pw_gecos, strsep(&p, ":"));
+ set(&pwd.pw_dir, strsep(&p, ":"));
+ set(&pwd.pw_shell, strsep(&p, "\n"));
+ p = line;
+ break;
+ }
+ fclose(fp);
+
+ /*
+ * If the encrypted password is valid or not found, return.
+ */
+ if (p == NULL) {
+ warnx(_("%s: no entry for root\n"), _PATH_PASSWD);
+ return &pwd;
+ }
+ if (valid(pwd.pw_passwd))
+ return &pwd;
+
+ /*
+ * The password is invalid. If there is a shadow password, try it.
+ */
+ *pwd.pw_passwd = '\0';
+ if ((fp = fopen(_PATH_SHADOW_PASSWD, "r")) == NULL) {
+ warn(_("cannot open %s"), _PATH_PASSWD);
+ return &pwd;
+ }
+ while ((p = fgets(sline, sizeof(sline), fp)) != NULL) {
+ if (strncmp(sline, "root:", 5) != 0)
+ continue;
+ p += 5;
+ set(&pwd.pw_passwd, strsep(&p, ":"));
+ break;
+ }
+ fclose(fp);
+
+ /*
+ * If the password is still invalid, NULL it, and return.
+ */
+ if (p == NULL) {
+ warnx(_("%s: no entry for root"), _PATH_SHADOW_PASSWD);
+ *pwd.pw_passwd = '\0';
+ }
+ /* locked account passwords are valid too */
+ if (!locked_account_password(pwd.pw_passwd) && !valid(pwd.pw_passwd)) {
+ warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD);
+ *pwd.pw_passwd = '\0';
+ }
+ return &pwd;
+}
+
+/*
+ * Ask by prompt for the password.
+ */
+static void doprompt(const char *crypted, struct console *con, int deny)
+{
+ struct termios tty;
+
+ if (con->flags & CON_SERIAL) {
+ tty = con->tio;
+ /*
+ * For prompting: map NL in output to CR-NL
+ * otherwise we may see stairs in the output.
+ */
+ tty.c_oflag |= (ONLCR | OPOST);
+ tcsetattr(con->fd, TCSADRAIN, &tty);
+ }
+ if (!con->file) {
+ con->file = fdopen(con->fd, "r+");
+ if (!con->file)
+ goto err;
+ }
+
+ if (deny)
+ fprintf(con->file, _("\nCannot open access to console, the root account is locked.\n"
+ "See sulogin(8) man page for more details.\n\n"
+ "Press Enter to continue.\n"));
+ else {
+#if defined(USE_ONELINE)
+ if (crypted[0] && !locked_account_password(crypted))
+ fprintf(con->file, _("Give root password for login: "));
+ else
+ fprintf(con->file, _("Press Enter for login: "));
+#else
+ if (crypted[0] && !locked_account_password(crypted))
+ fprintf(con->file, _("Give root password for maintenance\n"));
+ else
+ fprintf(con->file, _("Press Enter for maintenance\n"));
+ fprintf(con->file, _("(or press Control-D to continue): "));
+#endif
+ }
+ fflush(con->file);
+err:
+ if (con->flags & CON_SERIAL)
+ tcsetattr(con->fd, TCSADRAIN, &con->tio);
+}
+
+/*
+ * Make sure to have an own session and controlling terminal
+ */
+static void setup(struct console *con)
+{
+ int fd = con->fd;
+ const pid_t pid = getpid(), pgrp = getpgid(0), ppgrp =
+ getpgid(getppid()), ttypgrp = tcgetpgrp(fd);
+
+ if (con->flags & CON_NOTTY)
+ return;
+
+ /*
+ * Only go through this trouble if the new
+ * tty doesn't fall in this process group.
+ */
+ if (pgrp != ttypgrp && ppgrp != ttypgrp) {
+ if (pid != getsid(0)) {
+ if (pid == getpgid(0))
+ setpgid(0, getpgid(getppid()));
+ setsid();
+ }
+
+ mask_signal(SIGHUP, SIG_IGN, &saved_sighup);
+ if (ttypgrp > 0)
+ ioctl(STDIN_FILENO, TIOCNOTTY, (char *)1);
+ unmask_signal(SIGHUP, &saved_sighup);
+ if (fd > STDIN_FILENO) close(STDIN_FILENO);
+ if (fd > STDOUT_FILENO) close(STDOUT_FILENO);
+ if (fd > STDERR_FILENO) close(STDERR_FILENO);
+
+ ioctl(fd, TIOCSCTTY, (char *)1);
+ tcsetpgrp(fd, ppgrp);
+ }
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ con->fd = STDIN_FILENO;
+
+ for (fd = STDERR_FILENO+1; fd < 32; fd++) {
+ if (openfd & (1<<fd)) {
+ close(fd);
+ openfd &= ~(1<<fd);
+ }
+ }
+}
+
+/*
+ * Ask for the password. Note that there is no default timeout as we normally
+ * skip this during boot.
+ */
+static const char *getpasswd(struct console *con)
+{
+ struct sigaction sa;
+ struct termios tty;
+ static char pass[128], *ptr;
+ struct chardata *cp;
+ const char *ret = pass;
+ unsigned char tc;
+ char c, ascval;
+ int eightbit;
+ const int fd = con->fd;
+
+ if (con->flags & CON_NOTTY)
+ goto out;
+ cp = &con->cp;
+ tty = con->tio;
+
+ tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
+ tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ISIG);
+ tc = (tcsetattr(fd, TCSAFLUSH, &tty) == 0);
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = alrm_handler;
+ sa.sa_flags = 0;
+ sigaction(SIGALRM, &sa, NULL);
+
+ if (timeout)
+ alarm(timeout);
+
+ ptr = &pass[0];
+ cp->eol = *ptr = '\0';
+
+ eightbit = ((con->flags & CON_SERIAL) == 0 || (tty.c_cflag & (PARODD|PARENB)) == 0);
+ while (cp->eol == '\0') {
+ if (read(fd, &c, 1) < 1) {
+ if (errno == EINTR || errno == EAGAIN) {
+ if (alarm_rised) {
+ ret = NULL;
+ goto quit;
+ }
+ xusleep(250000);
+ continue;
+ }
+ ret = NULL;
+ switch (errno) {
+ case 0:
+ case EIO:
+ case ESRCH:
+ case EINVAL:
+ case ENOENT:
+ break;
+ default:
+ warn(_("cannot read %s"), con->tty);
+ break;
+ }
+ goto quit;
+ }
+
+ if (eightbit)
+ ascval = c;
+ else if (c != (ascval = (c & 0177))) {
+ uint32_t bits, mask;
+ for (bits = 1, mask = 1; mask & 0177; mask <<= 1) {
+ if (mask & ascval)
+ bits++;
+ }
+ cp->parity |= ((bits & 1) ? 1 : 2);
+ }
+
+ switch (ascval) {
+ case 0:
+ *ptr = '\0';
+ goto quit;
+ case CR:
+ case NL:
+ *ptr = '\0';
+ cp->eol = ascval;
+ break;
+ case BS:
+ case CERASE:
+ cp->erase = ascval;
+ if (ptr > &pass[0])
+ ptr--;
+ break;
+ case CKILL:
+ cp->kill = ascval;
+ while (ptr > &pass[0])
+ ptr--;
+ break;
+ case CEOF:
+ ret = NULL;
+ goto quit;
+ default:
+ if ((size_t)(ptr - &pass[0]) >= (sizeof(pass) -1 )) {
+ fprintf(stderr, "sulogin: input overrun at %s\n\r", con->tty);
+ ret = NULL;
+ goto quit;
+ }
+ *ptr++ = ascval;
+ break;
+ }
+ }
+quit:
+ alarm(0);
+ if (tc)
+ tcsetattr(fd, TCSAFLUSH, &con->tio);
+ tcfinal(con);
+ printf("\r\n");
+out:
+ return ret;
+}
+
+/*
+ * Password was OK, execute a shell.
+ */
+static void sushell(struct passwd *pwd)
+{
+ char shell[PATH_MAX];
+ char home[PATH_MAX];
+ char const *p;
+ char const *su_shell;
+
+ /*
+ * Set directory and shell.
+ */
+ if (chdir(pwd->pw_dir) != 0) {
+ warn(_("%s: change directory failed"), pwd->pw_dir);
+ printf(_("Logging in with home = \"/\".\n"));
+
+ if (chdir("/") != 0)
+ warn(_("change directory to system root failed"));
+ }
+
+ if ((p = getenv("SUSHELL")) != NULL)
+ su_shell = p;
+ else if ((p = getenv("sushell")) != NULL)
+ su_shell = p;
+ else {
+ if (pwd->pw_shell[0])
+ su_shell = pwd->pw_shell;
+ else
+ su_shell = "/bin/sh";
+ }
+ if ((p = strrchr(su_shell, '/')) == NULL)
+ p = su_shell;
+ else
+ p++;
+
+ snprintf(shell, sizeof(shell), profile ? "-%s" : "%s", p);
+
+ /*
+ * Set some important environment variables.
+ */
+ if (getcwd(home, sizeof(home)) == NULL)
+ strcpy(home, "/");
+
+ xsetenv("HOME", home, 1);
+ xsetenv("LOGNAME", "root", 1);
+ xsetenv("USER", "root", 1);
+ if (!profile)
+ xsetenv("SHLVL","0",1);
+
+ /*
+ * Try to execute a shell.
+ */
+ xsetenv("SHELL", su_shell, 1);
+ unmask_signal(SIGINT, &saved_sigint);
+ unmask_signal(SIGTSTP, &saved_sigtstp);
+ unmask_signal(SIGQUIT, &saved_sigquit);
+ mask_signal(SIGHUP, SIG_DFL, NULL);
+
+#ifdef HAVE_LIBSELINUX
+ if (is_selinux_enabled() > 0) {
+ security_context_t scon=NULL;
+ char *seuser=NULL;
+ char *level=NULL;
+ if (getseuserbyname("root", &seuser, &level) == 0) {
+ if (get_default_context_with_level(seuser, level, 0, &scon) == 0) {
+ if (setexeccon(scon) != 0)
+ warnx(_("setexeccon failed"));
+ freecon(scon);
+ }
+ }
+ free(seuser);
+ free(level);
+ }
+#endif
+ execl(su_shell, shell, NULL);
+ warn(_("failed to execute %s"), su_shell);
+
+ xsetenv("SHELL", "/bin/sh", 1);
+ execl("/bin/sh", profile ? "-sh" : "sh", NULL);
+ warn(_("failed to execute %s"), "/bin/sh");
+}
+
+static void usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(
+ " %s [options] [tty device]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Single-user login.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -p, --login-shell start a login shell\n"
+ " -t, --timeout <seconds> max time to wait for a password (default: no limit)\n"
+ " -e, --force examine password files directly if getpwnam(3) fails\n"),
+ out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(26));
+ printf(USAGE_MAN_TAIL("sulogin(8)"));
+}
+
+int main(int argc, char **argv)
+{
+ struct list_head *ptr, consoles;
+ struct console *con;
+ char *tty = NULL;
+ struct passwd *pwd;
+ const struct timespec sigwait = { .tv_sec = 0, .tv_nsec = 50000000 };
+ siginfo_t status = { 0 };
+ sigset_t set;
+ int c, reconnect = 0;
+ int opt_e = 0;
+ int wait = 0;
+ pid_t pid;
+
+ static const struct option longopts[] = {
+ { "login-shell", no_argument, NULL, 'p' },
+ { "timeout", required_argument, NULL, 't' },
+ { "force", no_argument, NULL, 'e' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ INIT_LIST_HEAD(&consoles);
+
+ /*
+ * If we are init we need to set up a own session.
+ */
+ if ((pid = getpid()) == 1) {
+ setsid();
+ ignore_result( ioctl(STDIN_FILENO, TIOCSCTTY, (char *) 1) );
+ }
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout); /* XXX */
+
+ /*
+ * See if we have a timeout flag.
+ */
+ while ((c = getopt_long(argc, argv, "ehpt:V", longopts, NULL)) != -1) {
+ switch(c) {
+ case 't':
+ timeout = strtou32_or_err(optarg, _("invalid timeout argument"));
+ break;
+ case 'p':
+ profile = 1;
+ break;
+ case 'e':
+ opt_e = 1;
+ break;
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ case 'h':
+ usage();
+ return EXIT_SUCCESS;
+ default:
+ /* Do not exit! getopt prints a warning. */
+ break;
+ }
+ }
+
+ if (geteuid() != 0)
+ errx(EXIT_FAILURE, _("only superuser can run this program"));
+
+ mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit);
+ mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp);
+ mask_signal(SIGINT, SIG_IGN, &saved_sigint);
+ mask_signal(SIGHUP, SIG_IGN, &saved_sighup);
+
+
+ emergency_do_mounts();
+ atexit( emergency_do_umounts );
+
+ /*
+ * See if we need to open an other tty device.
+ */
+ if (optind < argc)
+ tty = argv[optind];
+
+ if (!tty || *tty == '\0')
+ tty = getenv("CONSOLE");
+
+ /*
+ * Detect possible consoles, use stdin as fallback.
+ * If an optional tty is given, reconnect it to stdin.
+ */
+ reconnect = detect_consoles(tty, STDIN_FILENO, &consoles);
+
+ /*
+ * If previous stdin was not the specified tty and therefore reconnected
+ * to the specified tty also reconnect stdout and stderr.
+ */
+ if (reconnect) {
+ if (isatty(STDOUT_FILENO) == 0)
+ dup2(STDOUT_FILENO, STDIN_FILENO);
+ if (isatty(STDERR_FILENO) == 0)
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+ }
+
+ /*
+ * Should not happen
+ */
+ if (list_empty(&consoles)) {
+ if (!errno)
+ errno = ENOENT;
+ err(EXIT_FAILURE, _("cannot open console"));
+ }
+
+ /*
+ * Get the root password.
+ */
+ if ((pwd = getrootpwent(opt_e)) == NULL) {
+ warnx(_("cannot open password database"));
+ sleep(2);
+ return EXIT_FAILURE;
+ }
+
+ /*
+ * Ask for the password on the consoles.
+ */
+ list_for_each(ptr, &consoles) {
+ con = list_entry(ptr, struct console, entry);
+ if (con->id >= CONMAX)
+ break;
+ if (con->fd >= 0) {
+ openfd |= (1 << con->fd);
+ tcinit(con);
+ continue;
+ }
+ if ((con->fd = open(con->tty, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0)
+ continue;
+ openfd |= (1 << con->fd);
+ tcinit(con);
+ }
+ ptr = (&consoles)->next;
+
+ if (ptr->next == &consoles) {
+ con = list_entry(ptr, struct console, entry);
+ goto nofork;
+ }
+
+
+ mask_signal(SIGCHLD, chld_handler, &saved_sigchld);
+ do {
+ con = list_entry(ptr, struct console, entry);
+ if (con->id >= CONMAX)
+ break;
+
+ switch ((con->pid = fork())) {
+ case 0:
+ mask_signal(SIGCHLD, SIG_DFL, NULL);
+ nofork:
+ setup(con);
+ while (1) {
+ const char *passwd = pwd->pw_passwd;
+ const char *answer;
+ int doshell = 0;
+ int deny = !opt_e && locked_account_password(pwd->pw_passwd);
+
+ doprompt(passwd, con, deny);
+
+ if ((answer = getpasswd(con)) == NULL)
+ break;
+ if (deny)
+ exit(EXIT_FAILURE);
+
+ /* no password or locked account */
+ if (!passwd[0] || locked_account_password(passwd))
+ doshell++;
+ else {
+ const char *cryptbuf;
+ cryptbuf = crypt(answer, passwd);
+ if (cryptbuf == NULL)
+ warn(_("crypt failed"));
+ else if (strcmp(cryptbuf, pwd->pw_passwd) == 0)
+ doshell++;
+ }
+
+ if (doshell) {
+ /* sushell() unmask signals */
+ sushell(pwd);
+
+ mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit);
+ mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp);
+ mask_signal(SIGINT, SIG_IGN, &saved_sigint);
+
+ fprintf(stderr, _("cannot execute su shell\n\n"));
+ break;
+ }
+ fprintf(stderr, _("Login incorrect\n\n"));
+ }
+ if (alarm_rised) {
+ tcfinal(con);
+ warnx(_("Timed out\n\n"));
+ }
+ /*
+ * User pressed Control-D.
+ */
+ exit(0);
+ case -1:
+ warn(_("fork failed"));
+ /* fallthrough */
+ default:
+ break;
+ }
+
+ ptr = ptr->next;
+
+ } while (ptr != &consoles);
+
+ do {
+ int ret;
+
+ status.si_pid = 0;
+ ret = waitid(P_ALL, 0, &status, WEXITED);
+
+ if (ret == 0)
+ break;
+ if (ret < 0) {
+ if (errno == ECHILD)
+ break;
+ if (errno == EINTR)
+ continue;
+ }
+
+ errx(EXIT_FAILURE, _("cannot wait on su shell\n\n"));
+
+ } while (1);
+
+ list_for_each(ptr, &consoles) {
+ con = list_entry(ptr, struct console, entry);
+
+ if (con->fd < 0)
+ continue;
+ if (con->pid < 0)
+ continue;
+ if (con->pid == status.si_pid)
+ con->pid = -1;
+ else {
+ kill(con->pid, SIGTERM);
+ wait++;
+ }
+ }
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+
+ do {
+ int signum, ret;
+
+ if (!wait)
+ break;
+
+ status.si_pid = 0;
+ ret = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
+
+ if (ret < 0) {
+ if (errno == ECHILD)
+ break;
+ if (errno == EINTR)
+ continue;
+ }
+
+ if (!ret && status.si_pid > 0) {
+ list_for_each(ptr, &consoles) {
+ con = list_entry(ptr, struct console, entry);
+
+ if (con->fd < 0)
+ continue;
+ if (con->pid < 0)
+ continue;
+ if (con->pid == status.si_pid) {
+ con->pid = -1;
+ wait--;
+ }
+ }
+ continue;
+ }
+
+ signum = sigtimedwait(&set, NULL, &sigwait);
+ if (signum != SIGCHLD && signum < 0 && errno == EAGAIN)
+ break;
+
+ } while (1);
+
+ mask_signal(SIGCHLD, SIG_DFL, NULL);
+ return EXIT_SUCCESS;
+}
diff --git a/login-utils/utmpdump.1 b/login-utils/utmpdump.1
new file mode 100644
index 0000000..1f458e8
--- /dev/null
+++ b/login-utils/utmpdump.1
@@ -0,0 +1,93 @@
+.\" Copyright (C) 2010 Michael Krapp
+.\"
+.\" 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 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, write to the Free Software
+.\" Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+.\"
+.TH UTMPDUMP 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+utmpdump \- dump UTMP and WTMP files in raw format
+.SH SYNOPSIS
+.BR utmpdump " [options]"
+.RI [ filename ]
+.SH DESCRIPTION
+.B utmpdump
+is a simple program to dump UTMP and WTMP files in raw format, so they
+can be examined.
+.B utmpdump
+reads from stdin unless a
+.I filename
+is passed.
+.SH OPTIONS
+.TP
+.BR \-f , " \-\-follow"
+Output appended data as the file grows.
+.TP
+.BR \-o , " \-\-output " \fIfile
+Write command output to \fIfile\fR instead of standard output.
+.TP
+.BR \-r , " \-\-reverse"
+Undump, write back edited login information into the utmp or wtmp files.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NOTES
+.B utmpdump
+can be useful in cases of corrupted utmp or wtmp entries. It can dump
+out utmp/wtmp to an ASCII file, which can then be edited to remove
+bogus entries, and reintegrated using:
+.PP
+.RS
+.B utmpdump -r < ascii_file > wtmp
+.RE
+.PP
+But be warned,
+.B utmpdump
+was written for debugging purposes only.
+.SS File formats
+.PP
+The only binary version of the
+.BR utmp (5)
+is standardised. Textual dumps may become incompatible in future.
+.PP
+The version 2.28 was the last one that printed text output using
+.BR ctime (3)
+timestamp format. Newer dumps use millisecond precision ISO-8601 timestamp
+format in UTC-0 timezone. Conversion from former timestamp format can be
+made to binary, although attempt to do so can lead the timestamps to drift
+amount of timezone offset.
+.SH BUGS
+You may
+.B not
+use the
+.B \-r
+option, as the format for the utmp/wtmp files strongly depends on the input
+format. This tool was
+.B not
+written for normal use, but for debugging only.
+.SH AUTHOR
+Michael Krapp
+.SH "SEE ALSO"
+.BR last (1),
+.BR w (1),
+.BR who (1),
+.BR utmp (5)
+.SH AVAILABILITY
+The utmpdump command is part of the util-linux package and is available
+from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/utmpdump.c b/login-utils/utmpdump.c
new file mode 100644
index 0000000..5cc8783
--- /dev/null
+++ b/login-utils/utmpdump.c
@@ -0,0 +1,397 @@
+/*
+ * utmpdump
+ *
+ * Simple program to dump UTMP and WTMP files in raw format, so they can be
+ * examined.
+ *
+ * Based on utmpdump dump from sysvinit suite.
+ *
+ * Copyright (C) 1991-2000 Miquel van Smoorenburg <miquels@cistron.nl>
+ *
+ * Copyright (C) 1998 Danek Duvall <duvall@alumni.princeton.edu>
+ * Copyright (C) 2012 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 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <utmpx.h>
+#include <time.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/stat.h>
+#ifdef HAVE_INOTIFY_INIT
+#include <sys/inotify.h>
+#endif
+
+#include "c.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "closestream.h"
+#include "timeutils.h"
+
+static time_t strtotime(const char *s_time)
+{
+ struct tm tm;
+
+ memset(&tm, '\0', sizeof(struct tm));
+
+ if (s_time[0] == ' ' || s_time[0] == '\0')
+ return (time_t)0;
+
+ if (isdigit(s_time[0])) {
+ /* [1998-09-01T01:00:00,000000+00:00]
+ * Subseconds are parsed with strtousec(). Timezone is
+ * always UTC-0 */
+ strptime(s_time, "%Y-%m-%dT%H:%M:%S", &tm);
+ } else {
+ /* [Tue Sep 01 00:00:00 1998 GMT] */
+ strptime(s_time, "%a %b %d %T %Y", &tm);
+ /* Cheesy way of checking for DST. This could be needed
+ * with legacy dumps that used localtime(3). */
+ if (s_time[26] == 'D')
+ tm.tm_isdst = 1;
+ }
+ return timegm(&tm);
+}
+
+static suseconds_t strtousec(const char *s_time)
+{
+ const char *s = strchr(s_time, ',');
+ if (s)
+ return (suseconds_t) atoi(s + 1);
+ return 0;
+}
+
+#define cleanse(x) xcleanse(x, sizeof(x))
+static void xcleanse(char *s, int len)
+{
+ for ( ; *s && len-- > 0; s++)
+ if (!isprint(*s) || *s == '[' || *s == ']')
+ *s = '?';
+}
+
+static void print_utline(struct utmpx *ut, FILE *out)
+{
+ const char *addr_string;
+ char buffer[INET6_ADDRSTRLEN];
+ char time_string[40];
+ struct timeval tv;
+
+ if (ut->ut_addr_v6[1] || ut->ut_addr_v6[2] || ut->ut_addr_v6[3])
+ addr_string = inet_ntop(AF_INET6, &(ut->ut_addr_v6), buffer, sizeof(buffer));
+ else
+ addr_string = inet_ntop(AF_INET, &(ut->ut_addr_v6), buffer, sizeof(buffer));
+
+ tv.tv_sec = ut->ut_tv.tv_sec;
+ tv.tv_usec = ut->ut_tv.tv_usec;
+
+ if (strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA_GT, time_string,
+ sizeof(time_string)) != 0)
+ return;
+ cleanse(ut->ut_id);
+ cleanse(ut->ut_user);
+ cleanse(ut->ut_line);
+ cleanse(ut->ut_host);
+
+ /* type pid id user line host addr time */
+ fprintf(out, "[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15s] [%s]\n",
+ ut->ut_type, ut->ut_pid, ut->ut_id,
+ 8, (int)sizeof(ut->ut_user), ut->ut_user,
+ 12, (int)sizeof(ut->ut_line), ut->ut_line,
+ 20, (int)sizeof(ut->ut_host), ut->ut_host,
+ addr_string, time_string);
+}
+
+#ifdef HAVE_INOTIFY_INIT
+#define EVENTS (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)
+#define NEVENTS 4
+
+static void roll_file(const char *filename, off_t *size, FILE *out)
+{
+ FILE *in;
+ struct stat st;
+ struct utmpx ut;
+ off_t pos;
+
+ if (!(in = fopen(filename, "r")))
+ err(EXIT_FAILURE, _("cannot open %s"), filename);
+
+ if (fstat(fileno(in), &st) == -1)
+ err(EXIT_FAILURE, _("stat of %s failed"), filename);
+
+ if (st.st_size == *size)
+ goto done;
+
+ if (fseek(in, *size, SEEK_SET) != (off_t) -1) {
+ while (fread(&ut, sizeof(ut), 1, in) == 1)
+ print_utline(&ut, out);
+ }
+
+ pos = ftello(in);
+ /* If we've successfully read something, use the file position, this
+ * avoids data duplication. If we read nothing or hit an error,
+ * reset to the reported size, this handles truncated files.
+ */
+ *size = (pos != -1 && pos != *size) ? pos : st.st_size;
+
+done:
+ fclose(in);
+}
+
+static int follow_by_inotify(FILE *in, const char *filename, FILE *out)
+{
+ char buf[NEVENTS * sizeof(struct inotify_event)];
+ int fd, wd, event;
+ ssize_t length;
+ off_t size;
+
+ fd = inotify_init();
+ if (fd == -1)
+ return -1; /* probably reached any limit ... */
+
+ size = ftello(in);
+ fclose(in);
+
+ wd = inotify_add_watch(fd, filename, EVENTS);
+ if (wd == -1)
+ err(EXIT_FAILURE, _("%s: cannot add inotify watch."), filename);
+
+ while (wd >= 0) {
+ errno = 0;
+ length = read(fd, buf, sizeof(buf));
+
+ if (length < 0 && (errno == EINTR || errno == EAGAIN))
+ continue;
+ if (length < 0)
+ err(EXIT_FAILURE, _("%s: cannot read inotify events"),
+ filename);
+
+ for (event = 0; event < length;) {
+ struct inotify_event *ev =
+ (struct inotify_event *) &buf[event];
+
+ if (ev->mask & IN_MODIFY)
+ roll_file(filename, &size, out);
+ else {
+ close(wd);
+ wd = -1;
+ break;
+ }
+ event += sizeof(struct inotify_event) + ev->len;
+ }
+ }
+
+ close(fd);
+ return 0;
+}
+#endif /* HAVE_INOTIFY_INIT */
+
+static FILE *dump(FILE *in, const char *filename, int follow, FILE *out)
+{
+ struct utmpx ut;
+
+ if (follow)
+ ignore_result( fseek(in, -10 * sizeof(ut), SEEK_END) );
+
+ while (fread(&ut, sizeof(ut), 1, in) == 1)
+ print_utline(&ut, out);
+
+ if (!follow)
+ return in;
+
+#ifdef HAVE_INOTIFY_INIT
+ if (follow_by_inotify(in, filename, out) == 0)
+ return NULL; /* file already closed */
+ else
+#endif
+ /* fallback for systems without inotify or with non-free
+ * inotify instances */
+ for (;;) {
+ while (fread(&ut, sizeof(ut), 1, in) == 1)
+ print_utline(&ut, out);
+ sleep(1);
+ }
+
+ return in;
+}
+
+
+/* This function won't work properly if there's a ']' or a ' ' in the real
+ * token. Thankfully, this should never happen. */
+static int gettok(char *line, char *dest, int size, int eatspace)
+{
+ int bpos, epos, eaten;
+
+ bpos = strchr(line, '[') - line;
+ if (bpos < 0)
+ errx(EXIT_FAILURE, _("Extraneous newline in file. Exiting."));
+
+ line += 1 + bpos;
+ epos = strchr(line, ']') - line;
+ if (epos < 0)
+ errx(EXIT_FAILURE, _("Extraneous newline in file. Exiting."));
+
+ line[epos] = '\0';
+ eaten = bpos + epos + 1;
+
+ if (eatspace) {
+ char *t;
+ if ((t = strchr(line, ' ')))
+ *t = 0;
+ }
+ strncpy(dest, line, size);
+
+ return eaten + 1;
+}
+
+static void undump(FILE *in, FILE *out)
+{
+ struct utmpx ut;
+ char s_addr[INET6_ADDRSTRLEN + 1], s_time[29], *linestart, *line;
+
+ linestart = xmalloc(1024 * sizeof(*linestart));
+ s_time[28] = 0;
+
+ while (fgets(linestart, 1023, in)) {
+ line = linestart;
+ memset(&ut, '\0', sizeof(ut));
+ sscanf(line, "[%hd] [%d] [%4c] ", &ut.ut_type, &ut.ut_pid, ut.ut_id);
+
+ line += 19;
+ line += gettok(line, ut.ut_user, sizeof(ut.ut_user), 1);
+ line += gettok(line, ut.ut_line, sizeof(ut.ut_line), 1);
+ line += gettok(line, ut.ut_host, sizeof(ut.ut_host), 1);
+ line += gettok(line, s_addr, sizeof(s_addr) - 1, 1);
+ gettok(line, s_time, sizeof(s_time) - 1, 0);
+ if (strchr(s_addr, '.'))
+ inet_pton(AF_INET, s_addr, &(ut.ut_addr_v6));
+ else
+ inet_pton(AF_INET6, s_addr, &(ut.ut_addr_v6));
+
+ ut.ut_tv.tv_sec = strtotime(s_time);
+ ut.ut_tv.tv_usec = strtousec(s_time);
+
+ ignore_result( fwrite(&ut, sizeof(ut), 1, out) );
+ }
+
+ free(linestart);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+
+ fprintf(out,
+ _(" %s [options] [filename]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Dump UTMP and WTMP files in raw format.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -f, --follow output appended data as the file grows\n"), out);
+ fputs(_(" -r, --reverse write back dumped data into utmp file\n"), out);
+ fputs(_(" -o, --output <file> write to file instead of standard output\n"), out);
+ printf(USAGE_HELP_OPTIONS(22));
+
+ printf(USAGE_MAN_TAIL("utmpdump(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ FILE *in = NULL, *out = NULL;
+ int reverse = 0, follow = 0;
+ const char *filename = NULL;
+
+ static const struct option longopts[] = {
+ { "follow", no_argument, NULL, 'f' },
+ { "reverse", no_argument, NULL, 'r' },
+ { "output", required_argument, NULL, 'o' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ while ((c = getopt_long(argc, argv, "fro:hV", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'r':
+ reverse = 1;
+ break;
+
+ case 'f':
+ follow = 1;
+ break;
+
+ case 'o':
+ out = fopen(optarg, "w");
+ if (!out)
+ err(EXIT_FAILURE, _("cannot open %s"),
+ optarg);
+ break;
+
+ case 'h':
+ usage();
+ break;
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (!out)
+ out = stdout;
+
+ if (optind < argc) {
+ filename = argv[optind];
+ in = fopen(filename, "r");
+ if (!in)
+ err(EXIT_FAILURE, _("cannot open %s"), filename);
+ } else {
+ if (follow)
+ errx(EXIT_FAILURE, _("following standard input is unsupported"));
+ filename = "/dev/stdin";
+ in = stdin;
+ }
+
+ if (reverse) {
+ fprintf(stderr, _("Utmp undump of %s\n"), filename);
+ undump(in, out);
+ } else {
+ fprintf(stderr, _("Utmp dump of %s\n"), filename);
+ in = dump(in, filename, follow, out);
+ }
+
+ if (out != stdout && close_stream(out))
+ err(EXIT_FAILURE, _("write failed"));
+
+ if (in && in != stdin)
+ fclose(in);
+
+ return EXIT_SUCCESS;
+}
diff --git a/login-utils/vigr.8 b/login-utils/vigr.8
new file mode 100644
index 0000000..ff72d7a
--- /dev/null
+++ b/login-utils/vigr.8
@@ -0,0 +1 @@
+.so man8/vipw.8
diff --git a/login-utils/vipw.8 b/login-utils/vipw.8
new file mode 100644
index 0000000..41aa36e
--- /dev/null
+++ b/login-utils/vipw.8
@@ -0,0 +1,89 @@
+.\" Copyright (c) 1983, 1991 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)vipw.8 6.7 (Berkeley) 3/16/91
+.\"
+.TH VIPW "8" "September 2011" "util-linux" "System Administration"
+.SH NAME
+vipw, vigr \- edit the password or group file
+.SH SYNOPSIS
+.B vipw
+[options]
+.br
+.B vigr
+[options]
+.SH DESCRIPTION
+.B vipw
+edits the password file after setting the appropriate locks,
+and does any necessary processing after the password file is unlocked.
+If the password file is already locked for editing by another user,
+.B vipw
+will ask you
+to try again later. The default editor for
+.B vipw
+and
+.B vigr
+is
+.BR vi (1).
+.B vigr
+edits the group file in the same manner as
+.B vipw
+does the passwd file.
+.SH ENVIRONMENT
+If the following environment variable exists, it will be utilized by
+.B vipw
+and
+.BR vigr :
+.I
+.TP
+.B EDITOR
+The editor specified by the string
+.B EDITOR
+will be invoked instead of the default editor
+.BR vi (1).
+.SH SEE ALSO
+.BR vi (1),
+.BR passwd (1),
+.BR flock (2),
+.BR passwd (5)
+.SH HISTORY
+The
+.B vipw
+command appeared in 4.0BSD.
+.br
+The
+.B vigr
+command appeared in Util-Linux 2.6.
+.SH AVAILABILITY
+The vigr and vipw commands are part of the util-linux package and are available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/vipw.c b/login-utils/vipw.c
new file mode 100644
index 0000000..2502fcb
--- /dev/null
+++ b/login-utils/vipw.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 1987 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Updated Thu Oct 12 09:56:55 1995 by faith@cs.unc.edu with security
+ * patches from Zefram <A.Main@dcs.warwick.ac.uk>
+ *
+ * Updated Thu Nov 9 21:58:53 1995 by Martin Schulze
+ * <joey@finlandia.infodrom.north.de>. Support for vigr.
+ *
+ * Martin Schulze's patches adapted to Util-Linux by Nicolai Langfeldt.
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * - fixed strerr(errno) in gettext calls
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "closestream.h"
+#include "nls.h"
+#include "setpwnam.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "rpmatch.h"
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+#endif
+
+#define FILENAMELEN 67
+
+enum {
+ VIPW,
+ VIGR
+};
+static int program;
+static char orig_file[FILENAMELEN]; /* original file /etc/passwd or /etc/group */
+static char *tmp_file; /* tmp file */
+
+void pw_error (char *, int, int);
+
+static void copyfile(int from, int to)
+{
+ int nr, nw, off;
+ char buf[8 * 1024];
+
+ while ((nr = read(from, buf, sizeof(buf))) > 0)
+ for (off = 0; off < nr; nr -= nw, off += nw)
+ if ((nw = write(to, buf + off, nr)) < 0)
+ pw_error(tmp_file, 1, 1);
+
+ if (nr < 0)
+ pw_error(orig_file, 1, 1);
+#ifdef HAVE_EXPLICIT_BZERO
+ explicit_bzero(buf, sizeof(buf));
+#endif
+}
+
+static void pw_init(void)
+{
+ struct rlimit rlim;
+
+ /* Unlimited resource limits. */
+ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
+ (void)setrlimit(RLIMIT_CPU, &rlim);
+ (void)setrlimit(RLIMIT_FSIZE, &rlim);
+ (void)setrlimit(RLIMIT_STACK, &rlim);
+ (void)setrlimit(RLIMIT_DATA, &rlim);
+ (void)setrlimit(RLIMIT_RSS, &rlim);
+
+ /* Don't drop core (not really necessary, but GP's). */
+ rlim.rlim_cur = rlim.rlim_max = 0;
+ (void)setrlimit(RLIMIT_CORE, &rlim);
+
+ /* Turn off signals. */
+ (void)signal(SIGALRM, SIG_IGN);
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGPIPE, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGTSTP, SIG_IGN);
+ (void)signal(SIGTTOU, SIG_IGN);
+
+ /* Create with exact permissions. */
+ (void)umask(0);
+}
+
+static FILE * pw_tmpfile(int lockfd)
+{
+ FILE *fd;
+ char *tmpname = NULL;
+
+ if ((fd = xfmkstemp(&tmpname, "/etc", ".vipw")) == NULL) {
+ ulckpwdf();
+ err(EXIT_FAILURE, _("can't open temporary file"));
+ }
+
+ copyfile(lockfd, fileno(fd));
+ tmp_file = tmpname;
+ return fd;
+}
+
+static void pw_write(void)
+{
+ char tmp[FILENAMELEN + 4];
+
+ sprintf(tmp, "%s%s", orig_file, ".OLD");
+ unlink(tmp);
+
+ if (link(orig_file, tmp))
+ warn(_("%s: create a link to %s failed"), orig_file, tmp);
+
+#ifdef HAVE_LIBSELINUX
+ if (is_selinux_enabled() > 0) {
+ security_context_t passwd_context = NULL;
+ int ret = 0;
+ if (getfilecon(orig_file, &passwd_context) < 0) {
+ warnx(_("Can't get context for %s"), orig_file);
+ pw_error(orig_file, 1, 1);
+ }
+ ret = setfilecon(tmp_file, passwd_context);
+ freecon(passwd_context);
+ if (ret != 0) {
+ warnx(_("Can't set context for %s"), tmp_file);
+ pw_error(tmp_file, 1, 1);
+ }
+ }
+#endif
+
+ if (rename(tmp_file, orig_file) == -1) {
+ int errsv = errno;
+ errx(EXIT_FAILURE,
+ ("cannot write %s: %s (your changes are still in %s)"),
+ orig_file, strerror(errsv), tmp_file);
+ }
+ unlink(tmp_file);
+ free(tmp_file);
+ tmp_file = NULL;
+}
+
+static void pw_edit(void)
+{
+ int pstat;
+ pid_t pid;
+ char *p, *editor, *tk;
+
+ editor = getenv("EDITOR");
+ editor = xstrdup(editor ? editor : _PATH_VI);
+
+ tk = strtok(editor, " \t");
+ if (tk && (p = strrchr(tk, '/')) != NULL)
+ ++p;
+ else
+ p = editor;
+
+ pid = fork();
+ if (pid < 0)
+ err(EXIT_FAILURE, _("fork failed"));
+
+ if (!pid) {
+ execlp(editor, p, tmp_file, NULL);
+ errexec(editor);
+ }
+ for (;;) {
+ pid = waitpid(pid, &pstat, WUNTRACED);
+ if (WIFSTOPPED(pstat)) {
+ /* the editor suspended, so suspend us as well */
+ kill(getpid(), SIGSTOP);
+ kill(pid, SIGCONT);
+ } else {
+ break;
+ }
+ }
+ if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0)
+ pw_error(editor, 1, 1);
+
+ free(editor);
+}
+
+void __attribute__((__noreturn__))
+pw_error(char *name, int err, int eval)
+{
+ if (err) {
+ if (name)
+ warn("%s: ", name);
+ else
+ warn(NULL);
+ }
+ warnx(_("%s unchanged"), orig_file);
+
+ if (tmp_file)
+ unlink(tmp_file);
+ ulckpwdf();
+ exit(eval);
+}
+
+static void edit_file(int is_shadow)
+{
+ struct stat begin, end;
+ int passwd_file, ch_ret;
+ FILE *tmp_fd;
+
+ pw_init();
+
+ /* acquire exclusive lock */
+ if (lckpwdf() < 0)
+ err(EXIT_FAILURE, _("cannot get lock"));
+
+ passwd_file = open(orig_file, O_RDONLY | O_CLOEXEC, 0);
+ if (passwd_file < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), orig_file);
+ tmp_fd = pw_tmpfile(passwd_file);
+
+ if (fstat(fileno(tmp_fd), &begin))
+ pw_error(tmp_file, 1, 1);
+
+ pw_edit();
+
+ if (fstat(fileno(tmp_fd), &end))
+ pw_error(tmp_file, 1, 1);
+ /* Some editors, such as Vim with 'writebackup' mode enabled,
+ * use "atomic save" in which the old file is deleted and a new
+ * one with the same name created in its place. */
+ if (end.st_nlink == 0) {
+ if (close_stream(tmp_fd) != 0)
+ err(EXIT_FAILURE, _("write error"));
+ tmp_fd = fopen(tmp_file, "r" UL_CLOEXECSTR);
+ if (!tmp_fd)
+ err(EXIT_FAILURE, _("cannot open %s"), tmp_file);
+ if (fstat(fileno(tmp_fd), &end))
+ pw_error(tmp_file, 1, 1);
+ }
+ if (begin.st_mtime == end.st_mtime) {
+ warnx(_("no changes made"));
+ pw_error((char *)NULL, 0, 0);
+ }
+ /* pw_tmpfile() will create the file with mode 600 */
+ if (!is_shadow)
+ ch_ret = fchmod(fileno(tmp_fd), 0644);
+ else
+ ch_ret = fchmod(fileno(tmp_fd), 0400);
+ if (ch_ret < 0)
+ err(EXIT_FAILURE, "%s: %s", _("cannot chmod file"), orig_file);
+ if (close_stream(tmp_fd) != 0)
+ err(EXIT_FAILURE, _("write error"));
+ pw_write();
+ close(passwd_file);
+ ulckpwdf();
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, " %s\n", program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Edit the password or group file.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("vipw(8)"));
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ if (!strcmp(program_invocation_short_name, "vigr")) {
+ program = VIGR;
+ xstrncpy(orig_file, GROUP_FILE, sizeof(orig_file));
+ } else {
+ program = VIPW;
+ xstrncpy(orig_file, PASSWD_FILE, sizeof(orig_file));
+ }
+
+ while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (c) {
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ edit_file(0);
+
+ if (program == VIGR) {
+ strncpy(orig_file, SGROUP_FILE, FILENAMELEN - 1);
+ } else {
+ strncpy(orig_file, SHADOW_FILE, FILENAMELEN - 1);
+ }
+
+ if (access(orig_file, F_OK) == 0) {
+ char response[80];
+
+ fputs((program == VIGR)
+ ? _("You are using shadow groups on this system.\n")
+ : _("You are using shadow passwords on this system.\n"), stdout);
+
+ /* TRANSLATORS: this program uses for y and n rpmatch(3),
+ * which means they can be translated. */
+ printf(_("Would you like to edit %s now [y/n]? "), orig_file);
+
+ if (fgets(response, sizeof(response), stdin) &&
+ rpmatch(response) == RPMATCH_YES)
+ edit_file(1);
+ }
+ exit(EXIT_SUCCESS);
+}