$(DBUS_LIBS) \
$(UDEV_LIBS) \
$(CGROUP_LIBS) \
- $(LIBWRAP_LIBS)
+ $(LIBWRAP_LIBS) \
+ $(PAM_LIBS)
test_engine_SOURCES = \
$(COMMON_SOURCES) \
AC_ARG_ENABLE([tcpwrap],
AS_HELP_STRING([--disable-tcpwrap],[Disable optional TCP wrappers support]),
[case "${enableval}" in
- yes) tcpwrap=yes ;;
- no) tcpwrap=no ;;
+ yes) have_tcpwrap=yes ;;
+ no) have_tcpwrap=no ;;
*) AC_MSG_ERROR(bad value ${enableval} for --disable-tcpwrap) ;;
esac],
- [tcpwrap=auto])
+ [have_tcpwrap=auto])
-if test "x${tcpwrap}" != xno ; then
+if test "x${have_tcpwrap}" != xno ; then
ACX_LIBWRAP
if test "x${LIBWRAP_LIBS}" = x ; then
- if test "x$tcpwrap" = xyes ; then
- AC_MSG_ERROR([*** TCP wrappers support not found])
+ if test "x$have_tcpwrap" = xyes ; then
+ AC_MSG_ERROR([*** TCP wrappers support not found.])
fi
else
- tcpwrap=yes
+ have_tcpwrap=yes
fi
else
- LIBWRAP_LIBS=
+ LIBWRAP_LIBS=
fi
-
AC_SUBST(LIBWRAP_LIBS)
+AC_ARG_ENABLE([pam],
+ AS_HELP_STRING([--disable-pam],[Disable optional PAM support]),
+ [case "${enableval}" in
+ yes) have_pam=yes ;;
+ no) have_pam=no ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --disable-pam) ;;
+ esac],
+ [have_pam=auto])
+
+if test "x${have_pam}" != xno ; then
+ AC_CHECK_HEADERS(
+ [security/pam_modules.h security/pam_modutil.h security/pam_ext.h],
+ [have_pam=yes],
+ [if test "x$have_pam" = xyes ; then
+ AC_MSG_ERROR([*** PAM headers not found.])
+ fi])
+
+ AC_CHECK_LIB(
+ [pam],
+ [pam_syslog],
+ [have_pam=yes],
+ [if test "x$have_pam" = xyes ; then
+ AC_MSG_ERROR([*** libpam not found.])
+ fi])
+
+ if test "x$have_pam" = xyes ; then
+ PAM_LIBS="-lpam -lpam_misc"
+ AC_DEFINE(HAVE_PAM, 1, [PAM available])
+ fi
+else
+ PAM_LIBS=
+fi
+AC_SUBST(PAM_LIBS)
+AM_CONDITIONAL([HAVE_PAM], [test "x$have_pam" != xno])
+
have_gtk=no
AC_ARG_ENABLE(gtk, AS_HELP_STRING([--disable-gtk], [disable GTK tools]))
if test "x$enable_gtk" != "xno"; then
Syslog service: ${SPECIAL_SYSLOG_SERVICE}
D-Bus service: ${SPECIAL_DBUS_SERVICE}
Gtk: ${have_gtk}
- tcpwrap: ${tcpwrap}
+ tcpwrap: ${have_tcpwrap}
+ PAM: ${have_pam}
prefix: ${prefix}
root dir: ${with_rootdir}
udev rules dir: ${with_udevrulesdir}
* write utmp record a la upstart for processes
-* run PAM session stuff
+* follow property change dbus spec
-* use setproctitle() when forking, before exec() (waiting for (PR_SET_PROCTITLE_AREA to enter the kernel)
+* pam module
-* follow property change dbus spec
+* selinux
Regularly:
* look for close() vs. close_nointr() vs. close_nointr_nofail()
* check for strerror(r) instead of strerror(-r)
+
+* Use PR_SET_PROCTITLE_AREA if it becomes available in the kernel
" <property name=\"User\" type=\"s\" access=\"read\"/>\n" \
" <property name=\"Group\" type=\"s\" access=\"read\"/>\n" \
" <property name=\"SupplementaryGroups\" type=\"as\" access=\"read\"/>\n" \
- " <property name=\"TCPWrapName\" type=\"s\" access=\"read\"/>\n"
+ " <property name=\"TCPWrapName\" type=\"s\" access=\"read\"/>\n" \
+ " <property name=\"PAMName\" type=\"s\" access=\"read\"/>\n"
#define BUS_EXEC_CONTEXT_PROPERTIES(interface, context) \
{ interface, "Environment", bus_property_append_strv, "as", (context).environment }, \
{ 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);
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)
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)
#include <sys/mount.h>
#include <linux/fs.h>
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+#endif
+
#include "execute.h"
#include "strv.h"
#include "macro.h"
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,
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
}
}
+#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;
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;
}
fail:
strv_free(our_env);
strv_free(final_env);
+ strv_free(pam_env);
if (saved_stdin >= 0)
close_nointr_nofail(saved_stdin);
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;
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);
case EXIT_TCPWRAP:
return "TCPWRAP";
+ case EXIT_PAM:
+ return "PAM";
+
default:
return NULL;
}
char *group;
char **supplementary_groups;
+ char *pam_name;
+
char **read_write_dirs, **read_only_dirs, **inaccessible_dirs;
unsigned long mount_flags;
EXIT_SETSID, /* 220 */
EXIT_CONFIRM,
EXIT_STDERR,
- EXIT_TCPWRAP
+ EXIT_TCPWRAP,
+ EXIT_PAM
} ExitStatus;
" 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;
}
{ "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" },
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);
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");
" --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;
}
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)
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
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;
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;
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);
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)++)
" 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;
}
#include <sys/poll.h>
#include <libgen.h>
#include <ctype.h>
+#include <sys/prctl.h>
#include "macro.h"
#include "util.h"
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);
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",
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);
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);