[pkg-fso-commits] [SCM] Automatic Display Manager branch, bug540201, updated. debian/0.6-1-31-g284d64e

Enrico Zini enrico at enricozini.org
Tue Jul 5 17:29:30 UTC 2011


The following commit has been merged in the bug540201 branch:
commit 284d64e7520222d1d8efb297ad852d0aad78f6ff
Author: Enrico Zini <enrico at enricozini.org>
Date:   Tue Jul 5 19:29:25 2011 +0200

    More code cleanup, split child code from server code

diff --git a/Makefile.am b/Makefile.am
index 5733baa..fac0a4e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,14 @@
 ## Process this file with automake to produce Makefile.in
 
+CFLAGS += -Wall -Werror
+
 ACLOCAL_AMFLAGS = -I m4
 
 sbin_PROGRAMS = nodm
 
-dist_noinst_HEADERS = common.h log.h server.h session.h
+dist_noinst_HEADERS = common.h dm.h log.h xserver.h xsession.h xsession-child.h 
 
-libsources = common.c log.c server.c session.c
+libsources = common.c log.c xsession-child.c xserver.c xsession.c dm.c
 
 AM_CPPFLAGS = $(X11_CFLAGS)
 AM_LDFLAGS = $(PAM_LIBS) $(X11_LIBS)
diff --git a/common.h b/common.h
index e6c590b..9d9443b 100644
--- a/common.h
+++ b/common.h
@@ -48,4 +48,14 @@
  */
 const char* getenv_with_default(const char* envname, const char* def);
 
+/**
+ * Like strcpy but:
+ *
+ *  * it works only for sized character arrays (it expects sizeof on them)
+ *  * it always null-terminates the destination string
+ *  * it returns false if the string was truncated, else true
+ */
+#define bounded_strcpy(dst, src) (snprintf(dst, sizeof(dst), "%s", (src)) < sizeof(dst))
+
+
 #endif
diff --git a/dm.c b/dm.c
new file mode 100644
index 0000000..1f84cde
--- /dev/null
+++ b/dm.c
@@ -0,0 +1,108 @@
+/*
+ * session - nodm X display manager
+ *
+ * Copyright 2011  Enrico Zini <enrico at enricozini.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dm.h"
+#include "common.h"
+#include <wordexp.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+void nodm_display_manager_init(struct nodm_display_manager* dm)
+{
+    nodm_xserver_init(&(dm->srv));
+    nodm_xsession_init(&(dm->session));
+    dm->_srv_split_args = NULL;
+}
+
+void nodm_display_manager_cleanup(struct nodm_display_manager* dm)
+{
+    // Deallocate parsed arguments, if used
+    if (dm->_srv_split_args)
+    {
+        wordexp_t* we = (wordexp_t*)dm->_srv_split_args;
+        wordfree(we);
+        free(we);
+        dm->_srv_split_args = NULL;
+    }
+}
+
+int nodm_display_manager_parse_xcmdline(struct nodm_display_manager* s, const char* xcmdline)
+{
+    int return_code = E_SUCCESS;
+
+    // tokenize xoptions
+    wordexp_t* toks = (wordexp_t*)calloc(1, sizeof(wordexp_t));
+    switch (wordexp(xcmdline, toks, WRDE_NOCMD))
+    {
+        case 0: break;
+        case WRDE_NOSPACE:
+            return_code = E_OS_ERROR;
+            goto cleanup;
+        default:
+            toks->we_wordv = NULL;
+            return_code = E_BAD_ARG;
+            goto cleanup;
+    }
+
+    unsigned in_arg = 0;
+    unsigned argc = 0;
+    char **argv = (char**)malloc((toks->we_wordc + 3) * sizeof(char*));
+    if (argv == NULL)
+    {
+        return_code = E_OS_ERROR;
+        goto cleanup;
+    }
+
+    // Server command
+    if (in_arg < toks->we_wordc &&
+           (toks->we_wordv[in_arg][0] == '/' || toks->we_wordv[in_arg][0] == '.'))
+        argv[argc++] = toks->we_wordv[in_arg++];
+    else
+        argv[argc++] = "/usr/bin/X";
+
+    // Server name
+    if (in_arg < toks->we_wordc &&
+           toks->we_wordv[in_arg][0] == ':' && isdigit(toks->we_wordv[in_arg][1]))
+        argv[argc++] = toks->we_wordv[in_arg++];
+    else
+        argv[argc++] = ":0";
+
+    // Copy other args
+    while (in_arg < toks->we_wordc)
+        argv[argc++] = toks->we_wordv[in_arg++];
+    argv[argc] = NULL;
+
+    s->srv.argv = (const char**)argv;
+    s->_srv_split_args = toks;
+    argv = NULL;
+    toks = NULL;
+
+cleanup:
+    if (toks != NULL)
+    {
+        if (toks->we_wordv)
+            wordfree(toks);
+        free(toks);
+    }
+    if (argv != NULL)
+        free(argv);
+
+    return return_code;
+}
diff --git a/dm.h b/dm.h
new file mode 100644
index 0000000..5d4571b
--- /dev/null
+++ b/dm.h
@@ -0,0 +1,59 @@
+/*
+ * session - nodm X display manager
+ *
+ * Copyright 2011  Enrico Zini <enrico at enricozini.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef NODM_DISPLAY_MANAGER_H
+#define NODM_DISPLAY_MANAGER_H
+
+#include "xserver.h"
+#include "xsession.h"
+
+struct nodm_display_manager
+{
+    /// X server supervision
+    struct nodm_xserver srv;
+
+    /// X session supervision
+    struct nodm_xsession session;
+
+
+    /// Storage for split server arguments used by nodm_x_cmdline_split
+    void* _srv_split_args;
+};
+
+/// Initialise a display_manager structure with default values
+void nodm_display_manager_init(struct nodm_display_manager* dm);
+
+/// Cleanup at the end of the display manager
+void nodm_display_manager_cleanup(struct nodm_display_manager* dm);
+
+/**
+ * Split xcmdline using wordexp shell-like expansion and set dm->srv.argv.
+ *
+ * If the first token starts with '/' or '.', it is used as the X server, else
+ * "X" is used as the server.
+ *
+ * If the second token (or the first if the first was not recognised as a path
+ * to the X server) looks like ":<NUMBER>", it is used as the display name,
+ * else ":0" is used.
+ */
+int nodm_display_manager_parse_xcmdline(struct nodm_display_manager* dm, const char* xcmdline);
+
+
+#endif
diff --git a/nodm.c b/nodm.c
index d987cee..46f61d8 100644
--- a/nodm.c
+++ b/nodm.c
@@ -55,7 +55,7 @@
 
 #include "config.h"
 #include "common.h"
-#include "session.h"
+#include "dm.h"
 #include "log.h"
 #include <getopt.h>
 #include <signal.h>
