[Pkg-shadow-devel] Ubuntu shadow 1:4.2-3.2ubuntu2

Ubuntu Merge-o-Matic mom at ubuntu.com
Wed May 31 18:15:52 UTC 2017


This e-mail has been sent due to an upload to Ubuntu that contains Ubuntu
changes.  It contains the difference between the new version and the
previous version of the same source package in Ubuntu.
-------------- next part --------------
Format: 1.8
Date: Thu, 18 May 2017 14:39:32 -0400
Source: shadow
Binary: passwd login uidmap
Architecture: source
Version: 1:4.2-3.2ubuntu2
Distribution: artful
Urgency: medium
Maintainer: Ubuntu Developers <ubuntu-devel-discuss at lists.ubuntu.com>
Changed-By: Seth Arnold <seth.arnold at canonical.com>
Description: 
 login      - system login tools
 passwd     - change and administer password and group data
 uidmap     - programs to help use subuids
Changes: 
 shadow (1:4.2-3.2ubuntu2) artful; urgency=medium
 .
   * SECURITY UPDATE: su could be used to kill arbitrary processes.
     - debian/patches/CVE-2017-2616.patch: Check process's exit status before
       sending signal
     - debian/patches/CVE-2017-2616-regression.patch: Do not reset the
       pid_child to 0 if the child process is still running.
     - CVE-2017-2616
   * SECURITY UPDATE: getulong() function could accidentally parse negative
     numbers as large positive numbers.
     - debian/patches/CVE-2016-6252.patch: parse directly into unsigned long
     - CVE-2016-6252
Checksums-Sha1: 
 243c7fb21a7e30eb555799d1a13b8eb228bc8d48 2426 shadow_4.2-3.2ubuntu2.dsc
 57c447c937d3ae9d1a95ae74821e6fc206689e28 505468 shadow_4.2-3.2ubuntu2.debian.tar.xz
Checksums-Sha256: 
 27a07adb9f34d8b5d28c0d5a1844cb0ecb1f844fa65ba30c1bcc55ed5adfc4ce 2426 shadow_4.2-3.2ubuntu2.dsc
 cf6ee576049e124e3f80116911ff81b37da1bfbae475b7a116412cbae79e7b81 505468 shadow_4.2-3.2ubuntu2.debian.tar.xz
Files: 
 4e176821f81d29fd01110bebfd0c66e4 2426 admin required shadow_4.2-3.2ubuntu2.dsc
 69a4833dd1d4829861be079214474d95 505468 admin required shadow_4.2-3.2ubuntu2.debian.tar.xz
Original-Maintainer: Shadow package maintainers <pkg-shadow-devel at lists.alioth.debian.org>
-------------- next part --------------
diff -pruN 1:4.2-3.2ubuntu1/debian/changelog 1:4.2-3.2ubuntu2/debian/changelog
--- 1:4.2-3.2ubuntu1/debian/changelog	2016-09-20 07:43:54.000000000 +0000
+++ 1:4.2-3.2ubuntu2/debian/changelog	2017-05-18 18:39:32.000000000 +0000
@@ -1,3 +1,18 @@
+shadow (1:4.2-3.2ubuntu2) artful; urgency=medium
+
+  * SECURITY UPDATE: su could be used to kill arbitrary processes.
+    - debian/patches/CVE-2017-2616.patch: Check process's exit status before
+      sending signal
+    - debian/patches/CVE-2017-2616-regression.patch: Do not reset the
+      pid_child to 0 if the child process is still running.
+    - CVE-2017-2616
+  * SECURITY UPDATE: getulong() function could accidentally parse negative
+    numbers as large positive numbers.
+    - debian/patches/CVE-2016-6252.patch: parse directly into unsigned long
+    - CVE-2016-6252
+
+ -- Seth Arnold <seth.arnold at canonical.com>  Thu, 18 May 2017 14:39:32 -0400
+
 shadow (1:4.2-3.2ubuntu1) yakkety; urgency=medium
 
   * Merge with Debian; remaining changes:
diff -pruN 1:4.2-3.2ubuntu1/debian/patches/CVE-2016-6252.patch 1:4.2-3.2ubuntu2/debian/patches/CVE-2016-6252.patch
--- 1:4.2-3.2ubuntu1/debian/patches/CVE-2016-6252.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:4.2-3.2ubuntu2/debian/patches/CVE-2016-6252.patch	2017-03-10 02:46:50.000000000 +0000
@@ -0,0 +1,43 @@
+From 1d5a926cc2d6078d23a96222b1ef3e558724dad1 Mon Sep 17 00:00:00 2001
+From: Sebastian Krahmer <krahmer at suse.com>
+Date: Wed, 3 Aug 2016 11:51:07 -0500
+Subject: [PATCH] Simplify getulong
+
+Use strtoul to read an unsigned long, rather than reading
+a signed long long and casting it.
+
+https://bugzilla.suse.com/show_bug.cgi?id=979282
+---
+ lib/getulong.c | 9 +++------
+ 1 file changed, 3 insertions(+), 6 deletions(-)
+
+diff --git a/lib/getulong.c b/lib/getulong.c
+index 61579ca..08d2c1a 100644
+--- a/lib/getulong.c
++++ b/lib/getulong.c
+@@ -44,22 +44,19 @@
+  */
+ int getulong (const char *numstr, /*@out@*/unsigned long int *result)
+ {
+-	long long int val;
++	unsigned long int val;
+ 	char *endptr;
+ 
+ 	errno = 0;
+-	val = strtoll (numstr, &endptr, 0);
++	val = strtoul (numstr, &endptr, 0);
+ 	if (    ('\0' == *numstr)
+ 	     || ('\0' != *endptr)
+ 	     || (ERANGE == errno)
+-	     /*@+ignoresigns@*/
+-	     || (val != (unsigned long int)val)
+-	     /*@=ignoresigns@*/
+ 	   ) {
+ 		return 0;
+ 	}
+ 
+-	*result = (unsigned long int)val;
++	*result = val;
+ 	return 1;
+ }
+ 
diff -pruN 1:4.2-3.2ubuntu1/debian/patches/CVE-2017-2616.patch 1:4.2-3.2ubuntu2/debian/patches/CVE-2017-2616.patch
--- 1:4.2-3.2ubuntu1/debian/patches/CVE-2017-2616.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:4.2-3.2ubuntu2/debian/patches/CVE-2017-2616.patch	2017-03-10 02:23:51.000000000 +0000
@@ -0,0 +1,61 @@
+From 08fd4b69e84364677a10e519ccb25b71710ee686 Mon Sep 17 00:00:00 2001
+From: Tobias Stoeckmann <tobias at stoeckmann.org>
+Date: Thu, 23 Feb 2017 09:47:29 -0600
+Subject: [PATCH] su: properly clear child PID
+
+If su is compiled with PAM support, it is possible for any local user
+to send SIGKILL to other processes with root privileges. There are
+only two conditions. First, the user must be able to perform su with
+a successful login. This does NOT have to be the root user, even using
+su with the same id is enough, e.g. "su $(whoami)". Second, SIGKILL
+can only be sent to processes which were executed after the su process.
+It is not possible to send SIGKILL to processes which were already
+running. I consider this as a security vulnerability, because I was
+able to write a proof of concept which unlocked a screen saver of
+another user this way.
+---
+ src/su.c |   19 +++++++++++++++++--
+ 1 file changed, 17 insertions(+), 2 deletions(-)
+
+Index: b/src/su.c
+===================================================================
+--- a/src/su.c
++++ b/src/su.c
+@@ -376,20 +376,35 @@
+ 				/* wake child when resumed */
+ 				kill (pid, SIGCONT);
+ 				stop = false;
++			} else {
++				pid_child = 0;
+ 			}
+ 		} while (!stop);
+ 	}
+ 
+-	if (0 != caught) {
++	if (0 != caught && 0 != pid_child) {
+ 		(void) fputs ("\n", stderr);
+ 		(void) fputs (_("Session terminated, terminating shell..."),
+ 		              stderr);
+ 		(void) kill (-pid_child, caught);
+ 
+ 		(void) signal (SIGALRM, kill_child);
++		(void) signal (SIGCHLD, catch_signals);
+ 		(void) alarm (2);
+ 
+-		(void) wait (&status);
++		sigemptyset (&ourset);
++		if ((sigaddset (&ourset, SIGALRM) != 0)
++		    || (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0)) {
++			fprintf (stderr, _("%s: signal masking malfunction\n"), Prog);
++			kill_child (0);
++		} else {
++			while (0 == waitpid (pid_child, &status, WNOHANG)) {
++				sigsuspend (&ourset);
++			}
++			pid_child = 0;
++			(void) sigprocmask (SIG_UNBLOCK, &ourset, NULL);
++		}
++
+ 		(void) fputs (_(" ...terminated.\n"), stderr);
+ 	}
+ 
diff -pruN 1:4.2-3.2ubuntu1/debian/patches/CVE-2017-2616-regression.patch 1:4.2-3.2ubuntu2/debian/patches/CVE-2017-2616-regression.patch
--- 1:4.2-3.2ubuntu1/debian/patches/CVE-2017-2616-regression.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:4.2-3.2ubuntu2/debian/patches/CVE-2017-2616-regression.patch	2017-05-16 02:11:43.000000000 +0000
@@ -0,0 +1,31 @@
+From 7d82f203eeec881c584b2fa06539b39e82985d97 Mon Sep 17 00:00:00 2001
+From: Tobias Stoeckmann <tobias at stoeckmann.org>
+Date: Sun, 14 May 2017 17:58:10 +0200
+Subject: [PATCH] Reset pid_child only if waitpid was successful.
+
+Do not reset the pid_child to 0 if the child process is still
+running. This else-condition can be reached with pid being -1,
+therefore explicitly test this condition.
+
+This is a regression fix for CVE-2017-2616. If su receives a
+signal like SIGTERM, it is not propagated to the child.
+
+Reported-by: Radu Duta <raduduta at gmail.com>
+Signed-off-by: Tobias Stoeckmann <tobias at stoeckmann.org>
+---
+ src/su.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+Index: b/src/su.c
+===================================================================
+--- a/src/su.c
++++ b/src/su.c
+@@ -376,7 +376,7 @@
+ 				/* wake child when resumed */
+ 				kill (pid, SIGCONT);
+ 				stop = false;
+-			} else {
++			} else if (   (pid_t)-1 != pid) {
+ 				pid_child = 0;
+ 			}
+ 		} while (!stop);
diff -pruN 1:4.2-3.2ubuntu1/debian/patches/series 1:4.2-3.2ubuntu2/debian/patches/series
--- 1:4.2-3.2ubuntu1/debian/patches/series	2016-09-20 07:43:54.000000000 +0000
+++ 1:4.2-3.2ubuntu2/debian/patches/series	2017-05-16 02:11:39.000000000 +0000
@@ -40,3 +40,6 @@ userns/subuids-nonlocal-users
 1011_extrausers_toggle.patch
 1021_no_subuids_for_system_users.patch
 1012_extrausers_chfn.patch
