Blob Blame History Raw
From 35a4d47385d043cf4df62c2723508e4edce4dfb4 Mon Sep 17 00:00:00 2001
From: Xiaoguang Wang <xwang@suse.com>
Date: Thu, 16 May 2019 13:26:16 +0800
Subject: [PATCH 2/7] session-worker: kill user sessions when stopping gdm
 service

At the moment the session worker exits as soon as it gets SIGTERM.
That means it may fail to stop the user session (which only happens
in the orderly shutdown path).

This commit sets up a SIGTERM handler that integrates with and
quits the main loop after the session is started.

It still retains the _exit-on-SIGTERM behavior before the session
is started, to ensure a stuck pam module doesn't prevent the
process from dying.

Some small changes to commit by Ray Strode.

Closes #400
---
 daemon/gdm-session-worker.c  | 38 +++++++++++++++++++++++++++---------
 daemon/session-worker-main.c | 33 +++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+), 9 deletions(-)

diff --git a/daemon/gdm-session-worker.c b/daemon/gdm-session-worker.c
index d897779f3..e526fa5db 100644
--- a/daemon/gdm-session-worker.c
+++ b/daemon/gdm-session-worker.c
@@ -159,60 +159,61 @@ struct GdmSessionWorkerPrivate
         guint32           display_is_initial : 1;
         guint             state_change_idle_id;
         GdmSessionDisplayMode display_mode;
 
         char                 *server_address;
         GDBusConnection      *connection;
         GdmDBusWorkerManager *manager;
 
         GHashTable         *reauthentication_requests;
 
         GdmSessionAuditor  *auditor;
         GdmSessionSettings *user_settings;
 
         GDBusMethodInvocation *pending_invocation;
 };
 
 #ifdef SUPPORTS_PAM_EXTENSIONS
 static char gdm_pam_extension_environment_block[_POSIX_ARG_MAX];
 
 static const char * const
 gdm_supported_pam_extensions[] = {
         GDM_PAM_EXTENSION_CHOICE_LIST,
         NULL
 };
 #endif
 
 enum {
         PROP_0,
         PROP_SERVER_ADDRESS,
         PROP_IS_REAUTH_SESSION,
+        PROP_STATE,
 };
 
 static void     gdm_session_worker_class_init   (GdmSessionWorkerClass *klass);
 static void     gdm_session_worker_init         (GdmSessionWorker      *session_worker);
 static void     gdm_session_worker_finalize     (GObject               *object);
 
 static void     gdm_session_worker_set_environment_variable (GdmSessionWorker *worker,
                                                              const char       *key,
                                                              const char       *value);
 
 static void     queue_state_change              (GdmSessionWorker      *worker);
 
 static void     worker_interface_init           (GdmDBusWorkerIface *iface);
 
 
 typedef int (* GdmSessionWorkerPamNewMessagesFunc) (int,
                                                     const struct pam_message **,
                                                     struct pam_response **,
                                                     gpointer);
 
 G_DEFINE_TYPE_WITH_CODE (GdmSessionWorker,
                          gdm_session_worker,
                          GDM_DBUS_TYPE_WORKER_SKELETON,
                          G_IMPLEMENT_INTERFACE (GDM_DBUS_TYPE_WORKER,
                                                 worker_interface_init))
 
 /* adapted from glib script_execute */
 static void
 script_execute (const gchar *file,
                 char       **argv,
@@ -966,100 +967,111 @@ jump_to_vt (GdmSessionWorker  *worker,
 
                 g_debug ("GdmSessionWorker: first setting graphics mode to prevent flicker");
                 if (ioctl (fd, KDSETMODE, KD_GRAPHICS) < 0) {
                         g_debug ("GdmSessionWorker: couldn't set graphics mode: %m");
                 }
 
                 /* It's possible that the current VT was left in a broken
                  * combination of states (KD_GRAPHICS with VT_AUTO), that
                  * can't be switched away from.  This call makes sure things
                  * are set in a way that VT_ACTIVATE should work and
                  * VT_WAITACTIVE shouldn't hang.
                  */
                 fix_terminal_vt_mode (worker, active_vt_tty_fd);
         } else {
                 fd = active_vt_tty_fd;
         }
 
         handle_terminal_vt_switches (worker, fd);
 
         if (ioctl (fd, VT_ACTIVATE, vt_number) < 0) {
                 g_debug ("GdmSessionWorker: couldn't initiate jump to VT %d: %m",
                          vt_number);
         } else if (ioctl (fd, VT_WAITACTIVE, vt_number) < 0) {
                 g_debug ("GdmSessionWorker: couldn't finalize jump to VT %d: %m",
                          vt_number);
         }
 
         close (active_vt_tty_fd);
 }
 
