diff --git a/SOURCES/0001-Handle-conflicting-sessions.patch b/SOURCES/0001-Handle-conflicting-sessions.patch index bfebf39..7242fda 100644 --- a/SOURCES/0001-Handle-conflicting-sessions.patch +++ b/SOURCES/0001-Handle-conflicting-sessions.patch @@ -1,7 +1,7 @@ From e204ee23d7626ee09684494b49774d8fae4d6056 Mon Sep 17 00:00:00 2001 From: Joan Torres Date: Thu, 8 May 2025 11:27:03 +0200 -Subject: [PATCH 1/3] manager: Use full verification on incompatible login +Subject: [PATCH 1/4] manager: Use full verification on incompatible login session Two sessions are incompatible if they don't share the same @@ -142,7 +142,7 @@ index 9c10adff3..051420ac8 100644 From 239aefa42bcba99fa7eac12b98b4dd7f64ef9608 Mon Sep 17 00:00:00 2001 From: Joan Torres Date: Thu, 8 May 2025 11:50:26 +0200 -Subject: [PATCH 2/3] session: Stop conflicting session +Subject: [PATCH 2/4] session: Stop conflicting session This happens when at the login gnome-shell it's been displayed the dialog requesting to logout the existing session. When the user chose to stop @@ -376,7 +376,7 @@ index 137be5e27..e5a1e4bb3 100644 From b70145ecaabe57e947bfb703bbd5d2e7b953609d Mon Sep 17 00:00:00 2001 From: Joan Torres Date: Thu, 8 May 2025 11:52:40 +0200 -Subject: [PATCH 3/3] session: On greeter add SessionOpenedWithSessionId signal +Subject: [PATCH 3/4] session: On greeter add SessionOpenedWithSessionId signal This session_id will be used by the gnome-shell at login session when searching if there's already a user session opened, ignoring this one which is @@ -420,3 +420,110 @@ index f22e37c8c..9ba2fb213 100644 -- 2.49.0 +From c2d9ef68043a0a045b91a164071a7296d6db9017 Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Fri, 6 Mar 2026 16:40:01 +0100 +Subject: [PATCH 4/4] manager: Pass opened session ID to stop-conflicting-session + signal + +Track the session ID when a session is opened and pass it to the +stop-conflicting-session signal handler. This allows properly +identifying and terminating conflicting sessions, including remote +sessions started outside of GDM. + +Previously, sessions were only terminated if they were tracked in the +display store, which missed remote sessions not managed by GDM. +--- + daemon/gdm-manager.c | 8 ++------ + daemon/gdm-session.c | 14 ++++++++++++-- + 2 files changed, 14 insertions(+), 8 deletions(-) + +diff --git a/daemon/gdm-manager.c b/daemon/gdm-manager.c +index b904f93..6eb581d 100644 +--- a/daemon/gdm-manager.c ++++ b/daemon/gdm-manager.c +@@ -2175,6 +2175,7 @@ on_session_reauthenticated (GdmSession *session, + static void + on_stop_conflicting_session (GdmSession *login_session, + const char *username, ++ const char *opened_session_id, + GdmManager *manager) + { + g_auto (GStrv) session_ids = NULL; +@@ -2190,12 +2191,7 @@ on_stop_conflicting_session (GdmSession *login_session, + } + + for (i = 0; i < g_strv_length (session_ids); i++) { +- // Don't kill conflicting session if it's not tracked, +- // this ensures the new session isn't killed (because +- // it's considered a conflicting session). +- if (!gdm_display_store_find (manager->priv->display_store, +- lookup_by_session_id, +- (gpointer) session_ids[i])) ++ if (g_strcmp0 (opened_session_id, session_ids[i]) == 0) + continue; + + if (!gdm_terminate_session_by_id (manager->priv->connection, NULL, session_ids[i])) +diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c +index 5e57a38..9a332d7 100644 +--- a/daemon/gdm-session.c ++++ b/daemon/gdm-session.c +@@ -138,6 +138,8 @@ struct _GdmSession + guint32 is_program_session : 1; + guint32 display_is_initial : 1; + guint32 is_opened : 1; ++ ++ char *session_opened; + }; + + enum { +@@ -895,6 +897,9 @@ on_opened (GdmDBusWorker *worker, + + conversation->session_id = g_strdup (session_id); + ++ g_clear_pointer (&self->session_opened, g_free); ++ self->session_opened = g_strdup (session_id); ++ + if (self->user_verifier_interface != NULL) { + gdm_dbus_user_verifier_emit_verification_complete (self->user_verifier_interface, + service_name); +@@ -1607,7 +1612,7 @@ gdm_session_handle_client_stop_conflicting_session (GdmDBusGreeter *greet + return TRUE; + } + +- g_signal_emit (self, signals[STOP_CONFLICTING_SESSION], 0, self->selected_user); ++ g_signal_emit (self, signals[STOP_CONFLICTING_SESSION], 0, self->selected_user, self->session_opened); + + if (self->greeter_interface != NULL) { + gdm_dbus_greeter_complete_stop_conflicting_session (self->greeter_interface, +@@ -3058,6 +3063,9 @@ do_reset (GdmSession *self) + g_free (self->user_x11_authority_file); + self->user_x11_authority_file = NULL; + ++ g_free (self->session_opened); ++ self->session_opened = NULL; ++ + g_hash_table_remove_all (self->environment); + + self->session_pid = -1; +@@ -3732,6 +3740,7 @@ gdm_session_finalize (GObject *object) + g_free (self->selected_session); + g_free (self->saved_session); + g_free (self->saved_language); ++ g_free (self->session_opened); + + g_free (self->fallback_session_name); + +@@ -4006,7 +4015,8 @@ gdm_session_class_init (GdmSessionClass *session_class) + NULL, + NULL, + G_TYPE_NONE, +- 1, ++ 2, ++ G_TYPE_STRING, + G_TYPE_STRING); + + g_object_class_install_property (object_class, +-- +2.51.0 + diff --git a/SOURCES/0001-data-Add-PAM-substack-wrappers-to-prevent-auth-bypas.patch b/SOURCES/0001-data-Add-PAM-substack-wrappers-to-prevent-auth-bypas.patch new file mode 100644 index 0000000..8ef37fd --- /dev/null +++ b/SOURCES/0001-data-Add-PAM-substack-wrappers-to-prevent-auth-bypas.patch @@ -0,0 +1,124 @@ +From 2892ac533be818826b4a1ab3c10a9619632e3a81 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 9 Jul 2026 20:09:10 +0200 +Subject: [PATCH] data: Add PAM substack wrappers to prevent auth bypass on + empty service files + +When an authselect-managed service file (e.g., switchable-auth) is missing +or empty, a direct substack to it returns PAM_SUCCESS without checking any +credentials, allowing unauthenticated session access. + +Introduce generated wrapper PAM services (gdm-*-substack) that use include +instead of substack internally, followed by pam_deny.so as a safety net. +The gdm-* services now substack these wrappers: on a properly configured +system, sufficient modules trigger done before reaching pam_deny.so; on an +empty or missing service file, pam_deny.so catches the fallthrough. +--- + data/meson.build | 20 +++++++++++++++++++ + data/pam-redhat/gdm-fingerprint.pam | 2 +- + data/pam-redhat/gdm-password.pam | 4 ++-- + .../gdm-service-auth-substack.pam.in | 5 +++++ + data/pam-redhat/gdm-smartcard.pam | 2 +- + data/pam-redhat/gdm-switchable-auth.pam | 4 ++-- + 6 files changed, 31 insertions(+), 6 deletions(-) + create mode 100644 data/pam-redhat/gdm-service-auth-substack.pam.in + +diff --git a/data/meson.build b/data/meson.build +index de75c15..780878c 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -149,6 +149,26 @@ foreach _pam_filename : pam_data_files + ) + endforeach + ++if default_pam_config == 'redhat' ++ pam_substack_services = [ ++ 'password-auth', ++ 'smartcard-auth', ++ 'fingerprint-auth', ++ 'switchable-auth', ++ ] ++ foreach service : pam_substack_services ++ pam_conf = configuration_data() ++ pam_conf.set('PAM_SERVICE', service) ++ configure_file( ++ input: 'pam-redhat/gdm-service-auth-substack.pam.in', ++ output: 'gdm-@0@-substack'.format(service), ++ configuration: pam_conf, ++ install: true, ++ install_dir: pam_prefix / 'pam.d', ++ ) ++ endforeach ++endif ++ + gdm_rules = configure_file( + input: '61-gdm.rules.in', + output: '@BASENAME@', +diff --git a/data/pam-redhat/gdm-fingerprint.pam b/data/pam-redhat/gdm-fingerprint.pam +index 1483cdf..597448e 100644 +--- a/data/pam-redhat/gdm-fingerprint.pam ++++ b/data/pam-redhat/gdm-fingerprint.pam +@@ -1,4 +1,4 @@ +-auth substack fingerprint-auth ++auth substack gdm-fingerprint-auth-substack + auth include postlogin + + account required pam_nologin.so +diff --git a/data/pam-redhat/gdm-password.pam b/data/pam-redhat/gdm-password.pam +index 21c04ec..8940f38 100644 +--- a/data/pam-redhat/gdm-password.pam ++++ b/data/pam-redhat/gdm-password.pam +@@ -1,12 +1,12 @@ + auth [success=done ignore=ignore default=bad] pam_selinux_permit.so +-auth substack password-auth ++auth substack gdm-password-auth-substack + auth optional pam_gnome_keyring.so + auth include postlogin + + account required pam_nologin.so + account include password-auth + +-password substack password-auth ++password substack gdm-password-auth-substack + -password optional pam_gnome_keyring.so use_authtok + + session required pam_selinux.so close +diff --git a/data/pam-redhat/gdm-service-auth-substack.pam.in b/data/pam-redhat/gdm-service-auth-substack.pam.in +new file mode 100644 +index 0000000..db43cbb +--- /dev/null ++++ b/data/pam-redhat/gdm-service-auth-substack.pam.in +@@ -0,0 +1,5 @@ ++auth include @PAM_SERVICE@ ++auth required pam_deny.so ++ ++password include @PAM_SERVICE@ ++password required pam_deny.so +diff --git a/data/pam-redhat/gdm-smartcard.pam b/data/pam-redhat/gdm-smartcard.pam +index 5024e52..0d3c2d3 100644 +--- a/data/pam-redhat/gdm-smartcard.pam ++++ b/data/pam-redhat/gdm-smartcard.pam +@@ -1,4 +1,4 @@ +-auth substack smartcard-auth ++auth substack gdm-smartcard-auth-substack + auth include postlogin + + account required pam_nologin.so +diff --git a/data/pam-redhat/gdm-switchable-auth.pam b/data/pam-redhat/gdm-switchable-auth.pam +index 6648c3c..9b7e6de 100644 +--- a/data/pam-redhat/gdm-switchable-auth.pam ++++ b/data/pam-redhat/gdm-switchable-auth.pam +@@ -1,11 +1,11 @@ +-auth substack switchable-auth ++auth substack gdm-switchable-auth-substack + auth optional pam_gnome_keyring.so + auth include postlogin + + account required pam_nologin.so + account include switchable-auth + +-password substack switchable-auth ++password substack gdm-switchable-auth-substack + -password optional pam_gnome_keyring.so use_authtok + + session required pam_selinux.so close diff --git a/SOURCES/0001-data-Add-support-for-unified-authentication.patch b/SOURCES/0001-data-Add-support-for-unified-authentication.patch new file mode 100644 index 0000000..fc2250c --- /dev/null +++ b/SOURCES/0001-data-Add-support-for-unified-authentication.patch @@ -0,0 +1,132 @@ +From 4c3f937ec0b694819823ea3ffb0aea361b62976b Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Thu, 18 Sep 2025 16:42:37 +0200 +Subject: [PATCH 1/2] session: Log JSON request when GDM_DEBUG_JSON_REQUESTS is + set + +This is only useful for debugging and testing. +--- + daemon/gdm-session.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c +index 6f53b5dfa..2f820015c 100644 +--- a/daemon/gdm-session.c ++++ b/daemon/gdm-session.c +@@ -807,8 +807,9 @@ gdm_session_handle_custom_json_request (GdmDBusWorkerManager *worker_manager_in + if (conversation != NULL) { + set_pending_query (conversation, invocation); + +- g_debug ("GdmSession: emitting custom JSON request '%s' v%u", +- protocol, version); ++ if (g_getenv ("GDM_DEBUG_JSON_REQUESTS") != NULL) ++ g_message ("GdmSession: emitting custom JSON request '%s' v%u: %s", ++ protocol, version, request); + gdm_dbus_user_verifier_custom_json_emit_request (custom_json_interface, + service_name, + protocol, +-- +2.47.3 + + +From a823f0f0fd3262770599d24de7efd0c2462ca61f Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 22 Jan 2024 09:40:39 -0500 +Subject: [PATCH 2/2] data: Add support for unified authentication + +At the moment, every authentication mechanism gets its own +separate PAM conversation. + +Some PAM modules, like pam_sss, support more than one way +to authenticate the user. + +Rather than starting several conversations, one for each +mechanism, this commit adds a new "unified" authentication +setting. +--- + data/meson.build | 1 + + data/org.gnome.login-screen.gschema.xml | 30 +++++++++++++++++++++++++ + data/pam-redhat/gdm-switchable-auth.pam | 18 +++++++++++++++ + 3 files changed, 49 insertions(+) + create mode 100644 data/pam-redhat/gdm-switchable-auth.pam + +diff --git a/data/meson.build b/data/meson.build +index 20d39a366..8d39d7506 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -105,6 +105,7 @@ pam_data_files_map = { + 'gdm-smartcard', + 'gdm-password', + 'gdm-pin', ++ 'gdm-switchable-auth', + ], + 'openembedded': [ + 'gdm-autologin', +diff --git a/data/org.gnome.login-screen.gschema.xml b/data/org.gnome.login-screen.gschema.xml +index 7b5c54d1b..02dd4d0e4 100644 +--- a/data/org.gnome.login-screen.gschema.xml ++++ b/data/org.gnome.login-screen.gschema.xml +@@ -1,6 +1,36 @@ + + + ++ ++ true ++ ++ Whether or not to allow switchable authentication for login ++ ++ ++ The login screen can optionally allow a single PAM service to provide ++ multiple authentication mechanisms via a GDM PAM. ++ ++ ++ ++ true ++ ++ Whether or not to allow authentication via external web site ++ ++ ++ The login screen can optionally allow users to authenticate via ++ web login. ++ ++ ++ ++ true ++ ++ Whether or not to allow authentication using a passkey ++ ++ ++ The login screen can optionally allow users who have passkeys to log ++ in using those passkeys. ++ ++ + + true + +diff --git a/data/pam-redhat/gdm-switchable-auth.pam b/data/pam-redhat/gdm-switchable-auth.pam +new file mode 100644 +index 000000000..6648c3cec +--- /dev/null ++++ b/data/pam-redhat/gdm-switchable-auth.pam +@@ -0,0 +1,18 @@ ++auth substack switchable-auth ++auth optional pam_gnome_keyring.so ++auth include postlogin ++ ++account required pam_nologin.so ++account include switchable-auth ++ ++password substack switchable-auth ++-password optional pam_gnome_keyring.so use_authtok ++ ++session required pam_selinux.so close ++session required pam_loginuid.so ++session required pam_selinux.so open ++session optional pam_keyinit.so force revoke ++session required pam_namespace.so ++session include switchable-auth ++session optional pam_gnome_keyring.so auto_start ++session include postlogin +-- +2.47.3 + diff --git a/SOURCES/0001-data-Disable-network-configuration-on-login-screen.patch b/SOURCES/0001-data-Disable-network-configuration-on-login-screen.patch index 7a1d38d..62a8a3e 100644 --- a/SOURCES/0001-data-Disable-network-configuration-on-login-screen.patch +++ b/SOURCES/0001-data-Disable-network-configuration-on-login-screen.patch @@ -10,37 +10,10 @@ Subject: [PATCH] data: Disable network configuration on login screen create mode 100644 data/org.gnome.gdm.rules.in diff --git a/data/meson.build b/data/meson.build -index 7c5222ea..20d39a36 100644 +index 780878c..40b8720 100644 --- a/data/meson.build +++ b/data/meson.build -@@ -130,60 +130,70 @@ pam_data_files_map = { - ], - 'arch': [ - 'gdm-autologin', - 'gdm-launch-environment', - 'gdm-fingerprint', - 'gdm-smartcard', - 'gdm-password', - ], - 'none': [], - # We should no longer have 'autodetect' at this point - } - - pam_data_files = pam_data_files_map[default_pam_config] - pam_prefix = (get_option('pam-prefix') != '')? get_option('pam-prefix') : get_option('sysconfdir') - foreach _pam_filename : pam_data_files - install_data('pam-@0@/@1@.pam'.format(default_pam_config, _pam_filename), - rename: _pam_filename, - install_dir: pam_prefix / 'pam.d', - ) - endforeach - - gdm_rules = configure_file( - input: '61-gdm.rules.in', - output: '@BASENAME@', - configuration: { - 'libexecdir': gdm_prefix / get_option('libexecdir'), - }, +@@ -178,6 +178,16 @@ gdm_rules = configure_file( install_dir: udev_dir, ) @@ -57,36 +30,9 @@ index 7c5222ea..20d39a36 100644 # DBus service files service_config = configuration_data() service_config.set('sbindir', gdm_prefix / get_option('sbindir')) - service_config.set('GDM_INITIAL_VT', get_option('initial-vt')) - service_config.set('LANG_CONFIG_FILE', lang_config_file) - if plymouth_dep.found() - service_config.set('PLYMOUTH_QUIT_SERVICE', 'plymouth-quit.service') - else - service_config.set('PLYMOUTH_QUIT_SERVICE', '') - endif - - if get_option('systemdsystemunitdir') != '' - systemd_systemunitdir = get_option('systemdsystemunitdir') - else - systemd_systemunitdir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir') - endif - - if get_option('systemduserunitdir') != '' - systemd_userunitdir = get_option('systemduserunitdir') - else - systemd_userunitdir = systemd_dep.get_pkgconfig_variable('systemduserunitdir', - define_variable: ['prefix', get_option('prefix')]) - endif - - configure_file( - input: 'gdm.service.in', - output: '@BASENAME@', - configuration: service_config, - install_dir: systemd_systemunitdir, - format: 'cmake' diff --git a/data/org.gnome.gdm.rules.in b/data/org.gnome.gdm.rules.in new file mode 100644 -index 00000000..09544f11 +index 0000000..09544f1 --- /dev/null +++ b/data/org.gnome.gdm.rules.in @@ -0,0 +1,8 @@ @@ -98,6 +44,3 @@ index 00000000..09544f11 + + return polkit.Result.NOT_HANDLED; +}); --- -2.32.0 - diff --git a/SOURCES/0001-gdm-session-Force-reuse-vt-mode-for-legacy-Xorg-mode.patch b/SOURCES/0001-gdm-session-Force-reuse-vt-mode-for-legacy-Xorg-mode.patch index c7257b7..b4f54f6 100644 --- a/SOURCES/0001-gdm-session-Force-reuse-vt-mode-for-legacy-Xorg-mode.patch +++ b/SOURCES/0001-gdm-session-Force-reuse-vt-mode-for-legacy-Xorg-mode.patch @@ -1,7 +1,7 @@ From bcab8852cf7249a2220f6c737f7bb8a17b99249a Mon Sep 17 00:00:00 2001 From: rpm-build Date: Mon, 27 Nov 2023 15:29:09 -0500 -Subject: [PATCH 1/4] gdm-session: Force reuse vt mode for legacy Xorg mode +Subject: [PATCH 1/5] gdm-session: Force reuse vt mode for legacy Xorg mode In the legacy Xorg mode, the X session and user session are supposed to use the same VT. diff --git a/SOURCES/0001-manager-Schedule-deferred-plymouth-quit-on-session-r.patch b/SOURCES/0001-manager-Schedule-deferred-plymouth-quit-on-session-r.patch new file mode 100644 index 0000000..31c1a50 --- /dev/null +++ b/SOURCES/0001-manager-Schedule-deferred-plymouth-quit-on-session-r.patch @@ -0,0 +1,113 @@ +From 46642cb36d63687fdd52b7161b192a3ac9e18393 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 13 Apr 2026 17:06:05 +0200 +Subject: [PATCH] manager: Schedule deferred plymouth quit on session + registration + +On headless or no-monitor systems, gnome-shell can register the session +but won't register the display, leaving plymouth running indefinitely. +That makes the system unusable because it didn't reach graphical.target. + +Schedule a deferred plymouth quit when RegisterSession is called on a +local display, using the same REGISTER_DISPLAY_TIMEOUT (10s) as the +session wrappers. If RegisterDisplay arrives first, the timeout is +cancelled and plymouth is quit immediately with transition as before. + +Part-of: +--- + daemon/gdm-manager.c | 37 +++++++++++++++++++++++++++++++++++++ + 1 file changed, 37 insertions(+) + +diff --git a/daemon/gdm-manager.c b/daemon/gdm-manager.c +index 2f1b78a..b5685cd 100644 +--- a/daemon/gdm-manager.c ++++ b/daemon/gdm-manager.c +@@ -94,6 +94,7 @@ struct GdmManagerPrivate + + #ifdef WITH_PLYMOUTH + guint plymouth_is_running : 1; ++ guint plymouth_quit_timeout_id; + #endif + guint did_automatic_login : 1; + }; +@@ -207,6 +208,21 @@ plymouth_quit_without_transition (void) + standard_error?: ""); + } + } ++ ++static gboolean ++plymouth_quit_timeout_cb (gpointer user_data) ++{ ++ GdmManager *manager = user_data; ++ ++ manager->priv->plymouth_quit_timeout_id = 0; ++ ++ if (manager->priv->plymouth_is_running) { ++ plymouth_quit_without_transition (); ++ manager->priv->plymouth_is_running = FALSE; ++ } ++ ++ return G_SOURCE_REMOVE; ++} + #endif + + static char * +@@ -843,6 +859,23 @@ gdm_manager_handle_register_session (GdmDBusManager *manager, + "session-registered", TRUE, + NULL); + ++#ifdef WITH_PLYMOUTH ++ if (self->priv->plymouth_is_running) { ++ gboolean display_is_local = FALSE; ++ ++ g_object_get (G_OBJECT (display), ++ "is-local", &display_is_local, ++ NULL); ++ ++ if (display_is_local && self->priv->plymouth_quit_timeout_id == 0) { ++ self->priv->plymouth_quit_timeout_id = ++ g_timeout_add_seconds (REGISTER_DISPLAY_TIMEOUT, ++ plymouth_quit_timeout_cb, ++ self); ++ } ++ } ++#endif ++ + gdm_dbus_manager_complete_register_session (GDM_DBUS_MANAGER (manager), + invocation); + +@@ -1647,6 +1680,7 @@ on_display_status_changed (GdmDisplay *display, + if (status == GDM_DISPLAY_MANAGED && quit_plymouth) { + plymouth_quit_with_transition (); + manager->priv->plymouth_is_running = FALSE; ++ g_clear_handle_id (&manager->priv->plymouth_quit_timeout_id, g_source_remove); + } + #endif + break; +@@ -1659,6 +1693,7 @@ on_display_status_changed (GdmDisplay *display, + if (quit_plymouth) { + plymouth_quit_without_transition (); + manager->priv->plymouth_is_running = FALSE; ++ g_clear_handle_id (&manager->priv->plymouth_quit_timeout_id, g_source_remove); + } + #endif + +@@ -2620,6 +2655,10 @@ gdm_manager_stop (GdmManager *manager) + } + #endif + ++#ifdef WITH_PLYMOUTH ++ g_clear_handle_id (&manager->priv->plymouth_quit_timeout_id, g_source_remove); ++#endif ++ + manager->priv->started = FALSE; + } + +@@ -2647,6 +2686,7 @@ gdm_manager_start (GdmManager *manager) + if (!manager->priv->show_local_greeter && manager->priv->plymouth_is_running) { + plymouth_quit_without_transition (); + manager->priv->plymouth_is_running = FALSE; ++ g_clear_handle_id (&manager->priv->plymouth_quit_timeout_id, g_source_remove); + } + #endif + if (manager->priv->xdmcp_factory != NULL) { diff --git a/SOURCES/0001-manager-Update-RegisterSession-dbus-method.patch b/SOURCES/0001-manager-Update-RegisterSession-dbus-method.patch new file mode 100644 index 0000000..38d95af --- /dev/null +++ b/SOURCES/0001-manager-Update-RegisterSession-dbus-method.patch @@ -0,0 +1,437 @@ +From ecc9ab663cd698faa2eef596789f43df50976534 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 3 Feb 2026 14:29:18 +0100 +Subject: [PATCH 1/2] manager: Add RegisterDisplay again + +1. RegisterSession is used to record a session login. It should be + called when the new session is started. +2. RegisterDisplay is used to terminate pending greeters and plymouth + to reach graphical.target. It should be called once the new session + completes its graphics startup. + +RegisterSession was trying to do both before new session startup was completed. +This had a potential issue: the new session is setting up while the greeter +session is tearing down leading to configuration conflicts. + +Fixes: deeb4b8aba46e37a1f6dcb85252ed713183cb170 ("manager: Combine register +display with register session") +--- + daemon/gdm-local-display-factory.c | 8 +------ + daemon/gdm-manager.c | 38 +++++++++++++++++++++++++++--- + daemon/gdm-manager.xml | 2 ++ + 3 files changed, 38 insertions(+), 10 deletions(-) + +diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c +index ef2ff7c..789afe7 100644 +--- a/daemon/gdm-local-display-factory.c ++++ b/daemon/gdm-local-display-factory.c +@@ -518,7 +518,6 @@ on_display_status_changed (GdmDisplay *display, + char *session_class = NULL; + gboolean is_initial = TRUE; + gboolean is_local = TRUE; +- gboolean registered = FALSE; + + + if (!factory->is_started) +@@ -584,11 +583,7 @@ on_display_status_changed (GdmDisplay *display, + break; + case GDM_DISPLAY_MANAGED: + #if defined(ENABLE_USER_DISPLAY_SERVER) +- g_object_get (display, "session-registered", ®istered, NULL); +- if (registered) { +- g_debug ("GdmLocalDisplayFactory: session registered on display, looking for any background displays to kill"); +- finish_waiting_displays_on_seat (factory, "seat0"); +- } ++ finish_waiting_displays_on_seat (factory, "seat0"); + #endif + break; + case GDM_DISPLAY_WAITING_TO_FINISH: +diff --git a/daemon/gdm-manager.c b/daemon/gdm-manager.c +index 9af9de0..b67d2e5 100644 +--- a/daemon/gdm-manager.c ++++ b/daemon/gdm-manager.c +@@ -746,6 +746,39 @@ find_user_session_for_display (GdmManager *self, + return NULL; + } + ++static gboolean ++gdm_manager_handle_register_display (GdmDBusManager *manager, ++ GDBusMethodInvocation *invocation, ++ GVariant *details) ++{ ++ GdmManager *self = GDM_MANAGER (manager); ++ const char *sender; ++ GDBusConnection *connection; ++ GdmDisplay *display = NULL; ++ ++ sender = g_dbus_method_invocation_get_sender (invocation); ++ connection = g_dbus_method_invocation_get_connection (invocation); ++ get_display_and_details_for_bus_sender (self, connection, sender, &display, NULL, NULL, NULL, NULL, NULL, NULL, NULL); ++ ++ if (display == NULL) { ++ g_dbus_method_invocation_return_error_literal (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ _("No display available")); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ g_object_set (G_OBJECT (display), ++ "status", GDM_DISPLAY_MANAGED, ++ NULL); ++ ++ gdm_dbus_manager_complete_register_display (GDM_DBUS_MANAGER (manager), ++ invocation); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ + static gboolean + gdm_manager_handle_register_session (GdmDBusManager *manager, + GDBusMethodInvocation *invocation, +@@ -774,7 +807,7 @@ gdm_manager_handle_register_session (GdmDBusManager *manager, + G_DBUS_ERROR_ACCESS_DENIED, + _("No display available")); + +- return TRUE; ++ return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + g_variant_iter_init (&iter, details); +@@ -808,14 +841,13 @@ gdm_manager_handle_register_session (GdmDBusManager *manager, + } + + g_object_set (G_OBJECT (display), +- "status", GDM_DISPLAY_MANAGED, + "session-registered", TRUE, + NULL); + + gdm_dbus_manager_complete_register_session (GDM_DBUS_MANAGER (manager), + invocation); + +- return TRUE; ++ return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + static gboolean +@@ -1264,6 +1296,7 @@ gdm_manager_handle_open_reauthentication_channel (GdmDBusManager *manager + static void + manager_interface_init (GdmDBusManagerIface *interface) + { ++ interface->handle_register_display = gdm_manager_handle_register_display; + interface->handle_register_session = gdm_manager_handle_register_session; + interface->handle_open_session = gdm_manager_handle_open_session; + interface->handle_open_reauthentication_channel = gdm_manager_handle_open_reauthentication_channel; +diff --git a/daemon/gdm-manager.xml b/daemon/gdm-manager.xml +index aba079a..92ef1d0 100644 +--- a/daemon/gdm-manager.xml ++++ b/daemon/gdm-manager.xml +@@ -1,6 +1,9 @@ + + + ++ ++ ++ + + + +From d8e9406bfd6ebe3b395476ba0460f08d1db536c1 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Tue, 3 Feb 2026 14:31:39 +0100 +Subject: [PATCH 2/2] session: Call RegisterSession and RegisterDisplay from + session launchers + +RegisterSession is called when the session starts to record the login. +RegisterDisplay is called after a delay to terminate pending greeters +and plymouth once graphics have settled. + +This follows the workflow established in the previous commit where +RegisterSession and RegisterDisplay serve distinct purposes. +--- + common/gdm-common.h | 2 +- + daemon/gdm-session.c | 4 +-- + daemon/gdm-wayland-session.c | 63 ++++++++++++++++++++++-------------- + daemon/gdm-x-session.c | 47 +++++++++++++++++---------- + 4 files changed, 71 insertions(+), 45 deletions(-) + +diff --git a/common/gdm-common.h b/common/gdm-common.h +index ea012dc..96fda83 100644 +--- a/common/gdm-common.h ++++ b/common/gdm-common.h +@@ -27,7 +27,7 @@ + #include + #include + +-#define REGISTER_SESSION_TIMEOUT 10 ++#define REGISTER_DISPLAY_TIMEOUT 10 + + #define VE_IGNORE_EINTR(expr) \ + do { \ +diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c +index 9a332d7..6b37ab7 100644 +--- a/daemon/gdm-session.c ++++ b/daemon/gdm-session.c +@@ -2930,7 +2930,7 @@ gdm_session_start_session (GdmSession *self, + gboolean allow_remote_connections = FALSE; + char *command; + char *program; +- gboolean register_session; ++ gboolean needs_registration; + + g_return_if_fail (GDM_IS_SESSION (self)); + g_return_if_fail (self->session_conversation == NULL); +@@ -2956,7 +2956,7 @@ gdm_session_start_session (GdmSession *self, + run_launcher = TRUE; + } + +- register_session = !gdm_session_session_registers (self); ++ needs_registration = !gdm_session_session_registers (self); + + if (self->selected_program == NULL) { + gboolean run_xsession_script; +@@ -2976,13 +2976,13 @@ gdm_session_start_session (GdmSession *self, + if (run_launcher) { + if (is_x11) { + program = g_strdup_printf (LIBEXECDIR "/gdm-x-session %s%s %s\"%s\"", +- register_session ? "--register-session " : "", ++ needs_registration ? "--handle-registration " : "", + run_xsession_script? "--run-script " : "", + allow_remote_connections? "--allow-remote-connections " : "", + command); + } else { + program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session %s\"%s\"", +- register_session ? "--register-session " : "", ++ needs_registration ? "--handle-registration " : "", + command); + } + } else if (run_xsession_script) { +@@ -3010,11 +3010,11 @@ gdm_session_start_session (GdmSession *self, + if (run_launcher) { + if (is_x11) { + program = g_strdup_printf (LIBEXECDIR "/gdm-x-session %s\"dbus-run-session -- %s\"", +- register_session ? "--register-session " : "", ++ needs_registration ? "--handle-registration " : "", + self->selected_program); + } else { + program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session %s\"dbus-run-session -- %s\"", +- register_session ? "--register-session " : "", ++ needs_registration ? "--handle-registration " : "", + self->selected_program); + } + } else { +diff --git a/daemon/gdm-wayland-session.c b/daemon/gdm-wayland-session.c +index d4d1edd..73d245c 100644 +--- a/daemon/gdm-wayland-session.c ++++ b/daemon/gdm-wayland-session.c +@@ -54,7 +54,7 @@ typedef struct + char *session_command; + int session_exit_status; + +- guint register_session_id; ++ guint register_display_id; + + GMainLoop *main_loop; + +@@ -404,6 +404,22 @@ wait_on_subprocesses (State *state) + } + } + ++static gboolean ++register_session (State *state) ++{ ++ g_autoptr(GError) error = NULL; ++ ++ if (!gdm_dbus_manager_call_register_session_sync (state->display_manager_proxy, ++ g_variant_new ("a{sv}", NULL), ++ state->cancellable, ++ &error)) { ++ g_warning ("Could not register session: %s", error->message); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + static void + init_state (State **state) + { +@@ -422,7 +438,7 @@ clear_state (State **out_state) + g_clear_object (&state->session_subprocess); + g_clear_pointer (&state->environment, g_strfreev); + g_clear_pointer (&state->main_loop, g_main_loop_unref); +- g_clear_handle_id (&state->register_session_id, g_source_remove); ++ g_clear_handle_id (&state->register_display_id, g_source_remove); + *out_state = NULL; + } + +@@ -439,22 +455,16 @@ on_sigterm (State *state) + } + + static gboolean +-register_session_timeout_cb (gpointer user_data) ++register_display_timeout_cb (gpointer user_data) + { +- State *state; +- GError *error = NULL; +- +- state = (State *) user_data; ++ State *state = (State *) user_data; ++ g_autoptr(GError) error = NULL; + +- gdm_dbus_manager_call_register_session_sync (state->display_manager_proxy, +- g_variant_new ("a{sv}", NULL), +- state->cancellable, +- &error); +- +- if (error != NULL) { +- g_warning ("Could not register session: %s", error->message); +- g_error_free (error); +- } ++ if (!gdm_dbus_manager_call_register_display_sync (state->display_manager_proxy, ++ g_variant_new ("a{ss}", NULL), ++ state->cancellable, ++ &error)) ++ g_warning ("Could not display session: %s", error->message); + + return G_SOURCE_REMOVE; + } +@@ -491,10 +501,10 @@ main (int argc, + gboolean debug = FALSE; + gboolean ret; + int exit_status = EX_OK; +- static gboolean register_session = FALSE; ++ static gboolean handle_registration = FALSE; + + static GOptionEntry entries [] = { +- { "register-session", 0, 0, G_OPTION_ARG_NONE, ®ister_session, "Register session after a delay", NULL }, ++ { "handle-registration", 0, 0, G_OPTION_ARG_NONE, &handle_registration, "Handle session and display registration (fallback for non-GNOME sessions)", NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, "", "" }, + { NULL } + }; +@@ -561,10 +571,16 @@ main (int argc, + if (!connect_to_display_manager (state)) + goto out; + +- if (register_session) { +- g_debug ("gdm-wayland-session: Will register session in %d seconds", REGISTER_SESSION_TIMEOUT); +- state->register_session_id = g_timeout_add_seconds (REGISTER_SESSION_TIMEOUT, +- register_session_timeout_cb, ++ if (handle_registration) { ++ if (!register_session (state)) { ++ g_printerr ("Unable to register session with display manager\n"); ++ exit_status = EX_SOFTWARE; ++ goto out; ++ } ++ ++ g_debug ("gdm-wayland-session: Will register display in %d seconds", REGISTER_DISPLAY_TIMEOUT); ++ state->register_display_id = g_timeout_add_seconds (REGISTER_DISPLAY_TIMEOUT, ++ register_display_timeout_cb, + state); + } else { + g_debug ("gdm-wayland-session: Session will register itself"); +diff --git a/daemon/gdm-x-session.c b/daemon/gdm-x-session.c +index 36b3975..be65f07 100644 +--- a/daemon/gdm-x-session.c ++++ b/daemon/gdm-x-session.c +@@ -60,7 +60,7 @@ typedef struct + char *session_command; + int session_exit_status; + +- guint register_session_id; ++ guint register_display_id; + + GMainLoop *main_loop; + +@@ -778,7 +778,7 @@ clear_state (State **out_state) + g_clear_pointer (&state->auth_file, g_free); + g_clear_pointer (&state->display_name, g_free); + g_clear_pointer (&state->main_loop, g_main_loop_unref); +- g_clear_handle_id (&state->register_session_id, g_source_remove); ++ g_clear_handle_id (&state->register_display_id, g_source_remove); + *out_state = NULL; + } + +@@ -795,24 +795,34 @@ on_sigterm (State *state) + } + + static gboolean +-register_session_timeout_cb (gpointer user_data) ++register_display_timeout_cb (gpointer user_data) + { +- State *state; +- GError *error = NULL; ++ State *state = (State *) user_data; ++ g_autoptr(GError) error = NULL; + +- state = (State *) user_data; ++ if (!gdm_dbus_manager_call_register_display_sync (state->display_manager_proxy, ++ g_variant_new ("a{ss}", NULL), ++ state->cancellable, ++ &error)) ++ g_warning ("Could not register display: %s", error->message); + +- gdm_dbus_manager_call_register_session_sync (state->display_manager_proxy, +- g_variant_new ("a{sv}", NULL), +- state->cancellable, +- &error); ++ return G_SOURCE_REMOVE; ++} + +- if (error != NULL) { ++static gboolean ++register_session (State *state) ++{ ++ g_autoptr(GError) error = NULL; ++ ++ if (!gdm_dbus_manager_call_register_session_sync (state->display_manager_proxy, ++ g_variant_new ("a{sv}", NULL), ++ state->cancellable, ++ &error)) { + g_warning ("Could not register session: %s", error->message); +- g_error_free (error); ++ return FALSE; + } + +- return G_SOURCE_REMOVE; ++ return TRUE; + } + + static gboolean +@@ -849,12 +859,12 @@ main (int argc, + gboolean debug = FALSE; + gboolean ret; + int exit_status = EX_OK; +- static gboolean register_session = FALSE; ++ static gboolean handle_registration = FALSE; + + static GOptionEntry entries [] = { + { "run-script", 'r', 0, G_OPTION_ARG_NONE, &run_script, N_("Run program through /etc/gdm/Xsession wrapper script"), NULL }, + { "allow-remote-connections", 'a', 0, G_OPTION_ARG_NONE, &allow_remote_connections, N_("Listen on TCP socket"), NULL }, +- { "register-session", 0, 0, G_OPTION_ARG_NONE, ®ister_session, "Register session after a delay", NULL }, ++ { "handle-registration", 0, 0, G_OPTION_ARG_NONE, &handle_registration, "Handle session and display registration (fallback for non-GNOME sessions)", NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, "", "" }, + { NULL } + }; +@@ -937,10 +947,16 @@ main (int argc, + goto out; + } + +- if (register_session) { +- g_debug ("gdm-x-session: Will register session in %d seconds", REGISTER_SESSION_TIMEOUT); +- state->register_session_id = g_timeout_add_seconds (REGISTER_SESSION_TIMEOUT, +- register_session_timeout_cb, ++ if (handle_registration) { ++ if (!register_session (state)) { ++ g_printerr ("Unable to register session with display manager\n"); ++ exit_status = EX_SOFTWARE; ++ goto out; ++ } ++ ++ g_debug ("gdm-x-session: Will register display in %d seconds", REGISTER_DISPLAY_TIMEOUT); ++ state->register_display_id = g_timeout_add_seconds (REGISTER_DISPLAY_TIMEOUT, ++ register_display_timeout_cb, + state); + } else { + g_debug ("gdm-x-session: Session will register itself"); diff --git a/SOURCES/0001-pam-extensions-Add-support-for-generic-GDM-CustomJSON.patch b/SOURCES/0001-pam-extensions-Add-support-for-generic-GDM-CustomJSON.patch new file mode 100644 index 0000000..77f17f7 --- /dev/null +++ b/SOURCES/0001-pam-extensions-Add-support-for-generic-GDM-CustomJSON.patch @@ -0,0 +1,1441 @@ +From 7b55b08f184e99cdd16727dc8e87ce55e2ed1ee1 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 30 May 2023 09:31:32 -0400 +Subject: [PATCH 1/7] pam-extensions: Separate ChoiceList extension to its own + header + +We're going to be adding another extension or two in the near future, +so having everything in one header file isn't great for structure's +sake. + +This commit moves the common code to a common header, the extension +to its own header, and makes gdm-pam-extensions.h just include +the other headers. +--- + .../gdm-choice-list-pam-extension.h | 70 ++++++++ + pam-extensions/gdm-pam-extensions-common.h | 133 +++++++++++++++ + pam-extensions/gdm-pam-extensions.h | 155 +----------------- + pam-extensions/meson.build | 6 +- + 4 files changed, 209 insertions(+), 155 deletions(-) + create mode 100644 pam-extensions/gdm-choice-list-pam-extension.h + create mode 100644 pam-extensions/gdm-pam-extensions-common.h + +diff --git a/pam-extensions/gdm-choice-list-pam-extension.h b/pam-extensions/gdm-choice-list-pam-extension.h +new file mode 100644 +index 000000000..0e75ccc93 +--- /dev/null ++++ b/pam-extensions/gdm-choice-list-pam-extension.h +@@ -0,0 +1,70 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Red Hat, Inc. ++ * ++ * 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 GDM_CHOICE_LIST_PAM_EXTENSION_H ++#define GDM_CHOICE_LIST_PAM_EXTENSION_H ++ ++#include "gdm-pam-extensions-common.h" ++ ++typedef struct { ++ const char *key; ++ const char *text; ++} GdmChoiceListItems; ++ ++typedef struct { ++ size_t number_of_items; ++ GdmChoiceListItems items[]; ++} GdmChoiceList; ++ ++typedef struct { ++ GdmPamExtensionMessage header; ++ ++ char *prompt_message; ++ GdmChoiceList list; ++} GdmPamExtensionChoiceListRequest; ++ ++typedef struct { ++ GdmPamExtensionMessage header; ++ ++ char *key; ++} GdmPamExtensionChoiceListResponse; ++ ++#define GDM_PAM_EXTENSION_CHOICE_LIST "org.gnome.DisplayManager.UserVerifier.ChoiceList" ++ ++#define GDM_CHOICE_LIST_SIZE(num_items) (offsetof(GdmChoiceList, items) + (num_items) * sizeof (GdmChoiceListItems)) ++#define GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(num_items) (offsetof(GdmPamExtensionChoiceListRequest, list) + GDM_CHOICE_LIST_SIZE((num_items))) ++#define GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, title, num_items) \ ++{ \ ++ int _n = num_items; \ ++ GDM_PAM_EXTENSION_LOOK_UP_TYPE (GDM_PAM_EXTENSION_CHOICE_LIST, &request->header.type); \ ++ request->header.length = htobe32 (GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(_n)); \ ++ request->prompt_message = title; \ ++ request->list.number_of_items = _n; \ ++} ++ ++#define GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_SIZE sizeof (GdmPamExtensionChoiceListResponse) ++#define GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_INIT(response) \ ++{ \ ++ GDM_PAM_EXTENSION_LOOK_UP_TYPE (GDM_PAM_EXTENSION_CHOICE_LIST, &response->header.type); \ ++ response->header.length = htobe32 (GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_SIZE); \ ++ response->key = NULL; \ ++} ++#define GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply) ((GdmPamExtensionChoiceListResponse *) (void *) reply->resp) ++ ++#endif +diff --git a/pam-extensions/gdm-pam-extensions-common.h b/pam-extensions/gdm-pam-extensions-common.h +new file mode 100644 +index 000000000..5201823a4 +--- /dev/null ++++ b/pam-extensions/gdm-pam-extensions-common.h +@@ -0,0 +1,133 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2017 Red Hat, Inc. ++ * ++ * 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 GDM_PAM_EXTENSIONS_COMMON_H ++#define GDM_PAM_EXTENSIONS_COMMON_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++typedef struct { ++ uint32_t length; ++ ++ unsigned char type; ++ unsigned char data[]; ++} GdmPamExtensionMessage; ++ ++#define GDM_PAM_EXTENSION_MESSAGE_FROM_PAM_MESSAGE(query) (GdmPamExtensionMessage *) (void *) query->msg ++#define GDM_PAM_EXTENSION_MESSAGE_TO_PAM_REPLY(msg) (char *) (void *) msg ++#define GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(extended_message, binary_message) \ ++{ \ ++ (binary_message)->msg_style = PAM_BINARY_PROMPT; \ ++ (binary_message)->msg = (void *) extended_message; \ ++} ++#define GDM_PAM_EXTENSION_MESSAGE_TRUNCATED(msg) be32toh(msg->length) < sizeof (GdmPamExtensionMessage) ++#define GDM_PAM_EXTENSION_MESSAGE_INVALID_TYPE(msg) \ ++({ \ ++ bool _invalid = true; \ ++ int _n = -1; \ ++ const char *_supported_extensions; \ ++ _supported_extensions = getenv ("GDM_SUPPORTED_PAM_EXTENSIONS"); \ ++ if (_supported_extensions != NULL) { \ ++ const char *_p = _supported_extensions; \ ++ while (*_p != '\0' && _n < UCHAR_MAX) { \ ++ size_t _length; \ ++ _length = strcspn (_p, " "); \ ++ if (_length > 0) \ ++ _n++; \ ++ _p += _length; \ ++ _length = strspn (_p, " "); \ ++ _p += _length; \ ++ } \ ++ if (_n >= msg->type) \ ++ _invalid = false; \ ++ } \ ++ _invalid; \ ++}) ++#define GDM_PAM_EXTENSION_MESSAGE_MATCH(msg, supported_extensions, name) (strcmp (supported_extensions[msg->type], name) == 0) ++ ++/* environment block should be a statically allocated chunk of memory. This is important because ++ * putenv() will leak otherwise (and setenv isn't thread safe) ++ */ ++#define GDM_PAM_EXTENSION_ADVERTISE_SUPPORTED_EXTENSIONS(environment_block, supported_extensions) \ ++{ \ ++ size_t _size = 0; \ ++ unsigned char _t, _num_chunks; \ ++ char *_p; \ ++ _p = environment_block; \ ++ _p = stpncpy (_p, "GDM_SUPPORTED_PAM_EXTENSIONS", sizeof(environment_block)); \ ++ *_p = '\0'; \ ++ _size += strlen (_p); \ ++ for (_t = 0; supported_extensions[_t] != NULL && _t < UCHAR_MAX; _t++) {\ ++ size_t _next_chunk = strlen (supported_extensions[_t]) + strlen (" "); \ ++ if (_size + _next_chunk >= sizeof (environment_block)) \ ++ break; \ ++ _size += _next_chunk; \ ++ }\ ++ _num_chunks = _t; \ ++ if (_t != 0) { \ ++ _p = stpcpy (_p, "="); \ ++ for (_t = 0; _t < _num_chunks; _t++) { \ ++ if (_t != 0) \ ++ _p = stpcpy (_p, " "); \ ++ _p = stpcpy (_p, supported_extensions[_t]); \ ++ } \ ++ *_p = '\0'; \ ++ putenv (environment_block); \ ++ } \ ++} ++ ++#define GDM_PAM_EXTENSION_LOOK_UP_TYPE(name, extension_type) \ ++({ \ ++ bool _supported = false; \ ++ unsigned char _t = 0; \ ++ const char *_supported_extensions; \ ++ _supported_extensions = getenv ("GDM_SUPPORTED_PAM_EXTENSIONS"); \ ++ if (_supported_extensions != NULL) { \ ++ const char *_p = _supported_extensions; \ ++ while (*_p != '\0') { \ ++ size_t _length; \ ++ _length = strcspn (_p, " "); \ ++ if (strncmp (_p, name, _length) == 0) { \ ++ _supported = true; \ ++ break; \ ++ } \ ++ _p += _length; \ ++ _length = strspn (_p, " "); \ ++ _p += _length; \ ++ if (_t >= UCHAR_MAX) { \ ++ break; \ ++ } \ ++ _t++; \ ++ } \ ++ if (_supported && extension_type != NULL) \ ++ *extension_type = _t; \ ++ } \ ++ _supported; \ ++}) ++ ++#define GDM_PAM_EXTENSION_SUPPORTED(name) GDM_PAM_EXTENSION_LOOK_UP_TYPE(name, (unsigned char *) NULL) ++ ++#endif +diff --git a/pam-extensions/gdm-pam-extensions.h b/pam-extensions/gdm-pam-extensions.h +index cecb74225..cdf34f382 100644 +--- a/pam-extensions/gdm-pam-extensions.h ++++ b/pam-extensions/gdm-pam-extensions.h +@@ -20,159 +20,6 @@ + #ifndef GDM_PAM_EXTENSIONS_H + #define GDM_PAM_EXTENSIONS_H + +-#include +-#include +-#include +-#include +-#include +-#include +- +-#include +- +-typedef struct { +- uint32_t length; +- +- unsigned char type; +- unsigned char data[]; +-} GdmPamExtensionMessage; +- +-#define GDM_PAM_EXTENSION_MESSAGE_FROM_PAM_MESSAGE(query) (GdmPamExtensionMessage *) (void *) query->msg +-#define GDM_PAM_EXTENSION_MESSAGE_TO_PAM_REPLY(msg) (char *) (void *) msg +-#define GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(extended_message, binary_message) \ +-{ \ +- (binary_message)->msg_style = PAM_BINARY_PROMPT; \ +- (binary_message)->msg = (void *) extended_message; \ +-} +-#define GDM_PAM_EXTENSION_MESSAGE_TRUNCATED(msg) be32toh(msg->length) < sizeof (GdmPamExtensionMessage) +-#define GDM_PAM_EXTENSION_MESSAGE_INVALID_TYPE(msg) \ +-({ \ +- bool _invalid = true; \ +- int _n = -1; \ +- const char *_supported_extensions; \ +- _supported_extensions = getenv ("GDM_SUPPORTED_PAM_EXTENSIONS"); \ +- if (_supported_extensions != NULL) { \ +- const char *_p = _supported_extensions; \ +- while (*_p != '\0' && _n < UCHAR_MAX) { \ +- size_t _length; \ +- _length = strcspn (_p, " "); \ +- if (_length > 0) \ +- _n++; \ +- _p += _length; \ +- _length = strspn (_p, " "); \ +- _p += _length; \ +- } \ +- if (_n >= msg->type) \ +- _invalid = false; \ +- } \ +- _invalid; \ +-}) +-#define GDM_PAM_EXTENSION_MESSAGE_MATCH(msg, supported_extensions, name) (strcmp (supported_extensions[msg->type], name) == 0) +- +-/* environment block should be a statically allocated chunk of memory. This is important because +- * putenv() will leak otherwise (and setenv isn't thread safe) +- */ +-#define GDM_PAM_EXTENSION_ADVERTISE_SUPPORTED_EXTENSIONS(environment_block, supported_extensions) \ +-{ \ +- size_t _size = 0; \ +- unsigned char _t, _num_chunks; \ +- char *_p; \ +- _p = environment_block; \ +- _p = stpncpy (_p, "GDM_SUPPORTED_PAM_EXTENSIONS", sizeof(environment_block)); \ +- *_p = '\0'; \ +- _size += strlen (_p); \ +- for (_t = 0; supported_extensions[_t] != NULL && _t < UCHAR_MAX; _t++) {\ +- size_t _next_chunk = strlen (supported_extensions[_t]) + strlen (" "); \ +- if (_size + _next_chunk >= sizeof (environment_block)) \ +- break; \ +- _size += _next_chunk; \ +- }\ +- _num_chunks = _t; \ +- if (_t != 0) { \ +- _p = stpcpy (_p, "="); \ +- for (_t = 0; _t < _num_chunks; _t++) { \ +- if (_t != 0) \ +- _p = stpcpy (_p, " "); \ +- _p = stpcpy (_p, supported_extensions[_t]); \ +- } \ +- *_p = '\0'; \ +- putenv (environment_block); \ +- } \ +-} +- +-#define GDM_PAM_EXTENSION_LOOK_UP_TYPE(name, extension_type) \ +-({ \ +- bool _supported = false; \ +- unsigned char _t = 0; \ +- const char *_supported_extensions; \ +- _supported_extensions = getenv ("GDM_SUPPORTED_PAM_EXTENSIONS"); \ +- if (_supported_extensions != NULL) { \ +- const char *_p = _supported_extensions; \ +- while (*_p != '\0') { \ +- size_t _length; \ +- _length = strcspn (_p, " "); \ +- if (strncmp (_p, name, _length) == 0) { \ +- _supported = true; \ +- break; \ +- } \ +- _p += _length; \ +- _length = strspn (_p, " "); \ +- _p += _length; \ +- if (_t >= UCHAR_MAX) { \ +- break; \ +- } \ +- _t++; \ +- } \ +- if (_supported && extension_type != NULL) \ +- *extension_type = _t; \ +- } \ +- _supported; \ +-}) +- +-#define GDM_PAM_EXTENSION_SUPPORTED(name) GDM_PAM_EXTENSION_LOOK_UP_TYPE(name, (unsigned char *) NULL) +- +-typedef struct { +- const char *key; +- const char *text; +-} GdmChoiceListItems; +- +-typedef struct { +- size_t number_of_items; +- GdmChoiceListItems items[]; +-} GdmChoiceList; +- +-typedef struct { +- GdmPamExtensionMessage header; +- +- char *prompt_message; +- GdmChoiceList list; +-} GdmPamExtensionChoiceListRequest; +- +-typedef struct { +- GdmPamExtensionMessage header; +- +- char *key; +-} GdmPamExtensionChoiceListResponse; +- +-#define GDM_PAM_EXTENSION_CHOICE_LIST "org.gnome.DisplayManager.UserVerifier.ChoiceList" +- +-#define GDM_CHOICE_LIST_SIZE(num_items) (offsetof(GdmChoiceList, items) + (num_items) * sizeof (GdmChoiceListItems)) +-#define GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(num_items) (offsetof(GdmPamExtensionChoiceListRequest, list) + GDM_CHOICE_LIST_SIZE((num_items))) +-#define GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, title, num_items) \ +-{ \ +- int _n = num_items; \ +- GDM_PAM_EXTENSION_LOOK_UP_TYPE (GDM_PAM_EXTENSION_CHOICE_LIST, &request->header.type); \ +- request->header.length = htobe32 (GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(_n)); \ +- request->prompt_message = title; \ +- request->list.number_of_items = _n; \ +-} +- +-#define GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_SIZE sizeof (GdmPamExtensionChoiceListResponse) +-#define GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_INIT(response) \ +-{ \ +- GDM_PAM_EXTENSION_LOOK_UP_TYPE (GDM_PAM_EXTENSION_CHOICE_LIST, &response->header.type); \ +- response->header.length = htobe32 (GDM_PAM_EXTENSION_CHOICE_LIST_RESPONSE_SIZE); \ +- response->key = NULL; \ +-} +-#define GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply) ((GdmPamExtensionChoiceListResponse *) (void *) reply->resp) ++#include "gdm-choice-list-pam-extension.h" + + #endif +diff --git a/pam-extensions/meson.build b/pam-extensions/meson.build +index 4e7bc2e17..02f4ff601 100644 +--- a/pam-extensions/meson.build ++++ b/pam-extensions/meson.build +@@ -7,8 +7,12 @@ if pam_extensions_supported + subdirs: meson.project_name(), + ) + ++ header_files = files('gdm-pam-extensions.h', ++ 'gdm-pam-extensions-common.h', ++ 'gdm-choice-list-pam-extension.h') ++ + pam_extensions_inc = include_directories('.') +- install_headers('gdm-pam-extensions.h', ++ install_headers(header_files, + subdir: meson.project_name() + ) + endif +-- +2.47.3 + + +From 005c3a3ae509da1fea1d2875852a06f7734f783a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Mon, 23 Oct 2023 09:34:56 +0200 +Subject: [PATCH 2/7] gdm-client: Add user verifier PAM extensions getter + +We have a setter to enable the user verifier PAM extensions, but it's +not possible to get back this parameter. + +So add a getter for it. We're still returning the value that the library +user defined, more than the extensions that are actually enabled as that +may lead to a racy result, given that it depends on interaction with +GDM session. +--- + libgdm/gdm-client.c | 15 +++++++++++++++ + libgdm/gdm-client.h | 1 + + 2 files changed, 16 insertions(+) + +diff --git a/libgdm/gdm-client.c b/libgdm/gdm-client.c +index fb58f1a4c..abbbda316 100644 +--- a/libgdm/gdm-client.c ++++ b/libgdm/gdm-client.c +@@ -1422,3 +1422,18 @@ gdm_client_set_enabled_extensions (GdmClient *client, + { + client->enabled_extensions = g_strdupv ((char **) extensions); + } ++ ++/** ++ * gdm_client_get_enabled_extensions: ++ * @client: a #GdmClient ++ * ++ * Gets GDM's enabled pam extensions. Currently, only ++ * org.gnome.DisplayManager.UserVerifier.ChoiceList is supported. ++ * ++ * Returns: (array zero-terminated=1) (element-type utf8) (transfer: full): a list of extensions ++ */ ++GStrv ++gdm_client_get_enabled_extensions (GdmClient *client) ++{ ++ return g_strdupv (client->enabled_extensions); ++} +diff --git a/libgdm/gdm-client.h b/libgdm/gdm-client.h +index f73b5556a..4993cb86d 100644 +--- a/libgdm/gdm-client.h ++++ b/libgdm/gdm-client.h +@@ -41,6 +41,7 @@ GQuark gdm_client_error_quark (void); + GdmClient *gdm_client_new (void); + void gdm_client_set_enabled_extensions (GdmClient *client, + const char * const * extensions); ++GStrv gdm_client_get_enabled_extensions (GdmClient *client); + + void gdm_client_open_reauthentication_channel (GdmClient *client, + const char *username, +-- +2.47.3 + + +From 5953efd31d1be06ac7916166e0940a753107196f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Tue, 28 Nov 2023 16:04:23 +0100 +Subject: [PATCH 3/7] gdm-client: Use qdata to get user verifier extensions + +This object data is used multiple times, and depending on strings is +more error prone other than less efficient. So use a GQuark for this. +--- + libgdm/gdm-client.c | 51 ++++++++++++++++++++++++++++++++------------- + 1 file changed, 36 insertions(+), 15 deletions(-) + +diff --git a/libgdm/gdm-client.c b/libgdm/gdm-client.c +index abbbda316..32d6c80f7 100644 +--- a/libgdm/gdm-client.c ++++ b/libgdm/gdm-client.c +@@ -68,6 +68,20 @@ gdm_client_error_quark (void) + return error_quark; + } + ++static GQuark ++gdm_client_user_verifier_extensions_quark (void) ++{ ++ static gpointer quark_initialized = 0; ++ static GQuark quark = 0; ++ ++ if (g_once_init_enter (&quark_initialized)) { ++ quark = g_quark_from_static_string ("gdm-client-user-verifier-extensions"); ++ g_once_init_leave (&quark_initialized, GINT_TO_POINTER (TRUE)); ++ } ++ ++ return quark; ++} ++ + static GDBusConnection * + gdm_client_get_open_connection (GdmClient *client) + { +@@ -177,7 +191,8 @@ maybe_complete_user_verifier_proxy_operation (GdmClient *client, + GHashTableIter iter; + gpointer key, value; + +- user_verifier_extensions = g_object_get_data (G_OBJECT (data->user_verifier), "gdm-client-user-verifier-extensions"); ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (data->user_verifier), ++ gdm_client_user_verifier_extensions_quark ()); + if (user_verifier_extensions != NULL) { + g_hash_table_iter_init (&iter, user_verifier_extensions); + while (g_hash_table_iter_next (&iter, &key, &value)) { +@@ -201,7 +216,8 @@ on_user_verifier_choice_list_proxy_created (GObject *source, + + client = GDM_CLIENT (g_async_result_get_source_object (G_ASYNC_RESULT (data->task))); + +- user_verifier_extensions = g_object_get_data (G_OBJECT (data->user_verifier), "gdm-client-user-verifier-extensions"); ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (data->user_verifier), ++ gdm_client_user_verifier_extensions_quark ()); + choice_list = gdm_user_verifier_choice_list_proxy_new_finish (result, &error); + + if (choice_list == NULL) { +@@ -228,7 +244,8 @@ on_user_verifier_extensions_enabled (GdmUserVerifier *user_verifier, + + client = GDM_CLIENT (g_async_result_get_source_object (G_ASYNC_RESULT (data->task))); + cancellable = g_task_get_cancellable (data->task); +- user_verifier_extensions = g_object_get_data (G_OBJECT (user_verifier), "gdm-client-user-verifier-extensions"); ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (user_verifier), ++ gdm_client_user_verifier_extensions_quark ()); + + gdm_user_verifier_call_enable_extensions_finish (user_verifier, result, &error); + +@@ -313,10 +330,10 @@ on_user_verifier_proxy_created (GObject *source, + NULL, + (GDestroyNotify) + free_interface_skeleton); +- g_object_set_data_full (G_OBJECT (user_verifier), +- "gdm-client-user-verifier-extensions", +- user_verifier_extensions, +- (GDestroyNotify) g_hash_table_unref); ++ g_object_set_qdata_full (G_OBJECT (user_verifier), ++ gdm_client_user_verifier_extensions_quark (), ++ user_verifier_extensions, ++ (GDestroyNotify) g_hash_table_unref); + cancellable = g_task_get_cancellable (task); + gdm_user_verifier_call_enable_extensions (user_verifier, + (const char * const *) +@@ -690,10 +707,10 @@ gdm_client_get_user_verifier_sync (GdmClient *client, + NULL, + (GDestroyNotify) + free_interface_skeleton); +- g_object_set_data_full (G_OBJECT (client->user_verifier), +- "gdm-client-user-verifier-extensions", +- user_verifier_extensions, +- (GDestroyNotify) g_hash_table_unref); ++ g_object_set_qdata_full (G_OBJECT (client->user_verifier), ++ gdm_client_user_verifier_extensions_quark (), ++ user_verifier_extensions, ++ (GDestroyNotify) g_hash_table_unref); + + res = gdm_user_verifier_call_enable_extensions_sync (client->user_verifier, + (const char * const *) +@@ -836,11 +853,15 @@ gdm_client_get_user_verifier_choice_list (GdmClient *client) + { + GHashTable *user_verifier_extensions = NULL; + +- if (client->user_verifier_for_reauth != NULL) +- user_verifier_extensions = g_object_get_data (G_OBJECT (client->user_verifier_for_reauth), "gdm-client-user-verifier-extensions"); ++ if (client->user_verifier_for_reauth != NULL) { ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (client->user_verifier_for_reauth), ++ gdm_client_user_verifier_extensions_quark ()); ++ } + +- if (user_verifier_extensions == NULL && client->user_verifier != NULL) +- user_verifier_extensions = g_object_get_data (G_OBJECT (client->user_verifier), "gdm-client-user-verifier-extensions"); ++ if (user_verifier_extensions == NULL && client->user_verifier != NULL) { ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (client->user_verifier), ++ gdm_client_user_verifier_extensions_quark ()); ++ } + + if (user_verifier_extensions == NULL) + return NULL; +-- +2.47.3 + + +From eaad7cfe7fb219d87d83422e339ff5ae3c5e69f6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Thu, 23 Nov 2023 00:55:19 +0100 +Subject: [PATCH 4/7] gdm-pam-extensions-common: Include string header + +We do various string operations in the header, so include it +implicitly. +--- + pam-extensions/gdm-pam-extensions-common.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/pam-extensions/gdm-pam-extensions-common.h b/pam-extensions/gdm-pam-extensions-common.h +index 5201823a4..16bee5cab 100644 +--- a/pam-extensions/gdm-pam-extensions-common.h ++++ b/pam-extensions/gdm-pam-extensions-common.h +@@ -25,6 +25,7 @@ + #include + #include + #include ++#include + #include + + #include +-- +2.47.3 + + +From b904090c85e6372dcb74551120668b42a3caed73 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Mon, 23 Oct 2023 13:42:06 +0200 +Subject: [PATCH 5/7] Add support for generic GDM CustomJSON binary protocol + +GDM allows passing data through PAM binary conversations, however it +only supports an extension protocol for choice list. + +Due to the GDM architecture, creating a new custom protocol is quite +verbose and it implies changing multiple layers, at the same time it is +impossible for modules and GNOME Shell GDM/login extensions to easily +define specific protocols to perform custom actions, and they currently +need to rely on DBus or other ways to communicate without an easy way to +setup a secure communication channel. + +At the same time, extending a binary protocol is hard, once one has +been defined. + +To make this easier to manage both by modules and by gdm clients, add +a simple JSON-based custom protocol that it's easy to extend and +that moves the protocol definition at higher level than gdm. + +This moves the control of custom JSON protocols out from GDM, leaving +to the modules and to gdm clients (the shell) the control over them. + +GDM is still in charge of doing some validation check of the JSON +provided by the two parties, but it won't be in charge of any further +protocol operation. +--- + daemon/gdm-session-worker.c | 68 ++++++++ + daemon/gdm-session.c | 150 ++++++++++++++++++ + daemon/gdm-session.h | 5 + + daemon/gdm-session.xml | 24 +++ + daemon/meson.build | 1 + + libgdm/gdm-client.c | 102 +++++++++++- + libgdm/gdm-client.h | 2 + + meson.build | 13 +- + .../gdm-custom-json-pam-extension.h | 61 +++++++ + pam-extensions/gdm-pam-extensions.h | 2 + + 10 files changed, 420 insertions(+), 8 deletions(-) + create mode 100644 pam-extensions/gdm-custom-json-pam-extension.h + +diff --git a/daemon/gdm-session-worker.c b/daemon/gdm-session-worker.c +index 3ad94e2ab..e25f0647b 100644 +--- a/daemon/gdm-session-worker.c ++++ b/daemon/gdm-session-worker.c +@@ -47,6 +47,8 @@ + #include + #include + ++#include ++ + #include + + #include +@@ -178,6 +180,7 @@ static char gdm_pam_extension_environment_block[_POSIX_ARG_MAX]; + static const char * const + gdm_supported_pam_extensions[] = { + GDM_PAM_EXTENSION_CHOICE_LIST, ++ GDM_PAM_EXTENSION_CUSTOM_JSON, + NULL + }; + #endif +@@ -587,6 +590,55 @@ gdm_session_worker_process_choice_list_request (GdmSessionWorker + return gdm_session_worker_ask_list_of_choices (worker, request->prompt_message, &request->list, &response->key); + } + ++static gboolean ++gdm_session_worker_process_custom_json_protocol (GdmSessionWorker *worker, ++ GdmPamExtensionJSONProtocol *request, ++ GdmPamExtensionJSONProtocol *response) ++{ ++ g_autoptr(GError) error = NULL; ++ g_autoptr(JsonParser) parser = NULL; ++ g_autofree char *json_reply = NULL; ++ ++ g_debug ("GdmSessionWorker: sending custom JSON protocol request: %s v%d", ++ request->protocol_name, request->version); ++ g_debug ("GdmSessionWorker: (and waiting for reply)"); ++ ++ if (!request->json) { ++ g_warning ("GdmSessionWorker: custom JSON request is not valid"); ++ return FALSE; ++ } ++ ++ parser = json_parser_new_immutable (); ++ if (!json_parser_load_from_data (parser, request->json, -1, &error)) { ++ g_warning ("GdmSessionWorker: custom JSON request is not valid JSON: %s", ++ error->message); ++ return FALSE; ++ } ++ ++ if (!gdm_dbus_worker_manager_call_custom_json_request_sync (worker->priv->manager, ++ worker->priv->service, ++ request->protocol_name, ++ request->version, ++ request->json, ++ &response->json, ++ NULL, ++ &error)) { ++ g_warning ("GdmSessionWorker: custom JSON request failed: %s", ++ error->message); ++ return FALSE; ++ } ++ ++ if (!response->json) { ++ g_warning ("GdmSessionWorker: custom JSON request returned invalid data"); ++ return FALSE; ++ } ++ ++ /* No need to validate JSON reply again since that's what we got from ++ * the client and validation happens at daemon level. ++ */ ++ return TRUE; ++} ++ + static gboolean + gdm_session_worker_process_extended_pam_message (GdmSessionWorker *worker, + const struct pam_message *query, +@@ -624,6 +676,22 @@ gdm_session_worker_process_extended_pam_message (GdmSessionWorker *work + + *response = GDM_PAM_EXTENSION_MESSAGE_TO_PAM_REPLY (list_response); + return TRUE; ++ } else if (GDM_PAM_EXTENSION_MESSAGE_MATCH (extended_message, worker->priv->extensions, GDM_PAM_EXTENSION_CUSTOM_JSON)) { ++ GdmPamExtensionJSONProtocol *json_request = (GdmPamExtensionJSONProtocol *) extended_message; ++ g_autofree GdmPamExtensionJSONProtocol *json_response = malloc (GDM_PAM_EXTENSION_CUSTOM_JSON_SIZE); ++ ++ g_debug ("GdmSessionWorker: received extended pam message '%s'", GDM_PAM_EXTENSION_CUSTOM_JSON); ++ ++ GDM_PAM_EXTENSION_CUSTOM_JSON_RESPONSE_INIT (json_response, ++ json_request->protocol_name, ++ json_request->version); ++ ++ if (!gdm_session_worker_process_custom_json_protocol (worker, json_request, json_response)) { ++ return FALSE; ++ } ++ ++ *response = GDM_PAM_EXTENSION_MESSAGE_TO_PAM_REPLY (g_steal_pointer (&json_response)); ++ return TRUE; + } else { + g_debug ("GdmSessionWorker: received extended pam message of unknown type %u", (unsigned int) extended_message->type); + return FALSE; +diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c +index 12ce72066..904143677 100644 +--- a/daemon/gdm-session.c ++++ b/daemon/gdm-session.c +@@ -43,6 +43,8 @@ + #include + #include + ++#include ++ + #include "gdm-session.h" + #include "gdm-session-glue.h" + #include "gdm-dbus-util.h" +@@ -774,6 +776,49 @@ gdm_session_handle_choice_list_query (GdmDBusWorkerManager *worker_manager_inte + return TRUE; + } + ++static gboolean ++gdm_session_handle_custom_json_request (GdmDBusWorkerManager *worker_manager_interface, ++ GDBusMethodInvocation *invocation, ++ const char *service_name, ++ const char *protocol, ++ unsigned int version, ++ const char *request, ++ GdmSession *self) ++{ ++ GdmSessionConversation *conversation; ++ GdmDBusUserVerifierCustomJSON *custom_json_interface = NULL; ++ ++ g_debug ("GdmSession: custom JSON request for service '%s'", service_name); ++ ++ if (self->user_verifier_extensions != NULL) { ++ custom_json_interface = ++ g_hash_table_lookup (self->user_verifier_extensions, ++ gdm_dbus_user_verifier_custom_json_interface_info ()->name); ++ } ++ ++ if (custom_json_interface == NULL) { ++ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ "custom JSON interface not supported by client"); ++ return TRUE; ++ } ++ ++ conversation = find_conversation_by_name (self, service_name); ++ if (conversation != NULL) { ++ set_pending_query (conversation, invocation); ++ ++ g_debug ("GdmSession: emitting custom JSON request '%s' v%u", ++ protocol, version); ++ gdm_dbus_user_verifier_custom_json_emit_request (custom_json_interface, ++ service_name, ++ protocol, ++ version, ++ request); ++ } ++ ++ return TRUE; ++} ++ + static gboolean + gdm_session_handle_info_query (GdmDBusWorkerManager *worker_manager_interface, + GDBusMethodInvocation *invocation, +@@ -1259,6 +1304,10 @@ export_worker_manager_interface (GdmSession *self, + "handle-choice-list-query", + G_CALLBACK (gdm_session_handle_choice_list_query), + self); ++ g_signal_connect (worker_manager_interface, ++ "handle-custom-json-request", ++ G_CALLBACK (gdm_session_handle_custom_json_request), ++ self); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (worker_manager_interface), + connection, +@@ -1291,6 +1340,9 @@ unexport_worker_manager_interface (GdmSession *self, + g_signal_handlers_disconnect_by_func (worker_manager_interface, + G_CALLBACK (gdm_session_handle_choice_list_query), + self); ++ g_signal_handlers_disconnect_by_func (worker_manager_interface, ++ G_CALLBACK (gdm_session_handle_custom_json_request), ++ self); + } + + static gboolean +@@ -1380,6 +1432,81 @@ export_user_verifier_choice_list_interface (GdmSession *self, + interface); + } + ++static gboolean ++gdm_session_handle_client_custom_json_reply (GdmDBusUserVerifierCustomJSON *custom_json_interface, ++ GDBusMethodInvocation *invocation, ++ const char *service_name, ++ const char *json, ++ GdmSession *self) ++{ ++ g_autoptr(GError) error = NULL; ++ g_autoptr(JsonParser) parser = NULL; ++ ++ g_debug ("GdmSession: user replied with custom JSON"); ++ ++ parser = json_parser_new_immutable (); ++ if (!json_parser_load_from_data (parser, json, -1, &error)) { ++ g_autofree char *message = NULL; ++ ++ message = g_strdup_printf ("JSON reply is not valid: %s", error->message); ++ g_warning ("GdmSession: %s", message); ++ ++ g_dbus_method_invocation_return_error_literal (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ message); ++ gdm_session_report_error (self, service_name, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ message); ++ return TRUE; ++ } ++ ++ gdm_dbus_user_verifier_custom_json_complete_reply (custom_json_interface, invocation); ++ gdm_session_answer_query (self, service_name, json); ++ return TRUE; ++} ++ ++static gboolean ++gdm_session_handle_client_custom_json_report_error (GdmDBusUserVerifierCustomJSON *custom_json_interface, ++ GDBusMethodInvocation *invocation, ++ const char *service_name, ++ const char *message, ++ GdmSession *self) ++{ ++ g_debug ("GdmSession: user reported custom JSON error: %s", message); ++ ++ gdm_dbus_user_verifier_custom_json_complete_report_error (custom_json_interface, invocation); ++ gdm_session_report_error (self, service_name, G_DBUS_ERROR_ACCESS_DENIED, message); ++ return TRUE; ++} ++ ++static void ++export_user_verifier_custom_json_interface (GdmSession *self, ++ GDBusConnection *connection) ++{ ++ GdmDBusUserVerifierCustomJSON *interface; ++ ++ interface = GDM_DBUS_USER_VERIFIER_CUSTOM_JSON (gdm_dbus_user_verifier_custom_json_skeleton_new ()); ++ ++ g_signal_connect (interface, ++ "handle-reply", ++ G_CALLBACK (gdm_session_handle_client_custom_json_reply), ++ self); ++ g_signal_connect (interface, ++ "handle-report-error", ++ G_CALLBACK (gdm_session_handle_client_custom_json_report_error), ++ self); ++ ++ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interface), ++ connection, ++ GDM_SESSION_DBUS_OBJECT_PATH, ++ NULL); ++ ++ g_hash_table_insert (self->user_verifier_extensions, ++ gdm_dbus_user_verifier_custom_json_interface_info ()->name, ++ interface); ++} ++ + static gboolean + gdm_session_handle_client_enable_extensions (GdmDBusUserVerifier *user_verifier_interface, + GDBusMethodInvocation *invocation, +@@ -1399,6 +1526,10 @@ gdm_session_handle_client_enable_extensions (GdmDBusUserVerifier *user_verifi + gdm_dbus_user_verifier_choice_list_interface_info ()->name) == 0) + export_user_verifier_choice_list_interface (self, connection); + ++ if (g_str_equal (extensions[i], ++ gdm_dbus_user_verifier_custom_json_interface_info ()->name)) ++ export_user_verifier_custom_json_interface (self, connection); ++ + } + + gdm_dbus_user_verifier_complete_enable_extensions (user_verifier_interface, invocation); +@@ -3121,6 +3252,25 @@ gdm_session_answer_query (GdmSession *self, + } + } + ++void ++gdm_session_report_error (GdmSession *self, ++ const char *service_name, ++ GDBusError code, ++ const char *message) ++{ ++ GdmSessionConversation *conversation; ++ ++ g_return_if_fail (GDM_IS_SESSION (self)); ++ g_return_if_fail (service_name != NULL); ++ ++ conversation = find_conversation_by_name (self, service_name); ++ if (conversation == NULL) ++ return; ++ ++ g_dbus_method_invocation_return_error_literal (g_steal_pointer (&conversation->pending_invocation), ++ G_DBUS_ERROR, code, message); ++} ++ + void + gdm_session_cancel (GdmSession *self) + { +diff --git a/daemon/gdm-session.h b/daemon/gdm-session.h +index 3b64ecd23..b9769d6b3 100644 +--- a/daemon/gdm-session.h ++++ b/daemon/gdm-session.h +@@ -22,6 +22,7 @@ + #define __GDM_SESSION_H + + #include ++#include + #include + + G_BEGIN_DECLS +@@ -120,6 +121,10 @@ void gdm_session_close (GdmSession *session); + void gdm_session_answer_query (GdmSession *session, + const char *service_name, + const char *text); ++void gdm_session_report_error (GdmSession *session, ++ const char *service_name, ++ GDBusError code, ++ const char *message); + void gdm_session_select_program (GdmSession *session, + const char *command_line); + void gdm_session_select_session (GdmSession *session, +diff --git a/daemon/gdm-session.xml b/daemon/gdm-session.xml +index 7935aeeaa..9ba2fb213 100644 +--- a/daemon/gdm-session.xml ++++ b/daemon/gdm-session.xml +@@ -28,6 +28,14 @@ + + + ++ ++ ++ ++ ++ ++ ++ ++ + + + +@@ -95,6 +103,22 @@ + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +diff --git a/daemon/meson.build b/daemon/meson.build +index 41f30abef..c3a4c737b 100644 +--- a/daemon/meson.build ++++ b/daemon/meson.build +@@ -60,6 +60,7 @@ gdm_daemon_deps = [ + gobject_dep, + gio_dep, + gio_unix_dep, ++ json_glib, + libpam_dep, + x_deps, + xcb_dep, +diff --git a/libgdm/gdm-client.c b/libgdm/gdm-client.c +index 32d6c80f7..23e4b0bb6 100644 +--- a/libgdm/gdm-client.c ++++ b/libgdm/gdm-client.c +@@ -230,6 +230,35 @@ on_user_verifier_choice_list_proxy_created (GObject *source, + maybe_complete_user_verifier_proxy_operation (client, data); + } + ++static void ++on_user_verifier_custom_json_proxy_created (GObject *source, ++ GAsyncResult *result, ++ UserVerifierData *data) ++{ ++ GHashTable *user_verifier_extensions; ++ g_autoptr(GdmClient) client = NULL; ++ GdmUserVerifierCustomJSON *custom_json; ++ g_autoptr(GError) error = NULL; ++ ++ client = GDM_CLIENT (g_async_result_get_source_object (G_ASYNC_RESULT (data->task))); ++ ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (data->user_verifier), ++ gdm_client_user_verifier_extensions_quark ()); ++ custom_json = gdm_user_verifier_custom_json_proxy_new_finish (result, &error); ++ ++ if (custom_json == NULL) { ++ g_warning ("Couldn't create UserVerifier CustomJSON proxy: %s", error->message); ++ g_hash_table_remove (user_verifier_extensions, ++ gdm_user_verifier_custom_json_interface_info ()->name); ++ } else { ++ g_hash_table_replace (user_verifier_extensions, ++ gdm_user_verifier_custom_json_interface_info ()->name, ++ custom_json); ++ } ++ ++ maybe_complete_user_verifier_proxy_operation (client, data); ++} ++ + static void + on_user_verifier_extensions_enabled (GdmUserVerifier *user_verifier, + GAsyncResult *result, +@@ -273,6 +302,17 @@ on_user_verifier_extensions_enabled (GdmUserVerifier *user_verifier, + (GAsyncReadyCallback) + on_user_verifier_choice_list_proxy_created, + data); ++ } else if (g_str_equal (client->enabled_extensions[i], ++ gdm_user_verifier_custom_json_interface_info ()->name)) { ++ g_hash_table_insert (user_verifier_extensions, client->enabled_extensions[i], NULL); ++ gdm_user_verifier_custom_json_proxy_new (connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ NULL, ++ SESSION_DBUS_PATH, ++ cancellable, ++ (GAsyncReadyCallback) ++ on_user_verifier_custom_json_proxy_created, ++ data); + } else { + g_debug ("User verifier extension %s is unsupported", client->enabled_extensions[i]); + g_hash_table_remove (user_verifier_extensions, +@@ -732,6 +772,20 @@ gdm_client_get_user_verifier_sync (GdmClient *client, + NULL); + if (choice_list_interface != NULL) + g_hash_table_insert (user_verifier_extensions, client->enabled_extensions[i], choice_list_interface); ++ } else if (g_str_equal (client->enabled_extensions[i], ++ gdm_user_verifier_custom_json_interface_info ()->name)) { ++ GdmUserVerifierCustomJSON *custom_json_interface; ++ custom_json_interface = gdm_user_verifier_custom_json_proxy_new_sync (connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ NULL, ++ SESSION_DBUS_PATH, ++ cancellable, ++ NULL); ++ if (custom_json_interface != NULL) { ++ g_hash_table_insert (user_verifier_extensions, ++ client->enabled_extensions[i], ++ custom_json_interface); ++ } + } + } + } +@@ -838,6 +892,24 @@ gdm_client_get_user_verifier_finish (GdmClient *client, + return user_verifier; + } + ++static GHashTable * ++get_user_verifier_extensions (GdmClient *client) ++{ ++ GHashTable *user_verifier_extensions = NULL; ++ ++ if (client->user_verifier_for_reauth != NULL) { ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (client->user_verifier_for_reauth), ++ gdm_client_user_verifier_extensions_quark ()); ++ } ++ ++ if (user_verifier_extensions == NULL && client->user_verifier != NULL) { ++ user_verifier_extensions = g_object_get_qdata (G_OBJECT (client->user_verifier), ++ gdm_client_user_verifier_extensions_quark ()); ++ } ++ ++ return user_verifier_extensions; ++} ++ + /** + * gdm_client_get_user_verifier_choice_list: + * @client: a #GdmClient +@@ -851,7 +923,7 @@ gdm_client_get_user_verifier_finish (GdmClient *client, + GdmUserVerifierChoiceList * + gdm_client_get_user_verifier_choice_list (GdmClient *client) + { +- GHashTable *user_verifier_extensions = NULL; ++ GHashTable *user_verifier_extensions = get_user_verifier_extensions (client); + + if (client->user_verifier_for_reauth != NULL) { + user_verifier_extensions = g_object_get_qdata (G_OBJECT (client->user_verifier_for_reauth), +@@ -863,11 +935,31 @@ gdm_client_get_user_verifier_choice_list (GdmClient *client) + gdm_client_user_verifier_extensions_quark ()); + } + ++ return g_hash_table_lookup (user_verifier_extensions, ++ gdm_user_verifier_choice_list_interface_info ()->name); ++} ++ ++/** ++ * gdm_client_get_user_verifier_custom_json: ++ * @client: a #GdmClient ++ * ++ * Gets a #GdmUserVerifierCustomJSON object that can be used to ++ * verify a user's local account. ++ * ++ * Returns: (transfer none): #GdmUserVerifierCustomJSON or %NULL if user ++ * verifier isn't yet fetched, or daemon doesn't support the custom JSON ++ * protocol ++ */ ++GdmUserVerifierCustomJSON * ++gdm_client_get_user_verifier_custom_json (GdmClient *client) ++{ ++ GHashTable *user_verifier_extensions = get_user_verifier_extensions (client); ++ + if (user_verifier_extensions == NULL) + return NULL; + + return g_hash_table_lookup (user_verifier_extensions, +- gdm_user_verifier_choice_list_interface_info ()->name); ++ gdm_user_verifier_custom_json_interface_info ()->name); + } + + static void +@@ -1435,7 +1527,8 @@ gdm_client_new (void) + * @extensions: (array zero-terminated=1) (element-type utf8): a list of extensions + * + * Enables GDM's pam extensions. Currently, only +- * org.gnome.DisplayManager.UserVerifier.ChoiceList is supported. ++ * `org.gnome.DisplayManager.UserVerifier.ChoiceList` and ++ * `org.gnome.DisplayManager.UserVerifier.CustomJSON` are supported. + */ + void + gdm_client_set_enabled_extensions (GdmClient *client, +@@ -1449,7 +1542,8 @@ gdm_client_set_enabled_extensions (GdmClient *client, + * @client: a #GdmClient + * + * Gets GDM's enabled pam extensions. Currently, only +- * org.gnome.DisplayManager.UserVerifier.ChoiceList is supported. ++ * `org.gnome.DisplayManager.UserVerifier.ChoiceList` and ++ * `org.gnome.DisplayManager.UserVerifier.CustomJSON` are supported. + * + * Returns: (array zero-terminated=1) (element-type utf8) (transfer: full): a list of extensions + */ +diff --git a/libgdm/gdm-client.h b/libgdm/gdm-client.h +index 4993cb86d..2e97fca13 100644 +--- a/libgdm/gdm-client.h ++++ b/libgdm/gdm-client.h +@@ -71,6 +71,8 @@ GdmUserVerifier *gdm_client_get_user_verifier_sync (GdmClient *client, + + GdmUserVerifierChoiceList *gdm_client_get_user_verifier_choice_list (GdmClient *client); + ++GdmUserVerifierCustomJSON *gdm_client_get_user_verifier_custom_json (GdmClient *client); ++ + void gdm_client_get_greeter (GdmClient *client, + GCancellable *cancellable, + GAsyncReadyCallback callback, +diff --git a/meson.build b/meson.build +index 65963195a..d6bc7b955 100644 +--- a/meson.build ++++ b/meson.build +@@ -42,11 +42,20 @@ gudev_dep = dependency('gudev-1.0', version: '>= 232') + + glib_min_version = '2.56.0' + ++# PAM ++libpam_dep = cc.find_library('pam') ++pam_extensions_supported = cc.has_header_symbol( ++ 'security/pam_appl.h', 'PAM_BINARY_PROMPT', ++ dependencies: libpam_dep) ++ ++ + glib_dep = dependency('glib-2.0', version: '>=' + glib_min_version) + gobject_dep = dependency('gobject-2.0', version: '>=' + glib_min_version) + gio_dep = dependency('gio-2.0', version: '>=' + glib_min_version) + gio_unix_dep = dependency('gio-unix-2.0', version: '>=' + glib_min_version) + gtk_dep = dependency('gtk+-3.0', version: '>= 2.91.1') ++json_glib = dependency('json-glib-1.0', version: '>= 1.2.0', ++ required: pam_extensions_supported) + libcanberra_gtk_dep = dependency('libcanberra-gtk3', version: '>= 0.4') + accountsservice_dep = dependency('accountsservice', version: '>= 0.6.35') + xcb_dep = dependency('xcb') +@@ -162,10 +171,6 @@ int main(int argc, char **argv) { + } + ''') + +-# PAM +-libpam_dep = cc.find_library('pam') +-pam_extensions_supported = cc.has_header_symbol('security/pam_appl.h', 'PAM_BINARY_PROMPT') +- + default_pam_config = get_option('default-pam-config') + # If requested, try autodetecting from release files (see NetworkManager source) + if default_pam_config == 'autodetect' +diff --git a/pam-extensions/gdm-custom-json-pam-extension.h b/pam-extensions/gdm-custom-json-pam-extension.h +new file mode 100644 +index 000000000..a50ae1264 +--- /dev/null ++++ b/pam-extensions/gdm-custom-json-pam-extension.h +@@ -0,0 +1,61 @@ ++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- ++ * ++ * Copyright (C) 2023 Canonical Ltd. ++ * ++ * 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. ++ * ++ * Author: Marco Trevisan (TreviƱo) ++ * ++ */ ++ ++#pragma once ++ ++#include "gdm-pam-extensions-common.h" ++ ++typedef struct { ++ GdmPamExtensionMessage header; ++ ++ const char protocol_name[64]; ++ unsigned int version; ++ char *json; ++} GdmPamExtensionJSONProtocol; ++ ++#define GDM_PAM_EXTENSION_CUSTOM_JSON "org.gnome.DisplayManager.UserVerifier.CustomJSON" ++#define GDM_PAM_EXTENSION_CUSTOM_JSON_SIZE sizeof (GdmPamExtensionJSONProtocol) ++ ++#define GDM_PAM_EXTENSION_CUSTOM_JSON_REQUEST_INIT(request, proto_name, proto_version, json_str) \ ++{ \ ++ size_t proto_len = strnlen ((proto_name), sizeof ((request)->protocol_name) - 1); \ ++ GDM_PAM_EXTENSION_LOOK_UP_TYPE (GDM_PAM_EXTENSION_CUSTOM_JSON, &((request)->header.type)); \ ++ (request)->header.length = htobe32 (GDM_PAM_EXTENSION_CUSTOM_JSON_SIZE); \ ++ memcpy ((char *)(request)->protocol_name, (proto_name), proto_len); \ ++ ((char *)((request)->protocol_name))[proto_len] = '\0'; \ ++ (request)->version = (proto_version); \ ++ (request)->json = (char *) (json_str); \ ++} ++ ++#define GDM_PAM_EXTENSION_CUSTOM_JSON_RESPONSE_INIT(response, proto_name, proto_version) \ ++{ \ ++ size_t proto_len = strnlen ((proto_name), sizeof ((response)->protocol_name) - 1); \ ++ GDM_PAM_EXTENSION_LOOK_UP_TYPE (GDM_PAM_EXTENSION_CUSTOM_JSON, &((response)->header.type)); \ ++ (response)->header.length = htobe32 (GDM_PAM_EXTENSION_CUSTOM_JSON_SIZE); \ ++ memcpy ((char *)(response)->protocol_name, (proto_name), proto_len); \ ++ ((char *)((response)->protocol_name))[proto_len] = '\0'; \ ++ (response)->version = (proto_version); \ ++ (response)->json = NULL; \ ++} ++ ++#define GDM_PAM_EXTENSION_REPLY_TO_CUSTOM_JSON_RESPONSE(reply) \ ++ ((GdmPamExtensionJSONProtocol *) (void *) reply->resp) +diff --git a/pam-extensions/gdm-pam-extensions.h b/pam-extensions/gdm-pam-extensions.h +index cdf34f382..9df8e37b8 100644 +--- a/pam-extensions/gdm-pam-extensions.h ++++ b/pam-extensions/gdm-pam-extensions.h +@@ -1,6 +1,7 @@ + /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2017 Red Hat, Inc. ++ * Copyright (C) 2023 Canonical Ltd. + * + * 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 +@@ -21,5 +22,6 @@ + #define GDM_PAM_EXTENSIONS_H + + #include "gdm-choice-list-pam-extension.h" ++#include "gdm-custom-json-pam-extension.h" + + #endif +-- +2.47.3 + + +From b55b99e95199b22fa58b4522712b880224d5975c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Thu, 23 Nov 2023 00:54:35 +0100 +Subject: [PATCH 6/7] session: Use literal errors when we don't do any + formatting + +--- + daemon/gdm-session.c | 20 ++++++++++---------- + 1 file changed, 10 insertions(+), 10 deletions(-) + +diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c +index 904143677..6f53b5dfa 100644 +--- a/daemon/gdm-session.c ++++ b/daemon/gdm-session.c +@@ -756,9 +756,9 @@ gdm_session_handle_choice_list_query (GdmDBusWorkerManager *worker_manager_inte + gdm_dbus_user_verifier_choice_list_interface_info ()->name); + + if (choice_list_interface == NULL) { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, +- G_DBUS_ERROR_NOT_SUPPORTED, +- "ChoiceList interface not supported by client"); ++ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ "ChoiceList interface not supported by client"); + return TRUE; + } + +@@ -1201,9 +1201,9 @@ register_worker (GdmDBusWorkerManager *worker_manager_interface, + if (conversation == NULL) { + g_warning ("GdmSession: New worker connection is from unknown source"); + +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, +- G_DBUS_ERROR_ACCESS_DENIED, +- "Connection is not from a known conversation"); ++ g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Connection is not from a known conversation"); + g_dbus_connection_close_sync (connection, NULL, NULL); + return TRUE; + } +@@ -1387,10 +1387,10 @@ begin_verification_conversation (GdmSession *self, + } + + if (conversation == NULL) { +- g_dbus_method_invocation_return_error (invocation, +- G_DBUS_ERROR, +- G_DBUS_ERROR_SPAWN_FAILED, +- _("Could not create authentication helper process")); ++ g_dbus_method_invocation_return_error_literal (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_SPAWN_FAILED, ++ _("Could not create authentication helper process")); + } + + return conversation; +-- +2.47.3 + + +From edc1af093fa697aabe79d8042b82cbf108ba6ce2 Mon Sep 17 00:00:00 2001 +From: Sergio Durigan Junior +Date: Tue, 2 Jan 2024 19:27:33 -0500 +Subject: [PATCH 7/7] gdm-pam-extensions: Install + gdm-custom-json-pam-extension.h header + +Signed-off-by: Sergio Durigan Junior +--- + pam-extensions/meson.build | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/pam-extensions/meson.build b/pam-extensions/meson.build +index 02f4ff601..59567c9e1 100644 +--- a/pam-extensions/meson.build ++++ b/pam-extensions/meson.build +@@ -9,7 +9,8 @@ if pam_extensions_supported + + header_files = files('gdm-pam-extensions.h', + 'gdm-pam-extensions-common.h', +- 'gdm-choice-list-pam-extension.h') ++ 'gdm-choice-list-pam-extension.h', ++ 'gdm-custom-json-pam-extension.h') + + pam_extensions_inc = include_directories('.') + install_headers(header_files, +-- +2.47.3 + diff --git a/SOURCES/0002-gdm-x-session-run-session-bus-on-non-seat0-seats.patch b/SOURCES/0002-gdm-x-session-run-session-bus-on-non-seat0-seats.patch index 000b95c..23a1cba 100644 --- a/SOURCES/0002-gdm-x-session-run-session-bus-on-non-seat0-seats.patch +++ b/SOURCES/0002-gdm-x-session-run-session-bus-on-non-seat0-seats.patch @@ -13,70 +13,20 @@ seats get their own session bus. 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c -index a65fa0f9..f13b54af 100644 +index 71c0633..3834d40 100644 --- a/daemon/gdm-session.c +++ b/daemon/gdm-session.c -@@ -2864,119 +2864,128 @@ on_start_program_cb (GdmDBusWorker *worker, - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED) || - g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - return; - - self = conversation->session; - service_name = conversation->service_name; - - if (worked) { - self->session_pid = pid; - self->session_conversation = conversation; - - g_debug ("GdmSession: Emitting 'session-started' signal with pid '%d'", pid); - g_signal_emit (self, signals[SESSION_STARTED], 0, service_name, pid); - } else { - gdm_session_stop_conversation (self, service_name); - - g_debug ("GdmSession: Emitting 'session-start-failed' signal"); - g_signal_emit (self, signals[SESSION_START_FAILED], 0, service_name, error->message); - } - } - - void - gdm_session_start_session (GdmSession *self, - const char *service_name) - { - GdmSessionConversation *conversation; - GdmSessionDisplayMode display_mode; +@@ -3060,6 +3060,7 @@ gdm_session_start_session (GdmSession *self, gboolean is_x11 = TRUE; gboolean run_launcher = FALSE; gboolean allow_remote_connections = FALSE; + gboolean run_separate_bus = FALSE; char *command; char *program; - gboolean register_session; + gboolean needs_registration; +@@ -3090,6 +3091,10 @@ gdm_session_start_session (GdmSession *self, - g_return_if_fail (GDM_IS_SESSION (self)); - g_return_if_fail (self->session_conversation == NULL); - - conversation = find_conversation_by_name (self, service_name); - - if (conversation == NULL) { - g_warning ("GdmSession: Tried to start session of " - "nonexistent conversation %s", service_name); - return; - } - - stop_all_other_conversations (self, conversation, FALSE); - - display_mode = gdm_session_get_display_mode (self); - - #ifdef ENABLE_WAYLAND_SUPPORT - is_x11 = g_strcmp0 (self->session_type, "wayland") != 0; - #endif - - if (display_mode == GDM_SESSION_DISPLAY_MODE_LOGIND_MANAGED || - display_mode == GDM_SESSION_DISPLAY_MODE_NEW_VT) { - run_launcher = TRUE; - } - - register_session = !gdm_session_session_registers (self); + needs_registration = !gdm_session_session_registers (self); + if (g_strcmp0 (self->display_seat_id, "seat0") != 0 && !run_launcher) { + run_separate_bus = TRUE; @@ -85,28 +35,7 @@ index a65fa0f9..f13b54af 100644 if (self->selected_program == NULL) { gboolean run_xsession_script; - command = get_session_command (self); - - run_xsession_script = !gdm_session_bypasses_xsession (self); - - if (self->display_is_local) { - gboolean disallow_tcp = TRUE; - gdm_settings_direct_get_boolean (GDM_KEY_DISALLOW_TCP, &disallow_tcp); - allow_remote_connections = !disallow_tcp; - } else { - allow_remote_connections = TRUE; - } - - if (run_launcher) { - if (is_x11) { - program = g_strdup_printf (LIBEXECDIR "/gdm-x-session %s%s %s\"%s\"", - register_session ? "--register-session " : "", - run_xsession_script? "--run-script " : "", - allow_remote_connections? "--allow-remote-connections " : "", - command); - } else { - program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session %s\"%s\"", - register_session ? "--register-session " : "", +@@ -3118,7 +3123,11 @@ gdm_session_start_session (GdmSession *self, command); } } else if (run_xsession_script) { @@ -119,33 +48,3 @@ index a65fa0f9..f13b54af 100644 } else { program = g_strdup (command); } - - g_free (command); - } else { - /* FIXME: - * Always use a separate DBus bus for each greeter session. - * Firstly, this means that if we run multiple greeter session - * (which we really should not do, but have to currently), then - * each one will get its own DBus session bus. - * But, we also explicitly do this for seat0, because that way - * it cannot make use of systemd to run the GNOME session. This - * prevents the session lookup logic from getting confused. - * This has a similar effect as passing --builtin to gnome-session. - * - * We really should not be doing this. But the fix is to use - * separate dynamically created users and that requires some - * major refactorings. - */ - if (run_launcher) { - if (is_x11) { - program = g_strdup_printf (LIBEXECDIR "/gdm-x-session %s\"dbus-run-session -- %s\"", - register_session ? "--register-session " : "", - self->selected_program); - } else { - program = g_strdup_printf (LIBEXECDIR "/gdm-wayland-session %s\"dbus-run-session -- %s\"", - register_session ? "--register-session " : "", - self->selected_program); - } --- -2.37.3 - diff --git a/SOURCES/0002-local-display-factory-Fix-user-switching-with-legacy.patch b/SOURCES/0002-local-display-factory-Fix-user-switching-with-legacy.patch index dad68eb..a3c3d05 100644 --- a/SOURCES/0002-local-display-factory-Fix-user-switching-with-legacy.patch +++ b/SOURCES/0002-local-display-factory-Fix-user-switching-with-legacy.patch @@ -1,7 +1,7 @@ From 510566699c480226b189215c6222f7e72979baf8 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 22 May 2024 14:05:20 -0400 -Subject: [PATCH 2/4] local-display-factory: Fix user switching with legacy +Subject: [PATCH 2/5] local-display-factory: Fix user switching with legacy xorg legacy-xorg sessions currently fail to completely user switch. diff --git a/SOURCES/0003-local-display-factory-Ensure-displays-are-properly-h.patch b/SOURCES/0003-local-display-factory-Ensure-displays-are-properly-h.patch index a3896c4..ba7236e 100644 --- a/SOURCES/0003-local-display-factory-Ensure-displays-are-properly-h.patch +++ b/SOURCES/0003-local-display-factory-Ensure-displays-are-properly-h.patch @@ -1,7 +1,7 @@ From de73b654cd1b726b905a9bf3238c7eaabfe465d5 Mon Sep 17 00:00:00 2001 From: Joan Torres Date: Fri, 13 Jun 2025 13:05:07 +0200 -Subject: [PATCH 3/4] local-display-factory: Ensure displays are properly +Subject: [PATCH 3/5] local-display-factory: Ensure displays are properly handled on status change 1. There are some cases where a display will change its status from any diff --git a/SOURCES/0004-local-display-factory-Return-a-session-type-on-legac.patch b/SOURCES/0004-local-display-factory-Return-a-session-type-on-legac.patch index 6c226a5..5e6304d 100644 --- a/SOURCES/0004-local-display-factory-Return-a-session-type-on-legac.patch +++ b/SOURCES/0004-local-display-factory-Return-a-session-type-on-legac.patch @@ -1,7 +1,7 @@ From 8bcb9f43c203ab4818381cd707128eac74ab958d Mon Sep 17 00:00:00 2001 From: Joan Torres Date: Fri, 13 Jun 2025 12:56:41 +0200 -Subject: [PATCH 4/4] local-display-factory: Return a session type on +Subject: [PATCH 4/5] local-display-factory: Return a session type on legacy-xorg On legacy-xorg, there's graphic support through Xorg. diff --git a/SOURCES/0005-local-display-factory-Consider-tty-when-ensuring-dis.patch b/SOURCES/0005-local-display-factory-Consider-tty-when-ensuring-dis.patch new file mode 100644 index 0000000..655528a --- /dev/null +++ b/SOURCES/0005-local-display-factory-Consider-tty-when-ensuring-dis.patch @@ -0,0 +1,81 @@ +From ce2a019ad18e4e11eb45218fa5a86055fa8a1533 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Mon, 1 Jun 2026 20:04:50 +0200 +Subject: [PATCH 5/5] local-display-factory: Consider tty when ensuring display on seat + +In "leagcy-xorg" mode there can be cases where ensure_display() is called +because some new device was inserted or seat properties changed... in +those cases we were creating a new display when active_vt != GDM_INITIAL_VT while +there was already a display on that vt. + +To fix that, check if there's already a display in the active_vt. +--- + daemon/gdm-local-display-factory.c | 35 +++++++++++++++--------------- + 1 file changed, 17 insertions(+), 18 deletions(-) + +diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c +index 3a35018..393437c 100644 +--- a/daemon/gdm-local-display-factory.c ++++ b/daemon/gdm-local-display-factory.c +@@ -110,6 +110,10 @@ static gboolean lookup_by_session_id (const char *id, + GdmDisplay *display, + gpointer user_data); + ++static gboolean lookup_by_tty (const char *id, ++ GdmDisplay *display, ++ gpointer user_data); ++ + G_DEFINE_TYPE (GdmLocalDisplayFactory, gdm_local_display_factory, GDM_TYPE_DISPLAY_FACTORY) + + GQuark +@@ -674,18 +678,14 @@ lookup_prepared_display_by_seat_id (const char *id, + } + + static gboolean +-lookup_managed_display_by_seat_id (const char *id, +- GdmDisplay *display, +- gpointer user_data) ++lookup_managed_display_by_tty (const char *id, ++ GdmDisplay *display, ++ gpointer user_data) + { +- int status; +- +- status = gdm_display_get_status (display); +- +- if (status != GDM_DISPLAY_MANAGED) ++ if (gdm_display_get_status (display) != GDM_DISPLAY_MANAGED) + return FALSE; + +- return lookup_by_seat_id (id, display, user_data); ++ return lookup_by_tty (id, display, user_data); + } + + #ifdef HAVE_UDEV +@@ -919,15 +919,14 @@ ensure_display_for_seat (GdmLocalDisplayFactory *factory, + + if (is_seat0) { + if (g_strcmp0 (preferred_display_server, "legacy-xorg") == 0) { +- GdmDisplay *initial_display = NULL; +- +- display = gdm_display_store_find (store, lookup_managed_display_by_seat_id, (gpointer) seat_id); +- initial_display = gdm_display_store_find (store, lookup_initial_display, (gpointer) NULL); +- +- if (initial_display == NULL || factory->active_vt != GDM_INITIAL_VT) +- display = NULL; +- +- is_initial = initial_display == NULL; ++ display = gdm_display_store_find (store, lookup_initial_display, (gpointer) NULL); ++ if (display == NULL) { ++ is_initial = TRUE; ++ } else if (factory->active_vt != 0 && factory->active_vt != GDM_INITIAL_VT) { ++ g_autofree char *tty = g_strdup_printf ("tty%u", factory->active_vt); ++ display = gdm_display_store_find (store, lookup_managed_display_by_tty, (gpointer) tty); ++ is_initial = FALSE; ++ } + } else { + display = gdm_display_store_find (store, lookup_prepared_display_by_seat_id, (gpointer) seat_id); + is_initial = TRUE; +-- +2.54.0 + diff --git a/SPECS/gdm.spec b/SPECS/gdm.spec index 9f6bd30..db3ec1f 100644 --- a/SPECS/gdm.spec +++ b/SPECS/gdm.spec @@ -11,7 +11,7 @@ Name: gdm Epoch: 1 Version: 40.1 -Release: 41%{?dist} +Release: 48%{?dist} Summary: The GNOME Display Manager License: GPLv2+ @@ -57,6 +57,7 @@ Patch100001: 0001-gdm-session-Force-reuse-vt-mode-for-legacy-Xorg-mode.patch Patch100002: 0002-local-display-factory-Fix-user-switching-with-legacy.patch Patch100003: 0003-local-display-factory-Ensure-displays-are-properly-h.patch Patch100004: 0004-local-display-factory-Return-a-session-type-on-legac.patch +Patch100005: 0005-local-display-factory-Consider-tty-when-ensuring-dis.patch Patch110001: 0001-display-Add-new-FAILING-state.patch Patch110002: 0002-manager-Quit-plymouth-at-first-sign-of-failure.patch @@ -71,9 +72,17 @@ Patch140001: 0001-session-Fix-memory-leak-on-new-outside-connection.patch Patch150001: 0001-libgdm-Don-t-collect-twice-sessions-on-usr-share.patch Patch160001: 0001-Revert-hack-that-quits-plymouth-late.patch +Patch160002: 0001-manager-Update-RegisterSession-dbus-method.patch +Patch160003: 0001-manager-Schedule-deferred-plymouth-quit-on-session-r.patch Patch170001: 0001-session-record-Rework-wtmp-utmp-btmp-fields.patch +# Passwordless GDM patches +Patch180001: 0001-pam-extensions-Add-support-for-generic-GDM-CustomJSON.patch +Patch180002: 0001-data-Add-support-for-unified-authentication.patch + +Patch190001: 0001-data-Add-PAM-substack-wrappers-to-prevent-auth-bypas.patch + # Non-upstreamable workarounds Patch66610001: 0001-data-reap-gdm-sessions-on-shutdown.patch @@ -108,6 +117,7 @@ BuildRequires: pkgconfig(gobject-introspection-1.0) BuildRequires: pkgconfig(gtk+-3.0) >= %{gtk3_version} BuildRequires: pkgconfig(gudev-1.0) BuildRequires: pkgconfig(iso-codes) +BuildRequires: pkgconfig(json-glib-1.0) BuildRequires: pkgconfig(libcanberra-gtk3) BuildRequires: pkgconfig(libselinux) BuildRequires: pkgconfig(libsystemd) @@ -309,7 +319,7 @@ dconf update || : %config %{_sysconfdir}/gdm/PreSession/* %config %{_sysconfdir}/gdm/PostSession/* %config %{_sysconfdir}/pam.d/gdm-autologin -%config %{_sysconfdir}/pam.d/gdm-password +%config %{_sysconfdir}/pam.d/gdm-password* # not config files %{_sysconfdir}/gdm/Xsession %{_datadir}/gdm/gdm.schemas @@ -354,8 +364,9 @@ dconf update || : %attr(0600, gdm, gdm) %{_localstatedir}/lib/gdm/.config/pulse/default.pa %attr(0711, root, gdm) %dir /run/gdm %config %{_sysconfdir}/pam.d/gdm-pin -%config %{_sysconfdir}/pam.d/gdm-smartcard -%config %{_sysconfdir}/pam.d/gdm-fingerprint +%config %{_sysconfdir}/pam.d/gdm-smartcard* +%config %{_sysconfdir}/pam.d/gdm-switchable* +%config %{_sysconfdir}/pam.d/gdm-fingerprint* %{_sysconfdir}/pam.d/gdm-launch-environment %{_udevrulesdir}/61-gdm.rules %{_unitdir}/gdm.service @@ -368,15 +379,50 @@ dconf update || : %dir %{_includedir}/gdm %{_includedir}/gdm/*.h %exclude %{_includedir}/gdm/gdm-pam-extensions.h +%exclude %{_includedir}/gdm/gdm-choice-list-pam-extension.h +%exclude %{_includedir}/gdm/gdm-custom-json-pam-extension.h +%exclude %{_includedir}/gdm/gdm-pam-extensions-common.h %dir %{_datadir}/gir-1.0 %{_datadir}/gir-1.0/Gdm-1.0.gir %{_libdir}/pkgconfig/gdm.pc %files pam-extensions-devel %{_includedir}/gdm/gdm-pam-extensions.h +%{_includedir}/gdm/gdm-choice-list-pam-extension.h +%{_includedir}/gdm/gdm-custom-json-pam-extension.h +%{_includedir}/gdm/gdm-pam-extensions-common.h %{_libdir}/pkgconfig/gdm-pam-extensions.pc %changelog +* Fri Jul 10 2026 Joan Torres Lopez - 40.1-48 +- Fix auth bypass in authselect pam substacks + Resolves: https://redhat.atlassian.net/browse/RHEL-194090 + +* Mon Jun 1 2026 Joan Torres Lopez - 40.1-47 +- Create display in "legacy-xorg" mode checking displays in tty + Resolves: https://redhat.atlassian.net/browse/RHEL-180822 + +* Thu May 28 2026 Joan Torres Lopez - 40.1-46 +- Fix last change when setting xsession program + Related: https://redhat.atlassian.net/browse/RHEL-178686 + +* Mon May 25 2026 Joan Torres Lopez - 40.1-45 +- Update how GDM handles Registering session/display + to properly terminate plymouth + Resolves: https://redhat.atlassian.net/browse/RHEL-178686 + +* Wed Apr 22 2026 Joan Torres Lopez - 40.1-44 +- Add pam-extension helpers in pam-extensions-devel + Related: https://redhat.atlassian.net/browse/RHEL-139178 + +* Tue Apr 14 2026 Joan Torres Lopez - 40.1-43 +- Backport passwordless feature to RHEL 9 + Resolves: https://redhat.atlassian.net/browse/RHEL-139178 + +* Fri Mar 6 2026 Joan Torres Lopez - 40.1-42 +- Terminate conflicting sesions started outside of GDM + Resolves: RHEL-4108 + * Tue Nov 18 2025 Joan Torres Lopez - 40.1-41 - Fix recording wtmp/utmp/btmp Resolves: RHEL-129305