+CVE-2017-2616.patch
+CVE-2016-6252.patch
+CVE-2017-2616-regression.patch
diff -pruN 1:4.2-3.2ubuntu1/lib/getulong.c 1:4.2-3.2ubuntu2/lib/getulong.c
--- 1:4.2-3.2ubuntu1/lib/getulong.c	2014-03-01 17:56:04.000000000 +0000
+++ 1:4.2-3.2ubuntu2/lib/getulong.c	2017-05-31 18:10:31.000000000 +0000
@@ -44,22 +44,19 @@
  */
 int getulong (const char *numstr, /*@out@*/unsigned long int *result)
 {
-	long long int val;
+	unsigned long int val;
 	char *endptr;
 
 	errno = 0;
-	val = strtoll (numstr, &endptr, 0);
+	val = strtoul (numstr, &endptr, 0);
 	if (    ('\0' == *numstr)
 	     || ('\0' != *endptr)
 	     || (ERANGE == errno)
-	     /*@+ignoresigns@*/
-	     || (val != (unsigned long int)val)
-	     /*@=ignoresigns@*/
 	   ) {
 		return 0;
 	}
 
-	*result = (unsigned long int)val;
+	*result = val;
 	return 1;
 }
 
diff -pruN 1:4.2-3.2ubuntu1/.pc/applied-patches 1:4.2-3.2ubuntu2/.pc/applied-patches
--- 1:4.2-3.2ubuntu1/.pc/applied-patches	2017-05-31 18:10:32.485172124 +0000
+++ 1:4.2-3.2ubuntu2/.pc/applied-patches	2017-05-31 18:10:31.573147502 +0000
@@ -19,3 +19,6 @@ userns/subuids-nonlocal-users
 1011_extrausers_toggle.patch
 1021_no_subuids_for_system_users.patch
 1012_extrausers_chfn.patch