@@ -66,6 +66,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
+#include <sys/ioctl.h>
 #include <time.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -195,20 +196,20 @@ void run_and_restart(const char* xsession, const char* xoptions, int mst)
 
 	while (1)
 	{
-        struct session s;
-        nodm_session_init(&s);
+        struct nodm_display_manager dm;
+        nodm_display_manager_init(&dm);
 
-        int status = nodm_session_parse_cmdline(&s, xoptions);
+        int status = nodm_display_manager_parse_xcmdline(&dm, xoptions);
         if (status != E_SUCCESS)
             exit(status);
 
 		/* Run the X server */
 		time_t begin = time(NULL);
 		time_t end;
-        status = nodm_x_with_session(&s);
+        // TODO status = nodm_x_with_session(&s);
         log_info("X session exited with status %d", status);
         end = time(NULL);
-        nodm_session_cleanup(&s);
+        nodm_display_manager_cleanup(&dm);
 
 		/* Check if the session was too short */
 		if (end - begin < mst)
diff --git a/server.h b/server.h
deleted file mode 100644
index af632cc..0000000
--- a/server.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * server - X server startup functions
- *
- * Copyright 2011  Enrico Zini <enrico at enricozini.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef NODM_SERVER_H
-#define NODM_SERVER_H
-
-#include <sys/types.h>
-#include <X11/Xlib.h>
-
-struct server
-{
-    /// X server command line
-    const char **argv;
-    /// X display name
-    const char *name;
-    /// X window path (dynamically allocated and owned by this structure)
-    char *windowpath;
-    /// X server pid
-    pid_t pid;
-    /// xlib Display connected to the server
-    Display *dpy;
-};
-
-/**
- * Initialise a struct server with NULL values
- */
-void server_init(struct server* srv);
-
-/**
- * Start the X server and wait until it's ready to accept connections.
- *
- * @param srv
- *   The struct server with X server information. argv and name are expected to
- *   be filled, pid is filled.
- * @param timeout_sec
- *   Timeout in seconds after which if the X server is not ready, we give up
- *   and return an error.
- * @return
- *   Exit status as described by the E_* constants
- */
-int server_start(struct server* srv, unsigned timeout_sec);
-
-/// Kill the X server
-int server_stop(struct server* srv);
-
-/**
- * Connect to the X server
- *
- * Uses srv->name, sets srv->dpy.
- *
- * @return
- *   Exit status as described by the E_* constants
- */
-int server_connect(struct server* srv);
-
-/**
- * Close connection to the X server
- *
- * Uses srv->dpy, sets it to NULL.
- *
- * @return
- *   Exit status as described by the E_* constants
- */
-int server_disconnect(struct server* srv);
-
-/**
- * Get the WINDOWPATH value for the server
- *
- * Uses srv->dpy, sets srv->windowpath
- *
- * @return
- *   Exit status as described by the E_* constants
- */
-int server_read_window_path(struct server* srv);
-
-#endif
diff --git a/session.c b/session.c
deleted file mode 100644
index 0243c77..0000000
--- a/session.c
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- * session - nodm X session
- *
- * Copyright 2011  Enrico Zini <enrico at enricozini.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "session.h"
-#include "log.h"
-#include "common.h"
-#include <errno.h>
-#include <signal.h>
-#include <grp.h>
-#include <pwd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <security/pam_appl.h>
-#include <security/pam_misc.h>
-#include <wordexp.h>
-
-/* compatibility with different versions of Linux-PAM */
-#ifndef PAM_ESTABLISH_CRED
-#define PAM_ESTABLISH_CRED PAM_CRED_ESTABLISH
-#endif
-#ifndef PAM_DELETE_CRED
-#define PAM_DELETE_CRED PAM_CRED_DELETE
-#endif
-
-/* Copy string pointed by B to array A with size checking.  It was originally
-   in lmain.c but is _very_ useful elsewhere.  Some setuid root programs with
-   very sloppy coding used to assume that BUFSIZ will always be enough...  */
-                                        /* danger - side effects */
-#define STRFCPY(A,B) \
-        (strncpy((A), (B), sizeof(A) - 1), (A)[sizeof(A) - 1] = '\0')
-
-static int run_shell (const char** args, int* status);
-
-/*
- * setup_uid_gid() split in two functions for PAM support -
- * pam_setcred() needs to be called after initgroups(), but
- * before setuid().
- */
-static int setup_groups(const struct passwd *info)
-{
-    /*
-     * Set the real group ID to the primary group ID in the password
-     * file.
-     */
-    if (setgid (info->pw_gid) == -1) {
-        log_err("bad group ID `%d' for user `%s': %m\n", info->pw_gid, info->pw_name);
-        return E_OS_ERROR;
-    }
-
-    /*
-     * For systems which support multiple concurrent groups, go get
-     * the group set from the /etc/group file.
-     */
-    if (initgroups (info->pw_name, info->pw_gid) == -1) {
-        log_err("initgroups failed for user `%s': %m\n", info->pw_name);
-        return E_OS_ERROR;
-    }
-    return E_SUCCESS;
-}
-
-static int change_uid (const struct passwd *info)
-{
-    /*
-     * Set the real UID to the UID value in the password file.
-     */
-    if (setuid(info->pw_uid)) {
-        log_err("bad user ID `%d' for user `%s': %m\n", (int)info->pw_uid, info->pw_name);
-        return E_OS_ERROR;
-    }
-    return E_SUCCESS;
-}
-
-/*
- * Truncate ~/.xsession-errors if it is longer than \a maxsize.
- *
- * The function looks for .xsession-errors in the current directory, so when it
- * is called the current directory must be the user's homedir.
- *
- * The function also assumes that we are running as the user. As a consequence
- * it does not worry about symlink attacks, because they would only be possible
- * if the user's home directory is group or world writable.
- *
- * curdirname is the name of the current directory, and it is only used when
- * logging error messages.
- *
- * The function returns true on success, false on failure.
- */
-static int cleanup_xse(off_t maxsize, const char* curdirname)
-{
-    int ret = E_OS_ERROR;
-    int xse_fd = -1;
-    struct stat xse_st;
-
-    xse_fd = open(".xsession-errors", O_WRONLY | O_CREAT, 0600);
-    if (xse_fd < 0)
-    {
-        log_err("cannot open `%s/%s': %m", curdirname, ".xsession-errors");
-        goto cleanup;
-    }
-    if (fstat(xse_fd, &xse_st) < 0)
-    {
-        log_err("cannot stat `%s/%s': %m", curdirname, ".xsession-errors");
-        goto cleanup;
-    }
-    if (xse_st.st_size > maxsize)
-    {
-        if (ftruncate(xse_fd, 0) < 0)
-        {
-            log_err("cannot truncate `%s/%s': %m", curdirname, ".xsession-errors");
-            goto cleanup;
-        }
-    }
-
-    /* If we made it so far, we succeeded */
-    ret = E_SUCCESS;
-
-cleanup:
-    if (xse_fd >= 0)
-        close(xse_fd);
-    return ret;
-}
-
-static int session_user_setup(struct session* s)
-{
-    /*
-     * Validate the user using the normal system user database
-     */
-    struct passwd *pw = 0;
-    if (!(pw = getpwnam(s->conf_run_as))) {
-        log_err("Unknown username: %s", s->conf_run_as);
-        return E_OS_ERROR;
-    }
-    s->pwent = *pw;
-
-    return E_SUCCESS;
-}
-
-static int session_pam_setup(struct session* s)
-{
-    static struct pam_conv conv = {
-        misc_conv,
-        NULL
-    };
-
-    char *cp;
-    const char *tty = 0;    /* Name of tty SU is run from        */
-
-    /* We only run if we are root */
-    if (getuid() != 0)
-    {
-        log_err("can only be run by root");
-        return E_NOPERM;
-    }
-
-    /*
-     * Get the tty name. Entries will be logged indicating that the user
-     * tried to change to the named new user from the current terminal.
-     */
-    if (isatty (0) && (cp = ttyname (0))) {
-        if (strncmp (cp, "/dev/", 5) == 0)
-            tty = cp + 5;
-        else
-            tty = cp;
-    } else {
-        tty = "???";
-    }
-
-    s->pam_status = pam_start("nodm", s->pwent.pw_name, &conv, &s->pamh);
-    if (s->pam_status != PAM_SUCCESS) {
-        log_err("pam_start: error %d", s->pam_status);
-        return E_PAM_ERROR;
-    }
-
-    s->pam_status = pam_set_item(s->pamh, PAM_TTY, (const void *) tty);
-    if (s->pam_status == PAM_SUCCESS)
-        s->pam_status = pam_set_item(s->pamh, PAM_RUSER, (const void *) "root");
-    if (s->pam_status != PAM_SUCCESS) {
-        log_err("pam_set_item: %s", pam_strerror(s->pamh, s->pam_status));
-        return E_PAM_ERROR;
-    }
-
-    signal (SIGINT, SIG_IGN);
-    signal (SIGQUIT, SIG_IGN);
-
-    /* FIXME: should we ignore this, or honour it?
-     * this can fail if the current user's account is invalid. "This
-     * includes checking for password and account expiration, as well as
-     * verifying access hour restrictions."
-     */
-    s->pam_status = pam_acct_mgmt(s->pamh, 0);
-    if (s->pam_status != PAM_SUCCESS)
-        log_warn("%s (Ignored)", pam_strerror(s->pamh, s->pam_status));
-
-    signal (SIGINT, SIG_DFL);
-    signal (SIGQUIT, SIG_DFL);
-
-    /* save SU information */
-    log_info("Successful su on %s for %s by %s", tty, s->pwent.pw_name, "root");
-
-    /* set primary group id and supplementary groups */
-    if (setup_groups(&(s->pwent))) {
-        s->pam_status = PAM_ABORT;
-        return E_OS_ERROR;
-    }
-
-    /*
-     * pam_setcred() may do things like resource limits, console groups,
-     * and much more, depending on the configured modules
-     */
-    s->pam_status = pam_setcred(s->pamh, PAM_ESTABLISH_CRED);
-    if (s->pam_status != PAM_SUCCESS) {
-        log_err("pam_setcred: %s", pam_strerror(s->pamh, s->pam_status));
-        return E_PAM_ERROR;
-    }
-
-    s->pam_status = pam_open_session(s->pamh, 0);
-    if (s->pam_status != PAM_SUCCESS) {
-        log_err("pam_open_session: %s", pam_strerror(s->pamh, s->pam_status));
-        pam_setcred(s->pamh, PAM_DELETE_CRED);
-        return E_PAM_ERROR;
-    }
-
-    /* update environment with all pam set variables */
-    char **envcp = pam_getenvlist(s->pamh);
-    if (envcp) {
-        while (*envcp) {
-            putenv(*envcp);
-            envcp++;
-        }
-    }
-
-    /* become the new user */
-    if (change_uid(&(s->pwent)) != 0) {
-        pam_close_session(s->pamh, 0);
-        pam_setcred(s->pamh, PAM_DELETE_CRED);
-        s->pam_status = PAM_ABORT;
-        return E_OS_ERROR;
-    }
-
-    return E_SUCCESS;
-}
-
-void session_pam_shutdown(struct session* s)
-{
-    if (s->pam_status == PAM_SUCCESS)
-    {
-        s->pam_status = pam_close_session(s->pamh, 0);
-        if (s->pam_status != PAM_SUCCESS)
-            log_err("pam_close_session: %s", pam_strerror(s->pamh, s->pam_status));
-    }
-
-    pam_end(s->pamh, s->pam_status);
-    s->pamh = 0;
-}
-
-#define bounded_strcpy(dst, src) (snprintf(dst, sizeof(dst), "%s", (src)) < sizeof(dst))
-
-void nodm_session_init(struct session* s)
-{
-    server_init(&(s->srv));
-    s->pamh = NULL;
-    s->pam_status = PAM_SUCCESS;
-    s->srv_split_args = NULL;
-
-    s->conf_use_pam = true;
-    s->conf_cleanup_xse = true;
-
-    // Get the user we should run the session for
-    if (!bounded_strcpy(s->conf_run_as, getenv_with_default("NODM_USER", "root")))
-        log_warn("username has been truncated");
-
-    if (!bounded_strcpy(s->conf_session_command, getenv_with_default("NODM_XSESSION", "/etc/X11/Xsession")))
-        log_warn("session command has been truncated");
-}
-
-void nodm_session_cleanup(struct session* s)
-{
-    // End pam session if used
-    if (s->pamh)
-        session_pam_shutdown(s);
-
-    // Deallocate parsed arguments, if used
-    if (s->srv_split_args)
-    {
-        wordexp_t* we = (wordexp_t*)s->srv_split_args;
-        wordfree(we);
-        free(we);
-        s->srv_split_args = NULL;
-    }
-}
-
-/*
- * Start the session, with proper autologin and pam handling
- */
-int nodm_session(struct session* s)
-{
-    const char* args[5];
-    int res;
-
-    if ((res = session_user_setup(s)))
-        return res;
-
-    if (s->conf_use_pam && ((res = session_pam_setup(s))))
-        return res;
-
-    setenv ("HOME", s->pwent.pw_dir, 1);
-    setenv ("USER", s->pwent.pw_name, 1);
-    setenv ("USERNAME", s->pwent.pw_name, 1);
-    setenv ("LOGNAME", s->pwent.pw_name, 1);
-    setenv ("PWD", s->pwent.pw_dir, 1);
-    setenv ("SHELL", s->pwent.pw_shell, 1);
-    setenv ("DISPLAY", s->srv.name, 1);
-    setenv ("WINDOWPATH", s->srv.windowpath, 1);
-
-    // Variables that gdm sets but we do not:
-    //
-    // This is something that we should see how to handle.
-    // What I know so far is:
-    //  - It should point to ~/.Xauthority, which should exist.
-    //  - 'xauth generate' should be able to create it if missing, but I
-    //    have not tested it yet.
-    // g_setenv ("XAUTHORITY", d->userauth, TRUE);
-    //
-    // This is 'gnome', 'kde' and so on, and should probably be set by the
-    // X session script:
-    // g_setenv ("DESKTOP_SESSION", session, TRUE);
-    //
-    // This looks gdm specific:
-    // g_setenv ("GDMSESSION", session, TRUE);
-
-    // Variables that gdm sets but we delegate other tools to set:
-    //
-    // This is set by the pam_getenvlist loop above
-    // g_setenv ("XDG_SESSION_COOKIE", ck_session_cookie, TRUE);
-    //
-    // This is set by "sh -l" from /etc/profile
-    // if (pwent->pw_uid == 0)
-    //   g_setenv ("PATH", gdm_daemon_config_get_value_string (GDM_KEY_ROOT_PATH), TRUE);
-    // else
-    //   g_setenv ("PATH", gdm_daemon_config_get_value_string (GDM_KEY_PATH), TRUE);
-    //
-
-    /* Clear the NODM_* environment variables */
-    unsetenv("NODM_USER");
-    unsetenv("NODM_XINIT");
-    unsetenv("NODM_XSESSION");
-    unsetenv("NODM_X_OPTIONS");
-    unsetenv("NODM_MIN_SESSION_TIME");
-    unsetenv("NODM_RUN_SESSION");
-
-    if (chdir(s->pwent.pw_dir) == 0)
-        /* Truncate ~/.xsession-errors */
-        cleanup_xse(0, s->pwent.pw_dir);
-
-    args[0] = "/bin/sh";
-    args[1] = "-l";
-    args[2] = "-c";
-    args[3] = s->conf_session_command;
-    args[4] = NULL;
-
-    int status;
-    if ((res = run_shell(args, &status)))
-        return res;
-
-    nodm_session_cleanup(s);
-
-    return status;
-}
-
-
-/* Signal handler for parent process later */
-static int caught = 0;
-static void catch_signals (int sig)
-{
-    ++caught;
-}
-
-/* This I ripped out of su.c from sh-utils after the Mandrake pam patch
- * have been applied.  Some work was needed to get it integrated into
- * su.c from shadow.
- */
-static int run_shell (const char** args, int* status)
-{
-    int child;
-    sigset_t ourset;
-    struct sigaction action;
-
-    child = fork ();
-    if (child == 0) {   /* child shell */
-        /*
-         * This is a workaround for Linux libc bug/feature (?) - the
-         * /dev/log file descriptor is open without the close-on-exec flag
-         * and used to be passed to the new shell. There is "fcntl(LogFile,
-         * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
-         * least in 5.4.33). Why?  --marekm
-         */
-        log_end();
-
-        /*
-         * PAM_DATA_SILENT is not supported by some modules, and
-         * there is no strong need to clean up the process space's
-         * memory since we will either call exec or exit.
-        pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
-         */
-        (void) execv (args[0], (char **) args);
-        exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
-    } else if (child == -1) {
-        log_err("cannot fork user shell: %m");
-        return E_OS_ERROR;
-    }
-
-    /* parent only */
-
-    /* Reset caught signal flag */
-    caught = 0;
-
-    /* Block all signals */
-    sigfillset (&ourset);
-    if (sigprocmask (SIG_BLOCK, &ourset, NULL)) {
-        log_err("sigprocmask malfunction");
-        goto killed;
-    }
-
-    /* Catch SIGTERM and SIGALRM using 'catch_signals' */
-    action.sa_handler = catch_signals;
-    sigemptyset (&action.sa_mask);
-    action.sa_flags = 0;
-    sigemptyset (&ourset);
-
-    if (sigaddset (&ourset, SIGTERM)
-#ifdef DEBUG_NODM
-        || sigaddset (&ourset, SIGINT)
-        || sigaddset (&ourset, SIGQUIT)
-#endif
-        || sigaddset (&ourset, SIGALRM)
-        || sigaction (SIGTERM, &action, NULL)
-        || sigprocmask (SIG_UNBLOCK, &ourset, NULL)
-        ) {
-        log_err("signal masking malfunction");
-        goto killed;
-    }
-
-    do {
-        int pid;
-
-        pid = waitpid (-1, status, WUNTRACED);
-
-        if (WIFSTOPPED (*status)) {
-            kill (getpid (), SIGSTOP);
-            /* once we get here, we must have resumed */
-            kill (pid, SIGCONT);
-        }
-    } while (WIFSTOPPED (*status));
-
-    /* Unblock signals */
-    sigfillset (&ourset);
-    if (sigprocmask (SIG_UNBLOCK, &ourset, NULL)) {
-        log_err("signal malfunction");
-        goto killed;
-    }
-
-    if (caught)
-        goto killed;
-
-    return E_SUCCESS;
-
-killed:
-    log_warn("session terminated, killing shell...");
-    kill (child, SIGTERM);
-    sleep (2);
-    kill (child, SIGKILL);
-    log_warn(" ...shell killed.");
-    return E_SESSION_DIED;
-}
-
-int nodm_x_with_session(struct session* s)
-{
-    int return_code = 0;
-
-    return_code = server_start(&(s->srv), 5);
-    if (return_code != E_SUCCESS)
-        goto cleanup;
-
-    return_code = server_connect(&(s->srv));
-    if (return_code != E_SUCCESS)
-        goto cleanup_server;
-
-    return_code = server_read_window_path(&(s->srv));
-    if (return_code != E_SUCCESS)
-        goto cleanup_connection;
-
-    return_code = nodm_session(s);
-
-cleanup_connection:
-    return_code = server_disconnect(&(s->srv));
-
-cleanup_server:
-    return_code = server_stop(&(s->srv));
-
-cleanup:
-    return return_code;
-}
-
-int nodm_session_parse_cmdline(struct session* s, const char* xcmdline)
-{
-    int return_code = E_SUCCESS;
-
-    // tokenize xoptions
-    wordexp_t* toks = (wordexp_t*)calloc(1, sizeof(wordexp_t));
-    switch (wordexp(xcmdline, toks, WRDE_NOCMD))
-    {
-        case 0: break;
-        case WRDE_NOSPACE:
-            return_code = E_OS_ERROR;
-            goto cleanup;
-        default:
-            toks->we_wordv = NULL;
-            return_code = E_BAD_ARG;
-            goto cleanup;
-    }
-
-    unsigned in_arg = 0;
-    unsigned argc = 0;
-    char **argv = (char**)malloc((toks->we_wordc + 3) * sizeof(char*));
-
-    // Server command
-    if (in_arg < toks->we_wordc &&
-           (toks->we_wordv[in_arg][0] == '/' || toks->we_wordv[in_arg][0] == '.'))
-        argv[argc++] = toks->we_wordv[in_arg++];
-    else
-        argv[argc++] = "/usr/bin/X";
-
-    // Server name
-    if (in_arg < toks->we_wordc &&
-           toks->we_wordv[in_arg][0] == ':' && isdigit(toks->we_wordv[in_arg][1]))
-        argv[argc++] = toks->we_wordv[in_arg++];
-    else
-        argv[argc++] = ":0";
-
-    // Copy other args
-    while (in_arg < toks->we_wordc)
-        argv[argc++] = toks->we_wordv[in_arg++];
-    argv[argc] = NULL;
-
-    s->srv.argv = argv;
-    s->srv_split_args = toks;
-    argv = NULL;
-    toks = NULL;
-
-cleanup:
-    if (toks != NULL)
-    {
-        if (toks->we_wordv)
-            wordfree(toks);
-        free(toks);
-    }
-    if (argv != NULL)
-        free(argv);
-
-    return E_SUCCESS;
-}
diff --git a/session.h b/session.h
deleted file mode 100644
index adb7606..0000000
--- a/session.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * session - nodm X session
- *
- * Copyright 2011  Enrico Zini <enrico at enricozini.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef NODM_SESSION_H
-#define NODM_SESSION_H
-
-#include "server.h"
-#include <stdbool.h>
-#include <pwd.h>
-#include <security/pam_appl.h>
-
-struct session
-{
-    /// If set to true, do a PAM-aware session
-    bool conf_use_pam;
-
-    /// If set to true, perform ~/.xsession-errors cleanup
-    bool conf_cleanup_xse;
-
-    /**
-     * Username to use for the X session.
-     *
-     * Empty string means 'do not change user'
-     */
-    char conf_run_as[128];
-
-    /// Command to run as the X session
-    char conf_session_command[1024];
-
-    /// X server information
-    struct server srv;
-
-    /// Information about the user we run the session for
-    struct passwd pwent;
-
-    /// PAM session handle (or NULL if not used)
-    pam_handle_t *pamh;
-
-    /// Return code of the last PAM function called
-    int pam_status;
-
-    /// Storage for split server arguments used by nodm_x_cmdline_split
-    void* srv_split_args;
-};
-
-/// Initialise a session structure with default values
-void nodm_session_init(struct session* s);
-
-/// Cleanup at the end of a session
-void nodm_session_cleanup(struct session* s);
-
-/**
- * nodm X session
- *
- * Perform PAM bookkeeping, init the session environment and start the X
- * session requested by the user
- */
-int nodm_session(struct session* s);
-
-/**
- * Start the X server using the given command line, change user to $NODM_USER
- * and run $NODM_XSESSION
- */
-int nodm_x_with_session(struct session* s);
-
-/**
- * Split xcmdline using wordexp shell-like expansion and set s->srv.argv.
- *
- * If the first token starts with '/' or '.', it is used as the X server, else
- * "X" is used as the server.
- *
- * If the second token (or the first if the first was not recognised as a path
- * to the X server) looks like ":<NUMBER>", it is used as the display name,
- * else ":0" is used.
- */
-int nodm_session_parse_cmdline(struct session* s, const char* xcmdline);
-
-#endif
diff --git a/test-internals.c b/test-internals.c
index c16b872..ebb3ff0 100644
--- a/test-internals.c
+++ b/test-internals.c
@@ -20,9 +20,10 @@
 
 #include "log.h"
 #include "common.h"
-#include "session.h"
+#include "dm.h"
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 void ensure_equals(const char* a, const char* b)
 {
@@ -51,67 +52,67 @@ int main(int argc, char* argv[])
     unsetenv("FOO");
     ensure_equals(getenv_with_default("FOO", "bar"), "bar");
 
-    struct session s;
+    struct nodm_display_manager s;
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, "");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, "");
     ensure_equals(s.srv.argv[0], "/usr/bin/X");
     ensure_equals(s.srv.argv[1], ":0");
     ensure_equals(s.srv.argv[2], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, "foo");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, "foo");
     ensure_equals(s.srv.argv[0], "/usr/bin/X");
     ensure_equals(s.srv.argv[1], ":0");
     ensure_equals(s.srv.argv[2], "foo");
     ensure_equals(s.srv.argv[3], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, "/usr/bin/Xnest");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, "/usr/bin/Xnest");
     ensure_equals(s.srv.argv[0], "/usr/bin/Xnest");
     ensure_equals(s.srv.argv[1], ":0");
     ensure_equals(s.srv.argv[2], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, ":1");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, ":1");
     ensure_equals(s.srv.argv[0], "/usr/bin/X");
     ensure_equals(s.srv.argv[1], ":1");
     ensure_equals(s.srv.argv[2], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, "/usr/bin/Xnest :1");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, "/usr/bin/Xnest :1");
     ensure_equals(s.srv.argv[0], "/usr/bin/Xnest");
     ensure_equals(s.srv.argv[1], ":1");
     ensure_equals(s.srv.argv[2], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, "/usr/bin/Xnest foo");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, "/usr/bin/Xnest foo");
     ensure_equals(s.srv.argv[0], "/usr/bin/Xnest");
     ensure_equals(s.srv.argv[1], ":0");
     ensure_equals(s.srv.argv[2], "foo");
     ensure_equals(s.srv.argv[3], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, ":1 foo");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, ":1 foo");
     ensure_equals(s.srv.argv[0], "/usr/bin/X");
     ensure_equals(s.srv.argv[1], ":1");
     ensure_equals(s.srv.argv[2], "foo");
     ensure_equals(s.srv.argv[3], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
-    nodm_session_init(&s);
-    nodm_session_parse_cmdline(&s, "/usr/bin/Xnest :1 foo");
+    nodm_display_manager_init(&s);
+    nodm_display_manager_parse_xcmdline(&s, "/usr/bin/Xnest :1 foo");
     ensure_equals(s.srv.argv[0], "/usr/bin/Xnest");
     ensure_equals(s.srv.argv[1], ":1");
     ensure_equals(s.srv.argv[2], "foo");
     ensure_equals(s.srv.argv[3], NULL);
-    nodm_session_cleanup(&s);
+    nodm_display_manager_cleanup(&s);
 
     log_end();
     return 0;
diff --git a/test-xsession.c b/test-xsession.c
index c5f59c5..7321d3f 100644
--- a/test-xsession.c
+++ b/test-xsession.c
@@ -20,8 +20,9 @@
 
 #include "log.h"
 #include "common.h"
-#include "session.h"
+#include "xsession.h"
 #include <stdio.h>
+#include <stdlib.h>
 
 int main(int argc, char* argv[])
 {
@@ -33,20 +34,20 @@ int main(int argc, char* argv[])
     };
     log_start(&cfg);
 
-    const char* xcmdline = "/usr/bin/Xnest :1";
-    setenv("NODM_SESSION", "/bin/true");
-    setenv("NODM_USER", getenv_with_default("USER", "root"));
+    //const char* xcmdline = "/usr/bin/Xnest :1";
+    setenv("NODM_SESSION", "/bin/true", 1);
+    setenv("NODM_USER", getenv_with_default("USER", "root"), 1);
 
-    struct session s;
-    nodm_session_init(&s);
+    struct nodm_xsession s;
+    nodm_xsession_init(&s);
 
     int res = E_SUCCESS;
 
-    res = nodm_session_parse_cmdline(&s, xcmdline);
-    if (res != E_SUCCESS) goto cleanup;
+    //res = nodm_session_parse_cmdline(&s, xcmdline);
+    //if (res != E_SUCCESS) goto cleanup;
 
-    res = nodm_x_with_session(&s);
-    fprintf(stderr, "nodm_x_with_session_cmdline returned %d\n", res);
+    //res = nodm_x_with_session(&s);
+    //fprintf(stderr, "nodm_x_with_session_cmdline returned %d\n", res);
     if (res != E_SUCCESS) goto cleanup;
 
 cleanup:
diff --git a/test-xstart.c b/test-xstart.c
index 7d2de06..0d5d213 100644
--- a/test-xstart.c
+++ b/test-xstart.c
@@ -19,7 +19,7 @@
  */
 
 #include "log.h"
-#include "server.h"
+#include "xserver.h"
 #include "common.h"
 #include <stdio.h>
 
@@ -33,45 +33,24 @@ int main(int argc, char* argv[])
     };
     log_start(&cfg);
 
