summaryrefslogtreecommitdiff
path: root/login-utils
diff options
context:
space:
mode:
Diffstat (limited to 'login-utils')
-rw-r--r--login-utils/Makemodule.am146
-rw-r--r--login-utils/chfn.182
-rw-r--r--login-utils/chfn.c486
-rw-r--r--login-utils/chsh.165
-rw-r--r--login-utils/chsh.c380
-rw-r--r--login-utils/islocal.c115
-rw-r--r--login-utils/islocal.h1
-rw-r--r--login-utils/last.162
-rw-r--r--login-utils/last.c481
-rw-r--r--login-utils/login.1334
-rw-r--r--login-utils/login.c1477
-rw-r--r--login-utils/logindefs.c297
-rw-r--r--login-utils/logindefs.h12
-rw-r--r--login-utils/newgrp.134
-rw-r--r--login-utils/newgrp.c185
-rw-r--r--login-utils/selinux_utils.c51
-rw-r--r--login-utils/selinux_utils.h2
-rw-r--r--login-utils/setpwnam.c218
-rw-r--r--login-utils/setpwnam.h29
-rw-r--r--login-utils/su.c813
-rw-r--r--login-utils/sulogin.890
-rw-r--r--login-utils/sulogin.c609
-rw-r--r--login-utils/utmpdump.174
-rw-r--r--login-utils/utmpdump.c363
-rw-r--r--login-utils/vigr.81
-rw-r--r--login-utils/vipw.890
-rw-r--r--login-utils/vipw.c353
27 files changed, 6850 insertions, 0 deletions
diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am
new file mode 100644
index 0000000..41e32aa
--- /dev/null
+++ b/login-utils/Makemodule.am
@@ -0,0 +1,146 @@
+
+if BUILD_LAST
+usrbin_exec_PROGRAMS += last
+dist_man_MANS += login-utils/last.1
+last_SOURCES = login-utils/last.c
+endif
+
+if BUILD_SULOGIN
+sbin_PROGRAMS += sulogin
+dist_man_MANS += login-utils/sulogin.8
+sulogin_SOURCES = \
+ login-utils/sulogin.c
+sulogin_LDADD = $(LDADD) libcommon.la
+if HAVE_LIBCRYPT
+sulogin_LDADD += -lcrypt
+endif
+if HAVE_SELINUX
+sulogin_LDADD += -lselinux
+endif
+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 -lpam_misc
+if HAVE_AUDIT
+login_LDADD += -laudit
+endif
+if HAVE_SELINUX
+login_LDADD += -lselinux
+endif
+endif # BUILD_LOGIN
+
+
+if BUILD_UTMPDUMP
+usrbin_exec_PROGRAMS += utmpdump
+dist_man_MANS += login-utils/utmpdump.1
+utmpdump_SOURCES = login-utils/utmpdump.c
+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/islocal.c \
+ login-utils/islocal.h \
+ login-utils/setpwnam.c \
+ login-utils/setpwnam.h
+chfn_chsh_cflags = $(SUID_CFLAGS) $(AM_CFLAGS)
+chfn_chsh_ldflags = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+chfn_chsh_ldadd = libcommon.la -lpam -lpam_misc
+
+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 $(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
+su_SOURCES = \
+ login-utils/su.c \
+ login-utils/logindefs.c \
+ login-utils/logindefs.h
+su_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
+su_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+su_LDADD = $(LDADD) -lpam -lpam_misc
+endif
+
+
+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_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_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/chfn.1 b/login-utils/chfn.1
new file mode 100644
index 0000000..5329408
--- /dev/null
+++ b/login-utils/chfn.1
@@ -0,0 +1,82 @@
+.\"
+.\" 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.
+.\"
+.\" $Author: faith $
+.\" $Revision: 1.1 $
+.\" $Date: 1995/03/12 01:29:16 $
+.\"
+.TH CHFN 1 "July 2009" "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 ]
+.BR \-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.
+
+.B chfn
+is used to change local entries only. Use ypchfn, lchfn or any other
+implementation for non-local entries.
+.SS COMMAND LINE
+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.
+.SS INTERACTIVE MODE
+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.
+.SH OPTIONS
+.TP
+.BI "\-f, \-\-full-name " full-name
+Specify your real name.
+.TP
+.BI "\-o, \-\-office " office
+Specify your office room number.
+.TP
+.BI "\-p, \-\-office-phone " office-phone
+Specify your office phone number.
+.TP
+.BI "\-h, \-\-home-phone " home-phone
+Specify your home phone number.
+.TP
+.B "\-u, \-\-help"
+Print a usage message and exit.
+.TP
+.B "-v, \-\-version"
+Print version information and exit.
+.SH "SEE ALSO"
+.BR finger (1),
+.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
+ftp://ftp.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..02014c7
--- /dev/null
+++ b/login-utils/chfn.c
@@ -0,0 +1,486 @@
+/*
+ * chfn.c -- 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.
+ *
+ * $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 "pamfail.h"
+#include "setpwnam.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+# include <selinux/av_permissions.h>
+# include "selinux_utils.h"
+#endif
+
+static char buf[1024];
+
+struct finfo {
+ struct passwd *pw;
+ char *username;
+ char *full_name;
+ char *office;
+ char *office_phone;
+ char *home_phone;
+ char *other;
+};
+
+static int parse_argv(int argc, char *argv[], struct finfo *pinfo);
+static void parse_passwd(struct passwd *pw, struct finfo *pinfo);
+static void ask_info(struct finfo *oldfp, struct finfo *newfp);
+static char *prompt(char *question, char *def_val);
+static int check_gecos_string(char *msg, char *gecos);
+static int set_changed_data(struct finfo *oldfp, struct finfo *newfp);
+static int save_new_data(struct finfo *pinfo);
+
+/* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
+#define MAX_FIELD_SIZE 256
+
+static void __attribute__((__noreturn__)) usage(FILE *fp)
+{
+ fputs(USAGE_HEADER, fp);
+ fprintf(fp, _(" %s [options] [username]\n"), program_invocation_short_name);
+ 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);
+ fputs(_(" -u, --help display this help and exit\n"), fp);
+ fputs(_(" -v, --version output version information and exit\n"), fp);
+ fprintf(fp, USAGE_MAN_TAIL("chfn(1)"));
+ exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ uid_t uid;
+ struct finfo oldf, newf;
+ int interactive;
+ int status;
+
+ sanitize_env();
+ setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ /*
+ * "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".
+ *
+ * the reason the new finger information is not put _immediately_
+ * into "oldf" is that on the command line, new finger information
+ * can be specified before we know what user the information is
+ * being specified for.
+ */
+ uid = getuid();
+ memset(&oldf, 0, sizeof(oldf));
+ memset(&newf, 0, sizeof(newf));
+
+ interactive = parse_argv(argc, argv, &newf);
+ if (!newf.username) {
+ parse_passwd(getpwuid(uid), &oldf);
+ if (!oldf.username)
+ errx(EXIT_FAILURE, _("you (user %d) don't exist."),
+ uid);
+ } else {
+ parse_passwd(getpwnam(newf.username), &oldf);
+ if (!oldf.username)
+ errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
+ newf.username);
+ }
+
+ if (!(is_local(oldf.username)))
+ errx(EXIT_FAILURE, _("can only change local entries"));
+
+#ifdef HAVE_LIBSELINUX
+ if (is_selinux_enabled() > 0) {
+ if (uid == 0) {
+ if (checkAccess(oldf.username, PASSWD__CHFN) != 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"),
+ oldf.username);
+ }
+ }
+ if (setupDefaultContext(_PATH_PASSWD))
+ errx(EXIT_FAILURE,
+ _("can't set default context for %s"), _PATH_PASSWD);
+ }
+#endif
+
+ /* Reality check */
+ if (uid != 0 && uid != oldf.pw->pw_uid) {
+ errno = EACCES;
+ err(EXIT_FAILURE, NULL);
+ }
+
+ printf(_("Changing finger information for %s.\n"), oldf.username);
+
+#ifdef REQUIRE_PASSWORD
+ if (uid != 0) {
+ pam_handle_t *pamh = NULL;
+ struct pam_conv conv = { misc_conv, NULL };
+ int retcode;
+
+ retcode = pam_start("chfn", oldf.username, &conv, &pamh);
+ if (pam_fail_check(pamh, retcode))
+ exit(EXIT_FAILURE);
+
+ retcode = pam_authenticate(pamh, 0);
+ if (pam_fail_check(pamh, retcode))
+ exit(EXIT_FAILURE);
+
+ 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))
+ exit(EXIT_FAILURE);
+
+ retcode = pam_setcred(pamh, 0);
+ if (pam_fail_check(pamh, retcode))
+ exit(EXIT_FAILURE);
+
+ pam_end(pamh, 0);
+ /* no need to establish a session; this isn't a
+ * session-oriented activity... */
+ }
+#endif /* REQUIRE_PASSWORD */
+
+ if (interactive)
+ ask_info(&oldf, &newf);
+
+ if (!set_changed_data(&oldf, &newf)) {
+ printf(_("Finger information not changed.\n"));
+ return EXIT_SUCCESS;
+ }
+ status = save_new_data(&oldf);
+ return status;
+}
+
+/*
+ * parse_argv () --
+ * parse the command line arguments.
+ * returns true if no information beyond the username was given.
+ */
+static int parse_argv(int argc, char *argv[], struct finfo *pinfo)
+{
+ int index, c, status;
+ int info_given;
+
+ static struct option long_options[] = {
+ {"full-name", required_argument, 0, 'f'},
+ {"office", required_argument, 0, 'o'},
+ {"office-phone", required_argument, 0, 'p'},
+ {"home-phone", required_argument, 0, 'h'},
+ {"help", no_argument, 0, 'u'},
+ {"version", no_argument, 0, 'v'},
+ {NULL, no_argument, 0, '0'},
+ };
+
+ optind = 0;
+ info_given = false;
+ while (true) {
+ c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
+ &index);
+ if (c == -1)
+ break;
+ /* version? output version and exit. */
+ if (c == 'v') {
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ }
+ if (c == 'u')
+ usage(stdout);
+ /* all other options must have an argument. */
+ if (!optarg)
+ usage(stderr);
+ /* ok, we were given an argument */
+ info_given = true;
+ status = 0;
+
+ /* now store the argument */
+ switch (c) {
+ case 'f':
+ pinfo->full_name = optarg;
+ status = check_gecos_string(_("Name"), optarg);
+ break;
+ case 'o':
+ pinfo->office = optarg;
+ status = check_gecos_string(_("Office"), optarg);
+ break;
+ case 'p':
+ pinfo->office_phone = optarg;
+ status = check_gecos_string(_("Office Phone"), optarg);
+ break;
+ case 'h':
+ pinfo->home_phone = optarg;
+ status = check_gecos_string(_("Home Phone"), optarg);
+ break;
+ default:
+ usage(stderr);
+ }
+ if (status < 0)
+ exit(status);
+ }
+ /* done parsing arguments. check for a username. */
+ if (optind < argc) {
+ if (optind + 1 < argc)
+ usage(stderr);
+ pinfo->username = argv[optind];
+ }
+ return (!info_given);
+}
+
+/*
+ * parse_passwd () --
+ * take a struct password and fill in the fields of the struct finfo.
+ */
+static void parse_passwd(struct passwd *pw, struct finfo *pinfo)
+{
+ char *gecos;
+ char *cp;
+
+ if (pw) {
+ pinfo->pw = pw;
+ pinfo->username = pw->pw_name;
+ /* use pw_gecos - we take a copy since PAM destroys the original */
+ gecos = xstrdup(pw->pw_gecos);
+ cp = (gecos ? gecos : "");
+ pinfo->full_name = cp;
+ cp = strchr(cp, ',');
+ if (cp) {
+ *cp = 0, cp++;
+ } else
+ return;
+ pinfo->office = cp;
+ cp = strchr(cp, ',');
+ if (cp) {
+ *cp = 0, cp++;
+ } else
+ return;
+ pinfo->office_phone = cp;
+ cp = strchr(cp, ',');
+ if (cp) {
+ *cp = 0, cp++;
+ } else
+ return;
+ pinfo->home_phone = cp;
+ /* extra fields contain site-specific information, and can
+ * not be changed by this version of chfn. */
+ cp = strchr(cp, ',');
+ if (cp) {
+ *cp = 0, cp++;
+ } else
+ return;
+ pinfo->other = cp;
+ }
+}
+
+/*
+ * ask_info () --
+ * prompt the user for the finger information and store it.
+ */
+static void ask_info(struct finfo *oldfp, struct finfo *newfp)
+{
+ newfp->full_name = prompt(_("Name"), oldfp->full_name);
+ newfp->office = prompt(_("Office"), oldfp->office);
+ newfp->office_phone = prompt(_("Office Phone"), oldfp->office_phone);
+ newfp->home_phone = prompt(_("Home Phone"), oldfp->home_phone);
+ printf("\n");
+}
+
+/*
+ * prompt () --
+ * ask the user for a given field and check that the string is legal.
+ */
+static char *prompt(char *question, char *def_val)
+{
+ static char *blank = "none";
+ int len;
+ char *ans, *cp;
+
+ while (true) {
+ if (!def_val)
+ def_val = "";
+ printf("%s [%s]: ", question, def_val);
+ *buf = 0;
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ errx(EXIT_FAILURE, _("Aborted."));
+ /* remove the newline at the end of buf. */
+ ans = buf;
+ while (isspace(*ans))
+ ans++;
+ len = strlen(ans);
+ while (len > 0 && isspace(ans[len - 1]))
+ len--;
+ if (len <= 0)
+ return NULL;
+ ans[len] = 0;
+ if (!strcasecmp(ans, blank))
+ return "";
+ if (check_gecos_string(NULL, ans) >= 0)
+ break;
+ }
+ cp = (char *)xmalloc(len + 1);
+ strcpy(cp, ans);
+ return cp;
+}
+
+/*
+ * 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(char *msg, char *gecos)
+{
+ unsigned int i, c;
+
+ if (strlen(gecos) > MAX_FIELD_SIZE) {
+ if (msg)
+ warnx(_("field %s is too long"), msg);
+ else
+ warnx(_("field is too long"));
+ return -1;
+ }
+
+ for (i = 0; i < strlen(gecos); i++) {
+ c = gecos[i];
+ if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
+ if (msg)
+ warnx(_("%s: '%c' is not allowed"), msg, c);
+ else
+ warnx(_("'%c' is not allowed"), c);
+ return -1;
+ }
+ if (iscntrl(c)) {
+ if (msg)
+ warnx(_
+ ("%s: control characters are not allowed"),
+ msg);
+ else
+ warnx(_("control characters are not allowed"));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * set_changed_data () --
+ * incorporate the new data into the old finger info.
+ */
+static int set_changed_data(struct finfo *oldfp, struct finfo *newfp)
+{
+ int changed = false;
+
+ if (newfp->full_name) {
+ oldfp->full_name = newfp->full_name;
+ changed = true;
+ }
+ if (newfp->office) {
+ oldfp->office = newfp->office;
+ changed = true;
+ }
+ if (newfp->office_phone) {
+ oldfp->office_phone = newfp->office_phone;
+ changed = true;
+ }
+ if (newfp->home_phone) {
+ oldfp->home_phone = newfp->home_phone;
+ changed = true;
+ }
+
+ return changed;
+}
+
+/*
+ * save_new_data () --
+ * save the given finger info in /etc/passwd.
+ * return zero on success.
+ */
+static int save_new_data(struct finfo *pinfo)
+{
+ char *gecos;
+ int len;
+
+ /* null fields will confuse printf(). */
+ if (!pinfo->full_name)
+ pinfo->full_name = "";
+ if (!pinfo->office)
+ pinfo->office = "";
+ if (!pinfo->office_phone)
+ pinfo->office_phone = "";
+ if (!pinfo->home_phone)
+ pinfo->home_phone = "";
+ if (!pinfo->other)
+ pinfo->other = "";
+
+ /* create the new gecos string */
+ len = (strlen(pinfo->full_name) + strlen(pinfo->office) +
+ strlen(pinfo->office_phone) + strlen(pinfo->home_phone) +
+ strlen(pinfo->other) + 4);
+ gecos = (char *)xmalloc(len + 1);
+ sprintf(gecos, "%s,%s,%s,%s,%s", pinfo->full_name, pinfo->office,
+ pinfo->office_phone, pinfo->home_phone, pinfo->other);
+
+ /* remove trailing empty fields (but not subfields of pinfo->other) */
+ if (!pinfo->other[0]) {
+ while (len > 0 && gecos[len - 1] == ',')
+ len--;
+ gecos[len] = 0;
+ }
+
+ /* write the new struct passwd to the passwd file. */
+ pinfo->pw->pw_gecos = gecos;
+ if (setpwnam(pinfo->pw) < 0) {
+ warn("setpwnam");
+ printf(_
+ ("Finger information *NOT* changed. Try again later.\n"));
+ return -1;
+ }
+ printf(_("Finger information changed.\n"));
+ return 0;
+}
diff --git a/login-utils/chsh.1 b/login-utils/chsh.1
new file mode 100644
index 0000000..393ccb7
--- /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.
+.\"
+.\" $Author: faith $
+.\" $Revision: 1.1 $
+.\" $Date: 1995/03/12 01:28:58 $
+.\"
+.TH CHSH 1 "July 2009" "util-linux" "User Commands"
+.SH NAME
+chsh \- change your login shell
+.SH SYNOPSIS
+.B chsh
+.RB [ \-s
+.IR shell ]
+.RB [ \-l ]
+.RB [ \-u ]
+.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
+is used to change local entries only. Use ypchsh, lchsh or any other
+implementation for non-local entries.
+.SS 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 OPTIONS
+.TP
+.BI "\-s, \-\-shell " shell
+Specify your login shell.
+.TP
+.B "\-l, \-\-list-shells"
+Print the list of shells listed in
+.I /etc/shells
+and exit.
+.TP
+.B "\-u, \-\-help"
+Print a usage message and exit.
+.TP
+.B "-v, \-\-version"
+Print version information and exit.
+.SH "SEE ALSO"
+.BR login (1),
+.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
+ftp://ftp.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..7d944e1
--- /dev/null
+++ b/login-utils/chsh.c
@@ -0,0 +1,380 @@
+/*
+ * chsh.c -- 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.
+ *
+ * $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 <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 "pamfail.h"
+#include "pathnames.h"
+#include "setpwnam.h"
+#include "xalloc.h"
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+# include <selinux/av_permissions.h>
+# include "selinux_utils.h"
+#endif
+
+struct sinfo {
+ char *username;
+ char *shell;
+};
+
+static void parse_argv(int argc, char **argv, struct sinfo *pinfo);
+static char *prompt(char *question, char *def_val);
+static int check_shell(char *shell);
+static int get_shell_list(char *shell);
+
+static void __attribute__((__noreturn__)) usage (FILE *fp)
+{
+ fputs(USAGE_HEADER, fp);
+ fprintf(fp, _(" %s [options] [username]\n"), program_invocation_short_name);
+ 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);
+ fputs(_(" -u, --help display this help and exit\n"), fp);
+ fputs(_(" -v, --version output version information and exit\n"), fp);
+ fprintf(fp, USAGE_MAN_TAIL("chsh(1)"));
+ exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ char *shell, *oldshell;
+ uid_t uid;
+ struct sinfo info;
+ struct passwd *pw;
+
+ sanitize_env();
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ uid = getuid();
+ memset(&info, 0, sizeof(info));
+
+ parse_argv(argc, argv, &info);
+ pw = NULL;
+ 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);
+ }
+
+ if (!(is_local(pw->pw_name)))
+ errx(EXIT_FAILURE, _("can only change local entries."));
+
+#ifdef HAVE_LIBSELINUX
+ if (is_selinux_enabled() > 0) {
+ if (uid == 0) {
+ if (checkAccess(pw->pw_name, PASSWD__CHSH) != 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 */
+
+ /* reality check */
+ if (uid != 0 && uid != pw->pw_uid) {
+ errno = EACCES;
+ err(EXIT_FAILURE,
+ _("running UID doesn't match UID of user we're "
+ "altering, shell change denied"));
+ }
+ if (uid != 0 && !get_shell_list(oldshell)) {
+ errno = EACCES;
+ err(EXIT_FAILURE, _("your shell is not in %s, "
+ "shell change denied"), _PATH_SHELLS);
+ }
+
+ shell = info.shell;
+
+ printf(_("Changing shell for %s.\n"), pw->pw_name);
+
+#ifdef REQUIRE_PASSWORD
+ if (uid != 0) {
+ pam_handle_t *pamh = NULL;
+ struct pam_conv conv = { misc_conv, NULL };
+ int retcode;
+
+ retcode = pam_start("chsh", pw->pw_name, &conv, &pamh);
+ if (pam_fail_check(pamh, retcode))
+ exit(EXIT_FAILURE);
+
+ retcode = pam_authenticate(pamh, 0);
+ if (pam_fail_check(pamh, retcode))
+ exit(EXIT_FAILURE);
+
+ 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))
+ exit(EXIT_FAILURE);
+
+ retcode = pam_setcred(pamh, 0);
+ if (pam_fail_check(pamh, retcode))
+ exit(EXIT_FAILURE);
+
+ pam_end(pamh, 0);
+ /* no need to establish a session; this isn't a
+ * session-oriented activity... */
+ }
+#endif /* REQUIRE_PASSWORD */
+
+ if (!shell) {
+ shell = prompt(_("New shell"), oldshell);
+ if (!shell)
+ return EXIT_SUCCESS;
+ }
+
+ if (check_shell(shell) < 0)
+ return EXIT_FAILURE;
+
+ if (strcmp(oldshell, shell) == 0)
+ errx(EXIT_SUCCESS, _("Shell not changed."));
+ pw->pw_shell = shell;
+ if (setpwnam(pw) < 0) {
+ warn(_("setpwnam failed\n"
+ "Shell *NOT* changed. Try again later."));
+ return EXIT_FAILURE;
+ }
+ printf(_("Shell changed.\n"));
+ return EXIT_SUCCESS;
+}
+
+/*
+ * 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)
+{
+ int index, c;
+
+ static struct option long_options[] = {
+ {"shell", required_argument, 0, 's'},
+ {"list-shells", no_argument, 0, 'l'},
+ {"help", no_argument, 0, 'u'},
+ {"version", no_argument, 0, 'v'},
+ {NULL, no_argument, 0, '0'},
+ };
+
+ optind = c = 0;
+ while (c != EOF) {
+ c = getopt_long(argc, argv, "s:luv", long_options, &index);
+ switch (c) {
+ case -1:
+ break;
+ case 'v':
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ case 'u':
+ usage(stdout);
+ case 'l':
+ get_shell_list(NULL);
+ exit(EXIT_SUCCESS);
+ case 's':
+ if (!optarg)
+ usage(stderr);
+ pinfo->shell = optarg;
+ break;
+ default:
+ usage(stderr);
+ }
+ }
+ /* done parsing arguments. check for a username. */
+ if (optind < argc) {
+ if (optind + 1 < argc)
+ usage(stderr);
+ pinfo->username = argv[optind];
+ }
+}
+
+/*
+ * prompt () --
+ * ask the user for a given field and return it.
+ */
+static char *prompt(char *question, char *def_val)
+{
+ int len;
+ char *ans, *cp;
+ char buf[BUFSIZ];
+
+ if (!def_val)
+ def_val = "";
+ printf("%s [%s]: ", question, def_val);
+ *buf = 0;
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ errx(EXIT_FAILURE, _("Aborted."));
+ /* remove the newline at the end of buf. */
+ ans = buf;
+ while (isspace(*ans))
+ ans++;
+ len = strlen(ans);
+ while (len > 0 && isspace(ans[len - 1]))
+ len--;
+ if (len <= 0)
+ return NULL;
+ ans[len] = 0;
+ cp = (char *)xmalloc(len + 1);
+ strcpy(cp, ans);
+ return cp;
+}
+
+/*
+ * check_shell () -- if the shell is completely invalid, print
+ * an error and return (-1).
+ * if the shell is a bad idea, print a warning.
+ */
+static int check_shell(char *shell)
+{
+ unsigned int i, c;
+
+ if (!shell)
+ return -1;
+
+ if (*shell != '/') {
+ warnx(_("shell must be a full path name"));
+ return -1;
+ }
+ if (access(shell, F_OK) < 0) {
+ warnx(_("\"%s\" does not exist"), shell);
+ return -1;
+ }
+ if (access(shell, X_OK) < 0) {
+ printf(_("\"%s\" is not executable"), shell);
+ return -1;
+ }
+ /* keep /etc/passwd clean. */
+ for (i = 0; i < strlen(shell); i++) {
+ c = shell[i];
+ if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
+ warnx(_("'%c' is not allowed"), c);
+ return -1;
+ }
+ if (iscntrl(c)) {
+ warnx(_("control characters are not allowed"));
+ return -1;
+ }
+ }
+#ifdef ONLY_LISTED_SHELLS
+ if (!get_shell_list(shell)) {
+ 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
+ if (!get_shell_list(shell)) {
+ warnx(_("\"%s\" is not listed in %s.\n"
+ "Use %s -l to see list."), shell, _PATH_SHELLS,
+ program_invocation_short_name);
+ }
+#endif
+ return 0;
+}
+
+/*
+ * get_shell_list () -- if the given shell appears in /etc/shells,
+ * return true. if not, return false.
+ * if the given shell is NULL, /etc/shells is outputted to stdout.
+ */
+static int get_shell_list(char *shell_name)
+{
+ FILE *fp;
+ int found;
+ int len;
+ char buf[PATH_MAX];
+
+ found = false;
+ fp = fopen(_PATH_SHELLS, "r");
+ if (!fp) {
+ if (!shell_name)
+ warnx(_("No known shells."));
+ return true;
+ }
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ /* ignore comments */
+ if (*buf == '#')
+ continue;
+ len = strlen(buf);
+ /* strip the ending newline */
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = 0;
+ /* ignore lines that are too damn long */
+ else
+ continue;
+ /* check or output the shell */
+ if (shell_name) {
+ if (!strcmp(shell_name, buf)) {
+ found = true;
+ break;
+ }
+ } else
+ printf("%s\n", buf);
+ }
+ fclose(fp);
+ return found;
+}
diff --git a/login-utils/islocal.c b/login-utils/islocal.c
new file mode 100644
index 0000000..1f85166
--- /dev/null
+++ b/login-utils/islocal.c
@@ -0,0 +1,115 @@
+/*
+ * islocal.c - returns true if user is registered in the local
+ * /etc/passwd file. Written by Alvaro Martinez 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) {
+ perror(_PATH_PASSWD);
+ fprintf(stderr, _("cannot open %s"), _PATH_PASSWD);
+ exit(1);
+ } else {
+ return rv;
+ }
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+ atexit(close_stdout);
+ if (argc <= 2) {
+ fprintf(stderr, "usage: %s <passwdfile> <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..beb6917
--- /dev/null
+++ b/login-utils/last.1
@@ -0,0 +1,62 @@
+.TH LAST 1 "March 1992" "util-linux" "User Commands"
+.SH NAME
+last \(em indicate last logins by user or terminal
+.SH SYNOPSIS
+.ad l
+.B last
+.RB [ \-\fP\fInumber\fP ]
+.RB [ \-f
+.IR filename ]
+.RB [ \-t
+.IR tty ]
+.RB [ \-h
+.IR hostname ]
+.RB [ \-i
+.IR address ]
+.RB [ \-l ]
+.RB [ \-y ]
+.RI [ name ...]
+.ad b
+.SH DESCRIPTION
+\fBLast\fP looks back in the \fBwtmp\fP file which records all logins
+and logouts for information about a user, a teletype or any group of
+users and teletypes. Arguments specify names of users or teletypes of
+interest. If multiple arguments are given, the information which
+applies to any of the arguments is printed. For example ``\fBlast root
+console\fP'' would list all of root's sessions as well as all sessions
+on the console terminal. \fBLast\fP displays the sessions of the
+specified users and teletypes, most recent first, indicating the times
+at which the session began, the duration of the session, and the
+teletype which the session took place on. If the session is still
+continuing or was cut short by a reboot, \fBlast\fP so indicates.
+.LP
+The pseudo-user \fBreboot\fP logs in at reboots of the system.
+.LP
+\fBLast\fP with no arguments displays a record of all logins and
+logouts, in reverse order.
+.LP
+If \fBlast\fP is interrupted, it indicates how far the search has
+progressed in \fBwtmp\fP. If interrupted with a quit signal \fBlast\fP
+indicates how far the search has progressed so far, and the search
+continues.
+.SH OPTIONS
+.IP \fB\-\fP\fInumber\fP
+limit the number of entries displayed to that specified by \fInumber\fP.
+.IP "\fB\-f\fP \fIfilename\fP"
+Use \fIfilename\fP as the name of the accounting file instead of
+.BR /var/log/wtmp .
+.IP "\fB\-h\fP \fIhostname\fP"
+List only logins from \fIhostname\fP.
+.IP "\fB\-i\fP \fIIP address\fP"
+List only logins from \fIIP address\fP.
+.IP "\fB\-l\fP"
+List IP addresses of remote hosts instead of truncated host names.
+.IP "\fB\-t\fP \fItty\fP"
+List only logins on \fItty\fP.
+.IP "\fB\-y\fP"
+Also report year of dates.
+.SH FILES
+/var/log/wtmp \(em login data base
+.SH AVAILABILITY
+The last command is part of the util-linux package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
diff --git a/login-utils/last.c b/login-utils/last.c
new file mode 100644
index 0000000..77a890a
--- /dev/null
+++ b/login-utils/last.c
@@ -0,0 +1,481 @@
+/*
+ * Berkeley last for Linux. Currently maintained by poe@daimi.aau.dk at
+ * ftp://ftp.daimi.aau.dk/pub/linux/poe/admutil*
+ *
+ * Copyright (c) 1987 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.
+ */
+
+ /* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ */
+
+ /* 2001-02-14 Marek Zelem <marek@fornax.sk>
+ * - using mmap() on Linux - great speed improvement
+ */
+
+/*
+ * This command is deprecated. The utility is in maintenance mode,
+ * meaning we keep them in source tree for backward compatibility
+ * only. Do not waste time making this command better, unless the
+ * fix is about security or other very critical issue.
+ *
+ * See Documentation/deprecated.txt for more information.
+ */
+
+/*
+ * last
+ */
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+#include <utmp.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "closestream.h"
+#include "pathnames.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "c.h"
+
+#define SECDAY (24*60*60) /* seconds in a day */
+#define NO 0 /* false/no */
+#define YES 1 /* true/yes */
+
+static struct utmp utmpbuf;
+
+#define HMAX (int)sizeof(utmpbuf.ut_host) /* size of utmp host field */
+#define LMAX (int)sizeof(utmpbuf.ut_line) /* size of utmp tty field */
+#define NMAX (int)sizeof(utmpbuf.ut_name) /* size of utmp name field */
+
+/* maximum sizes used for printing */
+/* probably we want a two-pass version that computes the right length */
+#define P_HMAX min(HMAX, 16)
+#define P_LMAX min(LMAX, 8)
+#define P_NMAX min(NMAX, 16)
+
+typedef struct arg {
+ char *name; /* argument */
+#define HOST_TYPE -2
+#define TTY_TYPE -3
+#define USER_TYPE -4
+#define INET_TYPE -5
+ int type; /* type of arg */
+ struct arg *next; /* linked list pointer */
+} ARG;
+ARG *arglist; /* head of linked list */
+
+typedef struct ttytab {
+ long logout; /* log out time */
+ char tty[LMAX + 1]; /* terminal name */
+ struct ttytab *next; /* linked list pointer */
+} TTY;
+TTY *ttylist; /* head of linked list */
+
+static long currentout, /* current logout value */
+ maxrec; /* records to display */
+static char *file = _PATH_WTMP; /* wtmp file */
+
+static int doyear = 0; /* output year in dates */
+static int dolong = 0; /* print also ip-addr */
+
+static void wtmp(void);
+static void addarg(int, char *);
+static void hostconv(char *);
+static void onintr(int);
+static int want(struct utmp *, int);
+TTY *addtty(char *);
+static char *ttyconv(char *);
+
+int
+main(int argc, char **argv) {
+ int ch;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ while ((ch = getopt(argc, argv, "0123456789yli:f:h:t:")) != -1)
+ switch((char)ch) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ /*
+ * kludge: last was originally designed to take
+ * a number after a dash.
+ */
+ if (!maxrec)
+ maxrec = atol(argv[optind - 1] + 1);
+ break;
+ case 'f':
+ file = optarg;
+ break;
+ case 'h':
+ hostconv(optarg);
+ addarg(HOST_TYPE, optarg);
+ break;
+ case 't':
+ addarg(TTY_TYPE, ttyconv(optarg));
+ break;
+ case 'y':
+ doyear = 1;
+ break;
+ case 'l':
+ dolong = 1;
+ break;
+ case 'i':
+ addarg(INET_TYPE, optarg);
+ break;
+ case '?':
+ default:
+ fputs(_("usage: last [-#] [-f file] [-t tty] [-h hostname] [user ...]\n"), stderr);
+ exit(EXIT_FAILURE);
+ }
+ for (argv += optind; *argv; ++argv) {
+#define COMPATIBILITY
+#ifdef COMPATIBILITY
+ /* code to allow "last p5" to work */
+ addarg(TTY_TYPE, ttyconv(*argv));
+#endif
+ addarg(USER_TYPE, *argv);
+ }
+ wtmp();
+
+ return EXIT_SUCCESS;
+}
+
+static char *utmp_ctime(struct utmp *u)
+{
+ time_t t = (time_t) u->ut_time;
+ return ctime(&t);
+}
+
+/*
+ * print_partial_line --
+ * print the first part of each output line according to specified format
+ */
+static void
+print_partial_line(struct utmp *bp) {
+ char *ct;
+
+ ct = utmp_ctime(bp);
+ printf("%-*.*s %-*.*s ", P_NMAX, P_NMAX, bp->ut_name,
+ P_LMAX, P_LMAX, bp->ut_line);
+
+ if (dolong) {
+ if (bp->ut_addr) {
+ struct in_addr foo;
+ foo.s_addr = bp->ut_addr;
+ printf("%-*.*s ", P_HMAX, P_HMAX, inet_ntoa(foo));
+ } else {
+ printf("%-*.*s ", P_HMAX, P_HMAX, "");
+ }
+ } else {
+ printf("%-*.*s ", P_HMAX, P_HMAX, bp->ut_host);
+ }
+
+ if (doyear) {
+ printf("%10.10s %4.4s %5.5s ", ct, ct + 20, ct + 11);
+ } else {
+ printf("%10.10s %5.5s ", ct, ct + 11);
+ }
+}
+
+/*
+ * wtmp --
+ * read through the wtmp file
+ */
+static void
+wtmp(void) {
+ register struct utmp *bp; /* current structure */
+ register TTY *T; /* tty list entry */
+ long delta; /* time difference */
+ char *crmsg = NULL;
+ char *ct = NULL;
+ int fd;
+ struct utmp *utl;
+ struct stat st;
+ int utl_len;
+ int listnr = 0;
+ int i;
+
+ utmpname(file);
+
+ {
+#if defined(_HAVE_UT_TV)
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ utmpbuf.ut_tv.tv_sec = tv.tv_sec;
+ utmpbuf.ut_tv.tv_usec = tv.tv_usec;
+#else
+ time_t t;
+ time(&t);
+ utmpbuf.ut_time = t;
+#endif
+ }
+
+ (void)signal(SIGINT, onintr);
+ (void)signal(SIGQUIT, onintr);
+
+ if ((fd = open(file,O_RDONLY)) < 0)
+ err(EXIT_FAILURE, _("cannot open %s"), file);
+
+ fstat(fd, &st);
+ utl_len = st.st_size;
+ utl = mmap(NULL, utl_len, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_FILE, fd, 0);
+ if (utl == NULL)
+ err(EXIT_FAILURE, _("%s: mmap failed"), file);
+
+ listnr = utl_len/sizeof(struct utmp);
+
+ if(listnr)
+ ct = utmp_ctime(&utl[0]);
+
+ for(i = listnr - 1; i >= 0; i--) {
+ bp = utl+i;
+ /*
+ * if the terminal line is '~', the machine stopped.
+ * see utmp(5) for more info.
+ */
+ if (!strncmp(bp->ut_line, "~", LMAX)) {
+ /*
+ * utmp(5) also mentions that the user
+ * name should be 'shutdown' or 'reboot'.
+ * Not checking the name causes e.g. runlevel
+ * changes to be displayed as 'crash'. -thaele
+ */
+ if (!strncmp(bp->ut_user, "reboot", NMAX) ||
+ !strncmp(bp->ut_user, "shutdown", NMAX)) {
+ /* everybody just logged out */
+ for (T = ttylist; T; T = T->next)
+ T->logout = -bp->ut_time;
+ }
+
+ currentout = -bp->ut_time;
+ crmsg = (strncmp(bp->ut_name, "shutdown", NMAX)
+ ? "crash" : "down ");
+ if (!bp->ut_name[0])
+ (void)strcpy(bp->ut_name, "reboot");
+ if (want(bp, NO)) {
+ ct = utmp_ctime(bp);
+ if(bp->ut_type != LOGIN_PROCESS) {
+ print_partial_line(bp);
+ putchar('\n');
+ }
+ if (maxrec && !--maxrec)
+ return;
+ }
+ continue;
+ }
+ /* find associated tty */
+ for (T = ttylist;; T = T->next) {
+ if (!T) {
+ /* add new one */
+ T = addtty(bp->ut_line);
+ break;
+ }
+ if (!strncmp(T->tty, bp->ut_line, LMAX))
+ break;
+ }
+ if (bp->ut_name[0] && bp->ut_type != LOGIN_PROCESS
+ && bp->ut_type != DEAD_PROCESS
+ && want(bp, YES)) {
+
+ print_partial_line(bp);
+
+ if (!T->logout)
+ puts(_(" still logged in"));
+ else {
+ if (T->logout < 0) {
+ T->logout = -T->logout;
+ printf("- %s", crmsg);
+ }
+ else
+ printf("- %5.5s", ctime(&T->logout)+11);
+ delta = T->logout - bp->ut_time;
+ if (delta < SECDAY)
+ printf(" (%5.5s)\n", asctime(gmtime(&delta))+11);
+ else
+ printf(" (%ld+%5.5s)\n", delta / SECDAY, asctime(gmtime(&delta))+11);
+ }
+ if (maxrec != -1 && !--maxrec)
+ return;
+ }
+ T->logout = bp->ut_time;
+ utmpbuf.ut_time = bp->ut_time;
+ }
+ munmap(utl,utl_len);
+ close(fd);
+ if(ct) printf(_("\nwtmp begins %s"), ct); /* ct already ends in \n */
+}
+
+/*
+ * want --
+ * see if want this entry
+ */
+static int
+want(struct utmp *bp, int check) {
+ register ARG *step;
+
+ if (check) {
+ /*
+ * when uucp and ftp log in over a network, the entry in
+ * the utmp file is the name plus their process id. See
+ * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
+ */
+ if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
+ bp->ut_line[3] = '\0';
+ else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
+ bp->ut_line[4] = '\0';
+ }
+ if (!arglist)
+ return YES;
+
+ for (step = arglist; step; step = step->next)
+ switch(step->type) {
+ case HOST_TYPE:
+ if (!strncmp(step->name, bp->ut_host, HMAX))
+ return YES;
+ break;
+ case TTY_TYPE:
+ if (!strncmp(step->name, bp->ut_line, LMAX))
+ return YES;
+ break;
+ case USER_TYPE:
+ if (!strncmp(step->name, bp->ut_name, NMAX))
+ return YES;
+ break;
+ case INET_TYPE:
+ if ((in_addr_t) bp->ut_addr == inet_addr(step->name))
+ return YES;
+ break;
+ default:
+ abort();
+ }
+ return NO;
+}
+
+/*
+ * addarg --
+ * add an entry to a linked list of arguments
+ */
+static void
+addarg(int type, char *arg) {
+ register ARG *cur;
+
+ cur = xmalloc(sizeof(ARG));
+ cur->next = arglist;
+ cur->type = type;
+ cur->name = arg;
+ arglist = cur;
+}
+
+/*
+ * addtty --
+ * add an entry to a linked list of ttys
+ */
+TTY *
+addtty(char *ttyname) {
+ register TTY *cur;
+
+ cur = xmalloc(sizeof(TTY));
+ cur->next = ttylist;
+ cur->logout = currentout;
+ memcpy(cur->tty, ttyname, LMAX);
+ return(ttylist = cur);
+}
+
+/*
+ * hostconv --
+ * convert the hostname to search pattern; if the supplied host name
+ * has a domain attached that is the same as the current domain, rip
+ * off the domain suffix since that's what login(1) does.
+ */
+static void
+hostconv(char *arg) {
+ static int first = 1;
+ static char *hostdot,
+ name[MAXHOSTNAMELEN];
+ char *argdot;
+
+ if (!(argdot = strchr(arg, '.')))
+ return;
+ if (first) {
+ first = 0;
+ if (gethostname(name, sizeof(name)))
+ err(EXIT_FAILURE, _("gethostname failed"));
+
+ hostdot = strchr(name, '.');
+ }
+ if (hostdot && !strcmp(hostdot, argdot))
+ *argdot = '\0';
+}
+
+/*
+ * ttyconv --
+ * convert tty to correct name.
+ */
+static char *
+ttyconv(char *arg) {
+ char *mval;
+
+ /*
+ * kludge -- we assume that all tty's end with
+ * a two character suffix.
+ */
+ if (strlen(arg) == 2) {
+ /* either 6 for "ttyxx" or 8 for "console" */
+ mval = xmalloc(8);
+ if (!strncmp(arg, "co", 2))
+ (void)strcpy(mval, "console");
+ else {
+ (void)strcpy(mval, "tty");
+ (void)strncpy(mval + 3, arg, 4);
+ }
+ return mval;
+ }
+ if (!strncmp(arg, "/dev/", sizeof("/dev/") - 1))
+ return arg + 5;
+
+ return arg;
+}
+
+/*
+ * onintr --
+ * on interrupt, we inform the user how far we've gotten
+ */
+static void
+onintr(int signo) {
+ char *ct;
+
+ ct = utmp_ctime(&utmpbuf);
+ printf(_("\ninterrupted %10.10s %5.5s \n"), ct, ct + 11);
+ if (signo == SIGINT)
+ _exit(EXIT_FAILURE);
+ fflush(stdout); /* fix required for rsh */
+}
diff --git a/login-utils/login.1 b/login-utils/login.1
new file mode 100644
index 0000000..092213d
--- /dev/null
+++ b/login-utils/login.1
@@ -0,0 +1,334 @@
+.\" 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
+[
+.BR \-p
+] [
+.BR \-h
+.IR host
+] [
+.BR \-H
+] [
+.BR \-f
+.IR username
+|
+.IR 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 approprate. 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 adminitrator 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 other 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) or be initialize to the terminal type on your tty.
+.PP
+Then the user's shell is started. If no shell is specified for the
+user in
+.BR /etc\:/passwd ,
+then
+.B /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 a 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.
+.TP
+.B \-V
+Print version 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, ":" 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 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_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 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
+.IR 0600 .
+.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 not a full pathname is 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 does not exist then
+.I ~/.hushlogin
+.PP
+If the
+.B HUSHLOGIN_FILE
+item is empty then all checks are disabled.
+.RE
+.PP
+.B DEFAULT_HOME
+(boolean)
+.RS 4
+Indicate if login is allowed if we can not 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 an
+user enter 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 login. 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 login. 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 init (8),
+.BR getty (8),
+.BR mail (1),
+.BR passwd (1),
+.BR passwd (5),
+.BR environ (7),
+.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 PAM-only version by
+.MT kzak@\:redhat.\:com
+Karel Zak
+.ME
+.SH AVAILABILITY
+The
+.B login
+command is part of the util-linux package and is
+available from
+.UR ftp:\://ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/login-utils/login.c b/login-utils/login.c
new file mode 100644
index 0000000..c0cc00a
--- /dev/null
+++ b/login-utils/login.c
@@ -0,0 +1,1477 @@
+/*
+ * 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 <utmp.h>
+#include <stdlib.h>
+#include <sys/syslog.h>
+#include <sys/sysmacros.h>
+#ifdef HAVE_LINUX_MAJOR_H
+# include <linux/major.h>
+#endif
+#include <netdb.h>
+#include <lastlog.h>
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#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 "xalloc.h"
+#include "all-io.h"
+#include "fileutils.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 */
+
+ char *username; /* from command line or PAM */
+
+ struct passwd *pwd; /* user info */
+
+ 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[MAXHOSTNAMELEN + 1]; /* this machine */
+ char *thisdomain; /* this machine domain */
+ char *hostname; /* remote machine */
+ char hostaddress[16]; /* remote address */
+
+ pid_t pid;
+ int quiet; /* 1 is 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;
+
+#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);
+ /* TRANSLATORS: The standard value for %u is 60. */
+ warnx(_("timed out after %u seconds"), timeout);
+ 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 childrent by one signal to login
+ * process.
+ *
+ * Also, parent who is session leader is able (before setsid() in child) to
+ * inform child when controlling tty goes away (e.g. modem hangup, SIGHUP).
+ */
+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 use delay for all exit() calls when user is not authenticated or
+ * session 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) {
+ if (gethostname(cxt->thishost, sizeof(cxt->thishost))) {
+ if (domain)
+ *domain = NULL;
+ return NULL;
+ }
+ cxt->thishost[sizeof(cxt->thishost) -1] = '\0';
+ cxt->thisdomain = strchr(cxt->thishost, '.');
+ if (cxt->thisdomain)
+ *cxt->thisdomain++ = '\0';
+ }
+
+ if (domain)
+ *domain = cxt->thisdomain;
+ return cxt->thishost;
+}
+
+/*
+ * Output the /etc/motd file
+ *
+ * motd() 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. The empty MOTD_FILE option disables motd
+ * printing at all.
+ */
+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;
+
+ if (stat(motdfile, &st) || !st.st_size)
+ continue;
+ fd = open(motdfile, O_RDONLY, 0);
+ if (fd < 0)
+ continue;
+
+ sendfile(fileno(stdout), fd, NULL, st.st_size);
+ close(fd);
+ }
+
+ free(motdlist);
+}
+
+/*
+ * Nice and simple code provided by Linus Torvalds 16-Feb-93
+ * Nonblocking 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 the 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 currect terminal path and initialize cxt->tty_* variables.
+ */
+static void init_tty(struct login_context *cxt)
+{
+ const char *p;
+ struct stat st;
+ struct termios tt, ttt;
+
+ cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE);
+
+ cxt->tty_path = ttyname(0); /* libc calls istty() here */
+
+ /*
+ * 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);
+ }
+
+ if (strncmp(cxt->tty_path, "/dev/", 5) == 0)
+ cxt->tty_name = cxt->tty_path + 5;
+ else
+ cxt->tty_name = cxt->tty_path;
+
+ for (p = cxt->tty_name; p && *p; p++) {
+ if (isdigit(*p)) {
+ cxt->tty_number = p;
+ break;
+ }
+ }
+
+#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, TCSAFLUSH, &ttt);
+
+ /*
+ * Let's close file decriptors 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() wont 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);
+}
+
+
+/*
+ * Log failed login attempts in _PATH_BTMP if that 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 utmp ut;
+ struct timeval tv;
+
+ memset(&ut, 0, sizeof(ut));
+
+ strncpy(ut.ut_user,
+ cxt->username ? cxt->username : "(unknown)",
+ sizeof(ut.ut_user));
+
+ if (cxt->tty_number)
+ strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+ if (cxt->tty_name)
+ xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+
+#if defined(_HAVE_UT_TV) /* in <utmpbits.h> included by <utmp.h> */
+ gettimeofday(&tv, NULL);
+ ut.ut_tv.tv_sec = tv.tv_sec;
+ ut.ut_tv.tv_usec = tv.tv_usec;
+#else
+ {
+ time_t t;
+ time(&t);
+ ut.ut_time = t; /* ut_time is not always a time_t */
+ }
+#endif
+
+ ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
+ ut.ut_pid = cxt->pid;
+
+ if (cxt->hostname) {
+ xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
+ if (*cxt->hostaddress)
+ memcpy(&ut.ut_addr_v6, cxt->hostaddress,
+ sizeof(ut.ut_addr_v6));
+ }
+
+ updwtmp(_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 lastlog ll;
+ time_t t;
+ int fd;
+
+ if (!cxt->pwd)
+ return;
+
+ fd = open(_PATH_LASTLOG, O_RDWR, 0);
+ if (fd < 0)
+ return;
+
+ lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET);
+
+ /*
+ * 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);
+ }
+ lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET);
+ }
+
+ memset((char *)&ll, 0, sizeof(ll));
+
+ time(&t);
+ ll.ll_time = t; /* ll_time is always 32bit */
+
+ if (cxt->tty_name)
+ xstrncpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line));
+ if (cxt->hostname)
+ xstrncpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host));
+
+ if (write_all(fd, (char *)&ll, sizeof(ll)))
+ warn(_("write lastlog failed"));
+
+ close(fd);
+}
+
+/*
+ * Update wtmp and utmp logs
+ */
+static void log_utmp(struct login_context *cxt)
+{
+ struct utmp ut;
+ struct utmp *utp;
+ struct timeval tv;
+
+ utmpname(_PATH_UTMP);
+ setutent();
+
+ /* 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 = getutent()))
+ 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) {
+ setutent();
+ ut.ut_type = LOGIN_PROCESS;
+ if (cxt->tty_name)
+ strncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+ utp = getutline(&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)
+ strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
+ if (cxt->username)
+ strncpy(ut.ut_user, cxt->username, sizeof(ut.ut_user));
+ if (cxt->tty_name)
+ xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
+
+#ifdef _HAVE_UT_TV /* in <utmpbits.h> included by <utmp.h> */
+ gettimeofday(&tv, NULL);
+ ut.ut_tv.tv_sec = tv.tv_sec;
+ ut.ut_tv.tv_usec = tv.tv_usec;
+#else
+ {
+ time_t t;
+ time(&t);
+ ut.ut_time = t; /* ut_time is not always a time_t */
+ /* glibc2 #defines it as ut_tv.tv_sec */
+ }
+#endif
+ ut.ut_type = USER_PROCESS;
+ ut.ut_pid = cxt->pid;
+ if (cxt->hostname) {
+ xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
+ if (*cxt->hostaddress)
+ memcpy(&ut.ut_addr_v6, cxt->hostaddress,
+ sizeof(ut.ut_addr_v6));
+ }
+
+ pututline(&ut);
+ endutent();
+
+ updwtmp(_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);
+ }
+}
+
+static struct passwd *get_passwd_entry(const char *username,
+ char **pwdbuf,
+ struct passwd *pwd)
+{
+ struct passwd *res = NULL;
+ size_t sz = 16384;
+ int x;
+
+ if (!pwdbuf || !username)
+ return NULL;
+
+#ifdef _SC_GETPW_R_SIZE_MAX
+ {
+ long xsz = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (xsz > 0)
+ sz = (size_t) xsz;
+ }
+#endif
+ *pwdbuf = xrealloc(*pwdbuf, sz);
+
+ x = getpwnam_r(username, pwd, *pwdbuf, sz, &res);
+ if (!res) {
+ errno = x;
+ return NULL;
+ }
+ return res;
+}
+
+/* encapsulate stupid "void **" pam_get_item() API */
+static int loginpam_get_username(pam_handle_t *pamh, char **name)
+{
+ const void *item = (void *)*name;
+ int rc;
+ rc = pam_get_item(pamh, PAM_USER, &item);
+ *name = (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: " is -H is given
+ */
+static const char *loginpam_get_prompt(struct login_context *cxt)
+{
+ const char *host;
+ char *prompt, *dflt_prompt = _("login: ");
+ size_t sz;
+
+ if (cxt->nohost || !(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 need't the original username. We have to follow PAM. */
+ free(cxt->username);
+ 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
+ * an user enter 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 usage
+ * 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 position of the pam_setcred() call is discussable:
+ *
+ * - the PAM docs recommends 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 dependence between some session modules
+ * and 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);
+ }
+}
+
+/*
+ * We need to check effective UID/GID. For example $HOME could be on root
+ * squashed NFS or on NFS with UID mapping and access(2) uses real UID/GID.
+ * The open(2) seems as the surest solution.
+ * -- kzak@redhat.com (10-Apr-2009)
+ */
+static int effective_access(const char *path, int mode)
+{
+ int fd = open(path, mode);
+ if (fd != -1)
+ close(fd);
+ return fd == -1 ? -1 : 0;
+}
+
+/*
+ * Check per accout or global hush-login setting.
+ *
+ * Hushed mode is enabled:
+ *
+ * a) if global (e.g. /etc/hushlogins) hush file exists:
+ * 1) for ALL ACCOUNTS if the file is empty
+ * 2) for the current user if the username or shell are found in the file
+ *
+ * b) if ~/.hushlogin file exists
+ *
+ * The ~/.hushlogin is ignored if the global hush file exists.
+ *
+ * The HUSHLOGIN_FILE login.def variable overwrites the default hush filename.
+ *
+ * Note that shadow-utils login(1) does not support "a1)". The "a1)" is
+ * necessary if you want to use PAM for "Last login" message.
+ *
+ * -- Karel Zak <kzak@redhat.com> (26-Aug-2011)
+ *
+ *
+ * Per-account check requires some explanation: As root we may not be able to
+ * read the directory of the user if it is on an NFS mounted filesystem. We
+ * temporarily set our effective uid to the user-uid making sure that we keep
+ * root privs. in the real uid.
+ *
+ * A portable solution would require a fork(), but we rely on Linux having the
+ * BSD setreuid()
+ */
+static int get_hushlogin_status(struct passwd *pwd)
+{
+ const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL };
+ const char *file;
+ char buf[BUFSIZ];
+ int i;
+
+ file = getlogindefs_str("HUSHLOGIN_FILE", NULL);
+ if (file) {
+ if (!*file)
+ return 0; /* empty HUSHLOGIN_FILE defined */
+
+ files[0] = file;
+ files[1] = NULL;
+ }
+
+ for (i = 0; files[i]; i++) {
+ int ok = 0;
+
+ file = files[i];
+
+ /* Global hush-file*/
+ if (*file == '/') {
+ struct stat st;
+ FILE *f;
+
+ if (stat(file, &st) != 0)
+ continue; /* file does not exist */
+
+ if (st.st_size == 0)
+ return 1; /* for all accounts */
+
+ f = fopen(file, "r");
+ if (!f)
+ continue; /* ignore errors... */
+
+ while (ok == 0 && fgets(buf, sizeof(buf), f)) {
+ buf[strlen(buf) - 1] = '\0';
+ ok = !strcmp(buf, *buf == '/' ? pwd->pw_shell :
+ pwd->pw_name);
+ }
+ fclose(f);
+ if (ok)
+ return 1; /* found username/shell */
+
+ return 0; /* ignore per-account files */
+ }
+
+ /* Per-account setting */
+ if (strlen(pwd->pw_dir) + sizeof(file) + 2 > sizeof(buf))
+ continue;
+ else {
+ uid_t ruid = getuid();
+ gid_t egid = getegid();
+
+ sprintf(buf, "%s/%s", pwd->pw_dir, file);
+ setregid(-1, pwd->pw_gid);
+ setreuid(0, pwd->pw_uid);
+ ok = effective_access(buf, O_RDONLY) == 0;
+ setuid(0); /* setreuid doesn't do it alone! */
+ setreuid(ruid, 0);
+ setregid(-1, egid);
+
+ if (ok)
+ return 1; /* enabled by user */
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * 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 needn't the tty in parent who waits for child only.
+ * The child calls setsid() that detach from the tty as well.
+ */
+ ioctl(0, TIOCNOTTY, NULL);
+
+ /*
+ * We have care about SIGTERM, because leave PAM session without
+ * pam_close_session() is 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) {
+ /*
+ * fork() error
+ */
+ 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 cleanup session
+ */
+ close(0);
+ close(1);
+ close(2);
+ 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 = NULL, **env;
+ char tmp[PATH_MAX];
+ int len, i;
+
+ termenv = getenv("TERM");
+ termenv = termenv ? xstrdup(termenv) : "dumb";
+
+ /* destroy environment unless user has requested preservation (-p) */
+ if (!cxt->keep_env) {
+ environ = (char **) xmalloc(sizeof(char *));
+ memset(environ, 0, sizeof(char *));
+ }
+
+ setenv("HOME", pwd->pw_dir, 0); /* legal to override */
+ setenv("USER", pwd->pw_name, 1);
+ setenv("SHELL", pwd->pw_shell, 1);
+ setenv("TERM", termenv, 1);
+
+ if (pwd->pw_uid)
+ logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
+
+ else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0)
+ logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
+
+ /* 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 + 1 <= sizeof(tmp))
+ setenv("MAIL", tmp, 0);
+
+ /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll
+ * not allow modifying it.
+ */
+ setenv("LOGNAME", pwd->pw_name, 1);
+
+ env = pam_getenvlist(cxt->pamh);
+ for (i = 0; env && env[i]; i++)
+ putenv(env[i]);
+}
+
+/*
+ * Called for -h option, initialize 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;
+
+ memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr));
+ }
+ freeaddrinfo(info);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ int cnt;
+ char *childArgv[10];
+ char *buff;
+ int childArgc = 0;
+ int retcode;
+
+ char *pwdbuf = NULL;
+ struct passwd *pwd = NULL, _pwd;
+
+ struct login_context cxt = {
+ .tty_mode = TTY_MODE, /* tty chmod() */
+ .pid = getpid(), /* PID */
+ .conv = { misc_conv, NULL } /* PAM conversation function */
+ };
+
+ timeout = (unsigned int)getlogindefs_num("LOGIN_TIMEOUT", LOGIN_TIMEOUT);
+
+ signal(SIGALRM, timedout);
+ siginterrupt(SIGALRM, 1); /* we have to interrupt syscalls like ioclt() */
+ alarm(timeout);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ 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(argc, argv, "fHh:pV")) != -1)
+ switch (c) {
+ case 'f':
+ cxt.noauth = 1;
+ break;
+
+ case 'H':
+ cxt.nohost = 1;
+ break;
+
+ case 'h':
+ if (getuid()) {
+ fprintf(stderr,
+ _("login: -h for super-user 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 '?':
+ default:
+ fprintf(stderr, _("usage: login [ -p ] [ -h host ] [ -H ] [ -f username | username ]\n"));
+ exit(EXIT_FAILURE);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (*argv) {
+ char *p = *argv;
+ cxt.username = xstrdup(p);
+
+ /* wipe 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 */
+
+ openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
+
+ init_tty(&cxt);
+ 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 password expired (although
+ * kerberos ticket is valid). -- kzak@redhat.com (22-Feb-2006)
+ */
+ loginpam_acct(&cxt);
+
+ if (!(cxt.pwd = get_passwd_entry(cxt.username, &pwdbuf, &_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 the PAM modules might add groups during
+ * pam_setcred.
+ *
+ * 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);
+
+ 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, new session
+ * and reinilizalize syslog stuff.
+ */
+ fork_session(&cxt);
+
+ /* discard permissions last so 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..84f8d93
--- /dev/null
+++ b/login-utils/logindefs.c
@@ -0,0 +1,297 @@
+/*
+ * 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 "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;
+
+void (*logindefs_load_defaults)(void) = 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);
+}
+
+static void load_defaults()
+{
+ if (logindefs_load_defaults)
+ logindefs_load_defaults();
+ 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;
+}
+
+#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..c5ccbc9
--- /dev/null
+++ b/login-utils/logindefs.h
@@ -0,0 +1,12 @@
+#ifndef UTIL_LINUX_LOGINDEFS_H
+#define UTIL_LINUX_LOGINDEFS_H
+
+extern void logindefs_load_file(const char *filename);
+extern void (*logindefs_load_defaults)(void);
+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);
+
+#endif /* UTIL_LINUX_LOGINDEFS_H */
diff --git a/login-utils/newgrp.1 b/login-utils/newgrp.1
new file mode 100644
index 0000000..bb5eb00
--- /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
+ftp://ftp.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..4f6de12
--- /dev/null
+++ b/login-utils/newgrp.c
@@ -0,0 +1,185 @@
+/* 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
+ */
+
+/*
+ * This command is deprecated. The utility is in maintenance mode,
+ * meaning we keep them in source tree for backward compatibility
+ * only. Do not waste time making this command better, unless the
+ * fix is about security or other very critical issue.
+ *
+ * See Documentation/deprecated.txt for more information.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#endif
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "pathnames.h"
+
+/* try to read password from gshadow */
+static char *get_gshadow_pwd(char *groupname)
+{
+ 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 ? strdup(pwd) : NULL;
+}
+
+static int allow_setgid(struct passwd *pe, 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 = getpass(_("Password: "))))
+ if (strcmp(pwd, crypt(xpwd, pwd)) == 0)
+ /* password accepted */
+ return TRUE;
+
+ /* default to denial */
+ return FALSE;
+}
+
+static void __attribute__ ((__noreturn__)) usage(FILE * out)
+{
+ fprintf(out, USAGE_HEADER);
+ fprintf(out, _(" %s <group>\n"), program_invocation_short_name);
+ fprintf(out, USAGE_OPTIONS);
+ fprintf(out, USAGE_HELP);
+ fprintf(out, USAGE_VERSION);
+ fprintf(out, USAGE_MAN_TAIL("newgrp(1)"));
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct passwd *pw_entry;
+ struct group *gr_entry;
+ char *shell;
+ char 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(stdout);
+ default:
+ usage(stderr);
+ }
+
+ if (!(pw_entry = getpwuid(getuid())))
+ err(EXIT_FAILURE, _("who are you?"));
+
+ shell = (pw_entry->pw_shell && *pw_entry->pw_shell ?
+ pw_entry->pw_shell : _PATH_BSHELL);
+
+ 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
+ /* No group */
+ errx(EXIT_FAILURE, _("no such group"));
+ } else {
+ if (allow_setgid(pw_entry, gr_entry)) {
+ if (setgid(gr_entry->gr_gid) < 0)
+ err(EXIT_FAILURE, _("setgid failed"));
+ } else
+ errx(EXIT_FAILURE, _("permission denied"));
+ }
+ }
+
+ if (setuid(getuid()) < 0)
+ err(EXIT_FAILURE, _("setuid failed"));
+
+ fflush(stdout);
+ fflush(stderr);
+ execl(shell, shell, (char *)0);
+ warn(_("exec %s failed"), shell);
+ fflush(stderr);
+
+ return EXIT_FAILURE;
+}
diff --git a/login-utils/selinux_utils.c b/login-utils/selinux_utils.c
new file mode 100644
index 0000000..e709d00
--- /dev/null
+++ b/login-utils/selinux_utils.c
@@ -0,0 +1,51 @@
+#include <selinux/av_permissions.h>
+#include <selinux/context.h>
+#include <selinux/flask.h>
+#include <selinux/selinux.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "selinux_utils.h"
+
+int checkAccess(char *chuser, int access)
+{
+ int status = -1;
+ security_context_t user_context;
+ const char *user = NULL;
+ if (getprevcon(&user_context) == 0) {
+ context_t c = context_new(user_context);
+ user = context_user_get(c);
+ if (strcmp(chuser, user) == 0) {
+ status = 0;
+ } else {
+ struct av_decision avd;
+ int retval = security_compute_av(user_context,
+ user_context,
+ SECCLASS_PASSWD,
+ access,
+ &avd);
+ if ((retval == 0) &&
+ ((access & avd.allowed) == (unsigned)access))
+ status = 0;
+ }
+ context_free(c);
+ freecon(user_context);
+ }
+ return status;
+}
+
+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..5bf393c
--- /dev/null
+++ b/login-utils/selinux_utils.h
@@ -0,0 +1,2 @@
+extern int checkAccess(char *name,int access);
+extern int setupDefaultContext(char *orig_file);
diff --git a/login-utils/setpwnam.c b/login-utils/setpwnam.c
new file mode 100644
index 0000000..23aef53
--- /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 (né 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)
+{
+ FILE *fp = NULL, *pwf = NULL;
+ int save_errno;
+ int found;
+ int namelen;
+ int buflen = 256;
+ int contlen, rc;
+ char *linebuf = NULL;
+ char *tmpname = NULL;
+ char *atomic_dir = "/etc";
+
+ pw_init();
+
+ if ((fp = xfmkstemp(&tmpname, atomic_dir)) == 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();
+ 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..7d69445
--- /dev/null
+++ b/login-utils/setpwnam.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#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);
diff --git a/login-utils/su.c b/login-utils/su.c
new file mode 100644
index 0000000..35a4277
--- /dev/null
+++ b/login-utils/su.c
@@ -0,0 +1,813 @@
+/* su 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
+
+ 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. */
+
+/* Run a shell with the real and effective UID and GID and groups
+ of USER, default `root'.
+
+ The shell run is taken from USER's password entry, /bin/sh if
+ none is specified there. If the account has a password, su
+ prompts for a password unless run by a user with real UID 0.
+
+ Does not change the current directory.
+ Sets `HOME' and `SHELL' from the password entry for USER, and if
+ USER is not root, sets `USER' and `LOGNAME' to USER.
+ The subshell is not a login shell.
+
+ If one or more ARGs are given, they are passed as additional
+ arguments to the subshell.
+
+ Does not handle /bin/sh or other shells specially
+ (setting argv[0] to "-su", passing -c only to certain shells, etc.).
+ I don't see the point in doing that, and it's ugly.
+
+ Based on an implemenation by David MacKenzie <djm@gnu.ai.mit.edu>. */
+
+enum
+{
+ EXIT_CANNOT_INVOKE = 126,
+ EXIT_ENOENT = 127
+};
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <syslog.h>
+
+#include "err.h"
+
+#include <stdbool.h>
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "env.h"
+
+/* name of the pam configuration files. separate configs for su and su - */
+#define PAM_SERVICE_NAME "su"
+#define PAM_SERVICE_NAME_L "su-l"
+
+#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
+
+#include "logindefs.h"
+
+/* 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
+
+static void run_shell (char const *, char const *, char **, size_t)
+ __attribute__ ((__noreturn__));
+
+/* If true, pass the `-f' option to the subshell. */
+static bool fast_startup;
+
+/* If true, simulate a login instead of just starting a shell. */
+static bool simulate_login;
+
+/* If true, change some environment vars to indicate the user su'd to. */
+static bool change_environment;
+
+/* If true, then don't call setsid() with a command. */
+int same_session = 0;
+
+static bool _pam_session_opened;
+static bool _pam_cred_established;
+static sig_atomic_t volatile caught_signal = false;
+static pam_handle_t *pamh = NULL;
+
+static struct option const 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'},
+ {"shell", required_argument, NULL, 's'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {NULL, 0, NULL, 0}
+};
+
+/* 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_su (struct passwd const *pw, bool successful)
+{
+ const char *new_user, *old_user, *tty;
+
+ new_user = pw->pw_name;
+ /* The utmp entry (via getlogin) is probably the best way to identify
+ the user, especially if someone su's from a su-shell. */
+ old_user = getlogin ();
+ if (!old_user)
+ {
+ /* getlogin can fail -- usually due to lack of utmp entry.
+ Resort to getpwuid. */
+ struct passwd *pwd = getpwuid (getuid ());
+ old_user = (pwd ? pwd->pw_name : "");
+ }
+ tty = ttyname (STDERR_FILENO);
+ if (!tty)
+ tty = "none";
+
+ openlog (program_invocation_short_name, 0 , LOG_AUTH);
+ syslog (LOG_NOTICE, "%s(to %s) %s on %s",
+ successful ? "" : "FAILED SU ",
+ new_user, old_user, tty);
+ closelog ();
+}
+
+static struct pam_conv conv =
+{
+ misc_conv,
+ NULL
+};
+
+static void
+cleanup_pam (int retcode)
+{
+ int saved_errno = errno;
+
+ if (_pam_session_opened)
+ pam_close_session (pamh, 0);
+
+ if (_pam_cred_established)
+ pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
+
+ pam_end(pamh, retcode);
+
+ errno = saved_errno;
+}
+
+/* Signal handler for parent process. */
+static void
+su_catch_sig (int sig __attribute__((__unused__)))
+{
+ caught_signal = true;
+}
+
+/* Export env variables declared by PAM modules. */
+static void
+export_pamenv (void)
+{
+ char **env;
+
+ /* This is a copy but don't care to free as we exec later anyways. */
+ env = pam_getenvlist (pamh);
+ while (env && *env)
+ {
+ if (putenv (*env) != 0)
+ err (EXIT_FAILURE, NULL);
+ env++;
+ }
+}
+
+static void
+create_watching_parent (void)
+{
+ pid_t child;
+ sigset_t ourset;
+ int status = 0;
+ int retval;
+
+ retval = pam_open_session (pamh, 0);
+ if (is_pam_failure(retval))
+ {
+ cleanup_pam (retval);
+ errx (EXIT_FAILURE, _("cannot not open session: %s"),
+ pam_strerror (pamh, retval));
+ }
+ else
+ _pam_session_opened = 1;
+
+ child = fork ();
+ if (child == (pid_t) -1)
+ {
+ cleanup_pam (PAM_ABORT);
+ err (EXIT_FAILURE, _("cannot create child process"));
+ }
+
+ /* the child proceeds to run the shell */
+ if (child == 0)
+ return;
+
+ /* 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"), "/");
+
+ 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);
+ if (!same_session)
+ {
+ if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
+ {
+ warn (_("cannot set signal handler"));
+ caught_signal = true;
+ }
+ }
+ if (!caught_signal && (sigaddset(&ourset, SIGTERM)
+ || sigaddset(&ourset, SIGALRM)
+ || sigaction(SIGTERM, &action, NULL)
+ || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
+ warn (_("cannot set signal handler"));
+ caught_signal = true;
+ }
+ if (!caught_signal && !same_session && (sigaction(SIGINT, &action, NULL)
+ || sigaction(SIGQUIT, &action, NULL)))
+ {
+ warn (_("cannot set signal handler"));
+ caught_signal = true;
+ }
+ }
+ if (!caught_signal)
+ {
+ pid_t pid;
+ for (;;)
+ {
+ pid = waitpid (child, &status, WUNTRACED);
+
+ if (pid != (pid_t)-1 && WIFSTOPPED (status))
+ {
+ kill (getpid (), SIGSTOP);
+ /* once we get here, we must have resumed */
+ kill (pid, SIGCONT);
+ }
+ else
+ break;
+ }
+ if (pid != (pid_t)-1)
+ if (WIFSIGNALED (status))
+ status = WTERMSIG (status) + 128;
+ else
+ status = WEXITSTATUS (status);
+ else
+ status = 1;
+ }
+ else
+ status = 1;
+
+ if (caught_signal)
+ {
+ fprintf (stderr, _("\nSession terminated, killing shell..."));
+ kill (child, SIGTERM);
+ }
+
+ cleanup_pam (PAM_SUCCESS);
+
+ if (caught_signal)
+ {
+ sleep (2);
+ kill (child, SIGKILL);
+ fprintf (stderr, _(" ...killed.\n"));
+ }
+ exit (status);
+}
+
+static void
+authenticate (const struct passwd *pw)
+{
+ const struct passwd *lpw;
+ const char *cp;
+ int retval;
+
+ retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME,
+ pw->pw_name, &conv, &pamh);
+ if (is_pam_failure(retval))
+ goto done;
+
+ if (isatty (0) && (cp = ttyname (0)) != NULL)
+ {
+ const char *tty;
+
+ if (strncmp (cp, "/dev/", 5) == 0)
+ tty = cp + 5;
+ else
+ tty = cp;
+ retval = pam_set_item (pamh, PAM_TTY, tty);
+ if (is_pam_failure(retval))
+ goto done;
+ }
+
+ lpw = getpwuid (getuid ());
+ if (lpw && lpw->pw_name)
+ {
+ retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
+ if (is_pam_failure(retval))
+ goto done;
+ }
+
+ retval = pam_authenticate (pamh, 0);
+ if (is_pam_failure(retval))
+ goto done;
+
+ retval = pam_acct_mgmt (pamh, 0);
+ if (retval == PAM_NEW_AUTHTOK_REQD)
+ {
+ /* Password has expired. Offer option to change it. */
+ retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ }
+
+done:
+
+ log_su (pw, !is_pam_failure(retval));
+
+ if (is_pam_failure(retval))
+ {
+ const char *msg = pam_strerror(pamh, retval);
+ pam_end(pamh, retval);
+ sleep (getlogindefs_num ("FAIL_DELAY", 1));
+ errx (EXIT_FAILURE, "%s", msg?msg:_("incorrect password"));
+ }
+}
+
+/* Add or clear /sbin and /usr/sbin for the su command
+ used without `-'. */
+
+/* Set if /sbin is found in path. */
+#define SBIN_MASK 0x01
+/* Set if /usr/sbin is found in path. */
+#define USBIN_MASK 0x02
+
+static char *
+addsbin (const char *const path)
+{
+ unsigned char smask = 0;
+ char *ptr, *tmp, *cur, *ret = NULL;
+ size_t len;
+
+ if (!path || *path == 0)
+ return NULL;
+
+ tmp = xstrdup (path);
+ cur = tmp;
+ for (ptr = strsep (&cur, ":"); ptr != NULL; ptr = strsep (&cur, ":"))
+ {
+ if (!strcmp (ptr, "/sbin"))
+ smask |= SBIN_MASK;
+ if (!strcmp (ptr, "/usr/sbin"))
+ smask |= USBIN_MASK;
+ }
+
+ if ((smask & (USBIN_MASK|SBIN_MASK)) == (USBIN_MASK|SBIN_MASK))
+ {
+ free (tmp);
+ return NULL;
+ }
+
+ len = strlen (path);
+ if (!(smask & USBIN_MASK))
+ len += strlen ("/usr/sbin:");
+
+ if (!(smask & SBIN_MASK))
+ len += strlen (":/sbin");
+
+ ret = xmalloc (len + 1);
+ strcpy (tmp, path);
+
+ *ret = 0;
+ cur = tmp;
+ for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
+ {
+ if (!strcmp (ptr, "."))
+ continue;
+ if (*ret)
+ strcat (ret, ":");
+ if (!(smask & USBIN_MASK) && !strcmp (ptr, "/bin"))
+ {
+ strcat (ret, "/usr/sbin:");
+ strcat (ret, ptr);
+ smask |= USBIN_MASK;
+ continue;
+ }
+ if (!(smask & SBIN_MASK) && !strcmp (ptr, "/usr/bin"))
+ {
+ strcat (ret, ptr);
+ strcat (ret, ":/sbin");
+ smask |= SBIN_MASK;
+ continue;
+ }
+ strcat (ret, ptr);
+ }
+ free (tmp);
+
+ if (!(smask & USBIN_MASK))
+ strcat (ret, ":/usr/sbin");
+
+ if (!(smask & SBIN_MASK))
+ strcat (ret, ":/sbin");
+
+ return ret;
+}
+
+static char *
+clearsbin (const char *const path)
+{
+ char *ptr, *tmp, *cur, *ret = NULL;
+
+ if (!path || *path == 0)
+ return NULL;
+
+ tmp = xstrdup (path);
+
+ ret = xmalloc (strlen (path) + 1);
+ *ret = 0;
+ cur = tmp;
+ for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
+ {
+ if (!strcmp (ptr, "/sbin"))
+ continue;
+ if (!strcmp (ptr, "/usr/sbin"))
+ continue;
+ if (!strcmp (ptr, "/usr/local/sbin"))
+ continue;
+ if (*ret)
+ strcat (ret, ":");
+ strcat (ret, ptr);
+ }
+ free (tmp);
+
+ return ret;
+}
+
+static void
+set_path(const struct passwd* pw)
+{
+ int r;
+ if (pw->pw_uid)
+ r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
+
+ else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
+ r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
+
+ if (r != 0)
+ err (EXIT_FAILURE, _("failed to set PATH"));
+}
+
+/* Update `environ' for the new shell based on PW, with SHELL being
+ the value for the SHELL environment variable. */
+
+static void
+modify_environment (const struct passwd *pw, const char *shell)
+{
+ if (simulate_login)
+ {
+ /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
+ Unset all other environment variables. */
+ char const *term = getenv ("TERM");
+ if (term)
+ term = xstrdup (term);
+ environ = xmalloc ((6 + !!term) * sizeof (char *));
+ environ[0] = NULL;
+ if (term)
+ xsetenv ("TERM", term, 1);
+ xsetenv ("HOME", pw->pw_dir, 1);
+ xsetenv ("SHELL", shell, 1);
+ xsetenv ("USER", pw->pw_name, 1);
+ xsetenv ("LOGNAME", pw->pw_name, 1);
+ set_path(pw);
+ }
+ else
+ {
+ /* Set HOME, SHELL, and if not becoming a super-user,
+ USER and LOGNAME. */
+ if (change_environment)
+ {
+ xsetenv ("HOME", pw->pw_dir, 1);
+ xsetenv ("SHELL", shell, 1);
+ if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
+ set_path(pw);
+ else
+ {
+ char const *path = getenv ("PATH");
+ char *new = NULL;
+
+ if (pw->pw_uid)
+ new = clearsbin (path);
+ else
+ new = addsbin (path);
+
+ if (new)
+ {
+ xsetenv ("PATH", new, 1);
+ free (new);
+ }
+ }
+ if (pw->pw_uid)
+ {
+ xsetenv ("USER", pw->pw_name, 1);
+ xsetenv ("LOGNAME", pw->pw_name, 1);
+ }
+ }
+ }
+
+ export_pamenv ();
+}
+
+/* Become the user and group(s) specified by PW. */
+
+static void
+init_groups (const struct passwd *pw)
+{
+ int retval;
+ errno = 0;
+ if (initgroups (pw->pw_name, pw->pw_gid) == -1)
+ {
+ cleanup_pam (PAM_ABORT);
+ err (EXIT_FAILURE, _("cannot set groups"));
+ }
+ endgrent ();
+
+ retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
+ if (is_pam_failure(retval))
+ errx (EXIT_FAILURE, "%s", pam_strerror (pamh, retval));
+ else
+ _pam_cred_established = 1;
+}
+
+static void
+change_identity (const struct passwd *pw)
+{
+ 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, or DEFAULT_SHELL if SHELL is empty.
+ 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 (char const *shell, char const *command, char **additional_args,
+ size_t n_additional_args)
+{
+ size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
+ char const **args = xcalloc (n_args, sizeof *args);
+ size_t argno = 1;
+
+ if (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 (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);
+
+ {
+ int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
+ warn ("%s", shell);
+ exit (exit_status);
+ }
+}
+
+/* Return true if SHELL is a restricted shell (one not returned by
+ getusershell), else false, meaning it is a standard shell. */
+
+static bool
+restricted_shell (const char *shell)
+{
+ char *line;
+
+ setusershell ();
+ while ((line = getusershell ()) != NULL)
+ {
+ if (*line != '#' && !strcmp (line, shell))
+ {
+ endusershell ();
+ return false;
+ }
+ }
+ endusershell ();
+ return true;
+}
+
+static void __attribute__((__noreturn__))
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_invocation_short_name);
+ else
+ {
+ fputs(USAGE_HEADER, stdout);
+ printf (_(" %s [options] [-] [USER [arg]...]\n"), program_invocation_short_name);
+ fputs (_("\n\
+ Change the effective user id and group id to that of USER.\n\
+ A mere - implies -l. If USER not given, assume root.\n"), stdout);
+ fputs(USAGE_OPTIONS, stdout);
+ fputs (_("\
+ -, -l, --login make the shell a login shell\n\
+ -c, --command <command> pass a single command to the shell with -c\n\
+ --session-command <command> pass a single command to the shell with -c\n\
+ and do not create a new session\n\
+ -f, --fast pass -f to the shell (for csh or tcsh)\n\
+ -m, --preserve-environment do not reset environment variables\n\
+ -p same as -m\n\
+ -s, --shell <shell> run shell if /etc/shells allows it\n\
+"), stdout);
+
+ fputs(USAGE_SEPARATOR, stdout);
+ fputs(USAGE_HELP, stdout);
+ fputs(USAGE_VERSION, stdout);
+ printf(USAGE_MAN_TAIL("su(1)"));
+ }
+ exit (status);
+}
+
+static
+void load_config(void)
+{
+ logindefs_load_file("/etc/default/su");
+ logindefs_load_file(_PATH_LOGINDEFS);
+}
+
+int
+main (int argc, char **argv)
+{
+ int optc;
+ const char *new_user = DEFAULT_USER;
+ char *command = NULL;
+ int request_same_session = 0;
+ char *shell = NULL;
+ struct passwd *pw;
+ struct passwd pw_copy;
+
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ fast_startup = false;
+ simulate_login = false;
+ change_environment = true;
+
+ while ((optc = getopt_long (argc, argv, "c:flmps:hV", longopts, NULL)) != -1)
+ {
+ switch (optc)
+ {
+ case 'c':
+ command = optarg;
+ break;
+
+ case 'C':
+ command = optarg;
+ request_same_session = 1;
+ break;
+
+ case 'f':
+ fast_startup = true;
+ break;
+
+ case 'l':
+ simulate_login = true;
+ break;
+
+ case 'm':
+ case 'p':
+ change_environment = false;
+ break;
+
+ case 's':
+ shell = optarg;
+ break;
+
+ case 'h':
+ usage(0);
+
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (optind < argc && !strcmp (argv[optind], "-"))
+ {
+ simulate_login = true;
+ ++optind;
+ }
+ if (optind < argc)
+ new_user = argv[optind++];
+
+ logindefs_load_defaults = load_config;
+
+ pw = getpwnam (new_user);
+ if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
+ && pw->pw_passwd))
+ errx (EXIT_FAILURE, _("user %s does not exist"), new_user);
+
+ /* Make a copy of the password information and point pw at the local
+ copy instead. Otherwise, some systems (e.g. Linux) would clobber
+ the static data through the getlogin call from log_su.
+ Also, make sure pw->pw_shell is a nonempty string.
+ It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
+ but that doesn't have a default shell listed. */
+ pw_copy = *pw;
+ pw = &pw_copy;
+ pw->pw_name = xstrdup (pw->pw_name);
+ pw->pw_passwd = xstrdup (pw->pw_passwd);
+ pw->pw_dir = xstrdup (pw->pw_dir);
+ pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
+ ? pw->pw_shell
+ : DEFAULT_SHELL);
+ endpwent ();
+
+ authenticate (pw);
+
+ if (request_same_session || !command || !pw->pw_uid)
+ same_session = 1;
+
+ if (!shell && !change_environment)
+ shell = getenv ("SHELL");
+ if (shell && getuid () != 0 && restricted_shell (pw->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"), pw->pw_shell);
+ shell = NULL;
+ }
+ shell = xstrdup (shell ? shell : pw->pw_shell);
+
+ init_groups (pw);
+
+ create_watching_parent ();
+ /* Now we're in the child. */
+
+ change_identity (pw);
+ if (!same_session)
+ setsid ();
+
+ /* Set environment after pam_open_session, which may put KRB5CCNAME
+ into the pam_env, etc. */
+
+ modify_environment (pw, shell);
+
+ if (simulate_login && chdir (pw->pw_dir) != 0)
+ warn (_("warning: cannot change directory to %s"), pw->pw_dir);
+
+ run_shell (shell, command, argv + optind, max (0, argc - optind));
+}
+
+// vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1
diff --git a/login-utils/sulogin.8 b/login-utils/sulogin.8
new file mode 100644
index 0000000..b9dec16
--- /dev/null
+++ b/login-utils/sulogin.8
@@ -0,0 +1,90 @@
+'\" -*- coding: UTF-8 -*-
+.\" 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" "Jul 2012" "util-linux" "System Administration"
+.SH NAME
+sulogin \- Single-user login
+.SH SYNOPSIS
+.B sulogin
+.RB [ options ]
+.RB [ tty ]
+.SH DESCRIPTION
+.I 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
+.I sulogin
+will be connected to the current terminal, or to the optional tty device that
+can be specified on the command line (typically
+.BR /dev/console ).
+.PP
+After the user exits 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 via
+.BR getpwnam (3)
+from the system fails, manually examine
+.I /etc/passwd
+and
+.I /etc/shadow
+to get the password. If they are damaged or nonexistent, sulogin will start
+a root shell without asking for a password.
+.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 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,
+sulogin will wait forever.
+.IP "\fB\-h\fR, \fB\-\-help\fP"
+Print a help message.
+.IP "\fB\-V\fR, \fB\-\-version\fP"
+Output version.
+.SH ENVIRONMENT VARIABLES
+.I 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 ftp://\:ftp.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..d7d44b6
--- /dev/null
+++ b/login-utils/sulogin.c
@@ -0,0 +1,609 @@
+/*
+ * 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>
+ *
+ * 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 <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
+
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "ttyutils.h"
+
+static unsigned int timeout;
+static int profile;
+
+struct sigaction saved_sigint;
+struct sigaction saved_sigtstp;
+struct sigaction saved_sigquit;
+
+/*
+ * Called at timeout.
+ */
+static void alrm_handler(int sig __attribute__((unused)))
+{
+ return;
+}
+
+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, NULL, origaction);
+ sigaction(signal, &newaction, NULL);
+}
+
+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[256];
+ static char sline[256];
+ 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, 256, 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.
+ */
+ strcpy(pwd.pw_passwd, "");
+ if ((fp = fopen(_PATH_SHADOW_PASSWD, "r")) == NULL) {
+ warn(_("cannot open %s"), _PATH_PASSWD);
+ return &pwd;
+ }
+ while ((p = fgets(sline, 256, 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);
+ strcpy(pwd.pw_passwd, "");
+ }
+ if (!valid(pwd.pw_passwd)) {
+ warnx(_("%s: root password garbled"), _PATH_SHADOW_PASSWD);
+ strcpy(pwd.pw_passwd, "");
+ }
+ return &pwd;
+}
+
+/*
+ * Ask for the password. Note that there is no default timeout as we normally
+ * skip this during boot.
+ */
+static char *getpasswd(char *crypted)
+{
+ struct sigaction sa;
+ struct termios old, tty;
+ static char pass[128];
+ char *ret = pass;
+ size_t i;
+
+ if (crypted[0])
+ printf(_("Give root password for maintenance\n"));
+ else
+ printf(_("Press enter for maintenance"));
+ printf(_("(or type Control-D to continue): "));
+ fflush(stdout);
+
+ tcgetattr(0, &old);
+ tcgetattr(0, &tty);
+ tty.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY);
+ tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP);
+ tcsetattr(0, TCSANOW, &tty);
+
+ pass[sizeof(pass) - 1] = 0;
+
+ sa.sa_handler = alrm_handler;
+ sa.sa_flags = 0;
+ sigaction(SIGALRM, &sa, NULL);
+ if (timeout)
+ alarm(timeout);
+
+ if (read(0, pass, sizeof(pass) - 1) <= 0)
+ ret = NULL;
+ else {
+ for (i = 0; i < sizeof(pass) && pass[i]; i++)
+ if (pass[i] == '\r' || pass[i] == '\n') {
+ pass[i] = 0;
+ break;
+ }
+ }
+ alarm(0);
+ tcsetattr(0, TCSANOW, &old);
+ printf("\n");
+
+ return ret;
+}
+
+/*
+ * Password was OK, execute a shell.
+ */
+static void sushell(struct passwd *pwd)
+{
+ char shell[PATH_MAX];
+ char home[PATH_MAX];
+ char *p;
+ char *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)
+ setenv("HOME", home, 1);
+
+ setenv("LOGNAME", "root", 1);
+ setenv("USER", "root", 1);
+ if (!profile)
+ setenv("SHLVL","0",1);
+
+ /*
+ * Try to execute a shell.
+ */
+ setenv("SHELL", su_shell, 1);
+ unmask_signal(SIGINT, &saved_sigint);
+ unmask_signal(SIGTSTP, &saved_sigtstp);
+ unmask_signal(SIGQUIT, &saved_sigquit);
+
+#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(_("%s: exec failed"), su_shell);
+
+ setenv("SHELL", "/bin/sh", 1);
+ execl("/bin/sh", profile ? "-sh" : "sh", NULL);
+ warn(_("%s: exec failed"), "/bin/sh");
+}
+
+static void fixtty(void)
+{
+ struct termios tp;
+ int x = 0, fl = 0;
+
+ /* Skip serial console */
+ if (ioctl(STDIN_FILENO, TIOCMGET, (char *) &x) == 0)
+ return;
+
+#if defined(IUTF8) && defined(KDGKBMODE)
+ /* Detect mode of current keyboard setup, e.g. for UTF-8 */
+ if (ioctl(STDIN_FILENO, KDGKBMODE, &x) == 0 && x == K_UNICODE) {
+ setlocale(LC_CTYPE, "C.UTF-8");
+ fl |= UL_TTY_UTF8;
+ }
+#else
+ setlocale(LC_CTYPE, "POSIX");
+#endif
+ memset(&tp, 0, sizeof(struct termios));
+ if (tcgetattr(STDIN_FILENO, &tp) < 0) {
+ warn(_("tcgetattr failed"));
+ return;
+ }
+
+ reset_virtual_console(&tp, fl);
+
+ if (tcsetattr(0, TCSADRAIN, &tp))
+ warn(_("tcsetattr failed"));
+}
+
+static void usage(FILE *out)
+{
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(
+ " %s [options] [tty device]\n"), program_invocation_short_name);
+
+ 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);
+ fputs(USAGE_HELP, out);
+ fputs(USAGE_VERSION, out);
+ fprintf(out, USAGE_MAN_TAIL("sulogin(8)"));
+}
+
+int main(int argc, char **argv)
+{
+ char *tty = NULL;
+ char *p;
+ struct passwd *pwd;
+ int c, fd = -1;
+ int opt_e = 0;
+ pid_t pid, pgrp, ppgrp, ttypgrp;
+ struct sigaction saved_sighup;
+
+ static const struct option longopts[] = {
+ { "login-shell", 0, 0, 'p' },
+ { "timeout", 1, 0, 't' },
+ { "force", 0, 0, 'e' },
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'V' },
+ { NULL, 0, 0, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ /*
+ * 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(stdout);
+ return EXIT_SUCCESS;
+ default:
+ usage(stderr);
+ /* Do not exit! */
+ break;
+ }
+ }
+
+ if (geteuid() != 0)
+ errx(EXIT_FAILURE, _("only root can run this program."));
+
+ /*
+ * See if we need to open an other tty device.
+ */
+ mask_signal(SIGQUIT, SIG_IGN, &saved_sigquit);
+ mask_signal(SIGTSTP, SIG_IGN, &saved_sigtstp);
+ mask_signal(SIGINT, SIG_IGN, &saved_sigint);
+ if (optind < argc)
+ tty = argv[optind];
+
+ if (tty || (tty = getenv("CONSOLE"))) {
+
+ if ((fd = open(tty, O_RDWR)) < 0) {
+ warn(_("cannot open %s"), tty);
+ fd = dup(0);
+ }
+
+ if (!isatty(fd)) {
+ warn(_("%s: not a tty"), tty);
+ close(fd);
+ } else {
+
+ /*
+ * Only go through this trouble if the new tty doesn't
+ * fall in this process group.
+ */
+ pid = getpid();
+ pgrp = getpgid(0);
+ ppgrp = getpgid(getppid());
+ ttypgrp = tcgetpgrp(fd);
+
+ if (pgrp != ttypgrp && ppgrp != ttypgrp) {
+ if (pid != getsid(0)) {
+ if (pid == getpgid(0))
+ setpgid(0, getpgid(getppid()));
+ setsid();
+ }
+
+ sigaction(SIGHUP, NULL, &saved_sighup);
+ if (ttypgrp > 0)
+ ioctl(0, TIOCNOTTY, (char *)1);
+ sigaction(SIGHUP, &saved_sighup, NULL);
+ close(0);
+ close(1);
+ close(2);
+ if (fd > 2)
+ close(fd);
+ if ((fd = open(tty, O_RDWR|O_NOCTTY)) < 0)
+ warn(_("cannot open %s"), tty);
+ else {
+ ioctl(0, TIOCSCTTY, (char *)1);
+ tcsetpgrp(fd, ppgrp);
+ dup2(fd, 0);
+ dup2(fd, 1);
+ dup2(fd, 2);
+ if (fd > 2)
+ close(fd);
+ }
+ } else
+ if (fd > 2)
+ close(fd);
+ }
+ } else if (getpid() == 1) {
+ /* We are init. We hence need to set a session anyway */
+ setsid();
+ if (ioctl(0, TIOCSCTTY, (char *)1))
+ warn(_("TIOCSCTTY: ioctl failed"));
+ }
+
+ fixtty();
+
+ /*
+ * Get the root password.
+ */
+ if ((pwd = getrootpwent(opt_e)) == NULL) {
+ warnx(_("cannot open password database."));
+ sleep(2);
+ }
+
+ /*
+ * Ask for the password.
+ */
+ while (pwd) {
+ if ((p = getpasswd(pwd->pw_passwd)) == NULL)
+ break;
+ if (pwd->pw_passwd[0] == 0 ||
+ strcmp(crypt(p, pwd->pw_passwd), pwd->pw_passwd) == 0)
+ 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, _("Login incorrect\n\n"));
+ }
+
+ /*
+ * User pressed Control-D.
+ */
+ return EXIT_SUCCESS;
+}
diff --git a/login-utils/utmpdump.1 b/login-utils/utmpdump.1
new file mode 100644
index 0000000..35565a4
--- /dev/null
+++ b/login-utils/utmpdump.1
@@ -0,0 +1,74 @@
+'\" -*- coding: UTF-8 -*-
+.\" 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 2012" "util-linux" "System Administration"
+.SH NAME
+utmpdump \- dump UTMP and WTMP files in raw format
+.SH SYNOPSIS
+utmpdump [\-frh] [ 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
+eads from stdin unless a
+.I filename
+is passed.
+.SH OPTIONS
+.IP "\fB\-f\fR, \fB\-\-follow\fP"
+Output appended data as the file grows.
+.IP "\fB\-r\fR, \fB\-\-reverse\fP
+Undump, write back edited login information into utmp or wtmp files.
+.IP "\fB\-h\fR, \fB\-\-help\fP"
+Print a help text and exit.
+.IP "\fB\-V\fR, \fB\-\-version\fP"
+Output version information 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, then that file can be edited to remove
+bogus entries and reintegrated, using
+.PP
+.RS
+utmpdump -r < ascii_file > wtmp
+.RE
+.PP
+but be warned as
+.B utmpdump
+was written for debugging purpose only.
+.SH BUGS
+You may
+.B not
+use the option
+.B \-r
+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.
+.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 ftp://\:ftp.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..b015e1b
--- /dev/null
+++ b/login-utils/utmpdump.c
@@ -0,0 +1,363 @@
+/*
+ * 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 <utmp.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"
+
+static char *timetostr(const time_t time)
+{
+ static char s[29]; /* [Sun Sep 01 00:00:00 1998 PST] */
+
+ if (time != 0)
+ strftime(s, 29, "%a %b %d %T %Y %Z", localtime(&time));
+ else
+ s[0] = '\0';
+
+ return s;
+}
+
+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;
+
+ strptime(s_time, "%a %b %d %T %Y", &tm);
+
+ /* Cheesy way of checking for DST */
+ if (s_time[26] == 'D')
+ tm.tm_isdst = 1;
+
+ return mktime(&tm);
+}
+
+#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 utmp ut)
+{
+ char *addr_string, *time_string;
+ struct in_addr in;
+
+ in.s_addr = ut.ut_addr;
+ addr_string = inet_ntoa(in);
+ time_string = timetostr(ut.ut_time);
+ cleanse(ut.ut_id);
+ cleanse(ut.ut_user);
+ cleanse(ut.ut_line);
+ cleanse(ut.ut_host);
+
+ /* pid id user line host addr time */
+ printf("[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15.15s] [%-28.28s]\n",
+ ut.ut_type, ut.ut_pid, ut.ut_id, 8, UT_NAMESIZE, ut.ut_user,
+ 12, UT_LINESIZE, ut.ut_line, 20, UT_HOSTSIZE, 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 *fp;
+ struct stat st;
+ struct utmp ut;
+ off_t pos;
+
+ if (!(fp = fopen(filename, "r")))
+ err(EXIT_FAILURE, _("cannot open %s"), filename);
+
+ if (fstat(fileno(fp), &st) == -1)
+ err(EXIT_FAILURE, _("%s: stat failed"), filename);
+
+ if (st.st_size == *size)
+ goto done;
+
+ if (fseek(fp, *size, SEEK_SET) != (off_t) -1) {
+ while (fread(&ut, sizeof(ut), 1, fp) == 1)
+ print_utline(ut);
+ }
+
+ pos = ftello(fp);
+ /* 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(fp);
+}
+
+static int follow_by_inotify(FILE *fp, const char *filename)
+{
+ 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(fp);
+ fclose(fp);
+
+ 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);
+ 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 *fp, const char *filename, int follow)
+{
+ struct utmp ut;
+
+ if (follow)
+ fseek(fp, -10 * sizeof(ut), SEEK_END);
+
+ while (fread(&ut, sizeof(ut), 1, fp) == 1)
+ print_utline(ut);
+
+ if (!follow)
+ return fp;
+
+#ifdef HAVE_INOTIFY_INIT
+ if (follow_by_inotify(fp, filename) == 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, fp) == 1)
+ print_utline(ut);
+ sleep(1);
+ }
+
+ return fp;
+}
+
+
+/* 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 *fp)
+{
+ struct utmp ut;
+ char s_addr[16], s_time[29], *linestart, *line;
+ int count = 0;
+
+ line = linestart = xmalloc(1024 * sizeof(*linestart));
+ s_addr[15] = 0;
+ s_time[28] = 0;
+
+ while (fgets(linestart, 1023, fp)) {
+ 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);
+ line += gettok(line, s_time, sizeof(s_time) - 1, 0);
+
+ ut.ut_addr = inet_addr(s_addr);
+ ut.ut_time = strtotime(s_time);
+
+ ignore_result( fwrite(&ut, sizeof(ut), 1, stdout) );
+
+ ++count;
+ }
+
+ free(linestart);
+}
+
+static void __attribute__((__noreturn__)) usage(FILE *out)
+{
+ fputs(USAGE_HEADER, out);
+
+ fprintf(out,
+ _(" %s [options] [filename]\n"), program_invocation_short_name);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -f, --follow output appended data as the file grows\n"
+ " -r, --reverse write back dumped data into utmp file\n"), out);
+ fputs(USAGE_HELP, out);
+ fputs(USAGE_VERSION, out);
+
+ fprintf(out, USAGE_MAN_TAIL("utmpdump(1)"));
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ FILE *fp = NULL;
+ int reverse = 0, follow = 0;
+ const char *filename = NULL;
+
+ static const struct option longopts[] = {
+ { "follow", 0, 0, 'f' },
+ { "reverse", 0, 0, 'r' },
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'V' },
+ { NULL, 0, 0, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ while ((c = getopt_long(argc, argv, "frhV", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'r':
+ reverse = 1;
+ break;
+
+ case 'f':
+ follow = 1;
+ break;
+
+ case 'h':
+ usage(stdout);
+ break;
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ default:
+ usage(stderr);
+ }
+ }
+
+ if (optind < argc) {
+ filename = argv[optind];
+ fp = fopen(filename, "r");
+ if (!fp)
+ err(EXIT_FAILURE, _("cannot open %s"), filename);
+ } else {
+ if (follow)
+ errx(EXIT_FAILURE, _("following standard input is unsupported"));
+ filename = "/dev/stdin";
+ fp = stdin;
+ }
+
+ if (reverse) {
+ fprintf(stderr, _("Utmp undump of %s\n"), filename);
+ undump(fp);
+ } else {
+ fprintf(stderr, _("Utmp dump of %s\n"), filename);
+ fp = dump(fp, filename, follow);
+ }
+
+ if (fp && fp != stdin)
+ fclose(fp);
+
+ 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..62dcc85
--- /dev/null
+++ b/login-utils/vipw.8
@@ -0,0 +1,90 @@
+.\" 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.
+.\"
+.\" from: @(#)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).
+.El
+.SH SEE ALSO
+.BR passwd (1),
+.BR flock (2),
+.BR vi (1),
+.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 ftp://\:ftp.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..1eeeb0d
--- /dev/null
+++ b/login-utils/vipw.c
@@ -0,0 +1,353 @@
+/*
+ * 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
+ */
+
+/*
+ * This command is deprecated. The utility is in maintenance mode,
+ * meaning we keep them in source tree for backward compatibility
+ * only. Do not waste time making this command better, unless the
+ * fix is about security or other very critical issue.
+ *
+ * See Documentation/deprecated.txt for more information.
+ */
+
+#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 "c.h"
+#include "fileutils.h"
+#include "closestream.h"
+#include "nls.h"
+#include "setpwnam.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+#endif
+
+#define FILENAMELEN 67
+
+enum {
+ VIPW,
+ VIGR
+};
+int program;
+char orig_file[FILENAMELEN]; /* original file /etc/passwd or /etc/group */
+char *tmp_file; /* tmp file */
+
+void pw_error __P((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);
+}
+
+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;
+ char *dir = "/etc";
+
+ if ((fd = xfmkstemp(&tmpname, dir)) == 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);
+}
+
+static void pw_edit(int notsetuid)
+{
+ 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) {
+ if (notsetuid) {
+ (void)setgid(getgid());
+ (void)setuid(getuid());
+ }
+ execlp(editor, p, tmp_file, NULL);
+ /* Shouldn't get here */
+ _exit(EXIT_FAILURE);
+ }
+ 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);
+ 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, 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(0);
+
+ 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");
+ if (!tmp_file)
+ 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();
+}
+
+int main(int argc, char *argv[])
+{
+ 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));
+ }
+
+ if ((argc > 1) &&
+ (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version"))) {
+ printf(UTIL_LINUX_VERSION);
+ exit(EXIT_SUCCESS);
+ }
+
+ 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];
+
+ printf((program == VIGR)
+ ? _("You are using shadow groups on this system.\n")
+ : _("You are using shadow passwords on this system.\n"));
+ /* 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)) {
+ if (rpmatch(response) == 1)
+ edit_file(1);
+ }
+ }
+ exit(EXIT_SUCCESS);
+}