aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2021-05-05 21:38:14 -0700
committerAndrew G. Morgan <morgan@kernel.org>2021-08-01 17:00:14 -0700
commitef911c679721a2fd335bb9e66057ffd4ebcf240d (patch)
tree0b4b634d0932ba4dcc8c0d6ccf034c2dd4eb8e79
parente1af96aa58dde0ab8ba873293e7cc3cb5ae0b5a8 (diff)
downloadlibcap-ef911c679721a2fd335bb9e66057ffd4ebcf240d.tar.gz
Import an old version of su from SimplePAMApps-0.60
When Linux-PAM was getting its act together (more than two decades ago) we cobbled together a set of system apps and made them use Linux-PAM for authentication. Once Redhat shipped Linux-PAM, the mainstream versions of these apps adopted Linux-PAM and these simple ones withered. I want to explore some pam_cap.so related issues and so I've resurrected one of them, su, which announces itself to libpam with the name "sucap". I'm not sure where I'll go with this yet, but my first goal is to reproduce the issue: https://bugzilla.kernel.org/show_bug.cgi?id=212945 and validate the workaround I've added to that module is sufficient. Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r--contrib/sucap/Makefile9
-rw-r--r--contrib/sucap/README27
-rw-r--r--contrib/sucap/su.c1498
3 files changed, 1534 insertions, 0 deletions
diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile
new file mode 100644
index 0000000..74537ff
--- /dev/null
+++ b/contrib/sucap/Makefile
@@ -0,0 +1,9 @@
+all: su
+
+su: su.c
+ $(CC) -o $@ $< -lpam -lpam_misc -lcap
+ sudo chown root ./su
+ sudo chmod +s ./su
+
+clean:
+ rm -f su su.o *~
diff --git a/contrib/sucap/README b/contrib/sucap/README
new file mode 100644
index 0000000..4e0bcd9
--- /dev/null
+++ b/contrib/sucap/README
@@ -0,0 +1,27 @@
+This directory contains a port of the SimplePAMApp su to more
+aggressively use libcap. The point of doing this is to better test the
+full libcap implementation, and to also provide a use case for testing
+PAM interaction with libcap and pam_cap.so.
+
+The original sources were found here:
+
+https://kernel.org/pub/linux/libs/pam/pre/applications/SimplePAMApps-0.60.tar.gz
+
+The SimplePAMApps contain the same License as libcap (they were
+originally started by the same author!). The credited Authors in the
+above tarball were:
+
+ Andrew [G.] Morgan
+ Andrey V. Savochkin
+ Alexei V. Galatenko
+
+The code in this present directory is freely adapted from the above
+tar ball and is thus a derived work from that.
+
+(Andrew would like to apologize to Andrey for removing all of the
+config support he worked to add all those decades ago... I just wanted
+to make a quick tester for a potential workaround for this pam_cap
+issue: https://bugzilla.kernel.org/show_bug.cgi?id=212945 .)
+
+Andrew G. Morgan <morgan@kernel.org>
+2021-05-05
diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c
new file mode 100644
index 0000000..08c3079
--- /dev/null
+++ b/contrib/sucap/su.c
@@ -0,0 +1,1498 @@
+/*
+ * ( based on an implementation of `su' by
+ *
+ * Peter Orbaek <poe@daimi.aau.dk>
+ *
+ * obtained circa 1997 from ftp://ftp.daimi.aau.dk/pub/linux/poe/ )
+ *
+ * Rewritten for Linux-PAM by Andrew G. Morgan <morgan@linux.kernel.org>
+ * Modified by Andrey V. Savochkin <saw@msu.ru>
+ * Modified for use with libcap by Andrew G. Morgan <morgan@kernel.org>
+ */
+
+#define ROOT_UID 0
+#define PAM_APP_NAME "sucap"
+#define DEFAULT_HOME "/"
+#define DEFAULT_SHELL "/bin/bash"
+#define SLEEP_TO_KILL_CHILDREN 3 /* seconds to wait after SIGTERM before
+ SIGKILL */
+#define SU_FAIL_DELAY 2000000 /* usec on authentication failure */
+
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/wait.h>
+#include <utmp.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <netdb.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+
+/* -------------------------------------------- */
+/* ------ declarations ------------------------ */
+/* -------------------------------------------- */
+
+extern char **environ;
+static pam_handle_t *pamh = NULL;
+static int state;
+
+#define SU_STATE_PAM_INITIALIZED 1
+#define SU_STATE_AUTHENTICATED 2
+#define SU_STATE_AUTHORIZED 3
+#define SU_STATE_SESSION_OPENED 4
+#define SU_STATE_CREDENTIALS_GOTTEN 5
+#define SU_STATE_PROCESS_UNKILLABLE 6
+#define SU_STATE_TERMINAL_REOWNED 7
+#define SU_STATE_UTMP_WRITTEN 8
+
+#define ROOT_UID 0
+
+static int wait_for_child_caught=0;
+static int need_job_control=0;
+static int is_terminal = 0;
+static struct termios stored_mode; /* initial terminal mode settings */
+static uid_t terminal_uid = (uid_t) -1;
+static uid_t invoked_uid;
+
+/* -------------------------------------------- */
+/* ------ some local (static) functions ------- */
+/* -------------------------------------------- */
+
+/*
+ * We will attempt to transcribe the following env variables
+ * independent of whether we keep the whole environment. Others will
+ * be set elsewhere: either in modules; or after the identity of the
+ * user is known.
+ */
+
+static const char *posix_env[] = {
+ "LANG",
+ "LC_COLLATE",
+ "LC_CTYPE",
+ "LC_MONETARY",
+ "LC_NUMERIC",
+ "TZ",
+ NULL
+};
+
+static int make_environment(pam_handle_t *pamh, int keep_env)
+{
+ int retval;
+
+ if (keep_env) {
+
+ /* preserve the original environment */
+ retval = pam_misc_paste_env(pamh, (const char * const *)environ);
+
+ } else {
+ const char *tmpe;
+ int i;
+
+ /* we always transcribe some variables anyway */
+ {
+ tmpe = getenv("TERM");
+ if (tmpe == NULL) {
+ tmpe = "dumb";
+ }
+ retval = pam_misc_setenv(pamh, "TERM", tmpe, 0);
+ tmpe = NULL;
+ if (retval == PAM_SUCCESS) {
+ retval = pam_misc_setenv(pamh, "PATH", "/bin:/usr/bin", 0);
+ }
+
+ if (retval != PAM_SUCCESS) {
+ D(("error setting environment variables"));
+ return retval;
+ }
+ }
+
+ /* also propogate the POSIX specific ones */
+ for (i=0; retval == PAM_SUCCESS && posix_env[i]; ++i) {
+ tmpe = getenv(posix_env[i]);
+ if (tmpe != NULL) {
+ retval = pam_misc_setenv(pamh, posix_env[i], tmpe, 0);
+ }
+ }
+ tmpe = NULL;
+ }
+
+ return retval; /* how did we do? */
+}
+
+static void checkfds(void)
+{
+ struct stat st;
+ int fd;
+
+ if (fstat(1, &st) == -1) {
+ fd = open("/dev/null", O_WRONLY);
+ if (fd == -1) exit(1);
+ if (fd != 1) {
+ if (dup2(fd, 1) == -1) exit(1);
+ if (close(fd) == -1) exit(1);
+ }
+ }
+ if (fstat(2, &st) == -1) {
+ fd = open("/dev/null", O_WRONLY);
+ if (fd == -1) exit(1);
+ if (fd != 2) {
+ if (dup2(fd, 2) == -1) exit(1);
+ if (close(fd) == -1) exit(1);
+ }
+ }
+}
+
+/* should be called once at the beginning */
+static void store_terminal_modes(void)
+{
+ if (isatty(STDIN_FILENO)) {
+ is_terminal = 1;
+ if (tcgetattr(STDIN_FILENO, &stored_mode) != 0) {
+ (void) fprintf(stderr, PAM_APP_NAME ": couldn't copy terminal mode");
+ exit(1);
+ }
+ } else if (getuid()) {
+ (void) fprintf(stderr, PAM_APP_NAME ": must be run from a terminal\n");
+ exit(1);
+ } else
+ is_terminal = 0;
+}
+
+/*
+ * Returns:
+ * 0 ok
+ * !=0 error
+ */
+static int reset_terminal_modes(void)
+{
+ if (is_terminal && tcsetattr(STDIN_FILENO, TCSAFLUSH, &stored_mode) != 0) {
+ (void) fprintf(stderr, PAM_APP_NAME ": cannot reset terminal mode: %s\n"
+ , strerror(errno));
+ return 1;
+ } else
+ return 0;
+}
+
+/* ------ unexpected signals ------------------ */
+
+struct sigaction old_int_act, old_quit_act, old_tstp_act, old_pipe_act;
+
+static void disable_terminal_signals(void)
+{
+ /*
+ * Protect the process from dangerous terminal signals.
+ * The protection is implemented via sigaction() because
+ * the signals are sent regardless of the process' uid.
+ */
+ struct sigaction act;
+
+ act.sa_handler = SIG_IGN; /* ignore the signal */
+ sigemptyset(&act.sa_mask); /* no signal blocking on handler
+ call needed */
+ act.sa_flags = SA_RESTART; /* do not reset after first signal
+ arriving, restart interrupted
+ system calls if possible */
+ sigaction(SIGINT, &act, &old_int_act);
+ sigaction(SIGQUIT, &act, &old_quit_act);
+ /*
+ * Ignore SIGTSTP signals. Why? attacker could otherwise stop
+ * a process and a. kill it, or b. wait for the system to
+ * shutdown - either way, nothing appears in syslogs.
+ */
+ sigaction(SIGTSTP, &act, &old_tstp_act);
+ /*
+ * Ignore SIGPIPE. The parent `su' process may print something
+ * on stderr. Killing of the process would be undesired.
+ */
+ sigaction(SIGPIPE, &act, &old_pipe_act);
+}
+
+static void enable_terminal_signals(void)
+{
+ sigaction(SIGINT, &old_int_act, NULL);
+ sigaction(SIGQUIT, &old_quit_act, NULL);
+ sigaction(SIGTSTP, &old_tstp_act, NULL);
+ sigaction(SIGPIPE, &old_pipe_act, NULL);
+}
+
+/* ------ terminal ownership ------------------ */
+
+/*
+ * Change the ownership of STDIN if needed.
+ * Returns:
+ * 0 ok,
+ * -1 fatal error (continue of the work is impossible),
+ * 1 non-fatal error.
+ * In the case of an error "err_descr" is set to the error message
+ * and "callname" to the name of the failed call.
+ */
+static int change_terminal_owner(uid_t uid, int is_login,
+ const char **callname, const char **err_descr)
+{
+ /* determine who owns the terminal line */
+ if (is_terminal && is_login) {
+ struct stat stat_buf;
+
+ if (fstat(STDIN_FILENO,&stat_buf) != 0) {
+ *callname = "fstat to STDIN";
+ *err_descr = strerror(errno);
+ return -1;
+ }
+ if(fchown(STDIN_FILENO, uid, -1) != 0) {
+ *callname = "fchown to STDIN";
+ *err_descr = strerror(errno);
+ return 1;
+ }
+ terminal_uid = stat_buf.st_uid;
+ }
+ return 0;
+}
+
+static void restore_terminal_owner(void)
+{
+ if (terminal_uid != (uid_t) -1) {
+ if(fchown(STDIN_FILENO, terminal_uid, -1) != 0) {
+ openlog(PAM_APP_NAME, LOG_CONS | LOG_PERROR | LOG_PID, LOG_AUTHPRIV);
+ syslog(LOG_ALERT
+ , "Terminal owner hasn\'t been restored: %s"
+ , strerror(errno));
+ closelog();
+ }
+ terminal_uid = (uid_t) -1;
+ }
+}
+
+/*
+ * Make the process unkillable by the user invoked it.
+ * Returns:
+ * 0 ok,
+ * -1 fatal error (continue of the work is impossible),
+ * 1 non-fatal error.
+ * In the case of an error "err_descr" is set to the error message
+ * and "callname" to the name of the failed call.
+ */
+int make_process_unkillable(const char **callname
+ , const char **err_descr)
+{
+ invoked_uid = getuid();
+ if(setuid(geteuid()) != 0) {
+ *callname = "setuid";
+ *err_descr = strerror(errno);
+ return -1;
+ }else
+ return 0;
+}
+
+void make_process_killable()
+{
+ setreuid(invoked_uid, -1);
+}
+
+/* ------ command line parser ----------------- */
+
+void usage()
+{
+ (void) fprintf(stderr,"usage: su [-] [-c \"command\"] [username]\n");
+ exit(1);
+}
+
+void parse_command_line(int argc, char *argv[]
+ , int *is_login, const char **user, const char **command)
+{
+ int username_present, command_present;
+
+ *is_login = 0;
+ *user = NULL;
+ *command = NULL;
+ username_present = command_present = 0;
+
+ while ( --argc > 0 ) {
+ const char *token;
+
+ token = *++argv;
+ if (*token == '-') {
+ switch (*++token) {
+ case '\0': /* su as a login shell for the user */
+ if (*is_login)
+ usage();
+ *is_login = 1;
+ break;
+ case 'c':
+ if (command_present) {
+ usage();
+ } else { /* indicate we are running commands */
+ if (*++token != '\0') {
+ command_present = 1;
+ *command = token;
+ } else if (--argc > 0) {
+ command_present = 1;
+ *command = *++argv;
+ } else
+ usage();
+ }
+ break;
+ default:
+ usage();
+ }
+ } else { /* must be username */
+ if (username_present)
+ usage();
+ username_present = 1;
+ *user = *argv;
+ }
+ }
+
+ if (!username_present) { /* default user is superuser */
+ const struct passwd *pw;
+
+ pw = getpwuid(ROOT_UID);
+ if (pw == NULL) /* No ROOT_UID!? */
+ {
+ printf ("\nsu:no access to superuser identity!? (%d)\n",
+ ROOT_UID);
+ exit (1);
+ }
+
+ *user = NULL;
+ if (pw->pw_name != NULL)
+ *user = strdup(pw->pw_name);
+ }
+}
+
+
+/*
+ * This following contains code that waits for a child process to die.
+ * It also chooses to intercept a couple of signals that it will
+ * kindly pass on a SIGTERM to the child ;^). Waiting again for the
+ * child to exit. If the child resists dying, it will SIGKILL it!
+ */
+
+static void wait_for_child_catch_sig(int ignore)
+{
+ wait_for_child_caught = 1;
+}
+
+static void prepare_for_job_control(int need_it)
+{
+ sigset_t ourset;
+
+ (void) sigfillset(&ourset);
+ if (sigprocmask(SIG_BLOCK, &ourset, NULL) != 0) {
+ (void) fprintf(stderr,"[trouble blocking signals]\n");
+ wait_for_child_caught = 1;
+ return;
+ }
+ need_job_control = need_it;
+}
+
+int wait_for_child(pid_t child)
+{
+ int retval, status, exit_code;
+ sigset_t ourset;
+
+ exit_code = -1; /* no exit code yet, exit codes could be from 0 to 255 */
+
+ /*
+ * set up signal handling
+ */
+
+ if (!wait_for_child_caught) {
+ struct sigaction action, defaction;
+
+ action.sa_handler = wait_for_child_catch_sig;
+ (void) sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+
+ defaction.sa_handler = SIG_DFL;
+ (void) sigemptyset(&defaction.sa_mask);
+ defaction.sa_flags = 0;
+
+ (void) sigemptyset(&ourset);
+
+ if ( sigaddset(&ourset, SIGTERM)
+ || sigaction(SIGTERM, &action, NULL)
+ || sigaddset(&ourset, SIGHUP)
+ || sigaction(SIGHUP, &action, NULL)
+ || sigaddset(&ourset, SIGALRM) /* required by sleep(3) */
+ || (need_job_control && sigaddset(&ourset, SIGTSTP))
+ || (need_job_control && sigaction(SIGTSTP, &defaction, NULL))
+ || (need_job_control && sigaddset(&ourset, SIGTTIN))
+ || (need_job_control && sigaction(SIGTTIN, &defaction, NULL))
+ || (need_job_control && sigaddset(&ourset, SIGTTOU))
+ || (need_job_control && sigaction(SIGTTOU, &defaction, NULL))
+ || (need_job_control && sigaddset(&ourset, SIGCONT))
+ || (need_job_control && sigaction(SIGCONT, &defaction, NULL))
+ || sigprocmask(SIG_UNBLOCK, &ourset, NULL)
+ ) {
+ (void) fprintf(stderr,"[trouble setting signal intercept]\n");
+ wait_for_child_caught = 1;
+ }
+
+ /* application should be ready for receiving a SIGTERM/HUP now */
+ }
+
+ /* this code waits for the process to actually die. If it stops,
+ * then the parent attempts to mimic the behavior of the
+ * child.. There is a slight bug in the code when the 'su'd user
+ * attempts to restart the child independently of the parent --
+ * the child dies. */
+
+ while (!wait_for_child_caught) {
+
+ /* parent waits for child */
+ if ((retval = waitpid(child, &status, 0)) <= 0) {
+ if (errno == EINTR)
+ continue; /* recovering from a 'fg' */
+ (void) fprintf(stderr, "[error waiting child: %s]\n"
+ , strerror(errno));
+ /*
+ * Break the loop keeping exit_code undefined.
+ * Do we have a chance for a successfull wait() call
+ * after kill()? (SAW)
+ */
+ wait_for_child_caught = 1;
+ break;
+ }else {
+ /* the child is terminated via exit() or a fatal signal */
+ if (WIFEXITED(status))
+ exit_code = WEXITSTATUS(status);
+ else
+ exit_code = 1;
+ break;
+ }
+ }
+
+ if (wait_for_child_caught) {
+ (void) fprintf(stderr,"\nKilling shell...");
+ (void) kill(child, SIGTERM);
+ }
+
+ /*
+ * do we need to wait for the child to catch up?
+ */
+
+ if (wait_for_child_caught) {
+ (void) sleep(SLEEP_TO_KILL_CHILDREN);
+ (void) kill(child, SIGKILL);
+ (void) fprintf(stderr, "killed\n");
+ }
+
+ /*
+ * collect the zombie the shell was killed by ourself
+ */
+ if (exit_code == -1) {
+ do {
+ retval = waitpid(child, &status, 0);
+ }while (retval == -1 && errno == EINTR);
+ if (retval == -1) {
+ (void) fprintf(stderr, PAM_APP_NAME ": the final wait failed: %s\n"
+ , strerror(errno));
+ }
+ if (WIFEXITED(status))
+ exit_code = WEXITSTATUS(status);
+ else
+ exit_code = 1;
+ }
+
+ return exit_code;
+}
+
+
+/*
+ * Next some code that parses the spawned shell command line.
+ */
+
+static char * const *build_shell_args(const char *pw_shell,
+ int login, const char *command)
+{
+ int use_default = 1; /* flag to signal we should use the default shell */
+ const char **args=NULL; /* array of PATH+ARGS+NULL pointers */
+
+ D(("called."));
+ if (login) {
+ command = NULL; /* command always ignored for login */
+ }
+
+ if (pw_shell && *pw_shell != '\0') {
+ char *line;
+ const char *tmp, *tmpb=NULL;
+ int arg_no=0,i;
+
+ /* first find the number of arguments */
+ D(("non-null shell"));
+ for (tmp=pw_shell; *tmp; ++arg_no) {
+
+ /* skip leading spaces */
+ while (isspace(*tmp))
+ ++tmp;
+
+ if (tmpb == NULL) /* mark beginning token */
+ tmpb = tmp;
+ if (*tmp == '\0') /* end of line with no token */
+ break;
+
+ /* skip token */
+ while (*tmp && !isspace(*tmp))
+ ++tmp;
+ }
+
+ /*
+ * We disallow shells:
+ * - without a full specified path;
+ * - when we are not logging in and the #args != 1
+ * (unlikely a simple shell)
+ */
+
+ D(("shell so far = %s, arg_no = %d", tmpb, arg_no));
+ if (tmpb != NULL && tmpb[0] == '/' /* something (full path) */
+ && ( login || arg_no == 1 ) /* login, or single arg shells */
+ ) {
+
+ use_default = 0; /* we will use this shell */
+ D(("commited to using user's shell"));
+ if (command) {
+ arg_no += 2; /* will append "-c" "command" */
+ }
+
+ /* allocate an array of pointers long enough */
+
+ D(("building array of size %d", 2+arg_no));
+ args = (const char **) calloc(2+arg_no, sizeof(const char *));
+ if (args == NULL)
+ return NULL;
+ /* get a string long enough for all the arguments */
+
+ D(("an array of size %d chars", 2+strlen(tmpb)
+ + ( command ? 4:0 )));
+ line = (char *) malloc(2+strlen(tmpb)
+ + ( command ? 4:0 ));
+ if (line == NULL) {
+ free(args);
+ return NULL;
+ }
+
+ /* fill array - tmpb points to start of first non-space char */
+
+ line[0] = '-';
+ strcpy(line+1, tmpb);
+
+ /* append " -c" to line? */
+ if (command) {
+ strcat(line, " -c");
+ }
+
+ D(("complete command: %s [+] %s", line, command));
+
+ tmp = strtok(line, " \t");
+ D(("command path=%s", line+1));
+ args[0] = line+1;
+
+ if (login) { /* standard procedure for login shell */
+ D(("argv[0]=%s", line));
+ args[i=1] = line;
+ } else { /* not a login shell -- for use with su */
+ D(("argv[0]=%s", line+1));
+ args[i=1] = line+1;
+ }
+
+ while ((tmp = strtok(NULL, " \t"))) {
+ D(("adding argument %d: %s",i,tmp));
+ args[++i] = tmp;
+ }
+ if (command) {
+ D(("appending command [%s]", command));
+ args[++i] = command;
+ }
+ D(("terminating args with NULL"));
+ args[++i] = NULL;
+ D(("list completed."));
+
+ }
+ }
+
+ /* should we use the default shell instead of specific one? */
+
+ if (use_default && !login) {
+ int last_arg;
+
+ D(("selecting default shell"));
+ last_arg = command ? 5:3;
+
+ args = (const char **) calloc(last_arg--, sizeof(const char *));
+ if (args == NULL) {
+ return NULL;
+ }
+ args[1] = DEFAULT_SHELL; /* mapped to argv[0] (NOT login shell) */
+ args[0] = args[1]; /* path to program */
+ if (command) {
+ args[2] = "-c"; /* should perform command and exit */
+ args[3] = command; /* the desired command */
+ }
+ args[last_arg] = NULL; /* terminate list of args */
+ }
+
+ D(("returning"));
+ return (char * const *)args; /* return argument list */
+}
+
+
+/* ------ abnormal termination ---------------- */
+
+static void exit_now(int exit_code, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args,format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ if (pamh != NULL)
+ pam_end(pamh, exit_code ? PAM_ABORT:PAM_SUCCESS);
+
+ /* USER's shell may have completely broken terminal settings
+ restore the sane(?) initial conditions */
+ reset_terminal_modes();
+
+ exit(exit_code);
+}
+
+static void exit_child_now(int exit_code, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args,format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ if (pamh != NULL)
+ pam_end(pamh, (exit_code ? PAM_ABORT:PAM_SUCCESS) | PAM_DATA_SILENT);
+
+ exit(exit_code);
+}
+
+/* ------ PAM setup --------------------------- */
+
+static struct pam_conv conv = {
+ misc_conv, /* defined in <pam_misc/libmisc.h> */
+ NULL
+};
+
+static void do_pam_init(const char *user, int is_login)
+{
+ int retval;
+
+ retval = pam_start(PAM_APP_NAME, user, &conv, &pamh);
+ if (retval != PAM_SUCCESS) {
+ /*
+ * From my point of view failing of pam_start() means that
+ * pamh isn't a valid handler. Without a handler
+ * we couldn't call pam_strerror :-( 1998/03/29 (SAW)
+ */
+ (void) fprintf(stderr, PAM_APP_NAME ": pam_start failed with code %d\n", retval);
+ exit(1);
+ }
+
+ /*
+ * Fill in some blanks
+ */
+
+ retval = make_environment(pamh, !is_login);
+ D(("made_environment returned: %s", pam_strerror(pamh,retval)));
+
+ if (retval == PAM_SUCCESS && is_terminal) {
+ const char *terminal = ttyname(STDIN_FILENO);
+ if (terminal) {
+ retval = pam_set_item(pamh, PAM_TTY, (const void *)terminal);
+ } else {
+ retval = PAM_PERM_DENIED; /* how did we get here? */
+ }
+ terminal = NULL;
+ }
+
+ if (retval == PAM_SUCCESS && is_terminal) {
+ const char *ruser = getlogin(); /* Who is running this program? */
+ if (ruser) {
+ retval = pam_set_item(pamh, PAM_RUSER, (const void *)ruser);
+ } else {
+ retval = PAM_PERM_DENIED; /* must be known to system */
+ }
+ ruser = NULL;
+ }
+
+ if (retval == PAM_SUCCESS) {
+ retval = pam_set_item(pamh, PAM_RHOST, (const void *)"localhost");
+ }
+
+ if (retval != PAM_SUCCESS) {
+ exit_now(1, PAM_APP_NAME ": problem establishing environment\n");
+ }
+
+ /* have to pause on failure. At least this long (doubles..) */
+ retval = pam_fail_delay(pamh, SU_FAIL_DELAY);
+ if (retval != PAM_SUCCESS) {
+ exit_now(1, PAM_APP_NAME ": problem initializing failure delay\n");
+ }
+}
+
+/*
+ * Here we set the user's groups and return their uid
+ */
+
+static int set_user_credentials(pam_handle_t *pamh, int login,
+ const char **user, uid_t *uid,
+ const char **shell)
+{
+ const struct passwd *pw;
+ int retval;
+
+ /*
+ * Identify the user from PAM.
+ */
+
+ D(("get user from pam"));
+ retval = pam_get_item(pamh, PAM_USER, (const void **)user);
+ if (retval != PAM_SUCCESS || *user == NULL || **user == '\0') {
+ D(("error identifying user from PAM."));
+ return retval;
+ }
+
+ /*
+ * identify user, update their name too. This is most likely only
+ * useful with libpwdb where guest may be mapped to guest37 ...
+ */
+
+ pw = getpwnam(*user);
+ if (pw == NULL || (*user = x_strdup(pw->pw_name)) == NULL) {
+ D(("failed to identify user"));
+ return PAM_USER_UNKNOWN;
+ }
+
+ *uid = pw->pw_uid;
+
+ *shell = x_strdup(pw->pw_shell);
+ if (*shell == NULL) {
+ return PAM_CRED_ERR;
+ }
+
+ /* initialize groups */
+
+ if (initgroups(pw->pw_name, pw->pw_gid) != 0
+ || setgid(pw->pw_gid) != 0) {
+ return PAM_PERM_DENIED;
+ }
+
+ /*
+ * Add the LOGNAME and HOME environment variables.
+ */
+
+ D(("add some variables"));
+ if (login) {
+ /* set LOGNAME, HOME */
+ if (pam_misc_setenv(pamh, "LOGNAME", *user, 0) != PAM_SUCCESS) {
+ D(("failed to set LOGNAME"));
+ return PAM_CRED_ERR;
+ }
+ if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) {
+ D(("failed to set HOME"));
+ return PAM_CRED_ERR;
+ }
+ }
+
+ pw = NULL; /* be tidy */
+
+ /*
+ * next, we call the PAM framework to add/enhance the credentials
+ * of this user [it may change the user's home directory...]
+ */
+
+ retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+ if (retval != PAM_SUCCESS) {
+ D(("failed to set PAM credentials; %s", pam_strerror(pamh,retval)));
+ return retval;
+ }
+
+ return PAM_SUCCESS;
+}
+
+#define RHOST_UNKNOWN_NAME "" /* perhaps "[from.where?]" */
+
+#define DEVICE_FILE_PREFIX "/dev/"
+
+#define WTMP_LOCK_TIMEOUT 3 /* in seconds */
+
+#ifndef UT_IDSIZE
+#define UT_IDSIZE 4 /* XXX - this is sizeof(struct utmp.ut_id) */
+#endif
+
+/*
+ * Find entry for this terminal (if there is one).
+ * Utmp file should have been opened and rewinded for the call.
+ *
+ * XXX: the search should be more or less compatible with libc one.
+ * The caller expects that pututline with the same arguments
+ * will replace the found entry.
+ */
+
+static
+const struct utmp *find_utmp_entry(const char *ut_line
+ , const char *ut_id)
+{
+ struct utmp *u_tmp_p;
+
+ while ((u_tmp_p = getutent()) != NULL)
+ if ((u_tmp_p->ut_type == INIT_PROCESS ||
+ u_tmp_p->ut_type == LOGIN_PROCESS ||
+ u_tmp_p->ut_type == USER_PROCESS ||
+ u_tmp_p->ut_type == DEAD_PROCESS) &&
+ !strncmp(u_tmp_p->ut_id, ut_id, UT_IDSIZE) &&
+ !strncmp(u_tmp_p->ut_line, ut_line, UT_LINESIZE))
+ break;
+
+ return u_tmp_p;
+}
+
+/*
+ * Identify the terminal name and the abreviation we will use.
+ */
+
+static
+void set_terminal_name(const char *terminal, char *ut_line, char *ut_id)
+{
+ memset(ut_line, 0, UT_LINESIZE);
+ memset(ut_id, 0, UT_IDSIZE);
+
+ /* set the terminal entry */
+ if ( *terminal == '/' ) { /* now deal with filenames */
+ int o1, o2;
+
+ o1 = strncmp(DEVICE_FILE_PREFIX, terminal, 5) ? 0 : 5;
+ if (!strncmp("/dev/tty", terminal, 8)) {
+ o2 = 8;
+ } else {
+ o2 = strlen(terminal) - sizeof(UT_IDSIZE);
+ if (o2 < 0)
+ o2 = 0;
+ }
+
+ strncpy(ut_line, terminal + o1, UT_LINESIZE);
+ strncpy(ut_id, terminal + o2, UT_IDSIZE);
+ } else if (strchr(terminal, ':')) { /* deal with X-based session */
+ const char *suffix;
+
+ suffix = strrchr(terminal,':');
+ strncpy(ut_line, terminal, UT_LINESIZE);
+ strncpy(ut_id, suffix, UT_IDSIZE);
+ } else { /* finally deal with weird terminals */
+ strncpy(ut_line, terminal, UT_LINESIZE);
+ ut_id[0] = '?';
+ strncpy(ut_id + 1, terminal, UT_IDSIZE - 1);
+ }
+}
+
+/*
+ * Append an entry to wtmp. See utmp_open_session for the return convention.
+ * Be carefull: the function uses alarm().
+ */
+
+#define WWTMP_STATE_BEGINNING 0
+#define WWTMP_STATE_FILE_OPENED 1
+#define WWTMP_STATE_SIGACTION_SET 2
+#define WWTMP_STATE_LOCK_TAKEN 3
+
+static
+int write_wtmp(struct utmp *u_tmp_p
+ , const char **callname, const char **err_descr)
+{
+ int w_tmp_fd;
+ struct flock w_lock;
+ struct sigaction act1, act2;
+ int state;
+ int retval;
+
+ state = WWTMP_STATE_BEGINNING;
+ retval = 1;
+
+ do {
+ D(("writing to wtmp"));
+ w_tmp_fd = open(_PATH_WTMP, O_APPEND|O_WRONLY);
+ if (w_tmp_fd == -1) {
+ *callname = "wtmp open";
+ *err_descr = strerror(errno);
+ break;
+ }
+ state = WWTMP_STATE_FILE_OPENED;
+
+ /* prepare for blocking operation... */
+ act1.sa_handler = SIG_DFL;
+ (void) sigemptyset(&act1.sa_mask);
+ act1.sa_flags = 0;
+ if (sigaction(SIGALRM, &act1, &act2) == -1) {
+ *callname = "sigaction";
+ *err_descr = strerror(errno);
+ break;
+ }
+ (void) alarm(WTMP_LOCK_TIMEOUT);
+ state = WWTMP_STATE_SIGACTION_SET;
+
+ /* now we try to lock this file-rcord exclusively; non-blocking */
+ memset(&w_lock, 0, sizeof(w_lock));
+ w_lock.l_type = F_WRLCK;
+ w_lock.l_whence = SEEK_END;
+ if (fcntl(w_tmp_fd, F_SETLK, &w_lock) < 0) {
+ D(("locking %s failed.", _PATH_WTMP));
+ *callname = "fcntl(F_SETLK)";
+ *err_descr = strerror(errno);
+ break;
+ }
+ (void) alarm(0);
+ (void) sigaction(SIGALRM, &act2, NULL);
+ state = WWTMP_STATE_LOCK_TAKEN;
+
+ if (write(w_tmp_fd, u_tmp_p, sizeof(struct utmp)) != -1)
+ retval = 0;
+
+ } while(0); /* it's not a loop! */
+
+ if (state >= WWTMP_STATE_LOCK_TAKEN) {
+ w_lock.l_type = F_UNLCK; /* unlock wtmp file */
+ (void) fcntl(w_tmp_fd, F_SETLK, &w_lock);
+ }else if (state >= WWTMP_STATE_SIGACTION_SET) {
+ (void) alarm(0);
+ (void) sigaction(SIGALRM, &act2, NULL);
+ }
+
+ if (state >= WWTMP_STATE_FILE_OPENED) {
+ close(w_tmp_fd); /* close wtmp file */
+ D(("wtmp written"));
+ }
+
+ return retval;
+}
+
+/*
+ * XXX - if this gets turned into a module, make this a
+ * pam_data item. You should put the pid in the name so we can
+ * "probably" nest calls more safely...
+ */
+struct utmp *login_stored_utmp=NULL;
+
+/*
+ * Returns:
+ * 0 ok,
+ * 1 non-fatal error
+ * -1 fatal error
+ * callname and err_descr will be set
+ * Be carefull: the function indirectly uses alarm().
+ */
+static int utmp_do_open_session(const char *user, const char *terminal
+ , const char *rhost, pid_t pid
+ , const char **callname, const char **err_descr)
+{
+ struct utmp u_tmp;
+ const struct utmp *u_tmp_p;
+ char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
+ int retval;
+
+ set_terminal_name(terminal, ut_line, ut_id);
+
+ utmpname(_PATH_UTMP);
+ setutent(); /* rewind file */
+ u_tmp_p = find_utmp_entry(ut_line, ut_id);
+
+ /* reset new entry */
+ memset(&u_tmp, 0, sizeof(u_tmp)); /* reset new entry */
+ if (u_tmp_p == NULL) {
+ D(("[NEW utmp]"));
+ } else {
+ D(("[OLD utmp]"));
+
+ /*
+ * here, we make a record of the former entry. If the
+ * utmp_close_session code is attatched to the same process,
+ * the wtmp will be replaced, otherwise we leave init to pick
+ * up the pieces.
+ */
+ if (login_stored_utmp == NULL) {
+ login_stored_utmp = malloc(sizeof(struct utmp));
+ if (login_stored_utmp == NULL) {
+ *callname = "malloc";
+ *err_descr = "fail";
+ endutent();
+ return -1;
+ }
+ }
+ memcpy(login_stored_utmp, u_tmp_p, sizeof(struct utmp));
+ }
+
+ /* we adjust the entry to reflect the current session */
+ {
+ strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
+ memset(ut_line, 0, UT_LINESIZE);
+ strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
+ memset(ut_id, 0, UT_IDSIZE);
+ strncpy(u_tmp.ut_user, user
+ , sizeof(u_tmp.ut_user));
+ strncpy(u_tmp.ut_host, rhost ? rhost : RHOST_UNKNOWN_NAME
+ , sizeof(u_tmp.ut_host));
+
+ /* try to fill the host address entry */
+ if (rhost != NULL) {
+ struct hostent *hptr;
+
+ /* XXX: it isn't good to do DNS lookup here... 1998/05/29 SAW */
+ hptr = gethostbyname(rhost);
+ if (hptr != NULL && hptr->h_addr_list) {
+ memcpy(&u_tmp.ut_addr, hptr->h_addr_list[0]
+ , sizeof(u_tmp.ut_addr));
+ }
+ }
+
+ /* we fill in the remaining info */
+ u_tmp.ut_type = USER_PROCESS; /* a user process starting */
+ u_tmp.ut_pid = pid; /* session identifier */
+ u_tmp.ut_time = time(NULL);
+ }
+
+ setutent(); /* rewind file (replace old) */
+ pututline(&u_tmp); /* write it to utmp */
+ endutent(); /* close the file */
+
+ retval = write_wtmp(&u_tmp, callname, err_descr); /* write to wtmp file */
+ memset(&u_tmp, 0, sizeof(u_tmp)); /* reset entry */
+
+ return retval;
+}
+
+static int utmp_do_close_session(const char *terminal
+ , const char **callname, const char **err_descr)
+{
+ int retval;
+ struct utmp u_tmp;
+ const struct utmp *u_tmp_p;
+ char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
+
+ retval = 0;
+
+ set_terminal_name(terminal, ut_line, ut_id);
+
+ utmpname(_PATH_UTMP);
+ setutent(); /* rewind file */
+
+ /*
+ * if there was a stored entry, return it to the utmp file, else
+ * if there is a session to close, we close that
+ */
+ if (login_stored_utmp) {
+ pututline(login_stored_utmp);
+
+ memcpy(&u_tmp, login_stored_utmp, sizeof(u_tmp));
+ u_tmp.ut_time = time(NULL); /* a new time to restart */
+
+ retval = write_wtmp(&u_tmp, callname, err_descr);
+
+ memset(login_stored_utmp, 0, sizeof(u_tmp)); /* reset entry */
+ free(login_stored_utmp);
+ } else {
+ u_tmp_p = find_utmp_entry(ut_line, ut_id);
+ if (u_tmp_p != NULL) {
+ memset(&u_tmp, 0, sizeof(u_tmp));
+ strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
+ strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
+ memset(&u_tmp.ut_user, 0, sizeof(u_tmp.ut_user));
+ memset(&u_tmp.ut_host, 0, sizeof(u_tmp.ut_host));
+ u_tmp.ut_addr = 0;
+ u_tmp.ut_type = DEAD_PROCESS; /* `old' login process */
+ u_tmp.ut_pid = 0;
+ u_tmp.ut_time = time(NULL);
+ setutent(); /* rewind file (replace old) */
+ pututline(&u_tmp); /* mark as dead */
+
+ retval = write_wtmp(&u_tmp, callname, err_descr);
+ }
+ }
+
+ /* clean up */
+ memset(ut_line, 0, UT_LINESIZE);
+ memset(ut_id, 0, UT_IDSIZE);
+
+ endutent(); /* close utmp file */
+ memset(&u_tmp, 0, sizeof(u_tmp)); /* reset entry */
+
+ return 0;
+}
+
+/*
+ * Returns:
+ * 0 ok,
+ * 1 non-fatal error
+ * -1 fatal error
+ * callname and err_descr will be set
+ * Be carefull: the function indirectly uses alarm().
+ */
+static int utmp_open_session(pam_handle_t *pamh, pid_t pid
+ , const char **callname, const char **err_descr)
+{
+ const char *user, *terminal, *rhost;
+ int retval;
+
+ retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
+ if (retval != PAM_SUCCESS) {
+ *callname = "pam_get_item(PAM_USER)";
+ *err_descr = pam_strerror(pamh, retval);
+ return -1;
+ }
+ retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
+ if (retval != PAM_SUCCESS) {
+ *callname = "pam_get_item(PAM_TTY)";
+ *err_descr = pam_strerror(pamh, retval);
+ return -1;
+ }
+ retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
+ if (retval != PAM_SUCCESS)
+ rhost = NULL;
+
+ return
+ utmp_do_open_session(user, terminal, rhost, pid, callname, err_descr);
+}
+
+static int utmp_close_session(pam_handle_t *pamh
+ , const char **callname, const char **err_descr)
+{
+ int retval;
+ const char *terminal;
+
+ retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
+ if (retval != PAM_SUCCESS) {
+ *callname = "pam_get_item(PAM_TTY)";
+ *err_descr = pam_strerror(pamh, retval);
+ return -1;
+ }
+
+ return utmp_do_close_session(terminal, callname, err_descr);
+}
+
+/* ------ shell invoker ----------------------- */
+
+static void su_exec_shell(const char *shell, uid_t uid, int is_login
+ , const char *command, const char *user)
+{
+ char * const * shell_args;
+ char * const * shell_env;
+ const char *pw_dir;
+ int retval;
+
+ /*
+ * Now, find the home directory for the user
+ */
+
+ pw_dir = pam_getenv(pamh, "HOME");
+ if ( !pw_dir || pw_dir[0] == '\0' ) {
+ /* Not set so far, so we get it now. */
+ struct passwd *pwd;
+
+ pwd = getpwnam(user);
+ if (pwd != NULL && pwd->pw_name != NULL) {
+ pw_dir = x_strdup(pwd->pw_name);
+ }
+
+ /* Last resort, take default directory.. */
+ if ( !pw_dir || pw_dir[0] == '\0') {
+ (void) fprintf(stderr, "setting home directory for %s to %s\n"
+ , user, DEFAULT_HOME);
+ pw_dir = DEFAULT_HOME;
+ }
+ }
+
+ /*
+ * We may wish to change the current directory.
+ */
+
+ if (is_login && chdir(pw_dir)) {
+ exit_child_now(1, "%s not available; exiting\n", pw_dir);
+ }
+
+ /*
+ * If it is a login session, we should set the environment
+ * accordingly.
+ */
+
+ if (is_login
+ && pam_misc_setenv(pamh, "HOME", pw_dir, 0) != PAM_SUCCESS) {
+ D(("failed to set $HOME"));
+ (void) fprintf(stderr
+ , "Warning: unable to set HOME environment variable\n");
+ }
+
+ /*
+ * Break up the shell command into a command and arguments
+ */
+
+ shell_args = build_shell_args(shell, is_login, command);
+ if (shell_args == NULL) {
+ exit_child_now(1, PAM_APP_NAME ": could not identify appropriate shell\n");
+ }
+
+ /*
+ * and now copy the environment for non-PAM use
+ */
+
+ shell_env = pam_getenvlist(pamh);
+ if (shell_env == NULL) {
+ exit_child_now(1, PAM_APP_NAME ": corrupt environment\n");
+ }
+
+ /*
+ * close PAM (quietly = this is a forked process so ticket files
+ * should *not* be deleted logs should not be written - the parent
+ * will take care of this)
+ */
+
+ D(("pam_end"));
+ retval = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+ pamh = NULL;
+ user = NULL; /* user's name not valid now */
+ if (retval != PAM_SUCCESS) {
+ exit_child_now(1, PAM_APP_NAME ": failed to release authenticator\n");
+ }
+
+ /* assume user's identity */
+ if (setuid(uid) != 0) {
+ exit_child_now(1, PAM_APP_NAME ": cannot assume uid\n");
+ }
+
+ /*
+ * Restore a signal status: information if the signal is ingored
+ * is inherited accross exec() call. (SAW)
+ */
+ enable_terminal_signals();
+
+ execve(shell_args[0], shell_args+1, shell_env);
+ exit_child_now(1, PAM_APP_NAME ": exec failed\n");
+}
+
+/* -------------------------------------------- */
+/* ------ the application itself -------------- */
+/* -------------------------------------------- */
+
+int main(int argc, char *argv[])
+{
+ int retcode, is_login, status;
+ int retval, final_retval; /* PAM_xxx return values */
+ const char *command, *user;
+ const char *shell;
+ pid_t child;
+ uid_t uid;
+ const char *place, *err_descr;
+
+ checkfds();
+
+ /*
+ * Check whether stdin is a terminal and store terminal modes for later.
+ */
+ store_terminal_modes();
+
+ /*
+ * Turn off terminal signals - this is to be sure that su gets a
+ * chance to call pam_end() in spite of the frustrated user
+ * pressing Ctrl-C. (Only the superuser is exempt in the case that
+ * they are trying to run su without a controling tty).
+ */
+ disable_terminal_signals();
+
+ /* ------------ parse the argument list ----------- */
+
+ parse_command_line(argc, argv, &is_login, &user, &command);
+
+ /* ------ initialize the Linux-PAM interface ------ */
+
+ do_pam_init(user, is_login); /* call pam_start and set PAM items */
+ user = NULL; /* get this info later (it may change) */
+
+ /*
+ * Note. We have forgotten everything about the user. We will get
+ * this info back after the user has been authenticated..
+ */
+
+ /*
+ * Starting from here all changes to the process and environment
+ * state are reflected in the change of "state".
+ * Random exits are strictly prohibited :-) (SAW)
+ */
+ status = 1; /* fake exit status of a child */
+ err_descr = NULL; /* errors hasn't happened */
+ state = SU_STATE_PAM_INITIALIZED; /* state -- initial */
+
+ do { /* abuse loop to avoid using goto... */
+
+ place = "pam_authenticate";
+ retval = pam_authenticate(pamh, 0); /* authenticate the user */
+ if (retval != PAM_SUCCESS)
+ break;
+ state = SU_STATE_AUTHENTICATED;
+
+ /*
+ * The user is valid, but should they have access at this
+ * time?
+ */
+ place = "pam_acct_mgmt";
+ retval = pam_acct_mgmt(pamh, 0);
+ if (retval != PAM_SUCCESS) {
+ if (getuid() == 0) {
+ (void) fprintf(stderr, "Account management:- %s\n(Ignored)\n"
+ , pam_strerror(pamh, retval));
+ } else
+ break;
+ }
+ state = SU_STATE_AUTHORIZED;
+
+ /* Open the su-session */
+ place = "pam_open_session";
+ retval = pam_open_session(pamh, 0); /* Must take care to close */
+ if (retval != PAM_SUCCESS)
+ break;
+ /*
+ * Do not advance the state to SU_STATE_SESSION_OPENED here.
+ * The session will be closed explicitly if the next step fails.
+ */
+
+ /*
+ * Obtain all of the new credentials of the user
+ */
+ place = "set_user_credentials";
+ retval = set_user_credentials(pamh, is_login, &user, &uid, &shell);
+ if (retval != PAM_SUCCESS) {
+ (void) pam_close_session(pamh,retval);
+ break;
+ }
+ state = SU_STATE_CREDENTIALS_GOTTEN;
+
+ /*
+ * Prepare the new session: ...
+ */
+ if (make_process_unkillable(&place, &err_descr) != 0)
+ break;
+ state = SU_STATE_PROCESS_UNKILLABLE;
+
+ /*
+ * ... setup terminal, ...
+ */
+ retcode = change_terminal_owner(uid, is_login
+ , &place, &err_descr);
+ if (retcode > 0) {
+ (void) fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ err_descr = NULL; /* forget about the problem */
+ } else if (retcode < 0)
+ break;
+ state = SU_STATE_TERMINAL_REOWNED;
+
+ /*
+ * ... make [uw]tmp entries.
+ */
+ if (is_login) {
+ /*
+ * Note: we use the parent pid as a session identifier for
+ * the logging.
+ */
+ retcode = utmp_open_session(pamh, getpid(), &place, &err_descr);
+ if (retcode > 0) {
+ (void) fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ err_descr = NULL; /* forget about the problem */
+ } else if (retcode < 0)
+ break;
+ state = SU_STATE_UTMP_WRITTEN;
+ }
+
+ /* this is where we execute the user's shell */
+ child = fork();
+ if (child == -1) {
+ place = "fork";
+ err_descr = strerror(errno);
+ break;
+ }
+
+ if (child == 0) { /* child exec's shell */
+ su_exec_shell(shell, uid, is_login, command, user);
+ /* never reached */
+ }
+
+ /* wait for child to terminate */
+
+ /* job control is off for login sessions */
+ prepare_for_job_control(!is_login && command != NULL);
+ status = wait_for_child(child);
+ if (status != 0)
+ D(("shell returned %d", status));
+
+ }while (0); /* abuse loop to avoid using goto... */
+
+ if (retval != PAM_SUCCESS) { /* PAM has failed */
+ (void) fprintf(stderr, PAM_APP_NAME ": %s\n", pam_strerror(pamh, retval));
+ final_retval = PAM_ABORT;
+ } else if (err_descr != NULL) { /* a system error has happened */
+ (void) fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ final_retval = PAM_ABORT;
+ } else
+ final_retval = PAM_SUCCESS;
+
+ /* do [uw]tmp cleanup */
+ if (state >= SU_STATE_UTMP_WRITTEN) {
+ retcode = utmp_close_session(pamh, &place, &err_descr);
+ if (retcode)
+ (void) fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+ }
+
+ /* return terminal to local control */
+ if (state >= SU_STATE_TERMINAL_REOWNED)
+ restore_terminal_owner();
+
+ /*
+ * My impression is that PAM expects real uid to be restored.
+ * Effective uid of the process is kept
+ * unchanged: superuser. (SAW)
+ */
+ if (state >= SU_STATE_PROCESS_UNKILLABLE)
+ make_process_killable();
+
+ if (state >= SU_STATE_CREDENTIALS_GOTTEN) {
+ D(("setcred"));
+ /* Delete the user's credentials. */
+ retval = pam_setcred(pamh, PAM_DELETE_CRED);
+ if (retval != PAM_SUCCESS) {
+ (void) fprintf(stderr, "WARNING: could not delete credentials\n\t%s\n"
+ , pam_strerror(pamh,retval));
+ }
+ }
+
+ if (state >= SU_STATE_SESSION_OPENED) {
+ D(("session %p", pamh));
+
+ /* close down */
+ retval = pam_close_session(pamh,0);
+ if (retval != PAM_SUCCESS)
+ (void) fprintf(stderr, "WARNING: could not close session\n\t%s\n"
+ , pam_strerror(pamh,retval));
+ }
+
+ /* clean up */
+ D(("all done"));
+ (void) pam_end(pamh, final_retval);
+ pamh = NULL;
+
+ /* reset the terminal */
+ if (reset_terminal_modes() != 0 && !status)
+ status = 1;
+
+ exit(status); /* transparent exit */
+}