-    struct server srv;
-    server_init(&srv);
+    struct nodm_xserver srv;
+    nodm_xserver_init(&srv);
 
     const char* server_argv[] = { "/usr/bin/Xnest", ":1", NULL };
     srv.argv = server_argv;
     srv.name = ":1";
 
-    int res = server_start(&srv, 5);
+    int res = nodm_xserver_start(&srv);
     if (res != E_SUCCESS)
     {
-        fprintf(stderr, "server_start return code: %d\n", res);
+        fprintf(stderr, "nodm_xserver_start return code: %d\n", res);
         return 1;
     }
 
-    res = server_connect(&srv);
+    res = nodm_xserver_stop(&srv);
     if (res != E_SUCCESS)
     {
-        fprintf(stderr, "server_connect return code: %d\n", res);
-        return 2;
-    }
-
-    res = server_read_window_path(&srv);
-    if (res != E_SUCCESS)
-    {
-        fprintf(stderr, "read_window_path return code: %d\n", res);
-        return 3;
-    }
-
-    res = server_disconnect(&srv);
-    if (res != E_SUCCESS)
-    {
-        fprintf(stderr, "server_disconnect return code: %d\n", res);
-        return 4;
-    }
-
-    res = server_stop(&srv);
-    if (res != E_SUCCESS)
-    {
-        fprintf(stderr, "server_stop return code: %d\n", res);
+        fprintf(stderr, "nodm_xserver_stop return code: %d\n", res);
         return 4;
     }
 
