[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