X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fexecute.c;h=cf71f232b421635c7705d019c2b3b788bef47f70;hp=53f6d9ad06dcaf8169ef825278bae2753ebaae46;hb=1f812feafb4b98d5cfa2934886bbdd43325780bb;hpb=ff876e283a61320b718ec752d93b1fd40a5fdd0c diff --git a/src/execute.c b/src/execute.c index 53f6d9ad0..cf71f232b 100644 --- a/src/execute.c +++ b/src/execute.c @@ -37,6 +37,10 @@ #include #include +#ifdef HAVE_PAM +#include +#endif + #include "execute.h" #include "strv.h" #include "macro.h" @@ -46,6 +50,7 @@ #include "securebits.h" #include "cgroup.h" #include "namespace.h" +#include "tcpwrap.h" /* This assumes there is a 'tty' group */ #define TTY_MODE 0620 @@ -305,8 +310,8 @@ static int setup_output(const ExecContext *context, int socket_fd, const char *i return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO; /* For PID 1 stdout is always connected to /dev/null, - * hence reopen the console if necessary. */ - if (getpid() == 1) + * hence reopen the console if out parent is PID1. */ + if (getppid() == 1) return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_FILENO); return STDOUT_FILENO; @@ -352,7 +357,7 @@ static int setup_error(const ExecContext *context, int socket_fd, const char *id if (e == EXEC_OUTPUT_INHERIT && o == EXEC_OUTPUT_INHERIT && i != EXEC_INPUT_NULL && - getpid () != 1) + getppid () != 1) return STDERR_FILENO; /* Duplicate form stdout if possible */ @@ -719,6 +724,164 @@ static int enforce_user(const ExecContext *context, uid_t uid) { return 0; } +#ifdef HAVE_PAM + +static int null_conv( + int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr) { + + /* We don't support conversations */ + + return PAM_CONV_ERR; +} + +static int setup_pam( + const char *name, + const char *user, + const char *tty, + char ***pam_env, + int fds[], unsigned n_fds) { + + static const struct pam_conv conv = { + .conv = null_conv, + .appdata_ptr = NULL + }; + + pam_handle_t *handle = NULL; + sigset_t ss, old_ss; + int pam_code = PAM_SUCCESS; + char **e = NULL; + bool close_session = false; + pid_t pam_pid = 0, parent_pid; + + assert(name); + assert(user); + assert(pam_env); + + /* We set up PAM in the parent process, then fork. The child + * will then stay around untill killed via PR_GET_PDEATHSIG or + * systemd via the cgroup logic. It will then remove the PAM + * session again. The parent process will exec() the actual + * daemon. We do things this way to ensure that the main PID + * of the daemon is the one we initially fork()ed. */ + + if ((pam_code = pam_start(name, user, &conv, &handle)) != PAM_SUCCESS) { + handle = NULL; + goto fail; + } + + if (tty) + if ((pam_code = pam_set_item(handle, PAM_TTY, tty)) != PAM_SUCCESS) + goto fail; + + if ((pam_code = pam_acct_mgmt(handle, PAM_SILENT)) != PAM_SUCCESS) + goto fail; + + if ((pam_code = pam_open_session(handle, PAM_SILENT)) != PAM_SUCCESS) + goto fail; + + close_session = true; + + if ((pam_code = pam_setcred(handle, PAM_ESTABLISH_CRED | PAM_SILENT)) != PAM_SUCCESS) + goto fail; + + if ((!(e = pam_getenvlist(handle)))) { + pam_code = PAM_BUF_ERR; + goto fail; + } + + /* Block SIGTERM, so that we know that it won't get lost in + * the child */ + if (sigemptyset(&ss) < 0 || + sigaddset(&ss, SIGTERM) < 0 || + sigprocmask(SIG_BLOCK, &ss, &old_ss) < 0) + goto fail; + + parent_pid = getpid(); + + if ((pam_pid = fork()) < 0) + goto fail; + + if (pam_pid == 0) { + int sig; + int r = EXIT_PAM; + + /* The child's job is to reset the PAM session on + * termination */ + + /* This string must fit in 10 chars (i.e. the length + * of "/sbin/init") */ + rename_process("sd:pam"); + + /* Make sure we don't keep open the passed fds in this + child. We assume that otherwise only those fds are + open here that have been opened by PAM. */ + close_many(fds, n_fds); + + /* Wait until our parent died. This will most likely + * not work since the kernel does not allow + * unpriviliged paretns kill their priviliged children + * this way. We rely on the control groups kill logic + * to do the rest for us. */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + goto child_finish; + + /* Check if our parent process might already have + * died? */ + if (getppid() == parent_pid) { + if (sigwait(&ss, &sig) < 0) + goto child_finish; + + assert(sig == SIGTERM); + } + + /* Only if our parent died we'll end the session */ + if (getppid() != parent_pid) + if ((pam_code = pam_close_session(handle, PAM_DATA_SILENT)) != PAM_SUCCESS) + goto child_finish; + + r = 0; + + child_finish: + pam_end(handle, pam_code | PAM_DATA_SILENT); + _exit(r); + } + + /* If the child was forked off successfully it will do all the + * cleanups, so forget about the handle here. */ + handle = NULL; + + /* Unblock SIGSUR1 again in the parent */ + if (sigprocmask(SIG_SETMASK, &old_ss, NULL) < 0) + goto fail; + + /* We close the log explicitly here, since the PAM modules + * might have opened it, but we don't want this fd around. */ + closelog(); + + return 0; + +fail: + if (handle) { + if (close_session) + pam_code = pam_close_session(handle, PAM_DATA_SILENT); + + pam_end(handle, pam_code | PAM_DATA_SILENT); + } + + strv_free(e); + + closelog(); + + if (pam_pid > 1) + kill(pam_pid, SIGTERM); + + return EXIT_PAM; +} +#endif + int exec_spawn(ExecCommand *command, char **argv, const ExecContext *context, @@ -776,14 +939,24 @@ int exec_spawn(ExecCommand *command, const char *username = NULL, *home = NULL; uid_t uid = (uid_t) -1; gid_t gid = (gid_t) -1; - char **our_env = NULL, **final_env = NULL; + char **our_env = NULL, **pam_env = NULL, **final_env = NULL; unsigned n_env = 0; int saved_stdout = -1, saved_stdin = -1; bool keep_stdout = false, keep_stdin = false; /* child */ - reset_all_signal_handlers(); + /* This string must fit in 10 chars (i.e. the length + * of "/sbin/init") */ + rename_process("sd:exec"); + + /* We reset exactly these signals, since they are the + * only ones we set to SIG_IGN in the main daemon. All + * others we leave untouched because we set them to + * SIG_DFL or a valid handler initially, both of which + * will be demoted to SIG_DFL. */ + default_signals(SIGNALS_CRASH_HANDLER, + SIGNALS_IGNORE, -1); if (sigemptyset(&ss) < 0 || sigprocmask(SIG_SETMASK, &ss, NULL) < 0) { @@ -797,6 +970,12 @@ int exec_spawn(ExecCommand *command, goto fail; } + if (socket_fd >= 0 && context->tcpwrap_name) + if (!socket_tcpwrap(socket_fd, context->tcpwrap_name)) { + r = EXIT_TCPWRAP; + goto fail; + } + if (confirm_spawn) { char response; @@ -901,19 +1080,6 @@ int exec_spawn(ExecCommand *command, goto fail; } - if (strv_length(context->read_write_dirs) > 0 || - strv_length(context->read_only_dirs) > 0 || - strv_length(context->inaccessible_dirs) > 0 || - context->mount_flags != MS_SHARED || - context->private_tmp) - if ((r = setup_namespace( - context->read_write_dirs, - context->read_only_dirs, - context->inaccessible_dirs, - context->private_tmp, - context->mount_flags)) < 0) - goto fail; - if (context->user) { username = context->user; if (get_user_creds(&username, &uid, &gid, &home) < 0) { @@ -928,6 +1094,25 @@ int exec_spawn(ExecCommand *command, } } +#ifdef HAVE_PAM + if (context->pam_name && username) { + /* Make sure no fds leak into the PAM + * supervisor process. We will call this later + * on again to make sure that any fds leaked + * by the PAM modules get closed before our + * exec(). */ + if (close_all_fds(fds, n_fds) < 0) { + r = EXIT_FDS; + goto fail; + } + + if (setup_pam(context->pam_name, username, context->tty_path, &pam_env, fds, n_fds) < 0) { + r = EXIT_PAM; + goto fail; + } + } +#endif + if (apply_permissions) if (enforce_groups(context, username, uid) < 0) { r = EXIT_GROUP; @@ -936,6 +1121,19 @@ int exec_spawn(ExecCommand *command, umask(context->umask); + if (strv_length(context->read_write_dirs) > 0 || + strv_length(context->read_only_dirs) > 0 || + strv_length(context->inaccessible_dirs) > 0 || + context->mount_flags != MS_SHARED || + context->private_tmp) + if ((r = setup_namespace( + context->read_write_dirs, + context->read_only_dirs, + context->inaccessible_dirs, + context->private_tmp, + context->mount_flags)) < 0) + goto fail; + if (apply_chroot) { if (context->root_directory) if (chroot(context->root_directory) < 0) { @@ -1015,7 +1213,7 @@ int exec_spawn(ExecCommand *command, } if (n_fds > 0) - if (asprintf(our_env + n_env++, "LISTEN_PID=%llu", (unsigned long long) getpid()) < 0 || + if (asprintf(our_env + n_env++, "LISTEN_PID=%lu", (unsigned long) getpid()) < 0 || asprintf(our_env + n_env++, "LISTEN_FDS=%u", n_fds) < 0) { r = EXIT_MEMORY; goto fail; @@ -1036,7 +1234,13 @@ int exec_spawn(ExecCommand *command, assert(n_env <= 6); - if (!(final_env = strv_env_merge(environment, our_env, context->environment, NULL))) { + if (!(final_env = strv_env_merge( + 4, + environment, + our_env, + context->environment, + pam_env, + NULL))) { r = EXIT_MEMORY; goto fail; } @@ -1047,6 +1251,7 @@ int exec_spawn(ExecCommand *command, fail: strv_free(our_env); strv_free(final_env); + strv_free(pam_env); if (saved_stdin >= 0) close_nointr_nofail(saved_stdin); @@ -1065,10 +1270,10 @@ int exec_spawn(ExecCommand *command, if (cgroup_bondings) cgroup_bonding_install_list(cgroup_bondings, pid); - log_debug("Forked %s as %llu", command->path, (unsigned long long) pid); + log_debug("Forked %s as %lu", command->path, (unsigned long) pid); command->exec_status.pid = pid; - command->exec_status.start_timestamp = now(CLOCK_REALTIME); + dual_timestamp_get(&command->exec_status.start_timestamp); *ret = pid; return 0; @@ -1105,6 +1310,9 @@ void exec_context_done(ExecContext *c) { free(c->tty_path); c->tty_path = NULL; + free(c->tcpwrap_name); + c->tcpwrap_name = NULL; + free(c->syslog_identifier); c->syslog_identifier = NULL; @@ -1117,6 +1325,9 @@ void exec_context_done(ExecContext *c) { strv_free(c->supplementary_groups); c->supplementary_groups = NULL; + free(c->pam_name); + c->pam_name = NULL; + if (c->capabilities) { cap_free(c->capabilities); c->capabilities = NULL; @@ -1203,6 +1414,11 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { for (e = c->environment; *e; e++) fprintf(f, "%sEnvironment: %s\n", prefix, *e); + if (c->tcpwrap_name) + fprintf(f, + "%sTCPWrapName: %s\n", + prefix, c->tcpwrap_name); + if (c->nice_set) fprintf(f, "%sNice: %i\n", @@ -1301,9 +1517,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { } if (c->user) - fprintf(f, "%sUser: %s", prefix, c->user); + fprintf(f, "%sUser: %s\n", prefix, c->user); if (c->group) - fprintf(f, "%sGroup: %s", prefix, c->group); + fprintf(f, "%sGroup: %s\n", prefix, c->group); if (strv_length(c->supplementary_groups) > 0) { fprintf(f, "%sSupplementaryGroups:", prefix); @@ -1311,6 +1527,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { fputs("\n", f); } + if (c->pam_name) + fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name); + if (strv_length(c->read_write_dirs) > 0) { fprintf(f, "%sReadWriteDirs:", prefix); strv_fprintf(f, c->read_write_dirs); @@ -1334,7 +1553,7 @@ void exec_status_fill(ExecStatus *s, pid_t pid, int code, int status) { assert(s); s->pid = pid; - s->exit_timestamp = now(CLOCK_REALTIME); + dual_timestamp_get(&s->exit_timestamp); s->code = code; s->status = status; @@ -1353,20 +1572,20 @@ void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { return; fprintf(f, - "%sPID: %llu\n", - prefix, (unsigned long long) s->pid); + "%sPID: %lu\n", + prefix, (unsigned long) s->pid); - if (s->start_timestamp > 0) + if (s->start_timestamp.realtime > 0) fprintf(f, "%sStart Timestamp: %s\n", - prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp)); + prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); - if (s->exit_timestamp > 0) + if (s->exit_timestamp.realtime > 0) fprintf(f, "%sExit Timestamp: %s\n" "%sExit Code: %s\n" "%sExit Status: %i\n", - prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp), + prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime), prefix, sigchld_code_to_string(s->code), prefix, s->status); } @@ -1589,6 +1808,12 @@ const char* exit_status_to_string(ExitStatus status) { case EXIT_STDERR: return "STDERR"; + case EXIT_TCPWRAP: + return "TCPWRAP"; + + case EXIT_PAM: + return "PAM"; + default: return NULL; }