From 5b6319dceedd81f3f1ce7eb70ea5defaef43bcec Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 16 Jun 2010 21:54:17 +0200 Subject: service: optionally call into PAM when dropping priviliges --- src/dbus-execute.h | 6 +- src/dbus-manager.c | 4 +- src/execute.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/execute.h | 5 +- src/install.c | 2 +- src/load-fragment.c | 3 +- src/log.c | 4 +- src/main.c | 2 +- src/manager.c | 2 +- src/strv.c | 41 +++++------ src/strv.h | 4 +- src/systemctl.c | 2 +- src/util.c | 21 ++++++ src/util.h | 3 +- 14 files changed, 266 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/dbus-execute.h b/src/dbus-execute.h index 243854f893..1e83caca5a 100644 --- a/src/dbus-execute.h +++ b/src/dbus-execute.h @@ -44,7 +44,8 @@ " \n" \ " \n" \ " \n" \ - " \n" + " \n" \ + " \n" #define BUS_EXEC_CONTEXT_PROPERTIES(interface, context) \ { interface, "Environment", bus_property_append_strv, "as", (context).environment }, \ @@ -73,7 +74,8 @@ { interface, "User", bus_property_append_string, "s", (context).user }, \ { interface, "Group", bus_property_append_string, "s", (context).group }, \ { interface, "SupplementaryGroups", bus_property_append_strv, "as", (context).supplementary_groups }, \ - { interface, "TCPWrapName", bus_property_append_string, "s", (context).tcpwrap_name } + { interface, "TCPWrapName", bus_property_append_string, "s", (context).tcpwrap_name }, \ + { interface, "PAMName", bus_property_append_string, "s", (context).pam_name } int bus_execute_append_output(Manager *m, DBusMessageIter *i, const char *property, void *data); int bus_execute_append_input(Manager *m, DBusMessageIter *i, const char *property, void *data); diff --git a/src/dbus-manager.c b/src/dbus-manager.c index c80b22b9e6..d4c9e8f0e2 100644 --- a/src/dbus-manager.c +++ b/src/dbus-manager.c @@ -626,7 +626,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection return bus_send_error_reply(m, message, NULL, r); } - e = strv_env_merge(m->environment, l, NULL); + e = strv_env_merge(2, m->environment, l); strv_free(l); if (!e) @@ -650,7 +650,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection return bus_send_error_reply(m, message, NULL, r); } - e = strv_env_delete(m->environment, l, NULL); + e = strv_env_delete(m->environment, 1, l); strv_free(l); if (!e) diff --git a/src/execute.c b/src/execute.c index 1a7871b4e0..ed10ea2bfb 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" @@ -720,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, @@ -777,13 +939,17 @@ 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 */ + /* 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 @@ -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; @@ -1049,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; } @@ -1060,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); @@ -1133,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; @@ -1332,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", prefix, c->pam_name); + if (strv_length(c->read_write_dirs) > 0) { fprintf(f, "%sReadWriteDirs:", prefix); strv_fprintf(f, c->read_write_dirs); @@ -1613,6 +1811,9 @@ const char* exit_status_to_string(ExitStatus status) { case EXIT_TCPWRAP: return "TCPWRAP"; + case EXIT_PAM: + return "PAM"; + default: return NULL; } diff --git a/src/execute.h b/src/execute.h index 1adf41ea67..e618049497 100644 --- a/src/execute.h +++ b/src/execute.h @@ -116,6 +116,8 @@ struct ExecContext { char *group; char **supplementary_groups; + char *pam_name; + char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; unsigned long mount_flags; @@ -182,7 +184,8 @@ typedef enum ExitStatus { EXIT_SETSID, /* 220 */ EXIT_CONFIRM, EXIT_STDERR, - EXIT_TCPWRAP + EXIT_TCPWRAP, + EXIT_PAM } ExitStatus; diff --git a/src/install.c b/src/install.c index e3db63769f..c3dbe8b195 100644 --- a/src/install.c +++ b/src/install.c @@ -69,7 +69,7 @@ static int help(void) { " enable [NAME...] Enable one or more units\n" " disable [NAME...] Disable one or more units\n" " test [NAME...] Test whether any of the specified units are enabled\n", - __progname); + program_invocation_short_name); return 0; } diff --git a/src/load-fragment.c b/src/load-fragment.c index f409776e88..1f082d57b3 100644 --- a/src/load-fragment.c +++ b/src/load-fragment.c @@ -1392,7 +1392,8 @@ static int load_from_path(Unit *u, const char *path) { { "InaccessibleDirectories",config_parse_path_strv, &(context).inaccessible_dirs, section }, \ { "PrivateTmp", config_parse_bool, &(context).private_tmp, section }, \ { "MountFlags", config_parse_mount_flags, &(context), section }, \ - { "TCPWrapName", config_parse_string, &(context).tcpwrap_name, section } + { "TCPWrapName", config_parse_string, &(context).tcpwrap_name, section }, \ + { "PAMName", config_parse_string, &(context).pam_name, section } const ConfigItem items[] = { { "Names", config_parse_names, u, "Unit" }, diff --git a/src/log.c b/src/log.c index 265a7f10e2..b7173eb550 100644 --- a/src/log.c +++ b/src/log.c @@ -267,7 +267,7 @@ static int write_to_syslog( zero(iovec); IOVEC_SET_STRING(iovec[0], header_priority); IOVEC_SET_STRING(iovec[1], header_time); - IOVEC_SET_STRING(iovec[2], __progname); + IOVEC_SET_STRING(iovec[2], program_invocation_short_name); IOVEC_SET_STRING(iovec[3], header_pid); IOVEC_SET_STRING(iovec[4], buffer); @@ -302,7 +302,7 @@ static int write_to_kmsg( zero(iovec); IOVEC_SET_STRING(iovec[0], header_priority); - IOVEC_SET_STRING(iovec[1], __progname); + IOVEC_SET_STRING(iovec[1], program_invocation_short_name); IOVEC_SET_STRING(iovec[2], header_pid); IOVEC_SET_STRING(iovec[3], buffer); IOVEC_SET_STRING(iovec[4], "\n"); diff --git a/src/main.c b/src/main.c index 9cdbf2e896..e5bdf849be 100644 --- a/src/main.c +++ b/src/main.c @@ -504,7 +504,7 @@ static int help(void) { " --dump-configuration-items Dump understood unit configuration items\n" " --confirm-spawn Ask for confirmation when spawning processes\n" " --introspect[=INTERFACE] Extract D-Bus interface data\n", - __progname); + program_invocation_short_name); return 0; } diff --git a/src/manager.c b/src/manager.c index 97d05b52c3..385b371dbc 100644 --- a/src/manager.c +++ b/src/manager.c @@ -109,7 +109,7 @@ static int manager_setup_notify(Manager *m) { return -ENOMEM; ne[1] = NULL; - t = strv_env_merge(m->environment, ne, NULL); + t = strv_env_merge(2, m->environment, ne); free(ne[0]); if (!t) diff --git a/src/strv.c b/src/strv.c index 2ebd0ee53a..85599fe92c 100644 --- a/src/strv.c +++ b/src/strv.c @@ -374,7 +374,9 @@ char **strv_remove(char **l, const char *s) { static int env_append(char **r, char ***k, char **a) { assert(r); assert(k); - assert(a); + + if (!a) + return 0; /* Add the entries of a to *k unless they already exist in *r * in which case they are overriden instead. This assumes @@ -400,38 +402,33 @@ static int env_append(char **r, char ***k, char **a) { return 0; } -char **strv_env_merge(char **x, ...) { +char **strv_env_merge(unsigned n_lists, ...) { size_t n = 0; char **l, **k, **r; va_list ap; + unsigned i; /* Merges an arbitrary number of environment sets */ - if (x) { - n += strv_length(x); - - va_start(ap, x); - while ((l = va_arg(ap, char**))) - n += strv_length(l); - va_end(ap); + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + n += strv_length(l); } - + va_end(ap); if (!(r = new(char*, n+1))) return NULL; k = r; - if (x) { - if (env_append(r, &k, x) < 0) + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + if (env_append(r, &k, l) < 0) goto fail; - - va_start(ap, x); - while ((l = va_arg(ap, char**))) - if (env_append(r, &k, l) < 0) - goto fail; - va_end(ap); } + va_end(ap); *k = NULL; @@ -472,7 +469,7 @@ static bool env_match(const char *t, const char *pattern) { return false; } -char **strv_env_delete(char **x, ...) { +char **strv_env_delete(char **x, unsigned n_lists, ...) { size_t n = 0, i = 0; char **l, **k, **r, **j; va_list ap; @@ -486,12 +483,14 @@ char **strv_env_delete(char **x, ...) { return NULL; STRV_FOREACH(k, x) { - va_start(ap, x); + va_start(ap, n_lists); - while ((l = va_arg(ap, char**))) + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); STRV_FOREACH(j, l) if (env_match(*k, *j)) goto delete; + } va_end(ap); diff --git a/src/strv.h b/src/strv.h index 2d24b4e392..af13983993 100644 --- a/src/strv.h +++ b/src/strv.h @@ -55,8 +55,8 @@ char **strv_split_quoted(const char *s) _malloc_; char *strv_join(char **l, const char *separator) _malloc_; -char **strv_env_merge(char **x, ...) _sentinel_; -char **strv_env_delete(char **x, ...) _sentinel_; +char **strv_env_merge(unsigned n_lists, ...); +char **strv_env_delete(char **x, unsigned n_lists, ...); #define STRV_FOREACH(s, l) \ for ((s) = (l); (s) && *(s); (s)++) diff --git a/src/systemctl.c b/src/systemctl.c index 663cb833bc..35ca082ab4 100644 --- a/src/systemctl.c +++ b/src/systemctl.c @@ -1322,7 +1322,7 @@ static int help(void) { " show-environment Dump environment\n" " set-environment [NAME=VALUE...] Set one or more environment variables\n" " unset-environment [NAME...] Unset one or more environment variables\n", - __progname); + program_invocation_short_name); return 0; } diff --git a/src/util.c b/src/util.c index 71409034a4..ed0991a68b 100644 --- a/src/util.c +++ b/src/util.c @@ -43,6 +43,7 @@ #include #include #include +#include #include "macro.h" #include "util.h" @@ -222,6 +223,13 @@ void close_nointr_nofail(int fd) { errno = saved_errno; } +void close_many(const int fds[], unsigned n_fd) { + unsigned i; + + for (i = 0; i < n_fd; i++) + close_nointr_nofail(fds[i]); +} + int parse_boolean(const char *v) { assert(v); @@ -2161,6 +2169,19 @@ fallback: return random() * RAND_MAX + random(); } +void rename_process(const char name[8]) { + assert(name); + + prctl(PR_SET_NAME, name); + + /* This is a like a poor man's setproctitle(). The string + * passed should fit in 7 chars (i.e. the length of + * "systemd") */ + + if (program_invocation_name) + strncpy(program_invocation_name, name, strlen(program_invocation_name)); +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/src/util.h b/src/util.h index 0b397ac0d4..1e5ee28eba 100644 --- a/src/util.h +++ b/src/util.h @@ -104,6 +104,7 @@ bool first_word(const char *s, const char *word); int close_nointr(int fd); void close_nointr_nofail(int fd); +void close_many(const int fds[], unsigned n_fd); int parse_boolean(const char *v); int parse_usec(const char *t, usec_t *usec); @@ -252,7 +253,7 @@ bool is_device_path(const char *path); int dir_is_empty(const char *path); -extern char * __progname; +void rename_process(const char name[8]); const char *ioprio_class_to_string(int i); int ioprio_class_from_string(const char *s); -- cgit v1.2.3-54-g00ecf