+CVE-2017-2616.patch
+CVE-2016-6252.patch
+CVE-2017-2616-regression.patch
diff -pruN 1:4.2-3.2ubuntu1/.pc/CVE-2016-6252.patch/lib/getulong.c 1:4.2-3.2ubuntu2/.pc/CVE-2016-6252.patch/lib/getulong.c
--- 1:4.2-3.2ubuntu1/.pc/CVE-2016-6252.patch/lib/getulong.c	1970-01-01 00:00:00.000000000 +0000
+++ 1:4.2-3.2ubuntu2/.pc/CVE-2016-6252.patch/lib/getulong.c	2014-03-01 17:56:04.000000000 +0000
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2007 - 2009, Nicolas François
+ * 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. The name of the copyright holders or contributors may not be used to
+ *    endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+ * HOLDERS 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.
+ */
+
+#include <config.h>
+
+#ident "$Id: getlong.c 2763 2009-04-23 09:57:03Z nekral-guest $"
+
+#include <stdlib.h>
+#include <errno.h>
+#include "prototypes.h"
+
+/*
+ * getulong - extract an unsigned long integer provided by the numstr string in *result
+ *
+ * It supports decimal, hexadecimal or octal representations.
+ *
+ * Returns 0 on failure, 1 on success.
+ */
+int getulong (const char *numstr, /*@out@*/unsigned long int *result)
+{
+	long long int val;
+	char *endptr;
+
+	errno = 0;
+	val = strtoll (numstr, &endptr, 0);
+	if (    ('\0' == *numstr)
+	     || ('\0' != *endptr)
+	     || (ERANGE == errno)
+	     /*@+ignoresigns@*/
+	     || (val != (unsigned long int)val)
+	     /*@=ignoresigns@*/
+	   ) {
+		return 0;
+	}
+
+	*result = (unsigned long int)val;
+	return 1;
+}
+
diff -pruN 1:4.2-3.2ubuntu1/.pc/CVE-2017-2616.patch/src/su.c 1:4.2-3.2ubuntu2/.pc/CVE-2017-2616.patch/src/su.c
--- 1:4.2-3.2ubuntu1/.pc/CVE-2017-2616.patch/src/su.c	1970-01-01 00:00:00.000000000 +0000
+++ 1:4.2-3.2ubuntu2/.pc/CVE-2017-2616.patch/src/su.c	2017-05-31 18:10:31.000000000 +0000
@@ -0,0 +1,1217 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz KÅ‚oczko
+ * Copyright (c) 2007 - 2013, Nicolas François
+ * 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. The name of the copyright holders or contributors may not be used to
+ *    endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+ * HOLDERS 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.
+ */
+
+/* Some parts substantially derived from an ancestor of:
+   su for GNU.  Run a shell with substitute user and group IDs.
+
+   Copyright (C) 1992-2003 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 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.  */
+
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifndef USE_PAM
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif				/* !USE_PAM */
+#include "prototypes.h"
+#include "defines.h"
+#include "pwauth.h"
+#include "getdef.h"
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif				/* USE_PAM */
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+static /*@observer@*/const char *caller_tty = NULL;	/* Name of tty SU is run from */
+static bool caller_is_root = false;
+static uid_t caller_uid;
+#ifndef USE_PAM
+static bool caller_on_console = false;
+#ifdef SU_ACCESS
+static /*@only@*/char *caller_pass;
+#endif
+#endif				/* !USE_PAM */
+static bool doshell = false;
+static bool fakelogin = false;
+static /*@observer@*/const char *shellstr;
+static /*@null@*/char *command = NULL;
+
+
+/* not needed by sulog.c anymore */
+static char name[BUFSIZ];
+static char caller_name[BUFSIZ];
+
+/* If nonzero, change some environment vars to indicate the user su'd to. */
+static bool change_environment = true;
+
+/*
+ * If nonzero, keep the old Debian behavior:
+ *   * concatenate all the arguments and provide them to the -c option of
+ *     the shell
+ *   * If there are some additional arguments, but no -c, add a -c
+ *     argument anyway
+ * Drawbacks:
+ *   * you can't provide options to the shell (other than -c)
+ *   * you can't rely on the argument count
+ * See http://bugs.debian.org/276419
+ */
+static int old_debian_behavior;
+
+#ifdef USE_PAM
+static pam_handle_t *pamh = NULL;
+static int caught = 0;
+/* PID of the child, in case it needs to be killed */
+static pid_t pid_child = 0;
+#endif
+
+/*
+ * External identifiers
+ */
+
+extern char **newenvp; /* libmisc/env.c */
+extern size_t newenvc; /* libmisc/env.c */
+
+/* local function prototypes */
+
+static void execve_shell (const char *shellname,
+                          char *args[],
+                          char *const envp[]);
+#ifdef USE_PAM
+static RETSIGTYPE kill_child (int unused(s));
+static void prepare_pam_close_session (void);
+#else				/* !USE_PAM */
+static RETSIGTYPE die (int);
+static bool iswheel (const char *);
+#endif				/* !USE_PAM */
+static bool restricted_shell (const char *shellname);
+static /*@noreturn@*/void su_failure (const char *tty, bool su_to_root);
+static /*@only@*/struct passwd * check_perms (void);
+#ifdef USE_PAM
+static void check_perms_pam (const struct passwd *pw);
+#else				/* !USE_PAM */
+static void check_perms_nopam (const struct passwd *pw);
+#endif				/* !USE_PAM */
+static void save_caller_context (char **argv);
+static void process_flags (int argc, char **argv);
+static void set_environment (struct passwd *pw);
+
+#ifndef USE_PAM
+/*
+ * die - set or reset termio modes.
+ *
+ *	die() is called before processing begins. signal() is then called
+ *	with die() as the signal handler. If signal later calls die() with a
+ *	signal number, the terminal modes are then reset.
+ */
+static RETSIGTYPE die (int killed)
+{
+	static TERMIO sgtty;
+
+	if (killed != 0) {
+		STTY (0, &sgtty);
+	} else {
+		GTTY (0, &sgtty);
+	}
+
+	if (killed != 0) {
+		closelog ();
+		exit (128+killed);
+	}
+}
+
+static bool iswheel (const char *username)
+{
+	struct group *grp;
+
+	grp = getgrnam ("wheel"); /* !USE_PAM, no need for xgetgrnam */
+	if (   (NULL ==grp)
+	    || (NULL == grp->gr_mem)) {
+		return false;
+	}
+	return is_on_list (grp->gr_mem, username);
+}
+#else				/* USE_PAM */
+static RETSIGTYPE kill_child (int unused(s))
+{
+	if (0 != pid_child) {
+		(void) kill (-pid_child, SIGKILL);
+		(void) fputs (_(" ...killed.\n"), stderr);
+	} else {
+		(void) fputs (_(" ...waiting for child to terminate.\n"),
+		              stderr);
+	}
+	exit (255);
+}
+#endif				/* USE_PAM */
+
+/* borrowed from GNU sh-utils' "su.c" */
+static bool restricted_shell (const char *shellname)
+{
+	/*@observer@*/const char *line;
+
+	setusershell ();
+	while ((line = getusershell ()) != NULL) {
+		if (('#' != *line) && (strcmp (line, shellname) == 0)) {
+			endusershell ();
+			return false;
+		}
+	}
+	endusershell ();
+	return true;
+}
+
+static /*@noreturn@*/void su_failure (const char *tty, bool su_to_root)
+{
+	sulog (tty, false, caller_name, name);	/* log failed attempt */
+#ifdef USE_SYSLOG
+	if (getdef_bool ("SYSLOG_SU_ENAB")) {
+		SYSLOG ((su_to_root ? LOG_NOTICE : LOG_INFO,
+		         "- %s %s:%s", tty,
+		         ('\0' != caller_name[0]) ? caller_name : "???",
+		         ('\0' != name[0]) ? name : "???"));
+	}
+	closelog ();
+#endif
+	exit (1);
+}
+
+/*
+ * execve_shell - Execute a shell with execve, or interpret it with
+ * /bin/sh
+ */
+static void execve_shell (const char *shellname,
+                          char *args[],
+                          char *const envp[])
+{
+	int err;
+	(void) execve (shellname, (char **) args, envp);
+	err = errno;
+
+	if (access (shellname, R_OK|X_OK) == 0) {
+		/*
+		 * Assume this is a shell script (with no shebang).
+		 * Interpret it with /bin/sh
+		 */
+		size_t n_args = 0;
+		char **targs;
+		while (NULL != args[n_args]) {
+			n_args++;
+		}
+		targs = (char **) xmalloc ((n_args + 3) * sizeof (args[0]));
+		targs[0] = "sh";
+		targs[1] = "-";
+		targs[2] = xstrdup (shellname);
+		targs[n_args+2] = NULL;
+		while (1 != n_args) {
+			targs[n_args+1] = args[n_args - 1];
+			n_args--;
+		}
+
+		(void) execve (SHELL, targs, envp);
+	} else {
+		errno = err;
+	}
+}
+
+#ifdef USE_PAM
+/* Signal handler for parent process later */
+static void catch_signals (int sig)
+{
+	caught = sig;
+}
+
+/*
+ * prepare_pam_close_session - Fork and wait for the child to close the session
+ *
+ *	Only the child returns. The parent will wait for the child to
+ *	terminate and exit.
+ */
+static void prepare_pam_close_session (void)
+{
+	sigset_t ourset;
+	int status;
+	int ret;
+
+	pid_child = fork ();
+	if (pid_child == 0) {	/* child shell */
+		return; /* Only the child will return from pam_create_session */
+	} else if ((pid_t)-1 == pid_child) {
+		(void) fprintf (stderr,
+		                _("%s: Cannot fork user shell\n"),
+		                Prog);
+		SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
+		closelog ();
+		exit (1);
+		/* Only the child returns. See above. */
+	}
+
+	/* parent only */
+	sigfillset (&ourset);
+	if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) {
+		(void) fprintf (stderr,
+		                _("%s: signal malfunction\n"),
+		                Prog);
+		caught = SIGTERM;
+	}
+	if (0 == caught) {
+		struct sigaction action;
+
+		action.sa_handler = catch_signals;
+		sigemptyset (&action.sa_mask);
+		action.sa_flags = 0;
+		sigemptyset (&ourset);
+
+		if (   (sigaddset (&ourset, SIGTERM) != 0)
+		    || (sigaddset (&ourset, SIGALRM) != 0)
+		    || (sigaction (SIGTERM, &action, NULL) != 0)
+		    || (   !doshell /* handle SIGINT (Ctrl-C), SIGQUIT
+		                     * (Ctrl-\), and SIGTSTP (Ctrl-Z)
+		                     * since the child will not control
+		                     * the tty.
+		                     */
+		        && (   (sigaddset (&ourset, SIGINT)  != 0)
+		            || (sigaddset (&ourset, SIGQUIT) != 0)
+		            || (sigaddset (&ourset, SIGTSTP) != 0)
+		            || (sigaction (SIGINT,  &action, NULL) != 0)
+		            || (sigaction (SIGQUIT, &action, NULL) != 0)
+		            || (sigaction (SIGTSTP,  &action, NULL) != 0)))
+		    || (sigprocmask (SIG_UNBLOCK, &ourset, NULL) != 0)
+		    ) {
+			fprintf (stderr,
+			         _("%s: signal masking malfunction\n"),
+			         Prog);
+			caught = SIGTERM;
+		}
+	}
+
+	if (0 == caught) {
+		bool stop = true;
+
+		do {
+			pid_t pid;
+			stop = true;
+
+			pid = waitpid (-1, &status, WUNTRACED);
+
+			/* When interrupted by signal, the signal will be
+			 * forwarded to the child, and termination will be
+			 * forced later.
+			 */
+			if (   ((pid_t)-1 == pid)
+			    && (EINTR == errno)
+			    && (SIGTSTP == caught)) {
+				caught = 0;
+				/* Except for SIGTSTP, which request to
+				 * stop the child.
+				 * We will SIGSTOP ourself on the next
+				 * waitpid round.
+				 */
+				kill (pid_child, SIGSTOP);
+				stop = false;
+			} else if (   ((pid_t)-1 != pid)
+			           && (0 != WIFSTOPPED (status))) {
+				/* The child (shell) was suspended.
+				 * Suspend su. */
+				kill (getpid (), SIGSTOP);
+				/* wake child when resumed */
+				kill (pid, SIGCONT);
+				stop = false;
+			}
+		} while (!stop);
+	}
+
+	if (0 != caught) {
+		(void) fputs ("\n", stderr);
+		(void) fputs (_("Session terminated, terminating shell..."),
+		              stderr);
+		(void) kill (-pid_child, caught);
+
+		(void) signal (SIGALRM, kill_child);
+		(void) alarm (2);
+
+		(void) wait (&status);
+		(void) fputs (_(" ...terminated.\n"), stderr);
+	}
+
+	ret = pam_close_session (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_close_session: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+	}
+
+	(void) pam_setcred (pamh, PAM_DELETE_CRED);
+	(void) pam_end (pamh, PAM_SUCCESS);
+
+	exit ((0 != WIFEXITED (status)) ? WEXITSTATUS (status)
+	                                : WTERMSIG (status) + 128);
+	/* Only the child returns. See above. */
+}
+#endif				/* USE_PAM */
+
+/*
+ * usage - print command line syntax and exit
+ */
+static void usage (int status)
+{
+	(void)
+	fputs (_("Usage: su [options] [LOGIN]\n"
+	         "\n"
+	         "Options:\n"
+	         "  -c, --command COMMAND         pass COMMAND to the invoked shell\n"
+	         "  -h, --help                    display this help message and exit\n"
+	         "  -, -l, --login                make the shell a login shell\n"
+	         "  -m, -p,\n"
+	         "  --preserve-environment        do not reset environment variables, and\n"
+	         "                                keep the same shell\n"
+	         "  -s, --shell SHELL             use SHELL instead of the default in passwd\n"
+	         "\n"), (E_SUCCESS != status) ? stderr : stdout);
+	exit (status);
+}
+
+#ifdef USE_PAM
+static void check_perms_pam (const struct passwd *pw)
+{
+	int ret;
+	ret = pam_authenticate (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_authenticate: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		(void) pam_end (pamh, ret);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+
+	ret = pam_acct_mgmt (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		if (caller_is_root) {
+			fprintf (stderr,
+			         _("%s: %s\n(Ignored)\n"),
+			         Prog, pam_strerror (pamh, ret));
+		} else if (PAM_NEW_AUTHTOK_REQD == ret) {
+			ret = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+			if (PAM_SUCCESS != ret) {
+				SYSLOG ((LOG_ERR, "pam_chauthtok: %s",
+				         pam_strerror (pamh, ret)));
+				fprintf (stderr,
+				         _("%s: %s\n"),
+				         Prog, pam_strerror (pamh, ret));
+				(void) pam_end (pamh, ret);
+				su_failure (caller_tty, 0 == pw->pw_uid);
+			}
+		} else {
+			SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
+			         pam_strerror (pamh, ret)));
+			fprintf (stderr,
+			         _("%s: %s\n"),
+			         Prog, pam_strerror (pamh, ret));
+			(void) pam_end (pamh, ret);
+			su_failure (caller_tty, 0 == pw->pw_uid);
+		}
+	}
+}
+#else				/* !USE_PAM */
+static void check_perms_nopam (const struct passwd *pw)
+{
+	/*@observer@*/const struct spwd *spwd = NULL;
+	/*@observer@*/const char *password = pw->pw_passwd;
+	RETSIGTYPE (*oldsig) (int);
+
+	if (caller_is_root) {
+		return;
+	}
+
+	/*
+	 * BSD systems only allow "wheel" to SU to root. USG systems don't,
+	 * so we make this a configurable option.
+	 */
+
+	/* The original Shadow 3.3.2 did this differently. Do it like BSD:
+	 *
+	 * - check for UID 0 instead of name "root" - there are systems with
+	 *   several root accounts under different names,
+	 *
+	 * - check the contents of /etc/group instead of the current group
+	 *   set (you must be listed as a member, GID 0 is not sufficient).
+	 *
+	 * In addition to this traditional feature, we now have complete su
+	 * access control (allow, deny, no password, own password).  Thanks
+	 * to Chris Evans <lady0110 at sable.ox.ac.uk>.
+	 */
+
+	if (   (0 == pw->pw_uid)
+	    && getdef_bool ("SU_WHEEL_ONLY")
+	    && !iswheel (caller_name)) {
+		fprintf (stderr,
+		         _("You are not authorized to su %s\n"),
+		         name);
+		exit (1);
+	}
+	spwd = getspnam (name); /* !USE_PAM, no need for xgetspnam */
+#ifdef SU_ACCESS
+	if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+		if (NULL != spwd) {
+			password = spwd->sp_pwdp;
+		}
+	}
+
+	switch (check_su_auth (caller_name, name, 0 == pw->pw_uid)) {
+	case 0:	/* normal su, require target user's password */
+		break;
+	case 1:	/* require no password */
+		password = "";	/* XXX warning: const */
+		break;
+	case 2:	/* require own password */
+		(void) puts (_("(Enter your own password)"));
+		password = caller_pass;
+		break;
+	default:	/* access denied (-1) or unexpected value */
+		fprintf (stderr,
+		         _("You are not authorized to su %s\n"),
+		         name);
+		exit (1);
+	}
+#endif				/* SU_ACCESS */
+	/*
+	 * Set up a signal handler in case the user types QUIT.
+	 */
+	die (0);
+	oldsig = signal (SIGQUIT, die);
+
+	/*
+	 * See if the system defined authentication method is being used. 
+	 * The first character of an administrator defined method is an '@'
+	 * character.
+	 */
+	if (pw_auth (password, name, PW_SU, (char *) 0) != 0) {
+		SYSLOG (((pw->pw_uid != 0)? LOG_NOTICE : LOG_WARN,
+		         "Authentication failed for %s", name));
+		fprintf(stderr, _("%s: Authentication failure\n"), Prog);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+	(void) signal (SIGQUIT, oldsig);
+
+	/*
+	 * Check to see if the account is expired. root gets to ignore any
+	 * expired accounts, but normal users can't become a user with an
+	 * expired password.
+	 */
+	if (NULL != spwd) {
+		(void) expire (pw, spwd);
+	}
+
+	/*
+	 * Check to see if the account permits "su". root gets to ignore any
+	 * restricted accounts, but normal users can't become a user if
+	 * there is a "SU" entry in the /etc/porttime file denying access to
+	 * the account.
+	 */
+	if (!isttytime (name, "SU", time ((time_t *) 0))) {
+		SYSLOG (((0 != pw->pw_uid) ? LOG_WARN : LOG_CRIT,
+		         "SU by %s to restricted account %s",
+		         caller_name, name));
+		fprintf (stderr,
+		         _("%s: You are not authorized to su at that time\n"),
+		         Prog);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+}
+#endif				/* !USE_PAM */
+
+/*
+ * check_perms - check permissions to switch to the user 'name'
+ *
+ *	In case of subsystem login, the user is first authenticated in the
+ *	caller's root subsystem, and then in the user's target subsystem.
+ */
+static /*@only@*/struct passwd * check_perms (void)
+{
+#ifdef USE_PAM
+	const char *tmp_name;
+	int ret;
+#endif				/* !USE_PAM */
+	/*
+	 * The password file entries for the user is gotten and the account
+	 * validated.
+	 */
+	struct passwd *pw = xgetpwnam (name);
+	if (NULL == pw) {
+		(void) fprintf (stderr,
+		                _("No passwd entry for user '%s'\n"), name);
+		SYSLOG ((LOG_ERR, "No passwd entry for user '%s'", name));
+		su_failure (caller_tty, true);
+	}
+
+	(void) signal (SIGINT, SIG_IGN);
+	(void) signal (SIGQUIT, SIG_IGN);
+
+#ifdef USE_PAM
+	check_perms_pam (pw);
+	/* PAM authentication can request a change of account */
+	ret = pam_get_item(pamh, PAM_USER, (const void **) &tmp_name);
+	if (ret != PAM_SUCCESS) {
+		SYSLOG((LOG_ERR, "pam_get_item: internal PAM error\n"));
+		(void) fprintf (stderr,
+		                "%s: Internal PAM error retrieving username\n",
+		                Prog);
+		(void) pam_end (pamh, ret);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+	if (strcmp (name, tmp_name) != 0) {
+		SYSLOG ((LOG_INFO,
+		         "Change user from '%s' to '%s' as requested by PAM",
+		         name, tmp_name));
+		strncpy (name, tmp_name, sizeof(name) - 1);
+		name[sizeof(name) - 1] = '\0';
+		pw = xgetpwnam (name);
+		if (NULL == pw) {
+			(void) fprintf (stderr,
+			                _("No passwd entry for user '%s'\n"),
+			                name);
+			SYSLOG ((LOG_ERR,
+			         "No passwd entry for user '%s'", name));
+			su_failure (caller_tty, true);
+		}
+	}
+#else				/* !USE_PAM */
+	check_perms_nopam (pw);
+#endif				/* !USE_PAM */
+
+	(void) signal (SIGINT, SIG_DFL);
+	(void) signal (SIGQUIT, SIG_DFL);
+
+	/*
+	 * Even if --shell is specified, the subsystem login test is based on
+	 * the shell specified in /etc/passwd (not the one specified with
+	 * --shell, which will be the one executed in the chroot later).
+	 */
+	if ('*' == pw->pw_shell[0]) {	/* subsystem root required */
+		subsystem (pw);	/* change to the subsystem root */
+		endpwent ();		/* close the old password databases */
+		endspent ();
+		pw_free (pw);
+		return check_perms ();	/* authenticate in the subsystem */
+	}
+
+	return pw;
+}
+
+/*
+ * save_caller_context - save information from the call context
+ *
+ *	Save the program's name (Prog), caller's UID (caller_uid /
+ *	caller_is_root), name (caller_name), and password (caller_pass),
+ *	the TTY (ttyp), and whether su was called from a console
+ *	(is_console) for further processing and before they might change.
+ */
+static void save_caller_context (char **argv)
+{
+	struct passwd *pw = NULL;
+#ifndef USE_PAM
+#ifdef SU_ACCESS
+	const char *password = NULL;
+#endif				/* SU_ACCESS */
+#endif				/* !USE_PAM */
+	/*
+	 * Get the program name. The program name is used as a prefix to
+	 * most error messages.
+	 */
+	Prog = Basename (argv[0]);
+
+	caller_uid = getuid ();
+	caller_is_root = (caller_uid == 0);
+
+	/*
+	 * Get the tty name. Entries will be logged indicating that the user
+	 * tried to change to the named new user from the current terminal.
+	 */
+	caller_tty = ttyname (0);
+	if ((isatty (0) != 0) && (NULL != caller_tty)) {
+#ifndef USE_PAM
+		caller_on_console = console (caller_tty);
+#endif				/* !USE_PAM */
+	} else {
+		/*
+		 * Be more paranoid, like su from SimplePAMApps.  --marekm
+		 */
+		if (!caller_is_root) {
+			fprintf (stderr,
+			         _("%s: must be run from a terminal\n"),
+			         Prog);
+			exit (1);
+		}
+		caller_tty = "???";
+	}
+
+	/*
+	 * Get the user's real name. The current UID is used to determine
+	 * who has executed su. That user ID must exist.
+	 */
+	pw = get_my_pwent ();
+	if (NULL == pw) {
+		fprintf (stderr,
+		         _("%s: Cannot determine your user name.\n"),
+		         Prog);
+		SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+		         (unsigned long) caller_uid));
+		su_failure (caller_tty, true); /* unknown target UID*/
+	}
+	STRFCPY (caller_name, pw->pw_name);
+
+#ifndef USE_PAM
+#ifdef SU_ACCESS
+	/*
+	 * Sort out the password of user calling su, in case needed later
+	 * -- chris
+	 */
+	password = pw->pw_passwd;
+	if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+		const struct spwd *spwd = getspnam (caller_name);
+		if (NULL != spwd) {
+			password = spwd->sp_pwdp;
+		}
+	}
+	free (caller_pass);
+	caller_pass = xstrdup (password);
+#endif				/* SU_ACCESS */
+#endif				/* !USE_PAM */
+	pw_free (pw);
+}
+
+/*
+ * process_flags - Process the command line arguments
+ *
+ *	process_flags() interprets the command line arguments and sets
+ *	the values that the user will be created with accordingly. The
+ *	values are checked for sanity.
+ */
+static void process_flags (int argc, char **argv)
+{
+	int c;
+	static struct option long_options[] = {
+		{"command",              required_argument, NULL, 'c'},
+		{"help",                 no_argument,       NULL, 'h'},
+		{"login",                no_argument,       NULL, 'l'},
+		{"preserve-environment", no_argument,       NULL, 'p'},
+		{"shell",                required_argument, NULL, 's'},
+		{NULL, 0, NULL, '\0'}
+	};
+
+	while ((c = getopt_long (argc, argv, "c:hlmps:",
+	                         long_options, NULL)) != -1) {
+		switch (c) {
+		case 'c':
+			command = optarg;
+			break;
+		case 'h':
+			usage (E_SUCCESS);
+			break;
+		case 'l':
+			fakelogin = true;
+			break;
+		case 'm':
+		case 'p':
+			/* This will only have an effect if the target
+			 * user do not have a restricted shell, or if
+			 * su is called by root.
+			 */
+			change_environment = false;
+			break;
+		case 's':
+			shellstr = optarg;
+			break;
+		default:
+			usage (E_USAGE);	/* NOT REACHED */
+		}
+	}
+
+	if ((optind < argc) && (strcmp (argv[optind], "-") == 0)) {
+		fakelogin = true;
+		optind++;
+		if (   (optind < argc)
+		    && (strcmp (argv[optind], "--") == 0)) {
+			optind++;
+		}
+	}
+
+	/*
+	 * The next argument must be either a user ID, or some flag to a
+	 * subshell. Pretty sticky since you can't have an argument which
+	 * doesn't start with a "-" unless you specify the new user name.
+	 * Any remaining arguments will be passed to the user's login shell.
+	 */
+	if ((optind < argc) && ('-' != argv[optind][0])) {
+		STRFCPY (name, argv[optind++]);	/* use this login id */
+		if ((optind < argc) && (strcmp (argv[optind], "--") == 0)) {
+			optind++;
+		}
+	}
+	if ('\0' == name[0]) {		/* use default user */
+		struct passwd *root_pw = getpwnam ("root");
+		if ((NULL != root_pw) && (0 == root_pw->pw_uid)) {
+			(void) strcpy (name, "root");
+		} else {
+			root_pw = getpwuid (0);
+			if (NULL == root_pw) {
+				SYSLOG ((LOG_CRIT, "There is no UID 0 user."));
+				su_failure (caller_tty, true);
+			}
+			(void) strcpy (name, root_pw->pw_name);
+		}
+	}
+
+	doshell = (argc == optind);	/* any arguments remaining? */
+	if (NULL != command) {
+		doshell = false;
+	}
+}
+
+static void set_environment (struct passwd *pw)
+{
+	const char *cp;
+	/*
+	 * If a new login is being set up, the old environment will be
+	 * ignored and a new one created later on.
+	 */
+	if (change_environment && fakelogin) {
+		/*
+		 * The terminal type will be left alone if it is present in
+		 * the environment already.
+		 */
+		cp = getenv ("TERM");
+		if (NULL != cp) {
+			addenv ("TERM", cp);
+		}
+
+		/*
+		 * For some terminals COLORTERM seems to be the only way
+		 * for checking for that specific terminal. For instance,
+		 * gnome-terminal sets its TERM as "xterm" but its
+		 * COLORTERM as "gnome-terminal". The COLORTERM variable
+		 * is also of use when running GNU screen since it sets
+		 * TERM to "screen" but doesn't touch COLORTERM.
+		 */
+		cp = getenv ("COLORTERM");
+		if (NULL != cp) {
+			addenv ("COLORTERM", cp);
+		}
+
+#ifndef USE_PAM
+		cp = getdef_str ("ENV_TZ");
+		if (NULL != cp) {
+			addenv (('/' == *cp) ? tz (cp) : cp, NULL);
+		}
+
+		/*
+		 * The clock frequency will be reset to the login value if required
+		 */
+		cp = getdef_str ("ENV_HZ");
+		if (NULL != cp) {
+			addenv (cp, NULL);	/* set the default $HZ, if one */
+		}
+#endif				/* !USE_PAM */
+
+		/*
+		 * Also leave DISPLAY and XAUTHORITY if present, else
+		 * pam_xauth will not work.
+		 */
+		cp = getenv ("DISPLAY");
+		if (NULL != cp) {
+			addenv ("DISPLAY", cp);
+		}
+		cp = getenv ("XAUTHORITY");
+		if (NULL != cp) {
+			addenv ("XAUTHORITY", cp);
+		}
+	} else {
+		char **envp = environ;
+		while (NULL != *envp) {
+			addenv (*envp, NULL);
+			envp++;
+		}
+	}
+
+	cp = getdef_str ((pw->pw_uid == 0) ? "ENV_SUPATH" : "ENV_PATH");
+	if (NULL == cp) {
+		addenv ((pw->pw_uid == 0) ? "PATH=/sbin:/bin:/usr/sbin:/usr/bin" : "PATH=/bin:/usr/bin", NULL);
+	} else if (strchr (cp, '=') != NULL) {
+		addenv (cp, NULL);
+	} else {
+		addenv ("PATH", cp);
+	}
+
+	if (getenv ("IFS") != NULL) {	/* don't export user IFS ... */
+		addenv ("IFS= \t\n", NULL);	/* ... instead, set a safe IFS */
+	}
+
+#ifdef USE_PAM
+	/* we need to setup the environment *after* pam_open_session(),
+	 * else the UID is changed before stuff like pam_xauth could
+	 * run, and we cannot access /etc/shadow and co
+	 */
+	environ = newenvp;	/* make new environment active */
+
+	if (change_environment) {
+		/* update environment with all pam set variables */
+		char **envcp = pam_getenvlist (pamh);
+		if (NULL != envcp) {
+			while (NULL != *envcp) {
+				addenv (*envcp, NULL);
+				envcp++;
+			}
+		}
+	}
+
+#else				/* !USE_PAM */
+	environ = newenvp;	/* make new environment active */
+#endif				/* !USE_PAM */
+
+	if (change_environment) {
+		if (fakelogin) {
+			if (shellstr != pw->pw_shell) {
+				free (pw->pw_shell);
+				pw->pw_shell = xstrdup (shellstr);
+			}
+			setup_env (pw);
+		} else {
+			addenv ("HOME", pw->pw_dir);
+			addenv ("USER", pw->pw_name);
+			addenv ("LOGNAME", pw->pw_name);
+			addenv ("SHELL", shellstr);
+		}
+	}
+
+}
+
+/*
+ * su - switch user id
+ *
+ *	su changes the user's ids to the values for the specified user.  if
+ *	no new user name is specified, "root" or UID 0 is used by default.
+ *
+ *	Any additional arguments are passed to the user's shell. In
+ *	particular, the argument "-c" will cause the next argument to be
+ *	interpreted as a command by the common shell programs.
+ */
+int main (int argc, char **argv)
+{
+	const char *cp;
+	struct passwd *pw = NULL;
+
+#ifdef USE_PAM
+	int ret;
+#endif				/* USE_PAM */
+
+	old_debian_behavior = (getenv("SU_NO_SHELL_ARGS") != NULL);
+
+	(void) setlocale (LC_ALL, "");
+	(void) bindtextdomain (PACKAGE, LOCALEDIR);
+	(void) textdomain (PACKAGE);
+
+	save_caller_context (argv);
+
+	OPENLOG ("su");
+
+	process_flags (argc, argv);
+
+	initenv ();
+
+#ifdef USE_PAM
+	ret = pam_start ("su", name, &conv, &pamh);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
+		fprintf (stderr,
+		         _("%s: pam_start: error %d\n"),
+		         Prog, ret));
+		exit (1);
+	}
+
+	ret = pam_set_item (pamh, PAM_TTY, (const void *) caller_tty);
+	if (PAM_SUCCESS == ret) {
+		ret = pam_set_item (pamh, PAM_RUSER, (const void *) caller_name);
+	}
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_set_item: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		pam_end (pamh, ret);
+		exit (1);
+	}
+#endif				/* USE_PAM */
+
+	pw = check_perms ();
+
+	/* If the user do not want to change the environment,
+	 * use the current SHELL.
+	 * (unless another shell is required by the command line)
+	 */
+	if ((NULL == shellstr) && !change_environment) {
+		shellstr = getenv ("SHELL");
+	}
+
+	/* If su is not called by root, and the target user has a
+	 * restricted shell, the environment must be changed and the shell
+	 * must be the one specified in /etc/passwd.
+	 */
+	if (   !caller_is_root
+	    && restricted_shell (pw->pw_shell)) {
+		shellstr = NULL;
+		change_environment = true;
+	}
+
+	/* If the shell is not set at this time, use the shell specified
+	 * in /etc/passwd.
+	 */
+	if (NULL == shellstr) {
+		shellstr = pw->pw_shell;
+	}
+
+	/*
+	 * Set the default shell.
+	 */
+	if ((NULL == shellstr) || ('\0' == shellstr[0])) {
+		shellstr = SHELL;
+	}
+
+	sulog (caller_tty, true, caller_name, name);	/* save SU information */
+#ifdef USE_SYSLOG
+	if (getdef_bool ("SYSLOG_SU_ENAB")) {
+		SYSLOG ((LOG_INFO, "+ %s %s:%s", caller_tty,
+		         ('\0' != caller_name[0]) ? caller_name : "???",
+		         ('\0' != name[0]) ? name : "???"));
+	}
+#endif
+
+#ifdef USE_PAM
+	/* set primary group id and supplementary groups */
+	if (setup_groups (pw) != 0) {
+		pam_end (pamh, PAM_ABORT);
+		exit (1);
+	}
+
+	/*
+	 * pam_setcred() may do things like resource limits, console groups,
+	 * and much more, depending on the configured modules
+	 */
+	ret = pam_setcred (pamh, PAM_ESTABLISH_CRED);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_setcred: %s", pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		(void) pam_end (pamh, ret);
+		exit (1);
+	}
+
+	ret = pam_open_session (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_open_session: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		pam_setcred (pamh, PAM_DELETE_CRED);
+		(void) pam_end (pamh, ret);
+		exit (1);
+	}
+
+	prepare_pam_close_session ();
+
+	/* become the new user */
+	if (change_uid (pw) != 0) {
+		exit (1);
+	}
+#else				/* !USE_PAM */
+	/* no limits if su from root (unless su must fake login's behavior) */
+	if (!caller_is_root || fakelogin) {
+		setup_limits (pw);
+	}
+
+	if (setup_uid_gid (pw, caller_on_console) != 0) {
+		exit (1);
+	}
+#endif				/* !USE_PAM */
+
+	set_environment (pw);
+
+	if (!doshell) {
+		/* There is no need for a controlling terminal.
+		 * This avoids the callee to inject commands on
+		 * the caller's tty. */
+		int err = -1;
+
+#ifdef USE_PAM
+		/* When PAM is used, we are on the child */
+		err = setsid ();
+#else
+		/* Otherwise, we cannot use setsid */
+		int fd = open ("/dev/tty", O_RDWR);
+
+		if (fd >= 0) {
+			err = ioctl (fd, TIOCNOTTY, (char *) 0);
+			(void) close (fd);
+		} else if (ENXIO == errno) {
+			/* There are no controlling terminal already */
+			err = 0;
+		}
+#endif				/* USE_PAM */
+
+		if (-1 == err) {
+			(void) fprintf (stderr,
+			                _("%s: Cannot drop the controlling terminal\n"),
+			                Prog);
+			exit (1);
+		}
+	}
+
+	/*
+	 * PAM_DATA_SILENT is not supported by some modules, and
+	 * there is no strong need to clean up the process space's
+	 * memory since we will either call exec or exit.
+	pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+	 */
+
+	endpwent ();
+	endspent ();
+	/*
+	 * This is a workaround for Linux libc bug/feature (?) - the
+	 * /dev/log file descriptor is open without the close-on-exec flag
+	 * and used to be passed to the new shell. There is "fcntl(LogFile,
+	 * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
+	 * least in 5.4.33). Why?  --marekm
+	 */
+	closelog ();
+
+	/*
+	 * See if the user has extra arguments on the command line. In that
+	 * case they will be provided to the new user's shell as arguments.
+	 */
+	if (fakelogin) {
+		char *arg0;
+
+		cp = getdef_str ("SU_NAME");
+		if (NULL == cp) {
+			cp = Basename (shellstr);
+		}
+
+		arg0 = xmalloc (strlen (cp) + 2);
+		arg0[0] = '-';
+		strcpy (arg0 + 1, cp);
+		cp = arg0;
+	} else {
+		cp = Basename (shellstr);
+	}
+
+	if (!doshell) {
+		int err;
+		/* Position argv to the remaining arguments */
+		argv += optind;
+		if (NULL != command) {
+			argv -= 2;
+			argv[0] = "-c";
+			argv[1] = command;
+		}
+		/* On Debian, the arguments are concatenated and the
+		 * resulting string is always given to the shell with its
+		 * -c option.
+		 */
+		if (old_debian_behavior) {
+			char **parg;
+			unsigned int cmd_len = 0;
+			char *cmd = NULL;
+			if (strcmp(argv[0], "-c") != 0) {
+				argv--;
+				argv[0] = "-c";
+			}
+			/* Now argv[0] is always -c, and other arguments
+			 * can be concatenated
+			 */
+			cmd_len = 1; /* finale '\0' */
+			for (parg = &argv[1]; *parg; parg++) {
+				cmd_len += strlen (*parg) + 1;
+			}
+			cmd = (char *) xmalloc (sizeof (char) * cmd_len);
+			cmd[0] = '\0';
+			for (parg = &argv[1]; *parg; parg++) {
+				strcat (cmd, " ");
+				strcat (cmd, *parg);
+			}
+			cmd[cmd_len - 1] = '\0';
+			argv[1] = &cmd[1]; /* do not take first space */
+			argv[2] = NULL;
+		}
+		/*
+		 * Use the shell and create an argv
+		 * with the rest of the command line included.
+		 */
+		argv[-1] = cp;
+		execve_shell (shellstr, &argv[-1], environ);
+		err = errno;
+		(void) fprintf (stderr,
+		                _("Cannot execute %s\n"), shellstr);
+		errno = err;
+	} else {
+		(void) shell (shellstr, cp, environ);
+	}
+
+	pw_free (pw);
+
+	return (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+}
+
diff -pruN 1:4.2-3.2ubuntu1/.pc/CVE-2017-2616-regression.patch/src/su.c 1:4.2-3.2ubuntu2/.pc/CVE-2017-2616-regression.patch/src/su.c
--- 1:4.2-3.2ubuntu1/.pc/CVE-2017-2616-regression.patch/src/su.c	1970-01-01 00:00:00.000000000 +0000
+++ 1:4.2-3.2ubuntu2/.pc/CVE-2017-2616-regression.patch/src/su.c	2017-05-31 18:10:31.000000000 +0000
@@ -0,0 +1,1232 @@
+/*
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz KÅ‚oczko
+ * Copyright (c) 2007 - 2013, Nicolas François
+ * 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. The name of the copyright holders or contributors may not be used to
+ *    endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+ * HOLDERS 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.
+ */
+
+/* Some parts substantially derived from an ancestor of:
+   su for GNU.  Run a shell with substitute user and group IDs.
+
+   Copyright (C) 1992-2003 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 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.  */
+
+
+#include <config.h>
+
+#ident "$Id$"
+
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifndef USE_PAM
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif				/* !USE_PAM */
+#include "prototypes.h"
+#include "defines.h"
+#include "pwauth.h"
+#include "getdef.h"
+#ifdef USE_PAM
+#include "pam_defs.h"
+#endif				/* USE_PAM */
+/*@-exitarg@*/
+#include "exitcodes.h"
+
+/*
+ * Global variables
+ */
+const char *Prog;
+static /*@observer@*/const char *caller_tty = NULL;	/* Name of tty SU is run from */
+static bool caller_is_root = false;
+static uid_t caller_uid;
+#ifndef USE_PAM
+static bool caller_on_console = false;
+#ifdef SU_ACCESS
+static /*@only@*/char *caller_pass;
+#endif
+#endif				/* !USE_PAM */
+static bool doshell = false;
+static bool fakelogin = false;
+static /*@observer@*/const char *shellstr;
+static /*@null@*/char *command = NULL;
+
+
+/* not needed by sulog.c anymore */
+static char name[BUFSIZ];
+static char caller_name[BUFSIZ];
+
+/* If nonzero, change some environment vars to indicate the user su'd to. */
+static bool change_environment = true;
+
+/*
+ * If nonzero, keep the old Debian behavior:
+ *   * concatenate all the arguments and provide them to the -c option of
+ *     the shell
+ *   * If there are some additional arguments, but no -c, add a -c
+ *     argument anyway
+ * Drawbacks:
+ *   * you can't provide options to the shell (other than -c)
+ *   * you can't rely on the argument count
+ * See http://bugs.debian.org/276419
+ */
+static int old_debian_behavior;
+
+#ifdef USE_PAM
+static pam_handle_t *pamh = NULL;
+static int caught = 0;
+/* PID of the child, in case it needs to be killed */
+static pid_t pid_child = 0;
+#endif
+
+/*
+ * External identifiers
+ */
+
+extern char **newenvp; /* libmisc/env.c */
+extern size_t newenvc; /* libmisc/env.c */
+
+/* local function prototypes */
+
+static void execve_shell (const char *shellname,
+                          char *args[],
+                          char *const envp[]);
+#ifdef USE_PAM
+static RETSIGTYPE kill_child (int unused(s));
+static void prepare_pam_close_session (void);
+#else				/* !USE_PAM */
+static RETSIGTYPE die (int);
+static bool iswheel (const char *);
+#endif				/* !USE_PAM */
+static bool restricted_shell (const char *shellname);
+static /*@noreturn@*/void su_failure (const char *tty, bool su_to_root);
+static /*@only@*/struct passwd * check_perms (void);
+#ifdef USE_PAM
+static void check_perms_pam (const struct passwd *pw);
+#else				/* !USE_PAM */
+static void check_perms_nopam (const struct passwd *pw);
+#endif				/* !USE_PAM */
+static void save_caller_context (char **argv);
+static void process_flags (int argc, char **argv);
+static void set_environment (struct passwd *pw);
+
+#ifndef USE_PAM
+/*
+ * die - set or reset termio modes.
+ *
+ *	die() is called before processing begins. signal() is then called
+ *	with die() as the signal handler. If signal later calls die() with a
+ *	signal number, the terminal modes are then reset.
+ */
+static RETSIGTYPE die (int killed)
+{
+	static TERMIO sgtty;
+
+	if (killed != 0) {
+		STTY (0, &sgtty);
+	} else {
+		GTTY (0, &sgtty);
+	}
+
+	if (killed != 0) {
+		closelog ();
+		exit (128+killed);
+	}
+}
+
+static bool iswheel (const char *username)
+{
+	struct group *grp;
+
+	grp = getgrnam ("wheel"); /* !USE_PAM, no need for xgetgrnam */
+	if (   (NULL ==grp)
+	    || (NULL == grp->gr_mem)) {
+		return false;
+	}
+	return is_on_list (grp->gr_mem, username);
+}
+#else				/* USE_PAM */
+static RETSIGTYPE kill_child (int unused(s))
+{
+	if (0 != pid_child) {
+		(void) kill (-pid_child, SIGKILL);
+		(void) fputs (_(" ...killed.\n"), stderr);
+	} else {
+		(void) fputs (_(" ...waiting for child to terminate.\n"),
+		              stderr);
+	}
+	exit (255);
+}
+#endif				/* USE_PAM */
+
+/* borrowed from GNU sh-utils' "su.c" */
+static bool restricted_shell (const char *shellname)
+{
+	/*@observer@*/const char *line;
+
+	setusershell ();
+	while ((line = getusershell ()) != NULL) {
+		if (('#' != *line) && (strcmp (line, shellname) == 0)) {
+			endusershell ();
+			return false;
+		}
+	}
+	endusershell ();
+	return true;
+}
+
+static /*@noreturn@*/void su_failure (const char *tty, bool su_to_root)
+{
+	sulog (tty, false, caller_name, name);	/* log failed attempt */
+#ifdef USE_SYSLOG
+	if (getdef_bool ("SYSLOG_SU_ENAB")) {
+		SYSLOG ((su_to_root ? LOG_NOTICE : LOG_INFO,
+		         "- %s %s:%s", tty,
+		         ('\0' != caller_name[0]) ? caller_name : "???",
+		         ('\0' != name[0]) ? name : "???"));
+	}
+	closelog ();
+#endif
+	exit (1);
+}
+
+/*
+ * execve_shell - Execute a shell with execve, or interpret it with
+ * /bin/sh
+ */
+static void execve_shell (const char *shellname,
+                          char *args[],
+                          char *const envp[])
+{
+	int err;
+	(void) execve (shellname, (char **) args, envp);
+	err = errno;
+
+	if (access (shellname, R_OK|X_OK) == 0) {
+		/*
+		 * Assume this is a shell script (with no shebang).
+		 * Interpret it with /bin/sh
+		 */
+		size_t n_args = 0;
+		char **targs;
+		while (NULL != args[n_args]) {
+			n_args++;
+		}
+		targs = (char **) xmalloc ((n_args + 3) * sizeof (args[0]));
+		targs[0] = "sh";
+		targs[1] = "-";
+		targs[2] = xstrdup (shellname);
+		targs[n_args+2] = NULL;
+		while (1 != n_args) {
+			targs[n_args+1] = args[n_args - 1];
+			n_args--;
+		}
+
+		(void) execve (SHELL, targs, envp);
+	} else {
+		errno = err;
+	}
+}
+
+#ifdef USE_PAM
+/* Signal handler for parent process later */
+static void catch_signals (int sig)
+{
+	caught = sig;
+}
+
+/*
+ * prepare_pam_close_session - Fork and wait for the child to close the session
+ *
+ *	Only the child returns. The parent will wait for the child to
+ *	terminate and exit.
+ */
+static void prepare_pam_close_session (void)
+{
+	sigset_t ourset;
+	int status;
+	int ret;
+
+	pid_child = fork ();
+	if (pid_child == 0) {	/* child shell */
+		return; /* Only the child will return from pam_create_session */
+	} else if ((pid_t)-1 == pid_child) {
+		(void) fprintf (stderr,
+		                _("%s: Cannot fork user shell\n"),
+		                Prog);
+		SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
+		closelog ();
+		exit (1);
+		/* Only the child returns. See above. */
+	}
+
+	/* parent only */
+	sigfillset (&ourset);
+	if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) {
+		(void) fprintf (stderr,
+		                _("%s: signal malfunction\n"),
+		                Prog);
+		caught = SIGTERM;
+	}
+	if (0 == caught) {
+		struct sigaction action;
+
+		action.sa_handler = catch_signals;
+		sigemptyset (&action.sa_mask);
+		action.sa_flags = 0;
+		sigemptyset (&ourset);
+
+		if (   (sigaddset (&ourset, SIGTERM) != 0)
+		    || (sigaddset (&ourset, SIGALRM) != 0)
+		    || (sigaction (SIGTERM, &action, NULL) != 0)
+		    || (   !doshell /* handle SIGINT (Ctrl-C), SIGQUIT
+		                     * (Ctrl-\), and SIGTSTP (Ctrl-Z)
+		                     * since the child will not control
+		                     * the tty.
+		                     */
+		        && (   (sigaddset (&ourset, SIGINT)  != 0)
+		            || (sigaddset (&ourset, SIGQUIT) != 0)
+		            || (sigaddset (&ourset, SIGTSTP) != 0)
+		            || (sigaction (SIGINT,  &action, NULL) != 0)
+		            || (sigaction (SIGQUIT, &action, NULL) != 0)
+		            || (sigaction (SIGTSTP,  &action, NULL) != 0)))
+		    || (sigprocmask (SIG_UNBLOCK, &ourset, NULL) != 0)
+		    ) {
+			fprintf (stderr,
+			         _("%s: signal masking malfunction\n"),
+			         Prog);
+			caught = SIGTERM;
+		}
+	}
+
+	if (0 == caught) {
+		bool stop = true;
+
+		do {
+			pid_t pid;
+			stop = true;
+
+			pid = waitpid (-1, &status, WUNTRACED);
+
+			/* When interrupted by signal, the signal will be
+			 * forwarded to the child, and termination will be
+			 * forced later.
+			 */
+			if (   ((pid_t)-1 == pid)
+			    && (EINTR == errno)
+			    && (SIGTSTP == caught)) {
+				caught = 0;
+				/* Except for SIGTSTP, which request to
+				 * stop the child.
+				 * We will SIGSTOP ourself on the next
+				 * waitpid round.
+				 */
+				kill (pid_child, SIGSTOP);
+				stop = false;
+			} else if (   ((pid_t)-1 != pid)
+			           && (0 != WIFSTOPPED (status))) {
+				/* The child (shell) was suspended.
+				 * Suspend su. */
+				kill (getpid (), SIGSTOP);
+				/* wake child when resumed */
+				kill (pid, SIGCONT);
+				stop = false;
+			} else {
+				pid_child = 0;
+			}
+		} while (!stop);
+	}
+
+	if (0 != caught && 0 != pid_child) {
+		(void) fputs ("\n", stderr);
+		(void) fputs (_("Session terminated, terminating shell..."),
+		              stderr);
+		(void) kill (-pid_child, caught);
+
+		(void) signal (SIGALRM, kill_child);
+		(void) signal (SIGCHLD, catch_signals);
+		(void) alarm (2);
+
+		sigemptyset (&ourset);
+		if ((sigaddset (&ourset, SIGALRM) != 0)
+		    || (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0)) {
+			fprintf (stderr, _("%s: signal masking malfunction\n"), Prog);
+			kill_child (0);
+		} else {
+			while (0 == waitpid (pid_child, &status, WNOHANG)) {
+				sigsuspend (&ourset);
+			}
+			pid_child = 0;
+			(void) sigprocmask (SIG_UNBLOCK, &ourset, NULL);
+		}
+
+		(void) fputs (_(" ...terminated.\n"), stderr);
+	}
+
+	ret = pam_close_session (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_close_session: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+	}
+
+	(void) pam_setcred (pamh, PAM_DELETE_CRED);
+	(void) pam_end (pamh, PAM_SUCCESS);
+
+	exit ((0 != WIFEXITED (status)) ? WEXITSTATUS (status)
+	                                : WTERMSIG (status) + 128);
+	/* Only the child returns. See above. */
+}
+#endif				/* USE_PAM */
+
+/*
+ * usage - print command line syntax and exit
+ */
+static void usage (int status)
+{
+	(void)
+	fputs (_("Usage: su [options] [LOGIN]\n"
+	         "\n"
+	         "Options:\n"
+	         "  -c, --command COMMAND         pass COMMAND to the invoked shell\n"
+	         "  -h, --help                    display this help message and exit\n"
+	         "  -, -l, --login                make the shell a login shell\n"
+	         "  -m, -p,\n"
+	         "  --preserve-environment        do not reset environment variables, and\n"
+	         "                                keep the same shell\n"
+	         "  -s, --shell SHELL             use SHELL instead of the default in passwd\n"
+	         "\n"), (E_SUCCESS != status) ? stderr : stdout);
+	exit (status);
+}
+
+#ifdef USE_PAM
+static void check_perms_pam (const struct passwd *pw)
+{
+	int ret;
+	ret = pam_authenticate (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_authenticate: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		(void) pam_end (pamh, ret);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+
+	ret = pam_acct_mgmt (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		if (caller_is_root) {
+			fprintf (stderr,
+			         _("%s: %s\n(Ignored)\n"),
+			         Prog, pam_strerror (pamh, ret));
+		} else if (PAM_NEW_AUTHTOK_REQD == ret) {
+			ret = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+			if (PAM_SUCCESS != ret) {
+				SYSLOG ((LOG_ERR, "pam_chauthtok: %s",
+				         pam_strerror (pamh, ret)));
+				fprintf (stderr,
+				         _("%s: %s\n"),
+				         Prog, pam_strerror (pamh, ret));
+				(void) pam_end (pamh, ret);
+				su_failure (caller_tty, 0 == pw->pw_uid);
+			}
+		} else {
+			SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
+			         pam_strerror (pamh, ret)));
+			fprintf (stderr,
+			         _("%s: %s\n"),
+			         Prog, pam_strerror (pamh, ret));
+			(void) pam_end (pamh, ret);
+			su_failure (caller_tty, 0 == pw->pw_uid);
+		}
+	}
+}
+#else				/* !USE_PAM */
+static void check_perms_nopam (const struct passwd *pw)
+{
+	/*@observer@*/const struct spwd *spwd = NULL;
+	/*@observer@*/const char *password = pw->pw_passwd;
+	RETSIGTYPE (*oldsig) (int);
+
+	if (caller_is_root) {
+		return;
+	}
+
+	/*
+	 * BSD systems only allow "wheel" to SU to root. USG systems don't,
+	 * so we make this a configurable option.
+	 */
+
+	/* The original Shadow 3.3.2 did this differently. Do it like BSD:
+	 *
+	 * - check for UID 0 instead of name "root" - there are systems with
+	 *   several root accounts under different names,
+	 *
+	 * - check the contents of /etc/group instead of the current group
+	 *   set (you must be listed as a member, GID 0 is not sufficient).
+	 *
+	 * In addition to this traditional feature, we now have complete su
+	 * access control (allow, deny, no password, own password).  Thanks
+	 * to Chris Evans <lady0110 at sable.ox.ac.uk>.
+	 */
+
+	if (   (0 == pw->pw_uid)
+	    && getdef_bool ("SU_WHEEL_ONLY")
+	    && !iswheel (caller_name)) {
+		fprintf (stderr,
+		         _("You are not authorized to su %s\n"),
+		         name);
+		exit (1);
+	}
+	spwd = getspnam (name); /* !USE_PAM, no need for xgetspnam */
+#ifdef SU_ACCESS
+	if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+		if (NULL != spwd) {
+			password = spwd->sp_pwdp;
+		}
+	}
+
+	switch (check_su_auth (caller_name, name, 0 == pw->pw_uid)) {
+	case 0:	/* normal su, require target user's password */
+		break;
+	case 1:	/* require no password */
+		password = "";	/* XXX warning: const */
+		break;
+	case 2:	/* require own password */
+		(void) puts (_("(Enter your own password)"));
+		password = caller_pass;
+		break;
+	default:	/* access denied (-1) or unexpected value */
+		fprintf (stderr,
+		         _("You are not authorized to su %s\n"),
+		         name);
+		exit (1);
+	}
+#endif				/* SU_ACCESS */
+	/*
+	 * Set up a signal handler in case the user types QUIT.
+	 */
+	die (0);
+	oldsig = signal (SIGQUIT, die);
+
+	/*
+	 * See if the system defined authentication method is being used. 
+	 * The first character of an administrator defined method is an '@'
+	 * character.
+	 */
+	if (pw_auth (password, name, PW_SU, (char *) 0) != 0) {
+		SYSLOG (((pw->pw_uid != 0)? LOG_NOTICE : LOG_WARN,
+		         "Authentication failed for %s", name));
+		fprintf(stderr, _("%s: Authentication failure\n"), Prog);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+	(void) signal (SIGQUIT, oldsig);
+
+	/*
+	 * Check to see if the account is expired. root gets to ignore any
+	 * expired accounts, but normal users can't become a user with an
+	 * expired password.
+	 */
+	if (NULL != spwd) {
+		(void) expire (pw, spwd);
+	}
+
+	/*
+	 * Check to see if the account permits "su". root gets to ignore any
+	 * restricted accounts, but normal users can't become a user if
+	 * there is a "SU" entry in the /etc/porttime file denying access to
+	 * the account.
+	 */
+	if (!isttytime (name, "SU", time ((time_t *) 0))) {
+		SYSLOG (((0 != pw->pw_uid) ? LOG_WARN : LOG_CRIT,
+		         "SU by %s to restricted account %s",
+		         caller_name, name));
+		fprintf (stderr,
+		         _("%s: You are not authorized to su at that time\n"),
+		         Prog);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+}
+#endif				/* !USE_PAM */
+
+/*
+ * check_perms - check permissions to switch to the user 'name'
+ *
+ *	In case of subsystem login, the user is first authenticated in the
+ *	caller's root subsystem, and then in the user's target subsystem.
+ */
+static /*@only@*/struct passwd * check_perms (void)
+{
+#ifdef USE_PAM
+	const char *tmp_name;
+	int ret;
+#endif				/* !USE_PAM */
+	/*
+	 * The password file entries for the user is gotten and the account
+	 * validated.
+	 */
+	struct passwd *pw = xgetpwnam (name);
+	if (NULL == pw) {
+		(void) fprintf (stderr,
+		                _("No passwd entry for user '%s'\n"), name);
+		SYSLOG ((LOG_ERR, "No passwd entry for user '%s'", name));
+		su_failure (caller_tty, true);
+	}
+
+	(void) signal (SIGINT, SIG_IGN);
+	(void) signal (SIGQUIT, SIG_IGN);
+
+#ifdef USE_PAM
+	check_perms_pam (pw);
+	/* PAM authentication can request a change of account */
+	ret = pam_get_item(pamh, PAM_USER, (const void **) &tmp_name);
+	if (ret != PAM_SUCCESS) {
+		SYSLOG((LOG_ERR, "pam_get_item: internal PAM error\n"));
+		(void) fprintf (stderr,
+		                "%s: Internal PAM error retrieving username\n",
+		                Prog);
+		(void) pam_end (pamh, ret);
+		su_failure (caller_tty, 0 == pw->pw_uid);
+	}
+	if (strcmp (name, tmp_name) != 0) {
+		SYSLOG ((LOG_INFO,
+		         "Change user from '%s' to '%s' as requested by PAM",
+		         name, tmp_name));
+		strncpy (name, tmp_name, sizeof(name) - 1);
+		name[sizeof(name) - 1] = '\0';
+		pw = xgetpwnam (name);
+		if (NULL == pw) {
+			(void) fprintf (stderr,
+			                _("No passwd entry for user '%s'\n"),
+			                name);
+			SYSLOG ((LOG_ERR,
+			         "No passwd entry for user '%s'", name));
+			su_failure (caller_tty, true);
+		}
+	}
+#else				/* !USE_PAM */
+	check_perms_nopam (pw);
+#endif				/* !USE_PAM */
+
+	(void) signal (SIGINT, SIG_DFL);
+	(void) signal (SIGQUIT, SIG_DFL);
+
+	/*
+	 * Even if --shell is specified, the subsystem login test is based on
+	 * the shell specified in /etc/passwd (not the one specified with
+	 * --shell, which will be the one executed in the chroot later).
+	 */
+	if ('*' == pw->pw_shell[0]) {	/* subsystem root required */
+		subsystem (pw);	/* change to the subsystem root */
+		endpwent ();		/* close the old password databases */
+		endspent ();
+		pw_free (pw);
+		return check_perms ();	/* authenticate in the subsystem */
+	}
+
+	return pw;
+}
+
+/*
+ * save_caller_context - save information from the call context
+ *
+ *	Save the program's name (Prog), caller's UID (caller_uid /
+ *	caller_is_root), name (caller_name), and password (caller_pass),
+ *	the TTY (ttyp), and whether su was called from a console
+ *	(is_console) for further processing and before they might change.
+ */
+static void save_caller_context (char **argv)
+{
+	struct passwd *pw = NULL;
+#ifndef USE_PAM
+#ifdef SU_ACCESS
+	const char *password = NULL;
+#endif				/* SU_ACCESS */
+#endif				/* !USE_PAM */
+	/*
+	 * Get the program name. The program name is used as a prefix to
+	 * most error messages.
+	 */
+	Prog = Basename (argv[0]);
+
+	caller_uid = getuid ();
+	caller_is_root = (caller_uid == 0);
+
+	/*
+	 * Get the tty name. Entries will be logged indicating that the user
+	 * tried to change to the named new user from the current terminal.
+	 */
+	caller_tty = ttyname (0);
+	if ((isatty (0) != 0) && (NULL != caller_tty)) {
+#ifndef USE_PAM
+		caller_on_console = console (caller_tty);
+#endif				/* !USE_PAM */
+	} else {
+		/*
+		 * Be more paranoid, like su from SimplePAMApps.  --marekm
+		 */
+		if (!caller_is_root) {
+			fprintf (stderr,
+			         _("%s: must be run from a terminal\n"),
+			         Prog);
+			exit (1);
+		}
+		caller_tty = "???";
+	}
+
+	/*
+	 * Get the user's real name. The current UID is used to determine
+	 * who has executed su. That user ID must exist.
+	 */
+	pw = get_my_pwent ();
+	if (NULL == pw) {
+		fprintf (stderr,
+		         _("%s: Cannot determine your user name.\n"),
+		         Prog);
+		SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+		         (unsigned long) caller_uid));
+		su_failure (caller_tty, true); /* unknown target UID*/
+	}
+	STRFCPY (caller_name, pw->pw_name);
+
+#ifndef USE_PAM
+#ifdef SU_ACCESS
+	/*
+	 * Sort out the password of user calling su, in case needed later
+	 * -- chris
+	 */
+	password = pw->pw_passwd;
+	if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
+		const struct spwd *spwd = getspnam (caller_name);
+		if (NULL != spwd) {
+			password = spwd->sp_pwdp;
+		}
+	}
+	free (caller_pass);
+	caller_pass = xstrdup (password);
+#endif				/* SU_ACCESS */
+#endif				/* !USE_PAM */
+	pw_free (pw);
+}
+
+/*
+ * process_flags - Process the command line arguments
+ *
+ *	process_flags() interprets the command line arguments and sets
+ *	the values that the user will be created with accordingly. The
+ *	values are checked for sanity.
+ */
+static void process_flags (int argc, char **argv)
+{
+	int c;
+	static struct option long_options[] = {
+		{"command",              required_argument, NULL, 'c'},
+		{"help",                 no_argument,       NULL, 'h'},
+		{"login",                no_argument,       NULL, 'l'},
+		{"preserve-environment", no_argument,       NULL, 'p'},
+		{"shell",                required_argument, NULL, 's'},
+		{NULL, 0, NULL, '\0'}
+	};
+
+	while ((c = getopt_long (argc, argv, "c:hlmps:",
+	                         long_options, NULL)) != -1) {
+		switch (c) {
+		case 'c':
+			command = optarg;
+			break;
+		case 'h':
+			usage (E_SUCCESS);
+			break;
+		case 'l':
+			fakelogin = true;
+			break;
+		case 'm':
+		case 'p':
+			/* This will only have an effect if the target
+			 * user do not have a restricted shell, or if
+			 * su is called by root.
+			 */
+			change_environment = false;
+			break;
+		case 's':
+			shellstr = optarg;
+			break;
+		default:
+			usage (E_USAGE);	/* NOT REACHED */
+		}
+	}
+
+	if ((optind < argc) && (strcmp (argv[optind], "-") == 0)) {
+		fakelogin = true;
+		optind++;
+		if (   (optind < argc)
+		    && (strcmp (argv[optind], "--") == 0)) {
+			optind++;
+		}
+	}
+
+	/*
+	 * The next argument must be either a user ID, or some flag to a
+	 * subshell. Pretty sticky since you can't have an argument which
+	 * doesn't start with a "-" unless you specify the new user name.
+	 * Any remaining arguments will be passed to the user's login shell.
+	 */
+	if ((optind < argc) && ('-' != argv[optind][0])) {
+		STRFCPY (name, argv[optind++]);	/* use this login id */
+		if ((optind < argc) && (strcmp (argv[optind], "--") == 0)) {
+			optind++;
+		}
+	}
+	if ('\0' == name[0]) {		/* use default user */
+		struct passwd *root_pw = getpwnam ("root");
+		if ((NULL != root_pw) && (0 == root_pw->pw_uid)) {
+			(void) strcpy (name, "root");
+		} else {
+			root_pw = getpwuid (0);
+			if (NULL == root_pw) {
+				SYSLOG ((LOG_CRIT, "There is no UID 0 user."));
+				su_failure (caller_tty, true);
+			}
+			(void) strcpy (name, root_pw->pw_name);
+		}
+	}
+
+	doshell = (argc == optind);	/* any arguments remaining? */
+	if (NULL != command) {
+		doshell = false;
+	}
+}
+
+static void set_environment (struct passwd *pw)
+{
+	const char *cp;
+	/*
+	 * If a new login is being set up, the old environment will be
+	 * ignored and a new one created later on.
+	 */
+	if (change_environment && fakelogin) {
+		/*
+		 * The terminal type will be left alone if it is present in
+		 * the environment already.
+		 */
+		cp = getenv ("TERM");
+		if (NULL != cp) {
+			addenv ("TERM", cp);
+		}
+
+		/*
+		 * For some terminals COLORTERM seems to be the only way
+		 * for checking for that specific terminal. For instance,
+		 * gnome-terminal sets its TERM as "xterm" but its
+		 * COLORTERM as "gnome-terminal". The COLORTERM variable
+		 * is also of use when running GNU screen since it sets
+		 * TERM to "screen" but doesn't touch COLORTERM.
+		 */
+		cp = getenv ("COLORTERM");
+		if (NULL != cp) {
+			addenv ("COLORTERM", cp);
+		}
+
+#ifndef USE_PAM
+		cp = getdef_str ("ENV_TZ");
+		if (NULL != cp) {
+			addenv (('/' == *cp) ? tz (cp) : cp, NULL);
+		}
+
+		/*
+		 * The clock frequency will be reset to the login value if required
+		 */
+		cp = getdef_str ("ENV_HZ");
+		if (NULL != cp) {
+			addenv (cp, NULL);	/* set the default $HZ, if one */
+		}
+#endif				/* !USE_PAM */
+
+		/*
+		 * Also leave DISPLAY and XAUTHORITY if present, else
+		 * pam_xauth will not work.
+		 */
+		cp = getenv ("DISPLAY");
+		if (NULL != cp) {
+			addenv ("DISPLAY", cp);
+		}
+		cp = getenv ("XAUTHORITY");
+		if (NULL != cp) {
+			addenv ("XAUTHORITY", cp);
+		}
+	} else {
+		char **envp = environ;
+		while (NULL != *envp) {
+			addenv (*envp, NULL);
+			envp++;
+		}
+	}
+
+	cp = getdef_str ((pw->pw_uid == 0) ? "ENV_SUPATH" : "ENV_PATH");
+	if (NULL == cp) {
+		addenv ((pw->pw_uid == 0) ? "PATH=/sbin:/bin:/usr/sbin:/usr/bin" : "PATH=/bin:/usr/bin", NULL);
+	} else if (strchr (cp, '=') != NULL) {
+		addenv (cp, NULL);
+	} else {
+		addenv ("PATH", cp);
+	}
+
+	if (getenv ("IFS") != NULL) {	/* don't export user IFS ... */
+		addenv ("IFS= \t\n", NULL);	/* ... instead, set a safe IFS */
+	}
+
+#ifdef USE_PAM
+	/* we need to setup the environment *after* pam_open_session(),
+	 * else the UID is changed before stuff like pam_xauth could
+	 * run, and we cannot access /etc/shadow and co
+	 */
+	environ = newenvp;	/* make new environment active */
+
+	if (change_environment) {
+		/* update environment with all pam set variables */
+		char **envcp = pam_getenvlist (pamh);
+		if (NULL != envcp) {
+			while (NULL != *envcp) {
+				addenv (*envcp, NULL);
+				envcp++;
+			}
+		}
+	}
+
+#else				/* !USE_PAM */
+	environ = newenvp;	/* make new environment active */
+#endif				/* !USE_PAM */
+
+	if (change_environment) {
+		if (fakelogin) {
+			if (shellstr != pw->pw_shell) {
+				free (pw->pw_shell);
+				pw->pw_shell = xstrdup (shellstr);
+			}
+			setup_env (pw);
+		} else {
+			addenv ("HOME", pw->pw_dir);
+			addenv ("USER", pw->pw_name);
+			addenv ("LOGNAME", pw->pw_name);
+			addenv ("SHELL", shellstr);
+		}
+	}
+
+}
+
+/*
+ * su - switch user id
+ *
+ *	su changes the user's ids to the values for the specified user.  if
+ *	no new user name is specified, "root" or UID 0 is used by default.
+ *
+ *	Any additional arguments are passed to the user's shell. In
+ *	particular, the argument "-c" will cause the next argument to be
+ *	interpreted as a command by the common shell programs.
+ */
+int main (int argc, char **argv)
+{
+	const char *cp;
+	struct passwd *pw = NULL;
+
+#ifdef USE_PAM
+	int ret;
+#endif				/* USE_PAM */
+
+	old_debian_behavior = (getenv("SU_NO_SHELL_ARGS") != NULL);
+
+	(void) setlocale (LC_ALL, "");
+	(void) bindtextdomain (PACKAGE, LOCALEDIR);
+	(void) textdomain (PACKAGE);
+
+	save_caller_context (argv);
+
+	OPENLOG ("su");
+
+	process_flags (argc, argv);
+
+	initenv ();
+
+#ifdef USE_PAM
+	ret = pam_start ("su", name, &conv, &pamh);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
+		fprintf (stderr,
+		         _("%s: pam_start: error %d\n"),
+		         Prog, ret));
+		exit (1);
+	}
+
+	ret = pam_set_item (pamh, PAM_TTY, (const void *) caller_tty);
+	if (PAM_SUCCESS == ret) {
+		ret = pam_set_item (pamh, PAM_RUSER, (const void *) caller_name);
+	}
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_set_item: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		pam_end (pamh, ret);
+		exit (1);
+	}
+#endif				/* USE_PAM */
+
+	pw = check_perms ();
+
+	/* If the user do not want to change the environment,
+	 * use the current SHELL.
+	 * (unless another shell is required by the command line)
+	 */
+	if ((NULL == shellstr) && !change_environment) {
+		shellstr = getenv ("SHELL");
+	}
+
+	/* If su is not called by root, and the target user has a
+	 * restricted shell, the environment must be changed and the shell
+	 * must be the one specified in /etc/passwd.
+	 */
+	if (   !caller_is_root
+	    && restricted_shell (pw->pw_shell)) {
+		shellstr = NULL;
+		change_environment = true;
+	}
+
+	/* If the shell is not set at this time, use the shell specified
+	 * in /etc/passwd.
+	 */
+	if (NULL == shellstr) {
+		shellstr = pw->pw_shell;
+	}
+
+	/*
+	 * Set the default shell.
+	 */
+	if ((NULL == shellstr) || ('\0' == shellstr[0])) {
+		shellstr = SHELL;
+	}
+
+	sulog (caller_tty, true, caller_name, name);	/* save SU information */
+#ifdef USE_SYSLOG
+	if (getdef_bool ("SYSLOG_SU_ENAB")) {
+		SYSLOG ((LOG_INFO, "+ %s %s:%s", caller_tty,
+		         ('\0' != caller_name[0]) ? caller_name : "???",
+		         ('\0' != name[0]) ? name : "???"));
+	}
+#endif
+
+#ifdef USE_PAM
+	/* set primary group id and supplementary groups */
+	if (setup_groups (pw) != 0) {
+		pam_end (pamh, PAM_ABORT);
+		exit (1);
+	}
+
+	/*
+	 * pam_setcred() may do things like resource limits, console groups,
+	 * and much more, depending on the configured modules
+	 */
+	ret = pam_setcred (pamh, PAM_ESTABLISH_CRED);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_setcred: %s", pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		(void) pam_end (pamh, ret);
+		exit (1);
+	}
+
+	ret = pam_open_session (pamh, 0);
+	if (PAM_SUCCESS != ret) {
+		SYSLOG ((LOG_ERR, "pam_open_session: %s",
+		         pam_strerror (pamh, ret)));
+		fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
+		pam_setcred (pamh, PAM_DELETE_CRED);
+		(void) pam_end (pamh, ret);
+		exit (1);
+	}
+
+	prepare_pam_close_session ();
+
+	/* become the new user */
+	if (change_uid (pw) != 0) {
+		exit (1);
+	}
+#else				/* !USE_PAM */
+	/* no limits if su from root (unless su must fake login's behavior) */
+	if (!caller_is_root || fakelogin) {
+		setup_limits (pw);
+	}
+
+	if (setup_uid_gid (pw, caller_on_console) != 0) {
+		exit (1);
+	}
+#endif				/* !USE_PAM */
+
+	set_environment (pw);
+
+	if (!doshell) {
+		/* There is no need for a controlling terminal.
+		 * This avoids the callee to inject commands on
+		 * the caller's tty. */
+		int err = -1;
+
+#ifdef USE_PAM
+		/* When PAM is used, we are on the child */
+		err = setsid ();
+#else
+		/* Otherwise, we cannot use setsid */
+		int fd = open ("/dev/tty", O_RDWR);
+
+		if (fd >= 0) {
+			err = ioctl (fd, TIOCNOTTY, (char *) 0);
+			(void) close (fd);
+		} else if (ENXIO == errno) {
+			/* There are no controlling terminal already */
+			err = 0;
+		}
+#endif				/* USE_PAM */
+
+		if (-1 == err) {
+			(void) fprintf (stderr,
+			                _("%s: Cannot drop the controlling terminal\n"),
+			                Prog);
+			exit (1);
+		}
+	}
+
+	/*
+	 * PAM_DATA_SILENT is not supported by some modules, and
+	 * there is no strong need to clean up the process space's
+	 * memory since we will either call exec or exit.
+	pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+	 */
+
+	endpwent ();
+	endspent ();
+	/*
+	 * This is a workaround for Linux libc bug/feature (?) - the
+	 * /dev/log file descriptor is open without the close-on-exec flag
+	 * and used to be passed to the new shell. There is "fcntl(LogFile,
+	 * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
+	 * least in 5.4.33). Why?  --marekm
+	 */
+	closelog ();
+
+	/*
+	 * See if the user has extra arguments on the command line. In that
+	 * case they will be provided to the new user's shell as arguments.
+	 */
+	if (fakelogin) {
+		char *arg0;
+
+		cp = getdef_str ("SU_NAME");
+		if (NULL == cp) {
+			cp = Basename (shellstr);
+		}
+
+		arg0 = xmalloc (strlen (cp) + 2);
+		arg0[0] = '-';
+		strcpy (arg0 + 1, cp);
+		cp = arg0;
+	} else {
+		cp = Basename (shellstr);
+	}
+
+	if (!doshell) {
+		int err;
+		/* Position argv to the remaining arguments */
+		argv += optind;
+		if (NULL != command) {
+			argv -= 2;
+			argv[0] = "-c";
+			argv[1] = command;
+		}
+		/* On Debian, the arguments are concatenated and the
+		 * resulting string is always given to the shell with its
+		 * -c option.
+		 */
+		if (old_debian_behavior) {
+			char **parg;
+			unsigned int cmd_len = 0;
+			char *cmd = NULL;
+			if (strcmp(argv[0], "-c") != 0) {
+				argv--;
+				argv[0] = "-c";
+			}
+			/* Now argv[0] is always -c, and other arguments
+			 * can be concatenated
+			 */
+			cmd_len = 1; /* finale '\0' */
+			for (parg = &argv[1]; *parg; parg++) {
+				cmd_len += strlen (*parg) + 1;
+			}
+			cmd = (char *) xmalloc (sizeof (char) * cmd_len);
+			cmd[0] = '\0';
+			for (parg = &argv[1]; *parg; parg++) {
+				strcat (cmd, " ");
+				strcat (cmd, *parg);
+			}
+			cmd[cmd_len - 1] = '\0';
+			argv[1] = &cmd[1]; /* do not take first space */
+			argv[2] = NULL;
+		}
+		/*
+		 * Use the shell and create an argv
+		 * with the rest of the command line included.
+		 */
+		argv[-1] = cp;
+		execve_shell (shellstr, &argv[-1], environ);
+		err = errno;
+		(void) fprintf (stderr,
+		                _("Cannot execute %s\n"), shellstr);
+		errno = err;
+	} else {
+		(void) shell (shellstr, cp, environ);
+	}
+
+	pw_free (pw);
+
+	return (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+}
+
diff -pruN 1:4.2-3.2ubuntu1/src/su.c 1:4.2-3.2ubuntu2/src/su.c
--- 1:4.2-3.2ubuntu1/src/su.c	2017-05-31 18:10:32.000000000 +0000
+++ 1:4.2-3.2ubuntu2/src/su.c	2017-05-31 18:10:31.000000000 +0000
@@ -376,20 +376,35 @@ static void prepare_pam_close_session (v
 				/* wake child when resumed */
 				kill (pid, SIGCONT);
 				stop = false;
+			} else if (   (pid_t)-1 != pid) {
+				pid_child = 0;
 			}
 		} while (!stop);
 	}
 
-	if (0 != caught) {
+	if (0 != caught && 0 != pid_child) {
 		(void) fputs ("\n", stderr);
 		(void) fputs (_("Session terminated, terminating shell..."),
 		              stderr);
 		(void) kill (-pid_child, caught);
 
 		(void) signal (SIGALRM, kill_child);
+		(void) signal (SIGCHLD, catch_signals);
 		(void) alarm (2);
 
-		(void) wait (&status);
+		sigemptyset (&ourset);
+		if ((sigaddset (&ourset, SIGALRM) != 0)
+		    || (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0)) {
+			fprintf (stderr, _("%s: signal masking malfunction\n"), Prog);
+			kill_child (0);
+		} else {
+			while (0 == waitpid (pid_child, &status, WNOHANG)) {
+				sigsuspend (&ourset);
+			}
+			pid_child = 0;
+			(void) sigprocmask (SIG_UNBLOCK, &ourset, NULL);
+		}
+
 		(void) fputs (_(" ...terminated.\n"), stderr);
 	}
 


More information about the Pkg-shadow-devel mailing list