diff --git a/server.c b/xserver.c
similarity index 80%
rename from server.c
rename to xserver.c
index 8644d0b..e4752f9 100644
--- a/server.c
+++ b/xserver.c
@@ -1,5 +1,5 @@
 /*
- * server - X server startup functions
+ * xserver - X server startup functions
  *
  * Copyright 2011  Enrico Zini <enrico at enricozini.org>
  *
@@ -19,7 +19,7 @@
  */
 
 /*
- * server_read_window_path is taken from xdm's dm.c which is:
+ * nodm_xserver_read_window_path is taken from xdm's dm.c which is:
  *
  * Copyright 1988, 1998  The Open Group
  *
@@ -47,7 +47,8 @@
  */
 
 
-#include "server.h"
+#define _GNU_SOURCE
+#include "xserver.h"
 #include "common.h"
 #include "log.h"
 #include <signal.h>
@@ -60,6 +61,7 @@
 #include <X11/Xfuncproto.h>
 #include <X11/Xatom.h>
 #include <stdint.h>
+#include <stdio.h>
 
 
 // Signal handlers
@@ -67,8 +69,9 @@ static bool server_started = false;
 static void on_sigusr1(int sig) { server_started = true; }
 static void on_sigchld(int sig) {}
 
-void server_init(struct server* srv)
+void nodm_xserver_init(struct nodm_xserver* srv)
 {
+    srv->conf_timeout = 5;
     srv->argv = 0;
     srv->name = 0;
     srv->pid = -1;
@@ -76,7 +79,19 @@ void server_init(struct server* srv)
     srv->windowpath = NULL;
 }
 
