diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | configure.ac | 11 | ||||
-rw-r--r-- | daemon/Makefile.am | 5 | ||||
-rw-r--r-- | daemon/gdm-session-worker.c | 103 | ||||
-rw-r--r-- | daemon/gdm-session-worker.h | 2 | ||||
-rw-r--r-- | daemon/gdm-session.c | 49 | ||||
-rw-r--r-- | daemon/gdm-session.xml | 3 | ||||
-rw-r--r-- | pam-extensions/Makefile.am | 23 | ||||
-rw-r--r-- | pam-extensions/gdm-pam-extensions.h | 133 | ||||
-rw-r--r-- | pam-extensions/gdm-pam-extensions.pc.in | 9 |
10 files changed, 325 insertions, 14 deletions
diff --git a/Makefile.am b/Makefile.am index a6409e1b..25d679f6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,6 +3,7 @@ NULL = SUBDIRS = \ data \ common \ + pam-extensions \ daemon \ libgdm \ utils \ diff --git a/configure.ac b/configure.ac index c9f69cf4..3c91520b 100644 --- a/configure.ac +++ b/configure.ac @@ -539,6 +539,15 @@ if test "x$have_pam" = "xyes"; then )], [AC_MSG_RESULT(["not found - assume const, Linux-type PAM"])] ) + AC_CHECK_DECL(PAM_BINARY_PROMPT, + [supports_pam_extensions=yes], + [supports_pam_extensions=no], + #include <security/pam_appl.h> + ) +fi +if test "x$supports_pam_extensions" = "xyes" ; then + AM_CONDITIONAL(SUPPORTS_PAM_EXTENSIONS, true) + AC_DEFINE(SUPPORTS_PAM_EXTENSIONS, 1, [Define if PAM supports GDMs custom extensions]) fi AC_CHECK_LIB(keyutils, keyctl_read, [ @@ -1548,6 +1557,8 @@ AC_DEFINE_UNQUOTED(X_XNEST_UNSCALED_FONTPATH,"$X_XNEST_UNSCALED_FONTPATH",[]) AC_CONFIG_FILES([ Makefile +pam-extensions/Makefile +pam-extensions/gdm-pam-extensions.pc daemon/Makefile docs/Makefile chooser/Makefile diff --git a/daemon/Makefile.am b/daemon/Makefile.am index bf55cb67..5e9eb5e0 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -4,6 +4,7 @@ AM_CPPFLAGS = \ -I. \ -I.. \ -I$(top_srcdir)/common \ + -I$(top_srcdir)/pam-extensions \ -I$(top_builddir)/common \ -DBINDIR=\"$(bindir)\" \ -DDATADIR=\"$(datadir)\" \ @@ -133,6 +134,10 @@ gdm_session_worker_SOURCES = \ gdm-dbus-util.h \ $(NULL) +if SUPPORTS_PAM_EXTENSIONS +gdm_session_worker_SOURCES += $(top_srcdir)/pam-extensions/gdm-pam-extensions.h +endif + nodist_gdm_session_worker_SOURCES = \ gdm-session-glue.h \ gdm-session-glue.c \ diff --git a/daemon/gdm-session-worker.c b/daemon/gdm-session-worker.c index 66d9e6d1..aafc8880 100644 --- a/daemon/gdm-session-worker.c +++ b/daemon/gdm-session-worker.c @@ -61,6 +61,11 @@ #include "gdm-common.h" #include "gdm-log.h" + +#ifdef SUPPORTS_PAM_EXTENSIONS +#include "gdm-pam-extensions.h" +#endif + #include "gdm-session-worker.h" #include "gdm-session-glue.h" #include "gdm-session.h" @@ -149,6 +154,7 @@ struct GdmSessionWorkerPrivate uid_t uid; gid_t gid; gboolean password_is_required; + char **extensions; int cred_flags; int login_vt; @@ -177,6 +183,15 @@ struct GdmSessionWorkerPrivate GDBusMethodInvocation *pending_invocation; }; +#ifdef SUPPORTS_PAM_EXTENSIONS +static char gdm_pam_extension_environment_block[_POSIX_ARG_MAX]; + +static const char * const +gdm_supported_pam_extensions[] = { + NULL +}; +#endif + enum { PROP_0, PROP_SERVER_ADDRESS, @@ -519,6 +534,32 @@ gdm_session_worker_report_problem (GdmSessionWorker *worker, NULL); } +#ifdef SUPPORTS_PAM_EXTENSIONS +static gboolean +gdm_session_worker_process_extended_pam_message (GdmSessionWorker *worker, + const struct pam_message *query, + char **response) +{ + GdmPamExtensionMessage *extended_message; + gboolean res; + + extended_message = GDM_PAM_EXTENSION_MESSAGE_FROM_PAM_MESSAGE (query); + + if (GDM_PAM_EXTENSION_MESSAGE_TRUNCATED (extended_message)) { + g_warning ("PAM service requested binary response for truncated query"); + return FALSE; + } + + if (GDM_PAM_EXTENSION_MESSAGE_INVALID_TYPE (extended_message)) { + g_warning ("PAM service requested binary response for unadvertised query type"); + return FALSE; + } + + g_debug ("GdmSessionWorker: received extended pam message of unknown type %u", (unsigned int) extended_message->type); + return FALSE; +} +#endif + static char * convert_to_utf8 (const char *str) { @@ -563,6 +604,11 @@ gdm_session_worker_process_pam_message (GdmSessionWorker *worker, gdm_session_worker_update_username (worker); +#ifdef SUPPORTS_PAM_EXTENSIONS + if (query->msg_style == PAM_BINARY_PROMPT) + return gdm_session_worker_process_extended_pam_message (worker, query, response); +#endif + g_debug ("GdmSessionWorker: received pam message of type %u with payload '%s'", query->msg_style, query->msg); @@ -991,16 +1037,17 @@ out: } static gboolean -gdm_session_worker_initialize_pam (GdmSessionWorker *worker, - const char *service, - const char *username, - const char *hostname, - gboolean display_is_local, - const char *x11_display_name, - const char *x11_authority_file, - const char *display_device, - const char *seat_id, - GError **error) +gdm_session_worker_initialize_pam (GdmSessionWorker *worker, + const char *service, + const char * const *extensions, + const char *username, + const char *hostname, + gboolean display_is_local, + const char *x11_display_name, + const char *x11_authority_file, + const char *display_device, + const char *seat_id, + GError **error) { struct pam_conv pam_conversation; int error_code; @@ -1013,6 +1060,12 @@ gdm_session_worker_initialize_pam (GdmSessionWorker *worker, username ? username : "(null)", seat_id ? seat_id : "(null)"); +#ifdef SUPPORTS_PAM_EXTENSIONS + if (extensions != NULL) { + GDM_PAM_EXTENSION_ADVERTISE_SUPPORTED_EXTENSIONS (gdm_pam_extension_environment_block, extensions); + } +#endif + pam_conversation.conv = (GdmSessionWorkerPamNewMessagesFunc) gdm_session_worker_pam_new_messages_handler; pam_conversation.appdata_ptr = worker; @@ -2451,6 +2504,7 @@ do_setup (GdmSessionWorker *worker) error = NULL; res = gdm_session_worker_initialize_pam (worker, worker->priv->service, + (const char **) worker->priv->extensions, worker->priv->username, worker->priv->hostname, worker->priv->display_is_local, @@ -2817,6 +2871,32 @@ gdm_session_worker_handle_open (GdmDBusWorker *object, return TRUE; } +static char ** +filter_extensions (const char * const *extensions) +{ + size_t i, j; + GPtrArray *array = NULL; + char **filtered_extensions = NULL; + + array = g_ptr_array_new (); + + for (i = 0; extensions[i] != NULL; i++) { + for (j = 0; gdm_supported_pam_extensions[j] != NULL; j++) { + if (g_strcmp0 (extensions[i], gdm_supported_pam_extensions[j]) == 0) { + g_ptr_array_add (array, g_strdup (gdm_supported_pam_extensions[j])); + break; + } + } + } + g_ptr_array_add (array, NULL); + + filtered_extensions = g_strdupv ((char **) array->pdata); + + g_ptr_array_free (array, TRUE); + + return filtered_extensions; +} + static gboolean gdm_session_worker_handle_initialize (GdmDBusWorker *object, GDBusMethodInvocation *invocation, @@ -2833,6 +2913,8 @@ gdm_session_worker_handle_initialize (GdmDBusWorker *object, while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) { if (g_strcmp0 (key, "service") == 0) { worker->priv->service = g_strdup (g_variant_get_string (value, NULL)); + } else if (g_strcmp0 (key, "extensions") == 0) { + worker->priv->extensions = filter_extensions (g_variant_get_strv (value, NULL)); } else if (g_strcmp0 (key, "username") == 0) { worker->priv->username = g_strdup (g_variant_get_string (value, NULL)); } else if (g_strcmp0 (key, "is-program-session") == 0) { @@ -3365,6 +3447,7 @@ gdm_session_worker_finalize (GObject *object) g_free (worker->priv->username); g_free (worker->priv->server_address); g_strfreev (worker->priv->arguments); + g_strfreev (worker->priv->extensions); g_hash_table_unref (worker->priv->reauthentication_requests); diff --git a/daemon/gdm-session-worker.h b/daemon/gdm-session-worker.h index cba3a4ec..5603e80e 100644 --- a/daemon/gdm-session-worker.h +++ b/daemon/gdm-session-worker.h @@ -52,7 +52,5 @@ GType gdm_session_worker_get_type (void); GdmSessionWorker * gdm_session_worker_new (const char *server_address, gboolean is_for_reauth) G_GNUC_MALLOC; - G_END_DECLS - #endif /* GDM_SESSION_WORKER_H */ diff --git a/daemon/gdm-session.c b/daemon/gdm-session.c index 812c7be0..aa437e56 100644 --- a/daemon/gdm-session.c +++ b/daemon/gdm-session.c @@ -101,6 +101,7 @@ struct _GdmSessionPrivate char **conversation_environment; GdmDBusUserVerifier *user_verifier_interface; + GHashTable *user_verifier_extensions; GdmDBusGreeter *greeter_interface; GdmDBusRemoteGreeter *remote_greeter_interface; GdmDBusChooser *chooser_interface; @@ -1219,6 +1220,20 @@ begin_verification_conversation (GdmSession *self, } static gboolean +gdm_session_handle_client_enable_extensions (GdmDBusUserVerifier *user_verifier_interface, + GDBusMethodInvocation *invocation, + const char * const * extensions, + GDBusConnection *connection) +{ + GdmSession *self = g_object_get_data (G_OBJECT (connection), "gdm-session"); + + g_hash_table_remove_all (self->priv->user_verifier_extensions); + + gdm_dbus_user_verifier_complete_enable_extensions (user_verifier_interface, invocation); + + return TRUE; +} +static gboolean gdm_session_handle_client_begin_verification (GdmDBusUserVerifier *user_verifier_interface, GDBusMethodInvocation *invocation, const char *service_name, @@ -1372,6 +1387,13 @@ export_user_verifier_interface (GdmSession *self, { GdmDBusUserVerifier *user_verifier_interface; user_verifier_interface = GDM_DBUS_USER_VERIFIER (gdm_dbus_user_verifier_skeleton_new ()); + + g_object_set_data (G_OBJECT (connection), "gdm-session", self); + + g_signal_connect (user_verifier_interface, + "handle-enable-extensions", + G_CALLBACK (gdm_session_handle_client_enable_extensions), + connection); g_signal_connect (user_verifier_interface, "handle-begin-verification", G_CALLBACK (gdm_session_handle_client_begin_verification), @@ -1811,6 +1833,15 @@ next_line: } static void +unexport_and_free_user_verifier_extension (GDBusInterfaceSkeleton *interface) +{ + g_dbus_interface_skeleton_unexport (interface); + + g_object_run_dispose (G_OBJECT (interface)); + g_object_unref (interface); +} + +static void gdm_session_init (GdmSession *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, @@ -1826,6 +1857,11 @@ gdm_session_init (GdmSession *self) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_free); + self->priv->user_verifier_extensions = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify) + unexport_and_free_user_verifier_extension); load_lang_config_file (self); setup_worker_server (self); @@ -2099,14 +2135,19 @@ initialize (GdmSession *self, const char *username, const char *log_file) { - GVariantBuilder details; - GdmSessionConversation *conversation; + GVariantBuilder details; + const char **extensions; + GdmSessionConversation *conversation; g_assert (service_name != NULL); g_variant_builder_init (&details, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add_parsed (&details, "{'service', <%s>}", service_name); + extensions = (const char **) g_hash_table_get_keys_as_array (self->priv->user_verifier_extensions, NULL); + + g_variant_builder_add_parsed (&details, "{'extensions', <%^as>}", extensions); + if (username != NULL) g_variant_builder_add_parsed (&details, "{'username', <%s>}", username); @@ -2148,6 +2189,8 @@ initialize (GdmSession *self, (GAsyncReadyCallback) on_initialization_complete_cb, conversation); } + + g_free (extensions); } void @@ -3294,6 +3337,8 @@ gdm_session_dispose (GObject *object) g_hash_table_unref); g_clear_object (&self->priv->user_verifier_interface); + g_clear_pointer (&self->priv->user_verifier_extensions, + g_hash_table_unref); g_clear_object (&self->priv->greeter_interface); g_clear_object (&self->priv->chooser_interface); diff --git a/daemon/gdm-session.xml b/daemon/gdm-session.xml index 9d44005b..af2976a4 100644 --- a/daemon/gdm-session.xml +++ b/daemon/gdm-session.xml @@ -24,6 +24,9 @@ </method> </interface> <interface name="org.gnome.DisplayManager.UserVerifier"> + <method name="EnableExtensions"> + <arg name="extensions" direction="in" type="as"/> + </method> <method name="BeginVerification"> <arg name="service_name" direction="in" type="s"/> </method> diff --git a/pam-extensions/Makefile.am b/pam-extensions/Makefile.am new file mode 100644 index 00000000..572494ac --- /dev/null +++ b/pam-extensions/Makefile.am @@ -0,0 +1,23 @@ +NULL = + +AM_CPPFLAGS = \ + -I$(srcdir) \ + -I$(builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -DG_LOG_DOMAIN=\"Gdm\" \ + -DDMCONFDIR=\""$(dmconfdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(NULL) + +if SUPPORTS_PAM_EXTENSIONS +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = gdm-pam-extensions.pc + +pam_extensions_includedir = $(includedir)/gdm +pam_extensions_include_HEADERS = gdm-pam-extensions.h +endif + +EXTRA_DIST = \ + gdm-pam-extensions.pc \ + $(NULL) diff --git a/pam-extensions/gdm-pam-extensions.h b/pam-extensions/gdm-pam-extensions.h new file mode 100644 index 00000000..eff2f40a --- /dev/null +++ b/pam-extensions/gdm-pam-extensions.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_H +#define GDM_PAM_EXTENSIONS_H + +#include <alloca.h> +#include <endian.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <limits.h> + +#include <security/pam_appl.h> + +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.pc.in b/pam-extensions/gdm-pam-extensions.pc.in new file mode 100644 index 00000000..5fc64b01 --- /dev/null +++ b/pam-extensions/gdm-pam-extensions.pc.in @@ -0,0 +1,9 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: GDM PAM Extensions +Description: Macros for custom protocols over PAM +Version: @VERSION@ +Cflags: -I${includedir}/gdm |