+static void
+gdm_session_worker_set_state (GdmSessionWorker      *worker,
+                              GdmSessionWorkerState  state)
+{
+        if (worker->priv->state == state)
+                return;
+
+        worker->priv->state = state;
+        g_object_notify (G_OBJECT (worker), "state");
+}
+
 static void
 gdm_session_worker_uninitialize_pam (GdmSessionWorker *worker,
                                      int               status)
 {
         g_debug ("GdmSessionWorker: uninitializing PAM");
 
         if (worker->priv->pam_handle == NULL)
                 return;
 
         gdm_session_worker_get_username (worker, NULL);
 
         if (worker->priv->state >= GDM_SESSION_WORKER_STATE_SESSION_OPENED) {
                 pam_close_session (worker->priv->pam_handle, 0);
                 gdm_session_auditor_report_logout (worker->priv->auditor);
         } else {
                 gdm_session_auditor_report_login_failure (worker->priv->auditor,
                                                           status,
                                                           pam_strerror (worker->priv->pam_handle, status));
         }
 
         if (worker->priv->state >= GDM_SESSION_WORKER_STATE_ACCREDITED) {
                 pam_setcred (worker->priv->pam_handle, PAM_DELETE_CRED);
         }
 
         pam_end (worker->priv->pam_handle, status);
         worker->priv->pam_handle = NULL;
 
         gdm_session_worker_stop_auditor (worker);
 
         if (g_strcmp0 (worker->priv->display_seat_id, "seat0") == 0) {
                 if (worker->priv->login_vt != worker->priv->session_vt) {
                         jump_to_vt (worker, worker->priv->login_vt);
                 }
         }
 
         worker->priv->login_vt = 0;
         worker->priv->session_vt = 0;
 
         g_debug ("GdmSessionWorker: state NONE");
-        worker->priv->state = GDM_SESSION_WORKER_STATE_NONE;
+        gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_NONE);
 }
 
 static char *
 _get_tty_for_pam (const char *x11_display_name,
                   const char *display_device)
 {
 #ifdef __sun
         return g_strdup (display_device);
 #else
         return g_strdup (x11_display_name);
 #endif
 }
 
 #ifdef PAM_XAUTHDATA
 static struct pam_xauth_data *
 _get_xauth_for_pam (const char *x11_authority_file)
 {
         FILE                  *fh;
         Xauth                 *auth = NULL;
         struct pam_xauth_data *retval = NULL;
         gsize                  len = sizeof (*retval) + 1;
 
         fh = fopen (x11_authority_file, "r");
         if (fh) {
                 auth = XauReadAuth (fh);
                 fclose (fh);
         }
         if (auth) {
                 len += auth->name_length + auth->data_length;
                 retval = g_malloc0 (len);
@@ -1168,61 +1180,61 @@ gdm_session_worker_initialize_pam (GdmSessionWorker   *worker,
                         goto out;
                 }
         }
 
         /* set RHOST */
         if (hostname != NULL && hostname[0] != '\0') {
                 error_code = pam_set_item (worker->priv->pam_handle, PAM_RHOST, hostname);
                 g_debug ("error informing authentication system of user's hostname %s: %s",
                          hostname,
                          pam_strerror (worker->priv->pam_handle, error_code));
 
                 if (error_code != PAM_SUCCESS) {
                         g_set_error (error,
                                      GDM_SESSION_WORKER_ERROR,
                                      GDM_SESSION_WORKER_ERROR_AUTHENTICATING,
                                      "%s", "");
                         goto out;
                 }
         }
 
         /* set seat ID */
         if (seat_id != NULL && seat_id[0] != '\0') {
                 gdm_session_worker_set_environment_variable (worker, "XDG_SEAT", seat_id);
         }
 
         if (strcmp (service, "gdm-launch-environment") == 0) {
                 gdm_session_worker_set_environment_variable (worker, "XDG_SESSION_CLASS", "greeter");
         }
 
         g_debug ("GdmSessionWorker: state SETUP_COMPLETE");
-        worker->priv->state = GDM_SESSION_WORKER_STATE_SETUP_COMPLETE;
+        gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SETUP_COMPLETE);
 
         /* Temporarily set PAM_TTY with the currently active VT (login screen) 
            PAM_TTY will be reset with the users VT right before the user session is opened */
         ensure_login_vt (worker);
         g_snprintf (tty_string, 256, "/dev/tty%d", worker->priv->login_vt);
         pam_set_item (worker->priv->pam_handle, PAM_TTY, tty_string);
         if (!display_is_local)
                 worker->priv->password_is_required = TRUE;
 
  out:
         if (error_code != PAM_SUCCESS) {
                 gdm_session_worker_uninitialize_pam (worker, error_code);
                 return FALSE;
         }
 
         return TRUE;
 }
 
 static gboolean
 gdm_session_worker_authenticate_user (GdmSessionWorker *worker,
                                       gboolean          password_is_required,
                                       GError          **error)
 {
         int error_code;
         int authentication_flags;
 
         g_debug ("GdmSessionWorker: authenticating user %s", worker->priv->username);
 
         authentication_flags = 0;
 
@@ -1233,61 +1245,61 @@ gdm_session_worker_authenticate_user (GdmSessionWorker *worker,
         /* blocking call, does the actual conversation */
         error_code = pam_authenticate (worker->priv->pam_handle, authentication_flags);
 
         if (error_code == PAM_AUTHINFO_UNAVAIL) {
                 g_debug ("GdmSessionWorker: authentication service unavailable");
 
                 g_set_error (error,
                              GDM_SESSION_WORKER_ERROR,
                              GDM_SESSION_WORKER_ERROR_SERVICE_UNAVAILABLE,
                              "%s", "");
                 goto out;
         } else if (error_code != PAM_SUCCESS) {
                 g_debug ("GdmSessionWorker: authentication returned %d: %s", error_code, pam_strerror (worker->priv->pam_handle, error_code));
 
                 /*
                  * Do not display a different message for user unknown versus
                  * a failed password for a valid user.
                  */
                 if (error_code == PAM_USER_UNKNOWN) {
                         error_code = PAM_AUTH_ERR;
                 }
 
                 g_set_error (error,
                              GDM_SESSION_WORKER_ERROR,
                              GDM_SESSION_WORKER_ERROR_AUTHENTICATING,
                              "%s", get_friendly_error_message (error_code));
                 goto out;
         }
 
         g_debug ("GdmSessionWorker: state AUTHENTICATED");
-        worker->priv->state = GDM_SESSION_WORKER_STATE_AUTHENTICATED;
+        gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_AUTHENTICATED);
 
  out:
         if (error_code != PAM_SUCCESS) {
                 gdm_session_worker_uninitialize_pam (worker, error_code);
                 return FALSE;
         }
 
         return TRUE;
 }
 
 static gboolean
 gdm_session_worker_authorize_user (GdmSessionWorker *worker,
                                    gboolean          password_is_required,
                                    GError          **error)
 {
         int error_code;
         int authentication_flags;
 
         g_debug ("GdmSessionWorker: determining if authenticated user (password required:%d) is authorized to session",
                  password_is_required);
 
         authentication_flags = 0;
 
         if (password_is_required) {
                 authentication_flags |= PAM_DISALLOW_NULL_AUTHTOK;
         }
 
         /* check that the account isn't disabled or expired
          */
         error_code = pam_acct_mgmt (worker->priv->pam_handle, authentication_flags);
@@ -1298,61 +1310,61 @@ gdm_session_worker_authorize_user (GdmSessionWorker *worker,
                 g_debug ("GdmSessionWorker: authenticated user requires new auth token");
                 error_code = pam_chauthtok (worker->priv->pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK);
 
                 gdm_session_worker_get_username (worker, NULL);
 
                 if (error_code != PAM_SUCCESS) {
                         gdm_session_auditor_report_password_change_failure (worker->priv->auditor);
                 } else {
                         gdm_session_auditor_report_password_changed (worker->priv->auditor);
                 }
         }
 
         /* If the user is reauthenticating, then authorization isn't required to
          * proceed, the user is already logged in after all.
          */
         if (worker->priv->is_reauth_session) {
                 error_code = PAM_SUCCESS;
         }
 
         if (error_code != PAM_SUCCESS) {
                 g_debug ("GdmSessionWorker: user is not authorized to log in: %s",
                          pam_strerror (worker->priv->pam_handle, error_code));
                 g_set_error (error,
                              GDM_SESSION_WORKER_ERROR,
                              GDM_SESSION_WORKER_ERROR_AUTHORIZING,
                              "%s", get_friendly_error_message (error_code));
                 goto out;
         }
 
         g_debug ("GdmSessionWorker: state AUTHORIZED");
-        worker->priv->state = GDM_SESSION_WORKER_STATE_AUTHORIZED;
+        gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_AUTHORIZED);
 
  out:
         if (error_code != PAM_SUCCESS) {
                 gdm_session_worker_uninitialize_pam (worker, error_code);
                 return FALSE;
         }
 
         return TRUE;
 }
 
 static void
 gdm_session_worker_set_environment_variable (GdmSessionWorker *worker,
                                              const char       *key,
                                              const char       *value)
 {
         int error_code;
         char *environment_entry;
 
         if (value != NULL) {
                 environment_entry = g_strdup_printf ("%s=%s", key, value);
         } else {
                 /* empty value means "remove from environment" */
                 environment_entry = g_strdup (key);
         }
 
         error_code = pam_putenv (worker->priv->pam_handle,
                                  environment_entry);
 
         if (error_code != PAM_SUCCESS) {
                 g_warning ("cannot put %s in pam environment: %s\n",
@@ -1710,61 +1722,61 @@ gdm_session_worker_accredit_user (GdmSessionWorker  *worker,
 
         /* If the user is reauthenticating and they've made it this far, then there
          * is no reason we should lock them out of their session.  They've already
          * proved they are they same person who logged in, and that's all we care
          * about.
          */
         if (worker->priv->is_reauth_session) {
                 error_code = PAM_SUCCESS;
         }
 
         if (error_code != PAM_SUCCESS) {
                 g_set_error (error,
                              GDM_SESSION_WORKER_ERROR,
                              GDM_SESSION_WORKER_ERROR_GIVING_CREDENTIALS,
                              "%s",
                              pam_strerror (worker->priv->pam_handle, error_code));
                 goto out;
         }
 
         ret = TRUE;
 
  out:
         g_free (home);
         g_free (shell);
         if (ret) {
                 g_debug ("GdmSessionWorker: state ACCREDITED");
                 ret = TRUE;
 
                 gdm_session_worker_get_username (worker, NULL);
                 gdm_session_auditor_report_user_accredited (worker->priv->auditor);
-                worker->priv->state = GDM_SESSION_WORKER_STATE_ACCREDITED;
+                gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_ACCREDITED);
         } else {
                 gdm_session_worker_uninitialize_pam (worker, error_code);
         }
 
         return ret;
 }
 
 static const char * const *
 gdm_session_worker_get_environment (GdmSessionWorker *worker)
 {
         return (const char * const *) pam_getenvlist (worker->priv->pam_handle);
 }
 
 static gboolean
 run_script (GdmSessionWorker *worker,
             const char       *dir)
 {
         /* scripts are for non-program sessions only */
         if (worker->priv->is_program_session) {
                 return TRUE;
         }
 
         return gdm_run_script (dir,
                                worker->priv->username,
                                worker->priv->x11_display_name,
                                worker->priv->display_is_local? NULL : worker->priv->hostname,
                                worker->priv->x11_authority_file);
 }
 
 static void
@@ -2154,61 +2166,61 @@ gdm_session_worker_start_session (GdmSessionWorker  *worker,
                                      (char **)
                                      environment,
                                      TRUE);
 
                 gdm_log_init ();
                 g_debug ("GdmSessionWorker: child '%s' could not be started: %s",
                          worker->priv->arguments[0],
                          g_strerror (errno));
 
                 _exit (EXIT_FAILURE);
         }
 
         if (worker->priv->session_tty_fd > 0) {
                 close (worker->priv->session_tty_fd);
                 worker->priv->session_tty_fd = -1;
         }
 
         /* If we end up execing again, make sure we don't use the executable context set up
          * by pam_selinux durin pam_open_session
          */
 #ifdef HAVE_SELINUX
         setexeccon (NULL);
 #endif
 
         worker->priv->child_pid = session_pid;
 
         g_debug ("GdmSessionWorker: session opened creating reply...");
         g_assert (sizeof (GPid) <= sizeof (int));
 
         g_debug ("GdmSessionWorker: state SESSION_STARTED");
-        worker->priv->state = GDM_SESSION_WORKER_STATE_SESSION_STARTED;
+        gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SESSION_STARTED);
 
         gdm_session_worker_watch_child (worker);
 
  out:
         if (error_code != PAM_SUCCESS) {
                 gdm_session_worker_uninitialize_pam (worker, error_code);
                 return FALSE;
         }
 
         return TRUE;
 }
 
 static gboolean
 set_up_for_new_vt (GdmSessionWorker *worker)
 {
         int fd;
         char vt_string[256], tty_string[256];
         int session_vt = 0;
 
         fd = open ("/dev/tty0", O_RDWR | O_NOCTTY);
 
         if (fd < 0) {
                 g_debug ("GdmSessionWorker: couldn't open VT master: %m");
                 return FALSE;
         }
 
         if (worker->priv->display_is_initial) {
                 session_vt = atoi (GDM_INITIAL_VT);
         } else {
                 if (ioctl(fd, VT_OPENQRY, &session_vt) < 0) {
@@ -2377,61 +2389,61 @@ gdm_session_worker_open_session (GdmSessionWorker  *worker,
                 break;
         case GDM_SESSION_DISPLAY_MODE_NEW_VT:
         case GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED:
                 if (!set_up_for_new_vt (worker)) {
                         g_set_error (error,
                                      GDM_SESSION_WORKER_ERROR,
                                      GDM_SESSION_WORKER_ERROR_OPENING_SESSION,
                                      "Unable to open VT");
                         return FALSE;
                 }
                 break;
         }
 
         flags = 0;
 
         if (worker->priv->is_program_session) {
                 flags |= PAM_SILENT;
         }
 
         error_code = pam_open_session (worker->priv->pam_handle, flags);
 
         if (error_code != PAM_SUCCESS) {
                 g_set_error (error,
                              GDM_SESSION_WORKER_ERROR,
                              GDM_SESSION_WORKER_ERROR_OPENING_SESSION,
                              "%s", pam_strerror (worker->priv->pam_handle, error_code));
                 goto out;
         }
 
         g_debug ("GdmSessionWorker: state SESSION_OPENED");
-        worker->priv->state = GDM_SESSION_WORKER_STATE_SESSION_OPENED;
+        gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_SESSION_OPENED);
 
         session_id = gdm_session_worker_get_environment_variable (worker, "XDG_SESSION_ID");
 
         if (session_id != NULL) {
                 g_free (worker->priv->session_id);
                 worker->priv->session_id = session_id;
         }
 
  out:
         if (error_code != PAM_SUCCESS) {
                 gdm_session_worker_uninitialize_pam (worker, error_code);
                 return FALSE;
         }
 
         gdm_session_worker_get_username (worker, NULL);
         gdm_session_auditor_report_login (worker->priv->auditor);
 
         return TRUE;
 }
 
 static void
 gdm_session_worker_set_server_address (GdmSessionWorker *worker,
                                        const char       *address)
 {
         g_free (worker->priv->server_address);
         worker->priv->server_address = g_strdup (address);
 }
 
 static void
 gdm_session_worker_set_is_reauth_session (GdmSessionWorker *worker,
@@ -2454,61 +2466,61 @@ gdm_session_worker_set_property (GObject      *object,
         case PROP_SERVER_ADDRESS:
                 gdm_session_worker_set_server_address (self, g_value_get_string (value));
                 break;
         case PROP_IS_REAUTH_SESSION:
                 gdm_session_worker_set_is_reauth_session (self, g_value_get_boolean (value));
                 break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                 break;
         }
 }
 
 static void
 gdm_session_worker_get_property (GObject    *object,
                                 guint       prop_id,
                                 GValue     *value,
                                 GParamSpec *pspec)
 {
         GdmSessionWorker *self;
 
         self = GDM_SESSION_WORKER (object);
 
         switch (prop_id) {
         case PROP_SERVER_ADDRESS:
                 g_value_set_string (value, self->priv->server_address);
                 break;
         case PROP_IS_REAUTH_SESSION:
                 g_value_set_boolean (value, self->priv->is_reauth_session);
                 break;
         case PROP_STATE:
-                g_value_set_int (value, self->priv->state);
+                g_value_set_enum (value, self->priv->state);
                 break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                 break;
         }
 }
 
 static gboolean
 gdm_session_worker_handle_set_environment_variable (GdmDBusWorker         *object,
                                                     GDBusMethodInvocation *invocation,
                                                     const char            *key,
                                                     const char            *value)
 {
         GdmSessionWorker *worker = GDM_SESSION_WORKER (object);
         gdm_session_worker_set_environment_variable (worker, key, value);
         gdm_dbus_worker_complete_set_environment_variable (object, invocation);
         return TRUE;
 }
 
 static gboolean
 gdm_session_worker_handle_set_session_name (GdmDBusWorker         *object,
                                             GDBusMethodInvocation *invocation,
                                             const char            *session_name)
 {
         GdmSessionWorker *worker = GDM_SESSION_WORKER (object);
         g_debug ("GdmSessionWorker: session name set to %s", session_name);
         gdm_session_settings_set_session_name (worker->priv->user_settings,
                                                session_name);
         gdm_dbus_worker_complete_set_session_name (object, invocation);
         return TRUE;
@@ -2639,61 +2651,61 @@ do_authorize (GdmSessionWorker *worker)
                 g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error);
         }
         worker->priv->pending_invocation = NULL;
 }
 
 static void
 do_accredit (GdmSessionWorker *worker)
 {
         GError  *error;
         gboolean res;
 
         /* get kerberos tickets, setup group lists, etc
          */
         error = NULL;
         res = gdm_session_worker_accredit_user (worker, &error);
 
         if (res) {
                 gdm_dbus_worker_complete_establish_credentials (GDM_DBUS_WORKER (worker), worker->priv->pending_invocation);
         } else {
                 g_dbus_method_invocation_take_error (worker->priv->pending_invocation, error);
         }
         worker->priv->pending_invocation = NULL;
 }
 
 static void
 save_account_details_now (GdmSessionWorker *worker)
 {
         g_assert (worker->priv->state == GDM_SESSION_WORKER_STATE_ACCREDITED);
 
         g_debug ("GdmSessionWorker: saving account details for user %s", worker->priv->username);
-        worker->priv->state = GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED;
+        gdm_session_worker_set_state (worker, GDM_SESSION_WORKER_STATE_ACCOUNT_DETAILS_SAVED);
         if (!gdm_session_settings_save (worker->priv->user_settings,
                                         worker->priv->username)) {
                 g_warning ("could not save session and language settings");
         }
         queue_state_change (worker);
 }
 
 static void
 on_settings_is_loaded_changed (GdmSessionSettings *user_settings,
                                GParamSpec         *pspec,
                                GdmSessionWorker   *worker)
 {
         if (!gdm_session_settings_is_loaded (worker->priv->user_settings)) {
                 return;
         }
 
         /* These signal handlers should be disconnected after the loading,
          * so that gdm_session_settings_set_* APIs don't cause the emitting
          * of Saved*NameRead D-Bus signals any more.
          */
         g_signal_handlers_disconnect_by_func (worker->priv->user_settings,
                                               G_CALLBACK (on_saved_session_name_read),
                                               worker);
 
         g_signal_handlers_disconnect_by_func (worker->priv->user_settings,
                                               G_CALLBACK (on_saved_language_name_read),
                                               worker);
 
         if (worker->priv->state == GDM_SESSION_WORKER_STATE_NONE) {
                 g_debug ("GdmSessionWorker: queuing setup for user: %s %s",
@@ -3443,60 +3455,68 @@ worker_interface_init (GdmDBusWorkerIface *interface)
         interface->handle_start_reauthentication = gdm_session_worker_handle_start_reauthentication;
 }
 
 static void
 gdm_session_worker_class_init (GdmSessionWorkerClass *klass)
 {
         GObjectClass    *object_class = G_OBJECT_CLASS (klass);
 
         object_class->get_property = gdm_session_worker_get_property;
         object_class->set_property = gdm_session_worker_set_property;
         object_class->constructor = gdm_session_worker_constructor;
         object_class->finalize = gdm_session_worker_finalize;
 
         g_type_class_add_private (klass, sizeof (GdmSessionWorkerPrivate));
 
         g_object_class_install_property (object_class,
                                          PROP_SERVER_ADDRESS,
                                          g_param_spec_string ("server-address",
                                                               "server address",
                                                               "server address",
                                                               NULL,
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
         g_object_class_install_property (object_class,
                                          PROP_IS_REAUTH_SESSION,
                                          g_param_spec_boolean ("is-reauth-session",
                                                                "is reauth session",
                                                                "is reauth session",
                                                               FALSE,
                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+        g_object_class_install_property (object_class,
+                                         PROP_STATE,
+                                         g_param_spec_enum ("state",
+                                                            "state",
+                                                            "state",
+                                                            GDM_TYPE_SESSION_WORKER_STATE,
+                                                            GDM_SESSION_WORKER_STATE_NONE,
+                                                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
 reauthentication_request_free (ReauthenticationRequest *request)
 {
 
         g_signal_handlers_disconnect_by_func (request->session,
                                               G_CALLBACK (on_reauthentication_client_connected),
                                               request);
         g_signal_handlers_disconnect_by_func (request->session,
                                               G_CALLBACK (on_reauthentication_client_disconnected),
                                               request);
         g_signal_handlers_disconnect_by_func (request->session,
                                               G_CALLBACK (on_reauthentication_cancelled),
                                               request);
         g_signal_handlers_disconnect_by_func (request->session,
                                               G_CALLBACK (on_reauthentication_conversation_started),
                                               request);
         g_signal_handlers_disconnect_by_func (request->session,
                                               G_CALLBACK (on_reauthentication_conversation_stopped),
                                               request);
         g_signal_handlers_disconnect_by_func (request->session,
                                               G_CALLBACK (on_reauthentication_verification_complete),
                                               request);
         g_clear_object (&request->session);
         g_slice_free (ReauthenticationRequest, request);
 }
 
 static void
 gdm_session_worker_init (GdmSessionWorker *worker)
diff --git a/daemon/session-worker-main.c b/daemon/session-worker-main.c
index 4a3a8ebbe..d96844d2d 100644
--- a/daemon/session-worker-main.c
+++ b/daemon/session-worker-main.c
@@ -37,104 +37,137 @@
 #include <glib-object.h>
 
 #include "gdm-common.h"
 #include "gdm-log.h"
 #include "gdm-session-worker.h"
 
 #include "gdm-settings.h"
 #include "gdm-settings-direct.h"
 #include "gdm-settings-keys.h"
 
 static GdmSettings *settings = NULL;
 
 static gboolean
 on_sigusr1_cb (gpointer user_data)
 {
         g_debug ("Got USR1 signal");
 
         gdm_log_toggle_debug ();
 
         return TRUE;
 }
 
 static gboolean
 is_debug_set (void)
 {
         gboolean debug;
         gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug);
         return debug;
 }
 
+static gboolean
+on_shutdown_signal_cb (gpointer user_data)
+{
+        GMainLoop *mainloop = user_data;
+
+        g_main_loop_quit (mainloop);
+
+        return FALSE;
+}
+
+static void
+on_state_changed (GdmSessionWorker *worker,
+                  GParamSpec       *pspec,
+                  GMainLoop        *main_loop)
+{
+        GdmSessionWorkerState state;
+
+        g_object_get (G_OBJECT (worker), "state", &state, NULL);
+
+        if (state != GDM_SESSION_WORKER_STATE_SESSION_STARTED)
+                return;
+
+        g_unix_signal_add (SIGTERM, on_shutdown_signal_cb, main_loop);
+}
+
 static void
 on_sigterm_cb (int signal_number)
 {
         _exit (EXIT_SUCCESS);
 }
 
 int
 main (int    argc,
       char **argv)
 {
         GMainLoop        *main_loop;
         GOptionContext   *context;
         GdmSessionWorker *worker;
         const char       *address;
         gboolean          is_for_reauth;
         static GOptionEntry entries []   = {
                 { NULL }
         };
 
         signal (SIGTERM, on_sigterm_cb);
 
         bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
         textdomain (GETTEXT_PACKAGE);
         setlocale (LC_ALL, "");
 
         /* Translators: worker is a helper process that does the work
            of starting up a session */
         context = g_option_context_new (_("GNOME Display Manager Session Worker"));
         g_option_context_add_main_entries (context, entries, NULL);
 
         g_option_context_parse (context, &argc, &argv, NULL);
         g_option_context_free (context);
 
         gdm_log_init ();
 
         settings = gdm_settings_new ();
         if (settings == NULL) {
                 g_warning ("Unable to initialize settings");
                 exit (EXIT_FAILURE);
         }
 
         if (! gdm_settings_direct_init (settings, DATADIR "/gdm/gdm.schemas", "/")) {
                 g_warning ("Unable to initialize settings");
                 exit (EXIT_FAILURE);
         }
 
         gdm_log_set_debug (is_debug_set ());
 
         address = g_getenv ("GDM_SESSION_DBUS_ADDRESS");
         if (address == NULL) {
                 g_warning ("GDM_SESSION_DBUS_ADDRESS not set");
                 exit (EXIT_FAILURE);
         }
 
         is_for_reauth = g_getenv ("GDM_SESSION_FOR_REAUTH") != NULL;
 
         worker = gdm_session_worker_new (address, is_for_reauth);
 
         main_loop = g_main_loop_new (NULL, FALSE);
 
+        g_signal_connect (G_OBJECT (worker),
+                          "notify::state",
+                          G_CALLBACK (on_state_changed),
+                          main_loop);
+
         g_unix_signal_add (SIGUSR1, on_sigusr1_cb, NULL);
 
         g_main_loop_run (main_loop);
 
         if (worker != NULL) {
+                g_signal_handlers_disconnect_by_func (worker,
+                                                      G_CALLBACK (on_state_changed),
+                                                      main_loop);
                 g_object_unref (worker);
         }
 
         g_main_loop_unref (main_loop);
 
         g_debug ("Worker finished");
 
         return 0;
 }
-- 
2.21.0