-int server_start(struct server* srv, unsigned timeout_sec)
+/**
+ * Start the X server and wait until it's ready to accept connections.
+ *
+ * @param srv
+ *   The struct nodm_xserver with X server information. argv and name are expected to
+ *   be filled, pid is filled.
+ * @param timeout_sec
+ *   Timeout in seconds after which if the X server is not ready, we give up
+ *   and return an error.
+ * @return
+ *   Exit status as described by the E_* constants
+ */
+static int xserver_start(struct nodm_xserver* srv, unsigned timeout_sec)
 {
     // Function return code
     int return_code = E_SUCCESS;
@@ -119,7 +134,7 @@ int server_start(struct server* srv, unsigned timeout_sec)
         // when ready
         signal(SIGUSR1, SIG_IGN);
 
-        execv(srv->argv[0], srv->argv);
+        execv(srv->argv[0], (char *const*)srv->argv);
         log_err("cannot start %s: %m", srv->argv[0]);
         exit(errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
     } else if (child == -1) {
@@ -206,10 +221,14 @@ cleanup:
     return return_code;
 }
 
-int server_stop(struct server* srv)
+/// Kill the X server
+static int xserver_stop(struct nodm_xserver* srv)
 {
     kill(srv->pid, SIGTERM);
     kill(srv->pid, SIGCONT);
+    // TODO: wait
+    srv->pid = -1;
+    unsetenv("DISPLAY");
     return E_SUCCESS;
 }
 
@@ -220,7 +239,15 @@ static int xopendisplay_error_handler(Display* dpy)
     exit(E_XLIB_ERROR);
 }
 
