diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2021-08-01 17:15:32 -0700 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2021-08-01 17:15:32 -0700 |
commit | 919d1fefc29626cfabadea976b8518175a88684f (patch) | |
tree | 70941c49b3dada07b064d7aaf3c71e41dd70f695 | |
parent | ef911c679721a2fd335bb9e66057ffd4ebcf240d (diff) | |
download | libcap-919d1fefc29626cfabadea976b8518175a88684f.tar.gz |
Revive an ancient 'su' implementation to explore use with libcap.
That is, this 'su' is not to be installed setuid-root. It is intended
to be installed `setcap =p su`.
With latest PAM sources (ie., newer than Linux-PAM 1.5.1 [*]) and
libcap this is able to validate that ambient capabilities can be applied
by pam_cap.so. For discussion, see this bug:
https://bugzilla.kernel.org/show_bug.cgi?id=212945
Caution. I've done very little auditing of this binary. So, I expect
(and will be happy if folk find them) to hear about bugs etc. What makes
me excited is to explore the ways in which classic "setuid-root" exploit
vectors exhibit with bugs in this code...
[*] At the time of writing Linux-PAM 1.5.1 is the latest release and that
was before the needed pam_unix.so support was committed. See
https://github.com/linux-pam/linux-pam/issues/317#issuecomment-869064103
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r-- | contrib/sucap/Makefile | 6 | ||||
-rw-r--r-- | contrib/sucap/README | 27 | ||||
-rw-r--r-- | contrib/sucap/README.md | 40 | ||||
-rw-r--r-- | contrib/sucap/su.c | 1070 | ||||
-rw-r--r-- | contrib/sucap/sucap.pamconfig | 6 |
5 files changed, 638 insertions, 511 deletions
diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile index 74537ff..91947af 100644 --- a/contrib/sucap/Makefile +++ b/contrib/sucap/Makefile @@ -1,9 +1,9 @@ all: su su: su.c - $(CC) -o $@ $< -lpam -lpam_misc -lcap - sudo chown root ./su - sudo chmod +s ./su + $(CC) -DPAM_APP_NAME=\"sucap\" -o $@ $< -lpam -lpam_misc -lcap + # to permit all ambient capabilities, this needs all permitted. + sudo setcap =p ./su clean: rm -f su su.o *~ diff --git a/contrib/sucap/README b/contrib/sucap/README deleted file mode 100644 index 4e0bcd9..0000000 --- a/contrib/sucap/README +++ /dev/null @@ -1,27 +0,0 @@ -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/README.md b/contrib/sucap/README.md new file mode 100644 index 0000000..586f017 --- /dev/null +++ b/contrib/sucap/README.md @@ -0,0 +1,40 @@ +This directory contains a port of the SimplePAMApp su to more +aggressively use libcap. + +The Makefile builds a binary called `su` that registers with PAM as +the application `sucap`. We've provided a sample `/etc/pam.d/sucap` +file in this directory named `sucap.pamconfig`. + +The point of developing this is to better test the full libcap +implementation, and to also provide a non-setuid-root worked example +for testing PAM interaction with libcap and pam_cap.so. The +expectations for `pam_unix.so` are that it includes this commit: + + +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 authors!). 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. + +**NOTE** As of the time of writing, this adaptation is likely rife + with bugs. + +Finally, 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-06-30 diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c index 08c3079..e436f79 100644 --- a/contrib/sucap/su.c +++ b/contrib/sucap/su.c @@ -1,23 +1,40 @@ /* - * ( based on an implementation of `su' by + * Originally 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/ ) + * 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 PAM_DEBUG */ + +#include <sys/prctl.h> + +/* non-root user of convenience to block signals */ +#define TEMP_UID 1 + +#ifndef PAM_APP_NAME +#define PAM_APP_NAME "su" +#endif /* ndef PAM_APP_NAME */ + #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 */ +#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 + #include <stdlib.h> #include <signal.h> #include <stdio.h> @@ -36,9 +53,13 @@ #include <ctype.h> #include <stdarg.h> #include <netdb.h> +#include <unistd.h> #include <security/pam_appl.h> #include <security/pam_misc.h> +#include <sys/capability.h> + +#include <security/_pam_macros.h> /* -------------------------------------------- */ /* ------ declarations ------------------------ */ @@ -48,23 +69,12 @@ 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; +static uid_t invoked_uid = (uid_t) -1; /* -------------------------------------------- */ /* ------ some local (static) functions ------- */ @@ -87,50 +97,52 @@ static const char *posix_env[] = { NULL }; +/* + * make_environment transcribes a selection of environment variables + * from the invoking user. + */ static int make_environment(pam_handle_t *pamh, int keep_env) { + const char *tmpe; + int i; 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); - } + return pam_misc_paste_env(pamh, (const char * const *)environ); + } - if (retval != PAM_SUCCESS) { - D(("error setting environment variables")); - return retval; - } - } + /* we always transcribe some variables anyway */ + tmpe = getenv("TERM"); + if (tmpe == NULL) { + tmpe = "dumb"; + } + retval = pam_misc_setenv(pamh, "TERM", tmpe, 0); + if (retval == PAM_SUCCESS) { + retval = pam_misc_setenv(pamh, "PATH", "/bin:/usr/bin", 0); + } + if (retval != PAM_SUCCESS) { + tmpe = NULL; + 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); - } + /* 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; } + tmpe = NULL; - return retval; /* how did we do? */ + return retval; } +/* + * checkfds ensures that stdout and stderr filedescriptors are + * defined. If all else fails, it directs them to /dev/null. + */ static void checkfds(void) { struct stat st; @@ -154,41 +166,53 @@ static void checkfds(void) } } -/* should be called once at the beginning */ +/* + * store_terminal_modes captures the current state of the input + * terminal. Calling this at the start of the program, we ensure we + * can restore these default settings when su exits. + */ 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"); + 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; + return; + } + fprintf(stderr, PAM_APP_NAME ": must be run from a terminal\n"); + exit(1); } /* + * restore_terminal_modes resets the terminal to the state it was in + * when the program started. + * * Returns: * 0 ok - * !=0 error + * 1 error */ -static int reset_terminal_modes(void) +static int restore_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)); + fprintf(stderr, PAM_APP_NAME ": cannot restore terminal mode: %s\n", + strerror(errno)); return 1; - } else + } else { return 0; + } } /* ------ unexpected signals ------------------ */ struct sigaction old_int_act, old_quit_act, old_tstp_act, old_pipe_act; +/* + * disable_terminal_signals attempts to make the process resistant to + * being stopped - it helps ensure that the PAM stack can complete + * session and auth failure logging etc. + */ static void disable_terminal_signals(void) { /* @@ -230,10 +254,10 @@ static void enable_terminal_signals(void) /* ------ terminal ownership ------------------ */ /* - * Change the ownership of STDIN if needed. + * change_terminal_owner changes the ownership of STDIN if needed. * Returns: * 0 ok, - * -1 fatal error (continue of the work is impossible), + * -1 fatal error (continuing 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. @@ -244,30 +268,68 @@ static int change_terminal_owner(uid_t uid, int is_login, /* determine who owns the terminal line */ if (is_terminal && is_login) { struct stat stat_buf; + cap_t current, working; + int status; + cap_value_t cchown = CAP_CHOWN; - if (fstat(STDIN_FILENO,&stat_buf) != 0) { + 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); + + current = cap_get_proc(); + working = cap_dup(current); + cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET); + status = cap_set_proc(working); + cap_free(working); + + if (status != 0) { + *callname = "capset CHOWN"; + } else if ((status = fchown(STDIN_FILENO, uid, -1)) != 0) { + *callname = "fchown of STDIN"; + } else { + cap_set_proc(current); + } + cap_free(current); + + if (status != 0) { + *err_descr = strerror(errno); return 1; } + terminal_uid = stat_buf.st_uid; } return 0; } +/* + * restore_terminal_owner changes the terminal owner back to the value + * it had when su was started. + */ 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)); + cap_t current, working; + int status; + cap_value_t cchown = CAP_CHOWN; + + current = cap_get_proc(); + working = cap_dup(current); + cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET); + status = cap_set_proc(working); + cap_free(working); + + if (status == 0) { + status = fchown(STDIN_FILENO, terminal_uid, -1); + cap_set_proc(current); + } + cap_free(current); + + if (status != 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; @@ -275,7 +337,9 @@ static void restore_terminal_owner(void) } /* - * Make the process unkillable by the user invoked it. + * make_process_unkillable changes the uid of the process. TEMP_UID is + * used for this temporary state. + * * Returns: * 0 ok, * -1 fatal error (continue of the work is impossible), @@ -283,33 +347,45 @@ static void restore_terminal_owner(void) * 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) +int make_process_unkillable(const char **callname, const char **err_descr) { invoked_uid = getuid(); - if(setuid(geteuid()) != 0) { + if (invoked_uid == TEMP_UID) { + /* no change needed */ + return 0; + } + + if (cap_setuid(TEMP_UID) != 0) { *callname = "setuid"; *err_descr = strerror(errno); return -1; - }else - return 0; + } + return 0; } +/* + * make_process_killable restores the invoking uid to the current + * process. + */ void make_process_killable() { - setreuid(invoked_uid, -1); + (void) cap_setuid(invoked_uid); } /* ------ command line parser ----------------- */ -void usage() +void usage(int exit_val) { - (void) fprintf(stderr,"usage: su [-] [-c \"command\"] [username]\n"); - exit(1); + fprintf(stderr,"usage: su [-] [-h] [-c \"command\"] [username]\n"); + exit(exit_val); } -void parse_command_line(int argc, char *argv[] - , int *is_login, const char **user, const char **command) +/* + * parse_command_line extracts the options from the command line + * arguments. + */ +void parse_command_line(int argc, char *argv[], + int *is_login, const char **user, const char **command) { int username_present, command_present; @@ -326,12 +402,12 @@ void parse_command_line(int argc, char *argv[] switch (*++token) { case '\0': /* su as a login shell for the user */ if (*is_login) - usage(); + usage(1); *is_login = 1; break; case 'c': if (command_present) { - usage(); + usage(1); } else { /* indicate we are running commands */ if (*++token != '\0') { command_present = 1; @@ -340,38 +416,29 @@ void parse_command_line(int argc, char *argv[] command_present = 1; *command = *++argv; } else - usage(); + usage(1); } break; + case 'h': + usage(0); default: - usage(); + usage(1); } } else { /* must be username */ - if (username_present) - usage(); + if (username_present) { + usage(1); + } 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); + if (!username_present) { + fprintf(stderr, PAM_APP_NAME ": requires a username\n"); + usage(1); } } - /* * This following contains code that waits for a child process to die. * It also chooses to intercept a couple of signals that it will @@ -390,7 +457,7 @@ static void prepare_for_job_control(int need_it) (void) sigfillset(&ourset); if (sigprocmask(SIG_BLOCK, &ourset, NULL) != 0) { - (void) fprintf(stderr,"[trouble blocking signals]\n"); + fprintf(stderr,"[trouble blocking signals]\n"); wait_for_child_caught = 1; return; } @@ -403,6 +470,9 @@ int wait_for_child(pid_t child) sigset_t ourset; exit_code = -1; /* no exit code yet, exit codes could be from 0 to 255 */ + if (child == -1) { + return exit_code; + } /* * set up signal handling @@ -412,14 +482,14 @@ int wait_for_child(pid_t child) struct sigaction action, defaction; action.sa_handler = wait_for_child_catch_sig; - (void) sigemptyset(&action.sa_mask); + sigemptyset(&action.sa_mask); action.sa_flags = 0; defaction.sa_handler = SIG_DFL; - (void) sigemptyset(&defaction.sa_mask); + sigemptyset(&defaction.sa_mask); defaction.sa_flags = 0; - (void) sigemptyset(&ourset); + sigemptyset(&ourset); if ( sigaddset(&ourset, SIGTERM) || sigaction(SIGTERM, &action, NULL) @@ -436,27 +506,27 @@ int wait_for_child(pid_t child) || (need_job_control && sigaction(SIGCONT, &defaction, NULL)) || sigprocmask(SIG_UNBLOCK, &ourset, NULL) ) { - (void) fprintf(stderr,"[trouble setting signal intercept]\n"); + 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, + /* + * 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. */ - + * the child dies. + */ while (!wait_for_child_caught) { - /* parent waits for child */ if ((retval = waitpid(child, &status, 0)) <= 0) { - if (errno == EINTR) + if (errno == EINTR) { continue; /* recovering from a 'fg' */ - (void) fprintf(stderr, "[error waiting child: %s]\n" - , strerror(errno)); + } + 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 @@ -464,29 +534,29 @@ int wait_for_child(pid_t child) */ wait_for_child_caught = 1; break; - }else { + } else { /* the child is terminated via exit() or a fatal signal */ - if (WIFEXITED(status)) + if (WIFEXITED(status)) { exit_code = WEXITSTATUS(status); - else + } else { exit_code = 1; + } break; } } if (wait_for_child_caught) { - (void) fprintf(stderr,"\nKilling shell..."); - (void) kill(child, SIGTERM); + fprintf(stderr,"\nKilling shell..."); + 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"); + sleep(SLEEP_TO_KILL_CHILDREN); + kill(child, SIGKILL); + fprintf(stderr, "killed\n"); } /* @@ -495,15 +565,16 @@ int wait_for_child(pid_t child) if (exit_code == -1) { do { retval = waitpid(child, &status, 0); - }while (retval == -1 && errno == EINTR); + } while (retval == -1 && errno == EINTR); if (retval == -1) { - (void) fprintf(stderr, PAM_APP_NAME ": the final wait failed: %s\n" - , strerror(errno)); + fprintf(stderr, PAM_APP_NAME ": the final wait failed: %s\n", + strerror(errno)); } - if (WIFEXITED(status)) + if (WIFEXITED(status)) { exit_code = WEXITSTATUS(status); - else + } else { exit_code = 1; + } } return exit_code; @@ -514,8 +585,8 @@ int wait_for_child(pid_t child) * 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) +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 */ @@ -618,7 +689,6 @@ static char * const *build_shell_args(const char *pw_shell, D(("terminating args with NULL")); args[++i] = NULL; D(("list completed.")); - } } @@ -643,8 +713,8 @@ static char * const *build_shell_args(const char *pw_shell, args[last_arg] = NULL; /* terminate list of args */ } - D(("returning")); - return (char * const *)args; /* return argument list */ + D(("returning arg list")); + return (char * const *) args; } @@ -654,7 +724,7 @@ static void exit_now(int exit_code, const char *format, ...) { va_list args; - va_start(args,format); + va_start(args, format); vfprintf(stderr, format, args); va_end(args); @@ -663,7 +733,7 @@ static void exit_now(int exit_code, const char *format, ...) /* USER's shell may have completely broken terminal settings restore the sane(?) initial conditions */ - reset_terminal_modes(); + restore_terminal_modes(); exit(exit_code); } @@ -700,7 +770,8 @@ static void do_pam_init(const char *user, int is_login) * 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); + fprintf(stderr, PAM_APP_NAME ": pam_start failed with code %d\n", + retval); exit(1); } @@ -747,95 +818,43 @@ static void do_pam_init(const char *user, int is_login) } /* - * Here we set the user's groups and return their uid + * authenticate_user arranges for the PAM authentication stack to run. */ - -static int set_user_credentials(pam_handle_t *pamh, int login, - const char **user, uid_t *uid, - const char **shell) +static int authenticate_user(pam_handle_t *pamh, cap_t all, + int *retval, const char **place, + const char **err_descr) { - 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; - } + *place = "pre-auth cap_set_proc"; + if (cap_set_proc(all)) { + D(("failed to raise all capabilities")); + *err_descr = "cap_set_proc() failed"; + *retval = PAM_SUCCESS; + return 1; } - 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...] - */ + D(("attempt to authenticate user")); + *place = "pam_authenticate"; + *retval = pam_authenticate(pamh, 0); + return (*retval != PAM_SUCCESS); +} - retval = pam_setcred(pamh, PAM_ESTABLISH_CRED); - if (retval != PAM_SUCCESS) { - D(("failed to set PAM credentials; %s", pam_strerror(pamh,retval))); - return retval; +/* + * user_accounting confirms an authenticated user is permitted service. + */ +static int user_accounting(pam_handle_t *pamh, cap_t all, + int *retval, const char **place, + const char **err_descr) { + *place = "user_accounting"; + if (cap_set_proc(all)) { + D(("failed to raise all capabilities")); + *err_descr = "cap_set_proc() failed"; + return 1; } - - return PAM_SUCCESS; + *place = "pam_acct_mgmt"; + *retval = pam_acct_mgmt(pamh, 0); + return (*retval != 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. @@ -844,10 +863,8 @@ static int set_user_credentials(pam_handle_t *pamh, int login, * 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) +static const struct utmp *find_utmp_entry(const char *ut_line, + const char *ut_id) { struct utmp *u_tmp_p; @@ -866,9 +883,7 @@ const struct utmp *find_utmp_entry(const char *ut_line /* * Identify the terminal name and the abreviation we will use. */ - -static -void set_terminal_name(const char *terminal, char *ut_line, char *ut_id) +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); @@ -911,9 +926,8 @@ void set_terminal_name(const char *terminal, char *ut_line, char *ut_id) #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) +static int write_wtmp(struct utmp *u_tmp_p, const char **callname, + const char **err_descr) { int w_tmp_fd; struct flock w_lock; @@ -936,14 +950,14 @@ int write_wtmp(struct utmp *u_tmp_p /* prepare for blocking operation... */ act1.sa_handler = SIG_DFL; - (void) sigemptyset(&act1.sa_mask); + 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); + alarm(WTMP_LOCK_TIMEOUT); state = WWTMP_STATE_SIGACTION_SET; /* now we try to lock this file-rcord exclusively; non-blocking */ @@ -956,21 +970,21 @@ int write_wtmp(struct utmp *u_tmp_p *err_descr = strerror(errno); break; } - (void) alarm(0); - (void) sigaction(SIGALRM, &act2, NULL); + alarm(0); + sigaction(SIGALRM, &act2, NULL); state = WWTMP_STATE_LOCK_TAKEN; - if (write(w_tmp_fd, u_tmp_p, sizeof(struct utmp)) != -1) + 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); + fcntl(w_tmp_fd, F_SETLK, &w_lock); }else if (state >= WWTMP_STATE_SIGACTION_SET) { - (void) alarm(0); - (void) sigaction(SIGALRM, &act2, NULL); + alarm(0); + sigaction(SIGALRM, &act2, NULL); } if (state >= WWTMP_STATE_FILE_OPENED) { @@ -996,9 +1010,9 @@ struct utmp *login_stored_utmp=NULL; * 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) +static int utmp_do_open_session(const char *user, const char *terminal, + const char *rhost, pid_t pid, + const char **place, const char **err_descr) { struct utmp u_tmp; const struct utmp *u_tmp_p; @@ -1027,7 +1041,7 @@ static int utmp_do_open_session(const char *user, const char *terminal if (login_stored_utmp == NULL) { login_stored_utmp = malloc(sizeof(struct utmp)); if (login_stored_utmp == NULL) { - *callname = "malloc"; + *place = "malloc"; *err_descr = "fail"; endutent(); return -1; @@ -1069,14 +1083,14 @@ static int utmp_do_open_session(const char *user, const char *terminal pututline(&u_tmp); /* write it to utmp */ endutent(); /* close the file */ - retval = write_wtmp(&u_tmp, callname, err_descr); /* write to wtmp file */ + retval = write_wtmp(&u_tmp, place, 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) +static int utmp_do_close_session(const char *terminal, + const char **place, const char **err_descr) { int retval; struct utmp u_tmp; @@ -1100,7 +1114,7 @@ static int utmp_do_close_session(const char *terminal 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); + retval = write_wtmp(&u_tmp, place, err_descr); memset(login_stored_utmp, 0, sizeof(u_tmp)); /* reset entry */ free(login_stored_utmp); @@ -1119,7 +1133,7 @@ static int utmp_do_close_session(const char *terminal setutent(); /* rewind file (replace old) */ pututline(&u_tmp); /* mark as dead */ - retval = write_wtmp(&u_tmp, callname, err_descr); + retval = write_wtmp(&u_tmp, place, err_descr); } } @@ -1138,148 +1152,282 @@ static int utmp_do_close_session(const char *terminal * 0 ok, * 1 non-fatal error * -1 fatal error - * callname and err_descr will be set + * place 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) +static int utmp_open_session(pam_handle_t *pamh, pid_t pid, + int *retval, + const char **place, 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); + *retval = pam_get_item(pamh, PAM_USER, (const void **)&user); + if (*retval != PAM_SUCCESS) { return -1; } - retval = pam_get_item(pamh, PAM_TTY, (const void **)&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; } - retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost); - if (retval != PAM_SUCCESS) + *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); + return utmp_do_open_session(user, terminal, rhost, pid, place, err_descr); } static int utmp_close_session(pam_handle_t *pamh - , const char **callname, const char **err_descr) + , const char **place, 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)"; + *place = "pam_get_item(PAM_TTY)"; *err_descr = pam_strerror(pamh, retval); return -1; } - return utmp_do_close_session(terminal, callname, err_descr); + return utmp_do_close_session(terminal, place, err_descr); } -/* ------ shell invoker ----------------------- */ - -static void su_exec_shell(const char *shell, uid_t uid, int is_login - , const char *command, const char *user) +/* + * set_credentials raises all of the process and PAM credentials. + */ +static int set_credentials(pam_handle_t *pamh, cap_t all, int login, + const char **pw_shell, + int *retval, const char **place, + const char **err_descr) { - char * const * shell_args; - char * const * shell_env; - const char *pw_dir; - int retval; + const char *user; + char *shell; + cap_value_t csetgid = CAP_SETGID; + cap_t current; + int status; + struct passwd *pw; + uid_t uid; + + D(("get user from pam")); + *place = "set_credentials"; + *retval = pam_get_item(pamh, PAM_USER, (const void **)&user); + if (*retval != PAM_SUCCESS || user == NULL || *user == '\0') { + D(("error identifying user from PAM.")); + *retval = PAM_USER_UNKNOWN; + return 1; + } /* - * Now, find the home directory for the user + * Add the LOGNAME and HOME environment variables. */ - 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; + pw = getpwnam(user); + if (pw == NULL || (user = x_strdup(pw->pw_name)) == NULL) { + D(("failed to identify user")); + *retval = PAM_USER_UNKNOWN; + return 1; + } - pwd = getpwnam(user); - if (pwd != NULL && pwd->pw_name != NULL) { - pw_dir = x_strdup(pwd->pw_name); - } + uid = pw->pw_uid; + shell = x_strdup(pw->pw_shell); + if (shell == NULL) { + D(("user %s has no shell", user)); + *retval = PAM_CRED_ERR; + return 1; + } - /* 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; + if (login) { + /* set LOGNAME, HOME */ + if (pam_misc_setenv(pamh, "LOGNAME", user, 0) != PAM_SUCCESS) { + D(("failed to set LOGNAME")); + *retval = PAM_CRED_ERR; + return 1; + } + if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) { + D(("failed to set HOME")); + *retval = PAM_CRED_ERR; + return 1; } } + current = cap_get_proc(); + cap_set_flag(current, CAP_EFFECTIVE, 1, &csetgid, CAP_SET); + status = cap_set_proc(current); + cap_free(current); + if (status != 0) { + *err_descr = "unable to raise CAP_SETGID"; + return 1; + } + + /* initialize groups */ + if (initgroups(pw->pw_name, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0) { + D(("failed to setgid etc")); + *retval = PAM_PERM_DENIED; + return 1; + } + *pw_shell = shell; + + pw = NULL; /* be tidy */ + + D(("desired uid=%d", uid)); + + /* assume user's identity - but preserve the permitted set */ + if (cap_setuid(uid) != 0) { + D(("failed to setuid: %v", strerror(errno))); + *retval = PAM_PERM_DENIED; + return 1; + } + /* - * We may wish to change the current directory. + * Next, we call the PAM framework to add/enhance the credentials + * of this user [it may change the user's home directory in the + * pam_env, and add supplemental group memberships...]. */ + D(("setting credentials")); + if (cap_set_proc(all)) { + D(("failed to raise all capabilities")); + *retval = PAM_PERM_DENIED; + return 1; + } - if (is_login && chdir(pw_dir)) { - exit_child_now(1, "%s not available; exiting\n", pw_dir); + D(("calling pam_setcred to establish credentials")); + *retval = pam_setcred(pamh, PAM_ESTABLISH_CRED); + + return (*retval != PAM_SUCCESS); +} + +/* + * open_session invokes the open session PAM stack. + */ +static int open_session(pam_handle_t *pamh, cap_t all, + int *retval, const char **place, const char **err_descr) +{ + /* Open the su-session */ + *place = "pam_open_session"; + if (cap_set_proc(all)) { + D(("failed to raise t_caps capabilities")); + *err_descr = "capability setting failed"; + return 1; + } + *retval = pam_open_session(pamh, 0); /* Must take care to close */ + if (*retval != PAM_SUCCESS) { + return 1; + } + return 0; +} + +/* ------ shell invoker ----------------------- */ + +static int launch_callback_fn(void *h) +{ + pam_handle_t *pamh = h; + int retval; + + D(("pam_end")); + retval = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT); + pamh = NULL; + if (retval != PAM_SUCCESS) { + return -1; } /* - * If it is a login session, we should set the environment - * accordingly. + * Restore a signal status: information if the signal is ingored + * is inherited accross exec() call. (SAW) */ + enable_terminal_signals(); + + D(("about to launch")); + return 0; +} + +/* Returns PAM_<STATUS>. */ +static int perform_launch_and_cleanup(cap_t all, int is_login, + const char *shell, const char *command) +{ + int retval, status; + const char *user, *home; + uid_t uid; + char * const * shell_args; + char * const * shell_env; + cap_launch_t launcher; + pid_t child; - 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"); + D(("failed to compute shell arguments")); + return PAM_SYSTEM_ERR; } - /* - * and now copy the environment for non-PAM use - */ + home = pam_getenv(pamh, "HOME"); + if ( !home || home[0] == '\0' ) { + fprintf(stderr, "setting home directory for %s to %s\n", + user, DEFAULT_HOME); + home = DEFAULT_HOME; + if (pam_misc_setenv(pamh, "HOME", home, 0) != PAM_SUCCESS) { + D(("unable to set $HOME")); + fprintf(stderr, + "Warning: unable to set HOME environment variable\n"); + } + } + if (is_login) { + if (chdir(home) && chdir(DEFAULT_HOME)) { + D(("failed to change directory")); + return PAM_SYSTEM_ERR; + } + } shell_env = pam_getenvlist(pamh); if (shell_env == NULL) { - exit_child_now(1, PAM_APP_NAME ": corrupt environment\n"); + D(("failed to obtain environment for child")); + return PAM_SYSTEM_ERR; } - /* - * 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) - */ + launcher = cap_new_launcher(shell_args[0], + (const char * const *) &shell_args[1], + (const char * const *) shell_env); + if (launcher == NULL) { + D(("failed to initialize launcher")); + return PAM_SYSTEM_ERR; + } + cap_launcher_set_iab(launcher, cap_iab_get_proc()); + cap_launcher_callback(launcher, launch_callback_fn); - 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"); + child = cap_launch(launcher, pamh); + cap_free(launcher); + + /* job control is off for login sessions */ + prepare_for_job_control(!is_login && command != NULL); + + if (cap_setuid(TEMP_UID) != 0) { + fprintf(stderr, "[failed to change monitor UID=%d]\n", TEMP_UID); } - /* assume user's identity */ - if (setuid(uid) != 0) { - exit_child_now(1, PAM_APP_NAME ": cannot assume uid\n"); + /* wait for child to terminate */ + status = wait_for_child(child); + if (status != 0) { + D(("shell returned %d", status)); } + return status; +} - /* - * Restore a signal status: information if the signal is ingored - * is inherited accross exec() call. (SAW) - */ - enable_terminal_signals(); +static void close_session(pam_handle_t *pamh, cap_t all) +{ + int retval; - execve(shell_args[0], shell_args+1, shell_env); - exit_child_now(1, PAM_APP_NAME ": exec failed\n"); + D(("session %p closing", pamh)); + if (cap_set_proc(all)) { + fprintf(stderr, "WARNING: could not raise all caps\n"); + } + retval = pam_close_session(pamh, 0); + if (retval != PAM_SUCCESS) { + fprintf(stderr, "WARNING: could not close session\n\t%s\n", + pam_strerror(pamh,retval)); + } } /* -------------------------------------------- */ @@ -1290,11 +1438,14 @@ 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; + const char *command, *shell; pid_t child; uid_t uid; - const char *place, *err_descr; + const char *place = NULL, *err_descr = NULL; + cap_t all, t_caps; + + all = cap_get_proc(); + cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED); checkfds(); @@ -1303,196 +1454,153 @@ int main(int argc, char *argv[]) */ store_terminal_modes(); + /* ---------- parse the argument list and --------- */ + /* ------ initialize the Linux-PAM interface ------ */ + { + const char *user; /* transient until PAM_USER defined */ + parse_command_line(argc, argv, &is_login, &user, &command); + place = "do_pam_init"; + do_pam_init(user, is_login); /* call pam_start and set PAM items */ + } + /* * 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). + * chance to call pam_end() and restore the terminal modes in + * spite of the frustrated user pressing Ctrl-C. */ disable_terminal_signals(); - /* ------------ parse the argument list ----------- */ - - parse_command_line(argc, argv, &is_login, &user, &command); + /* + * Random exits from here are strictly prohibited :-) (SAW) AGM + * achieves this with goto's and a single exit at the end of main. + */ + status = 1; /* fake exit status of a child */ + err_descr = NULL; /* errors haven't happened */ - /* ------ initialize the Linux-PAM interface ------ */ + if (make_process_unkillable(&place, &err_descr) != 0) { + goto su_exit; + } - do_pam_init(user, is_login); /* call pam_start and set PAM items */ - user = NULL; /* get this info later (it may change) */ + if (authenticate_user(pamh, all, &retval, &place, &err_descr) != 0) { + goto auth_exit; + } /* - * Note. We have forgotten everything about the user. We will get - * this info back after the user has been authenticated.. + * The user is valid, but should they have access at this + * time? */ + if (user_accounting(pamh, all, &retval, &place, &err_descr) != 0) { + goto auth_exit; + } + + D(("su attempt is confirmed as authorized")); /* - * Starting from here all changes to the process and environment - * state are reflected in the change of "state". - * Random exits are strictly prohibited :-) (SAW) + * ... setup terminal, ... */ - 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; + retcode = change_terminal_owner(uid, is_login, &place, &err_descr); + if (retcode > 0) { + fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr); + err_descr = NULL; /* forget about the problem */ + } else if (retcode < 0) { + D(("terminal owner to uid=%d change failed", uid)); + goto auth_exit; + } - /* - * 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; + if (set_credentials(pamh, all, is_login, + &shell, &retval, &place, &err_descr) != 0) { + D(("failed to set credentials")); + goto auth_exit; + } - /* 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. - */ + /* + * Here the IAB value is fixed and may differ from all's + * Inheritable value. So synthesize what we need to proceed in the + * child, for now, in this current process. + */ + place = "preserving inheritable parts"; + t_caps = cap_get_proc(); + if (t_caps == NULL) { + D(("failed to read capabilities")); + err_descr = "capability read failed"; + goto delete_cred; + } + if (cap_fill(t_caps, CAP_EFFECTIVE, CAP_PERMITTED)) { + D(("failed to fill effective bits")); + err_descr = "capability fill failed"; + goto delete_cred; + } + /* + * ... make [uw]tmp entries. + */ + if (is_login) { /* - * Obtain all of the new credentials of the user + * Note: we use the parent pid as a session identifier for + * the logging. */ - 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); + retcode = utmp_open_session(pamh, getpid(), + &retval, &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; + fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr); + err_descr = NULL; /* forget about this non-critical problem */ + } else if (retcode < 0) { + goto delete_cred; + } + } - /* - * ... 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; - } + if (open_session(pamh, t_caps, &retval, &place, &err_descr) != 0) { + goto utmp_closer; + } - /* this is where we execute the user's shell */ - child = fork(); - if (child == -1) { - place = "fork"; - err_descr = strerror(errno); - break; - } + status = perform_launch_and_cleanup(t_caps, is_login, shell, command); + close_session(pamh, all); - if (child == 0) { /* child exec's shell */ - su_exec_shell(shell, uid, is_login, command, user); - /* never reached */ - } +utmp_closer: + if (is_login) { + /* do [uw]tmp cleanup */ + retcode = utmp_close_session(pamh, &place, &err_descr); + if (retcode) { + fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr); + } + } - /* wait for child to terminate */ +delete_cred: + D(("delete credentials")); + if (cap_set_proc(all)) { + D(("failed to raise all capabilities")); + } + retcode = pam_setcred(pamh, PAM_DELETE_CRED); + if (retcode != PAM_SUCCESS) { + fprintf(stderr, "WARNING: could not delete credentials\n\t%s\n", + pam_strerror(pamh, retcode)); + } - /* 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)); +old_owner: + D(("return terminal to local control")); + restore_terminal_owner(); - }while (0); /* abuse loop to avoid using goto... */ +auth_exit: + D(("for clean up we restore the launching user")); + make_process_killable(); + D(("all done - closing down pam")); if (retval != PAM_SUCCESS) { /* PAM has failed */ - (void) fprintf(stderr, PAM_APP_NAME ": %s\n", pam_strerror(pamh, retval)); + 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); + fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr); final_retval = PAM_ABORT; - } else + } 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) + if (restore_terminal_modes() != 0 && !status) { status = 1; + } +su_exit: exit(status); /* transparent exit */ } diff --git a/contrib/sucap/sucap.pamconfig b/contrib/sucap/sucap.pamconfig new file mode 100644 index 0000000..02b70f2 --- /dev/null +++ b/contrib/sucap/sucap.pamconfig @@ -0,0 +1,6 @@ +#%PAM-1.0 +auth required pam_cap.so config=/etc/security/capability.conf +auth required pam_unix.so +account required pam_unix.so +password required pam_unix.so +session required pam_unix.so |