| /* |
| * Originally based on an implementation of `su' by |
| * |
| * Peter Orbaek <[email protected]> |
| * |
| * obtained circa 1997 from ftp://ftp.daimi.aau.dk/pub/linux/poe/ |
| * |
| * Rewritten for Linux-PAM by Andrew G. Morgan <[email protected]> |
| * Modified by Andrey V. Savochkin <[email protected]> |
| * Modified for use with libcap by Andrew G. Morgan <[email protected]> |
| */ |
| |
| /* #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> |
| #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 <unistd.h> |
| |
| #include <security/pam_appl.h> |
| #include <security/pam_misc.h> |
| #include <sys/capability.h> |
| |
| #include <security/_pam_macros.h> |
| |
| /* -------------------------------------------- */ |
| /* ------ declarations ------------------------ */ |
| /* -------------------------------------------- */ |
| |
| extern char **environ; |
| static pam_handle_t *pamh = NULL; |
| static int state; |
| |
| 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 = (uid_t) -1; |
| |
| /* -------------------------------------------- */ |
| /* ------ 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 |
| }; |
| |
| /* |
| * 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 */ |
| return pam_misc_paste_env(pamh, (const char * const *)environ); |
| } |
| |
| /* 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 propagate 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; |
| } |
| |
| /* |
| * 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; |
| 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); |
| } |
| } |
| } |
| |
| /* |
| * 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) { |
| fprintf(stderr, PAM_APP_NAME ": couldn't copy terminal mode"); |
| exit(1); |
| } |
| 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 |
| * 1 error |
| */ |
| static int restore_terminal_modes(void) |
| { |
| if (is_terminal && tcsetattr(STDIN_FILENO, TCSAFLUSH, &stored_mode) != 0) { |
| fprintf(stderr, PAM_APP_NAME ": cannot restore 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; |
| |
| /* |
| * 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) |
| { |
| /* |
| * 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_terminal_owner changes the ownership of STDIN if needed. |
| * Returns: |
| * 0 ok, |
| * -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. |
| */ |
| 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; |
| cap_t current, working; |
| int status; |
| cap_value_t cchown = CAP_CHOWN; |
| |
| if (fstat(STDIN_FILENO, &stat_buf) != 0) { |
| *callname = "fstat to STDIN"; |
| *err_descr = strerror(errno); |
| return -1; |
| } |
| |
| 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) { |
| 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; |
| } |
| } |
| |
| /* |
| * 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), |
| * 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 (invoked_uid == TEMP_UID) { |
| /* no change needed */ |
| return 0; |
| } |
| |
| if (cap_setuid(TEMP_UID) != 0) { |
| *callname = "setuid"; |
| *err_descr = strerror(errno); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * make_process_killable restores the invoking uid to the current |
| * process. |
| */ |
| void make_process_killable() |
| { |
| (void) cap_setuid(invoked_uid); |
| } |
| |
| /* ------ command line parser ----------------- */ |
| |
| void usage(int exit_val) |
| { |
| fprintf(stderr,"usage: su [-] [-h] [-c \"command\"] [username]\n"); |
| exit(exit_val); |
| } |
| |
| /* |
| * 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; |
| |
| *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(1); |
| *is_login = 1; |
| break; |
| case 'c': |
| if (command_present) { |
| usage(1); |
| } 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(1); |
| } |
| break; |
| case 'h': |
| usage(0); |
| default: |
| usage(1); |
| } |
| } else { /* must be username */ |
| if (username_present) { |
| usage(1); |
| } |
| username_present = 1; |
| *user = *argv; |
| } |
| } |
| |
| 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 |
| * 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) { |
| 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 */ |
| if (child == -1) { |
| return exit_code; |
| } |
| |
| /* |
| * set up signal handling |
| */ |
| |
| if (!wait_for_child_caught) { |
| struct sigaction action, defaction; |
| |
| action.sa_handler = wait_for_child_catch_sig; |
| sigemptyset(&action.sa_mask); |
| action.sa_flags = 0; |
| |
| defaction.sa_handler = SIG_DFL; |
| sigemptyset(&defaction.sa_mask); |
| defaction.sa_flags = 0; |
| |
| 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) |
| ) { |
| 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' */ |
| } |
| fprintf(stderr, "[error waiting child: %s]\n", strerror(errno)); |
| /* |
| * Break the loop keeping exit_code undefined. |
| * Do we have a chance for a successful 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) { |
| fprintf(stderr,"\nKilling shell..."); |
| kill(child, SIGTERM); |
| } |
| |
| /* |
| * do we need to wait for the child to catch up? |
| */ |
| if (wait_for_child_caught) { |
| sleep(SLEEP_TO_KILL_CHILDREN); |
| kill(child, SIGKILL); |
| 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) { |
| 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(("committed 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 arg list")); |
| return (char * const *) args; |
| } |
| |
| |
| /* ------ 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 */ |
| restore_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) |
| */ |
| 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"); |
| } |
| } |
| |
| /* |
| * authenticate_user arranges for the PAM authentication stack to run. |
| */ |
| static int authenticate_user(pam_handle_t *pamh, cap_t all, |
| int *retval, const char **place, |
| const char **err_descr) |
| { |
| *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; |
| } |
| |
| D(("attempt to authenticate user")); |
| *place = "pam_authenticate"; |
| *retval = pam_authenticate(pamh, 0); |
| return (*retval != PAM_SUCCESS); |
| } |
| |
| /* |
| * 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; |
| } |
| *place = "pam_acct_mgmt"; |
| *retval = pam_acct_mgmt(pamh, 0); |
| return (*retval != PAM_SUCCESS); |
| } |
| |
| /* |
| * 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 abbreviation 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 careful: 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; |
| sigemptyset(&act1.sa_mask); |
| act1.sa_flags = 0; |
| if (sigaction(SIGALRM, &act1, &act2) == -1) { |
| *callname = "sigaction"; |
| *err_descr = strerror(errno); |
| break; |
| } |
| 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; |
| } |
| alarm(0); |
| 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 */ |
| fcntl(w_tmp_fd, F_SETLK, &w_lock); |
| }else if (state >= WWTMP_STATE_SIGACTION_SET) { |
| alarm(0); |
| 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 careful: 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 **place, 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 attached 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) { |
| *place = "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, 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 **place, 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, place, 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, place, 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 |
| * place and err_descr will be set |
| * Be careful: the function indirectly uses alarm(). |
| */ |
| 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; |
| |
| *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); |
| if (retval != PAM_SUCCESS) { |
| 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, place, err_descr); |
| } |
| |
| static int utmp_close_session(pam_handle_t *pamh |
| , 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) { |
| *place = "pam_get_item(PAM_TTY)"; |
| *err_descr = pam_strerror(pamh, retval); |
| return -1; |
| } |
| |
| return utmp_do_close_session(terminal, place, err_descr); |
| } |
| |
| /* |
| * 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) |
| { |
| 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; |
| } |
| |
| /* |
| * Add the LOGNAME and HOME environment variables. |
| */ |
| |
| pw = getpwnam(user); |
| if (pw == NULL || (user = x_strdup(pw->pw_name)) == NULL) { |
| D(("failed to identify user")); |
| *retval = PAM_USER_UNKNOWN; |
| return 1; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| 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; |
| } |
| |
| /* |
| * Restore a signal status: information if the signal is ignored |
| * is inherited across 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; |
| |
| |
| /* |
| * Break up the shell command into a command and arguments |
| */ |
| shell_args = build_shell_args(shell, is_login, command); |
| if (shell_args == NULL) { |
| D(("failed to compute shell arguments")); |
| return PAM_SYSTEM_ERR; |
| } |
| |
| 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) { |
| D(("failed to obtain environment for child")); |
| return PAM_SYSTEM_ERR; |
| } |
| |
| 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); |
| |
| 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); |
| } |
| |
| /* wait for child to terminate */ |
| status = wait_for_child(child); |
| if (status != 0) { |
| D(("shell returned %d", status)); |
| } |
| return status; |
| } |
| |
| static void close_session(pam_handle_t *pamh, cap_t all) |
| { |
| int retval; |
| |
| 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)); |
| } |
| } |
| |
| /* -------------------------------------------- */ |
| /* ------ 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, *shell; |
| pid_t child; |
| uid_t uid; |
| const char *place = NULL, *err_descr = NULL; |
| cap_t all, t_caps; |
| |
| all = cap_get_proc(); |
| cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED); |
| |
| checkfds(); |
| |
| /* |
| * Check whether stdin is a terminal and store terminal modes for later. |
| */ |
| 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() and restore the terminal modes in |
| * spite of the frustrated user pressing Ctrl-C. |
| */ |
| disable_terminal_signals(); |
| |
| /* |
| * 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 */ |
| |
| if (make_process_unkillable(&place, &err_descr) != 0) { |
| goto su_exit; |
| } |
| |
| if (authenticate_user(pamh, all, &retval, &place, &err_descr) != 0) { |
| goto auth_exit; |
| } |
| |
| /* |
| * 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")); |
| |
| /* |
| * ... setup terminal, ... |
| */ |
| 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; |
| } |
| |
| if (set_credentials(pamh, all, is_login, |
| &shell, &retval, &place, &err_descr) != 0) { |
| D(("failed to set credentials")); |
| goto auth_exit; |
| } |
| |
| /* |
| * 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) { |
| /* |
| * Note: we use the parent pid as a session identifier for |
| * the logging. |
| */ |
| retcode = utmp_open_session(pamh, getpid(), |
| &retval, &place, &err_descr); |
| if (retcode > 0) { |
| 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; |
| } |
| } |
| |
| if (open_session(pamh, t_caps, &retval, &place, &err_descr) != 0) { |
| goto utmp_closer; |
| } |
| |
| status = perform_launch_and_cleanup(t_caps, is_login, shell, command); |
| close_session(pamh, all); |
| |
| 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); |
| } |
| } |
| |
| 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)); |
| } |
| |
| old_owner: |
| D(("return terminal to local control")); |
| restore_terminal_owner(); |
| |
| 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 */ |
| 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 */ |
| fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr); |
| final_retval = PAM_ABORT; |
| } else { |
| final_retval = PAM_SUCCESS; |
| } |
| (void) pam_end(pamh, final_retval); |
| pamh = NULL; |
| |
| if (restore_terminal_modes() != 0 && !status) { |
| status = 1; |
| } |
| |
| su_exit: |
| exit(status); /* transparent exit */ |
| } |