-int server_connect(struct server* srv)
+/**
+ * Connect to the X server
+ *
+ * Uses srv->name, sets srv->dpy.
+ *
+ * @return
+ *   Exit status as described by the E_* constants
+ */
+static int xserver_connect(struct nodm_xserver* srv)
 {
     XSetIOErrorHandler(xopendisplay_error_handler);
     srv->dpy = XOpenDisplay(srv->name);
@@ -232,7 +259,15 @@ int server_connect(struct server* srv)
     return srv->dpy == NULL ? E_X_SERVER_CONNECT : E_SUCCESS;
 }
 
-int server_disconnect(struct server* srv)
+/**
+ * Close connection to the X server
+ *
+ * Uses srv->dpy, sets it to NULL.
+ *
+ * @return
+ *   Exit status as described by the E_* constants
+ */
+static int xserver_disconnect(struct nodm_xserver* srv)
 {
     // TODO: get/check pending errors (how?)
     XCloseDisplay(srv->dpy);
@@ -240,7 +275,15 @@ int server_disconnect(struct server* srv)
     return E_SUCCESS;
 }
 
-int server_read_window_path(struct server* srv)
+/**
+ * Get the WINDOWPATH value for the server
+ *
+ * Uses srv->dpy, sets srv->windowpath
+ *
+ * @return
+ *   Exit status as described by the E_* constants
+ */
+static int xserver_read_window_path(struct nodm_xserver* srv)
 {
     /* setting WINDOWPATH for clients */
     Atom prop;
@@ -313,3 +356,39 @@ int server_read_window_path(struct server* srv)
 
     return E_SUCCESS;
 }
+
+int nodm_xserver_start(struct nodm_xserver* srv)
+{
+    int return_code = E_SUCCESS;
+
+    return_code = xserver_start(srv, srv->conf_timeout);
+    if (return_code != E_SUCCESS)
+        goto cleanup;
+
+    return_code = xserver_connect(srv);
+    if (return_code != E_SUCCESS)
+        goto cleanup;
+
+    return_code = xserver_read_window_path(srv);
+    if (return_code != E_SUCCESS)
+        goto cleanup;
+
+cleanup:
+    if (return_code != E_SUCCESS)
+        nodm_xserver_stop(srv);
+    return return_code;
+}
+
+int nodm_xserver_stop(struct nodm_xserver* srv)
+{
+    int res1 = E_SUCCESS, res2 = E_SUCCESS;
+
+    if (srv->dpy != NULL)
+        res1 = xserver_disconnect(srv);
+    if (srv->pid != -1)
+        res2 = xserver_stop(srv);
+
+    if (res1 != E_SUCCESS) return res1;
+    return res2;
+}
+
diff --git a/xserver.h b/xserver.h
new file mode 100644
index 0000000..aae2975
--- /dev/null
+++ b/xserver.h
@@ -0,0 +1,56 @@
+/*
+ * xserver - X server startup functions
+ *
+ * Copyright 2011  Enrico Zini <enrico at enricozini.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef NODM_SERVER_H
+#define NODM_SERVER_H
+
+#include <sys/types.h>
+#include <X11/Xlib.h>
+
+/// Supervise an X server
+struct nodm_xserver
+{
+    /// Timeout (in seconds) to use waiting for X to start
+    int conf_timeout;
+
+    /// X server command line
+    const char **argv;
+    /// X display name
+    const char *name;
+    /// X window path (dynamically allocated and owned by this structure)
+    char *windowpath;
+    /// X server pid
+    pid_t pid;
+    /// xlib Display connected to the server
+    Display *dpy;
+};
+
+/**
+ * Initialise a struct nodm_xserver with NULL values
+ */
+void nodm_xserver_init(struct nodm_xserver* srv);
+
+/// Start the X server
+int nodm_xserver_start(struct nodm_xserver* srv);
+
+/// Stop the X server
+int nodm_xserver_stop(struct nodm_xserver* srv);
+
+#endif
diff --git a/xsession-child.c b/xsession-child.c
new file mode 100644
index 0000000..2a152d9
--- /dev/null
+++ b/xsession-child.c
@@ -0,0 +1,334 @@
+/*
+ * xsession-child - child side of X session
+ *
+ * Copyright 2011  Enrico Zini <enrico at enricozini.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "xsession-child.h"
+#include "common.h"
+#include "log.h"
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <grp.h>
+#include <signal.h>
+#include <errno.h>
+
+/* compatibility with different versions of Linux-PAM */
+#ifndef PAM_ESTABLISH_CRED
+#define PAM_ESTABLISH_CRED PAM_CRED_ESTABLISH
+#endif
+#ifndef PAM_DELETE_CRED
+#define PAM_DELETE_CRED PAM_CRED_DELETE
+#endif
+
+/*
+ * setup_uid_gid() split in two functions for PAM support -
+ * pam_setcred() needs to be called after initgroups(), but
+ * before setuid().
+ */
+static int setup_groups(const struct passwd *info)
+{
+    /*
+     * Set the real group ID to the primary group ID in the password
+     * file.
+     */
+    if (setgid (info->pw_gid) == -1) {
+        log_err("bad group ID `%d' for user `%s': %m\n", info->pw_gid, info->pw_name);
+        return E_OS_ERROR;
+    }
+
+    /*
+     * For systems which support multiple concurrent groups, go get
+     * the group set from the /etc/group file.
+     */
+    if (initgroups (info->pw_name, info->pw_gid) == -1) {
+        log_err("initgroups failed for user `%s': %m\n", info->pw_name);
+        return E_OS_ERROR;
+    }
+    return E_SUCCESS;
+}
+
+static int change_uid (const struct passwd *info)
+{
+    /*
+     * Set the real UID to the UID value in the password file.
+     */
+    if (setuid(info->pw_uid)) {
+        log_err("bad user ID `%d' for user `%s': %m\n", (int)info->pw_uid, info->pw_name);
+        return E_OS_ERROR;
+    }
+    return E_SUCCESS;
+}
+
+static int setup_pam(struct nodm_xsession_child* s)
+{
+    static struct pam_conv conv = {
+        misc_conv,
+        NULL
+    };
+
+    char *cp;
+    const char *tty = 0;    // Name of tty SU is run from
+
+    /* We only run if we are root */
+    if (getuid() != 0)
+    {
+        log_err("can only be run by root");
+        return E_NOPERM;
+    }
+
+    // Set up the nodm_xsession_child structure
+    s->pamh = NULL;
+    s->pam_status = PAM_SUCCESS;
+
+    /*
+     * Get the tty name. Entries will be logged indicating that the user
+     * tried to change to the named new user from the current terminal.
+     */
+    if (isatty (0) && (cp = ttyname (0))) {
+        if (strncmp (cp, "/dev/", 5) == 0)
+            tty = cp + 5;
+        else
+            tty = cp;
+    } else {
+        tty = "???";
+    }
+
+    s->pam_status = pam_start("nodm", s->pwent.pw_name, &conv, &s->pamh);
+    if (s->pam_status != PAM_SUCCESS) {
+        log_err("pam_start: error %d", s->pam_status);
+        return E_PAM_ERROR;
+    }
+
+    s->pam_status = pam_set_item(s->pamh, PAM_TTY, (const void *) tty);
+    if (s->pam_status == PAM_SUCCESS)
+        s->pam_status = pam_set_item(s->pamh, PAM_RUSER, (const void *) "root");
+    if (s->pam_status != PAM_SUCCESS) {
+        log_err("pam_set_item: %s", pam_strerror(s->pamh, s->pam_status));
+        return E_PAM_ERROR;
+    }
+
+    signal (SIGINT, SIG_IGN);
+    signal (SIGQUIT, SIG_IGN);
+
+    /* FIXME: should we ignore this, or honour it?
+     * this can fail if the current user's account is invalid. "This
+     * includes checking for password and account expiration, as well as
+     * verifying access hour restrictions."
+     */
+    s->pam_status = pam_acct_mgmt(s->pamh, 0);
+    if (s->pam_status != PAM_SUCCESS)
+        log_warn("%s (Ignored)", pam_strerror(s->pamh, s->pam_status));
+
+    signal (SIGINT, SIG_DFL);
+    signal (SIGQUIT, SIG_DFL);
+
+    /* save SU information */
+    log_info("Successful su on %s for %s by %s", tty, s->pwent.pw_name, "root");
+
+    /* set primary group id and supplementary groups */
+    if (setup_groups(&(s->pwent))) {
+        s->pam_status = PAM_ABORT;
+        return E_OS_ERROR;
+    }
+
+    /*
+     * pam_setcred() may do things like resource limits, console groups,
+     * and much more, depending on the configured modules
+     */
+    s->pam_status = pam_setcred(s->pamh, PAM_ESTABLISH_CRED);
+    if (s->pam_status != PAM_SUCCESS) {
+        log_err("pam_setcred: %s", pam_strerror(s->pamh, s->pam_status));
+        return E_PAM_ERROR;
+    }
+
+    s->pam_status = pam_open_session(s->pamh, 0);
+    if (s->pam_status != PAM_SUCCESS) {
+        log_err("pam_open_session: %s", pam_strerror(s->pamh, s->pam_status));
+        pam_setcred(s->pamh, PAM_DELETE_CRED);
+        return E_PAM_ERROR;
+    }
+
+    /* update environment with all pam set variables */
+    char **envcp = pam_getenvlist(s->pamh);
+    if (envcp) {
+        while (*envcp) {
+            putenv(*envcp);
+            envcp++;
+        }
+    }
+
+    /* become the new user */
+    if (change_uid(&(s->pwent)) != 0) {
+        pam_close_session(s->pamh, 0);
+        pam_setcred(s->pamh, PAM_DELETE_CRED);
+        s->pam_status = PAM_ABORT;
+        return E_OS_ERROR;
+    }
+
+    return E_SUCCESS;
+}
+
+static void shutdown_pam(struct nodm_xsession_child* s)
+{
+    if (s->pam_status == PAM_SUCCESS)
+    {
+        s->pam_status = pam_close_session(s->pamh, 0);
+        if (s->pam_status != PAM_SUCCESS)
+            log_err("pam_close_session: %s", pam_strerror(s->pamh, s->pam_status));
+    }
+
+    pam_end(s->pamh, s->pam_status);
+    s->pamh = 0;
+}
+
+int nodm_xsession_child(struct nodm_xsession_child* s)
+{
+    /*
+     * This is a workaround for Linux libc bug/feature (?) - the
+     * /dev/log file descriptor is open without the close-on-exec flag
+     * and used to be passed to the new shell. There is "fcntl(LogFile,
+     * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
+     * least in 5.4.33). Why?  --marekm
+     */
+    log_end();
+
+    /*
+     * PAM_DATA_SILENT is not supported by some modules, and
+     * there is no strong need to clean up the process space's
+     * memory since we will either call exec or exit.
+    pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+     */
+    (void)execv(s->argv[0], (char **)s->argv);
+    exit(errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+}
+
+/* Signal handler for parent process later */
+static int caught = 0;
+static void catch_signals (int sig)
+{
+    ++caught;
+}
+
+/* This I ripped out of su.c from sh-utils after the Mandrake pam patch
+ * have been applied.  Some work was needed to get it integrated into
+ * su.c from shadow.
+ */
+int nodm_xsession_child_pam(struct nodm_xsession_child* s)
+{
+    int child;
+    sigset_t ourset;
+    struct sigaction action;
+
+    int res = setup_pam(s);
+    if (res != E_SUCCESS)
+        return res;
+
+    child = fork ();
+    if (child == 0) {   /* child shell */
+        /*
+         * This is a workaround for Linux libc bug/feature (?) - the
+         * /dev/log file descriptor is open without the close-on-exec flag
+         * and used to be passed to the new shell. There is "fcntl(LogFile,
+         * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
+         * least in 5.4.33). Why?  --marekm
+         */
+        log_end();
+
+        /*
+         * PAM_DATA_SILENT is not supported by some modules, and
+         * there is no strong need to clean up the process space's
+         * memory since we will either call exec or exit.
+        pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+         */
+        (void) execv(s->argv[0], (char **)s->argv);
+        exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
+    } else if (child == -1) {
+        log_err("cannot fork user shell: %m");
+        return E_OS_ERROR;
+    }
+
+    /* parent only */
+
+    /* Reset caught signal flag */
+    caught = 0;
+
+    /* Block all signals */
+    sigfillset (&ourset);
+    if (sigprocmask (SIG_BLOCK, &ourset, NULL)) {
+        log_err("sigprocmask malfunction");
+        goto killed;
+    }
+
+    /* Catch SIGTERM and SIGALRM using 'catch_signals' */
+    action.sa_handler = catch_signals;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = 0;
+    sigemptyset (&ourset);
+
+    if (sigaddset (&ourset, SIGTERM)
+#ifdef DEBUG_NODM
+        || sigaddset (&ourset, SIGINT)
+        || sigaddset (&ourset, SIGQUIT)
+#endif
+        || sigaddset (&ourset, SIGALRM)
+        || sigaction (SIGTERM, &action, NULL)
+        || sigprocmask (SIG_UNBLOCK, &ourset, NULL)
+        ) {
+        log_err("signal masking malfunction");
+        goto killed;
+    }
+
+    do {
+        int pid;
+
+        pid = waitpid (-1, &(s->exit_status), WUNTRACED);
+
+        if (WIFSTOPPED (s->exit_status)) {
+            kill (getpid (), SIGSTOP);
+            /* once we get here, we must have resumed */
+            kill (pid, SIGCONT);
+        }
+    } while (WIFSTOPPED (s->exit_status));
+
+    /* Unblock signals */
+    sigfillset (&ourset);
+    if (sigprocmask (SIG_UNBLOCK, &ourset, NULL)) {
+        log_err("signal malfunction");
+        goto killed;
+    }
+
+    if (caught)
+        goto killed;
+
+    shutdown_pam(s);
+
+    return E_SUCCESS;
+
+killed:
+    log_warn("session terminated, killing shell...");
+    kill (child, SIGTERM);
+    sleep (2);
+    kill (child, SIGKILL);
+    log_warn(" ...shell killed.");
+
+    shutdown_pam(s);
+
+    return E_SESSION_DIED;
+}
diff --git a/common.c b/xsession-child.h
similarity index 52%
copy from common.c
copy to xsession-child.h
index a26d5fe..8a453d6 100644
--- a/common.c
+++ b/xsession-child.h
@@ -1,5 +1,5 @@
 /*
- * common - common nodm definitions and utility functions
+ * xsession-child - child side of X session
  *
  * Copyright 2011  Enrico Zini <enrico at enricozini.org>
  *
@@ -18,14 +18,34 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "common.h"
-#include <stdlib.h>
+#ifndef NODM_XSESSION_CHILD_H
+#define NODM_XSESSION_CHILD_H
 
-const char* getenv_with_default(const char* envname, const char* def)
+#include <pwd.h>
+#include <security/pam_appl.h>
+
+struct nodm_xsession_child
 {
-    const char* res = getenv(envname);
-    if (res != NULL)
-        return res;
-    else
-        return def;
-}
+    /// Information about the user we run the session for
+    struct passwd pwent;
+
+    /// PAM session handle (or NULL if not used)
+    pam_handle_t *pamh;
+
+    /// Return code of the last PAM function called
+    int pam_status;
+
+    /// Command line to run
+    const char** argv;
+
+    /// Child exit status
+    int exit_status;
+};
+
+/// Just exec the session
+int nodm_xsession_child(struct nodm_xsession_child* s);
+
+/// Run a child process inside a PAM session
+int nodm_xsession_child_pam(struct nodm_xsession_child* s);
+
+#endif
diff --git a/xsession.c b/xsession.c
new file mode 100644
index 0000000..35515a3
--- /dev/null
+++ b/xsession.c
@@ -0,0 +1,202 @@
+/*
+ * xsession - nodm X session
+ *
+ * Copyright 2011  Enrico Zini <enrico at enricozini.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "xsession.h"
+#include "xsession-child.h"
+#include "xserver.h"
+#include "log.h"
+#include "common.h"
+#include <errno.h>
+#include <signal.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * Truncate ~/.xsession-errors if it is longer than \a maxsize.
+ *
+ * The function looks for .xsession-errors in the current directory, so when it
+ * is called the current directory must be the user's homedir.
+ *
+ * The function also assumes that we are running as the user. As a consequence
+ * it does not worry about symlink attacks, because they would only be possible
+ * if the user's home directory is group or world writable.
+ *
+ * curdirname is the name of the current directory, and it is only used when
+ * logging error messages.
+ *
+ * The function returns true on success, false on failure.
+ */
+static int cleanup_xse(off_t maxsize, const char* curdirname)
+{
+    int ret = E_OS_ERROR;
+    int xse_fd = -1;
+    struct stat xse_st;
+
+    xse_fd = open(".xsession-errors", O_WRONLY | O_CREAT, 0600);
+    if (xse_fd < 0)
+    {
+        log_err("cannot open `%s/%s': %m", curdirname, ".xsession-errors");
+        goto cleanup;
+    }
+    if (fstat(xse_fd, &xse_st) < 0)
+    {
+        log_err("cannot stat `%s/%s': %m", curdirname, ".xsession-errors");
+        goto cleanup;
+    }
+    if (xse_st.st_size > maxsize)
+    {
+        if (ftruncate(xse_fd, 0) < 0)
+        {
+            log_err("cannot truncate `%s/%s': %m", curdirname, ".xsession-errors");
+            goto cleanup;
+        }
+    }
+
+    /* If we made it so far, we succeeded */
+    ret = E_SUCCESS;
+
+cleanup:
+    if (xse_fd >= 0)
+        close(xse_fd);
+    return ret;
+}
+
+int nodm_xsession_init(struct nodm_xsession* s)
+{
+    s->conf_use_pam = true;
+    s->conf_cleanup_xse = true;
+
+    // Get the user we should run the session for
+    if (!bounded_strcpy(s->conf_run_as, getenv_with_default("NODM_USER", "root")))
+        log_warn("username has been truncated");
+
+    if (!bounded_strcpy(s->conf_session_command, getenv_with_default("NODM_XSESSION", "/etc/X11/Xsession")))
+        log_warn("session command has been truncated");
+
+    s->pid = -1;
+
+    return E_SUCCESS;
+}
+
+int nodm_xsession_start(struct nodm_xsession* s, struct nodm_xserver* srv)
+{
+    struct nodm_xsession_child child;
+
+    // Validate the user using the normal system user database
+    struct passwd *pw = 0;
+    if (!(pw = getpwnam(s->conf_run_as))) {
+        log_err("Unknown username: %s", s->conf_run_as);
+        return E_OS_ERROR;
+    }
+    child.pwent = *pw;
+
+    // Create the argument list
+    const char* args[5];
+    args[0] = "/bin/sh";
+    args[1] = "-l";
+    args[2] = "-c";
+    args[3] = s->conf_session_command;
+    args[4] = NULL;
+    child.argv = args;
+
+    // Variables that gdm sets but we do not:
+    //
+    // This is something that we should see how to handle.
+    // What I know so far is:
+    //  - It should point to ~/.Xauthority, which should exist.
+    //  - 'xauth generate' should be able to create it if missing, but I
+    //    have not tested it yet.
+    // g_setenv ("XAUTHORITY", d->userauth, TRUE);
+    //
+    // This is 'gnome', 'kde' and so on, and should probably be set by the
+    // X session script:
+    // g_setenv ("DESKTOP_SESSION", session, TRUE);
+    //
+    // This looks gdm specific:
+    // g_setenv ("GDMSESSION", session, TRUE);
+
+    // Variables that gdm sets but we delegate other tools to set:
+    //
+    // This is set by the pam_getenvlist loop above
+    // g_setenv ("XDG_SESSION_COOKIE", ck_session_cookie, TRUE);
+    //
+    // This is set by "sh -l" from /etc/profile
+    // if (pwent->pw_uid == 0)
+    //   g_setenv ("PATH", gdm_daemon_config_get_value_string (GDM_KEY_ROOT_PATH), TRUE);
+    // else
+    //   g_setenv ("PATH", gdm_daemon_config_get_value_string (GDM_KEY_PATH), TRUE);
+    //
+
+    s->pid = fork();
+    if (s->pid == 0)
+    {
+        // Setup environment
+        setenv("HOME", pw->pw_dir, 1);
+        setenv("USER", pw->pw_name, 1);
+        setenv("USERNAME", pw->pw_name, 1);
+        setenv("LOGNAME", pw->pw_name, 1);
+        setenv("PWD", pw->pw_dir, 1);
+        setenv("SHELL", pw->pw_shell, 1);
+        setenv("DISPLAY", srv->name, 1);
+        setenv("WINDOWPATH", srv->windowpath, 1);
+
+
+        // Clear the NODM_* environment variables
+        unsetenv("NODM_USER");
+        unsetenv("NODM_XINIT");
+        unsetenv("NODM_XSESSION");
+        unsetenv("NODM_X_OPTIONS");
+        unsetenv("NODM_MIN_SESSION_TIME");
+        unsetenv("NODM_RUN_SESSION");
+
+        // Move to home directory
+        if (chdir(pw->pw_dir) == 0)
+        {
+            // Truncate ~/.xsession-errors
+            if (s->conf_cleanup_xse)
+                cleanup_xse(0, pw->pw_dir);
+        }
+
+        // child shell */
+        if (s->conf_use_pam)
+            exit(nodm_xsession_child_pam(&child));
+        else
+            exit(nodm_xsession_child(&child));
+    } else if (s->pid == -1) {
+        log_err("cannot fork user shell: %m");
+        return E_OS_ERROR;
+    }
+
+    return E_SUCCESS;
+}
+
+int nodm_xsession_stop(struct nodm_xsession* s)
+{
+    kill(s->pid, SIGTERM);
+    kill(s->pid, SIGCONT);
+    // TODO: wait
+    s->pid = -1;
+    return E_SUCCESS;
+}
diff --git a/xsession.h b/xsession.h
new file mode 100644
index 0000000..a960bbb
--- /dev/null
+++ b/xsession.h
@@ -0,0 +1,61 @@
+/*
+ * xsession - nodm X session
+ *
+ * Copyright 2011  Enrico Zini <enrico at enricozini.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef NODM_SESSION_H
+#define NODM_SESSION_H
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+struct nodm_xserver;
+
+/// Supervise an X session
+struct nodm_xsession
+{
+    /// Command to run as the X session
+    char conf_session_command[1024];
+
+    /**
+     * Username to use for the X session.
+     *
+     * Empty string means 'do not change user'
+     */
+    char conf_run_as[128];
+
+    /// If true, wrap the session in a PAM session
+    bool conf_use_pam;
+
+    /// If set to true, perform ~/.xsession-errors cleanup
+    bool conf_cleanup_xse;
+
+    /// X session pid
+    pid_t pid;
+};
+
+/// Initialise a struct nodm_session with default values
+int nodm_xsession_init(struct nodm_xsession* s);
+
+/// Start the X session
+int nodm_xsession_start(struct nodm_xsession* s, struct nodm_xserver* srv);
+
+/// Stop the X session
+int nodm_xsession_stop(struct nodm_xsession* s);
+
+#endif

-- 
Automatic Display Manager



More information about the pkg-fso-commits mailing list