From 88023d834abcd5b7639f0d27c7aa788ee8a2edb9 Mon Sep 17 00:00:00 2001 From: Joan Torres Lopez Date: Wed, 29 Jul 2026 00:00:51 +0200 Subject: [PATCH] Add smartcard redirection support Resolves: RHEL-219073 --- add-smartcard-redirection-support.patch | 7533 +++++++++++++++++++++++ gnome-remote-desktop.spec | 16 +- 2 files changed, 7548 insertions(+), 1 deletion(-) create mode 100644 add-smartcard-redirection-support.patch diff --git a/add-smartcard-redirection-support.patch b/add-smartcard-redirection-support.patch new file mode 100644 index 0000000..9972403 --- /dev/null +++ b/add-smartcard-redirection-support.patch @@ -0,0 +1,7533 @@ +From 7f1620a727e698a3b73c01d13063b53e7473ef65 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Jul 2026 00:33:21 +0200 +Subject: [PATCH 01/11] session-rdp: Add device redirection channel support + +Add GrdRdpDeviceRedirection and GrdRdpSmartcard to handle the RDPDR +static virtual channel. When the client joins the channel, grd-session-rdp +creates the device redirection context and starts it. + +GrdRdpDeviceRedirection wraps FreeRDP's RdpdrServerContext and owns +GrdRdpSmartcard, which registers the RDPDR smartcard callbacks. This +is the entry point for forwarding local requests to the remote RDP +client through the RDPDR channel. + +Part-of: +--- + src/grd-rdp-device-redirection.c | 112 ++++++++++++++++++++ + src/grd-rdp-device-redirection.h | 37 +++++++ + src/grd-rdp-private.h | 1 + + src/grd-rdp-smartcard.c | 171 +++++++++++++++++++++++++++++++ + src/grd-rdp-smartcard.h | 37 +++++++ + src/grd-session-rdp.c | 8 ++ + src/grd-types.h | 2 + + src/meson.build | 4 + + 8 files changed, 372 insertions(+) + create mode 100644 src/grd-rdp-device-redirection.c + create mode 100644 src/grd-rdp-device-redirection.h + create mode 100644 src/grd-rdp-smartcard.c + create mode 100644 src/grd-rdp-smartcard.h + +diff --git a/src/grd-rdp-device-redirection.c b/src/grd-rdp-device-redirection.c +new file mode 100644 +index 0000000..7032d8a +--- /dev/null ++++ b/src/grd-rdp-device-redirection.c +@@ -0,0 +1,112 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include "grd-rdp-device-redirection.h" ++ ++#include ++ ++#include "grd-rdp-smartcard.h" ++ ++struct _GrdRdpDeviceRedirection ++{ ++ GObject parent; ++ ++ RdpdrServerContext *rdpdr_context; ++ ++ GrdRdpSmartcard *smartcard; ++}; ++ ++G_DEFINE_TYPE (GrdRdpDeviceRedirection, grd_rdp_device_redirection, ++ G_TYPE_OBJECT) ++ ++GrdRdpSmartcard * ++grd_rdp_device_redirection_get_smartcard (GrdRdpDeviceRedirection *device_redirection) ++{ ++ return device_redirection->smartcard; ++} ++ ++GrdRdpDeviceRedirection * ++grd_rdp_device_redirection_new (GrdSessionRdp *session_rdp, ++ HANDLE vcm) ++{ ++ g_autoptr (GrdRdpDeviceRedirection) device_redirection = NULL; ++ RdpdrServerContext *rdpdr_context; ++ ++ device_redirection = g_object_new (GRD_TYPE_RDP_DEVICE_REDIRECTION, NULL); ++ ++ rdpdr_context = rdpdr_server_context_new (vcm); ++ if (!rdpdr_context) ++ g_error ("[RDP.RDPDR] Failed to create server context (OOM)"); ++ ++ device_redirection->rdpdr_context = rdpdr_context; ++ device_redirection->smartcard = grd_rdp_smartcard_new (session_rdp, ++ rdpdr_context); ++ ++ rdpdr_context->data = device_redirection; ++ ++ if (rdpdr_context->Start (rdpdr_context) != CHANNEL_RC_OK) ++ { ++ g_warning ("[RDP.RDPDR] Failed to start RDPDR channel"); ++ g_clear_pointer (&device_redirection->rdpdr_context, ++ rdpdr_server_context_free); ++ return NULL; ++ } ++ ++ return g_steal_pointer (&device_redirection); ++} ++ ++static void ++grd_rdp_device_redirection_dispose (GObject *object) ++{ ++ GrdRdpDeviceRedirection *device_redirection = ++ GRD_RDP_DEVICE_REDIRECTION (object); ++ ++ if (device_redirection->smartcard) ++ grd_rdp_smartcard_invoke_shutdown (device_redirection->smartcard); ++ ++ if (device_redirection->rdpdr_context) ++ { ++ device_redirection->rdpdr_context->Stop ( ++ device_redirection->rdpdr_context); ++ g_clear_pointer (&device_redirection->rdpdr_context, ++ rdpdr_server_context_free); ++ } ++ ++ g_clear_object (&device_redirection->smartcard); ++ ++ G_OBJECT_CLASS (grd_rdp_device_redirection_parent_class)->dispose (object); ++} ++ ++static void ++grd_rdp_device_redirection_init (GrdRdpDeviceRedirection *device_redirection) ++{ ++} ++ ++static void ++grd_rdp_device_redirection_class_init (GrdRdpDeviceRedirectionClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->dispose = grd_rdp_device_redirection_dispose; ++} +diff --git a/src/grd-rdp-device-redirection.h b/src/grd-rdp-device-redirection.h +new file mode 100644 +index 0000000..2f8e9f2 +--- /dev/null ++++ b/src/grd-rdp-device-redirection.h +@@ -0,0 +1,37 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include "grd-types.h" ++ ++#define GRD_TYPE_RDP_DEVICE_REDIRECTION (grd_rdp_device_redirection_get_type ()) ++G_DECLARE_FINAL_TYPE (GrdRdpDeviceRedirection, grd_rdp_device_redirection, ++ GRD, RDP_DEVICE_REDIRECTION, GObject) ++ ++GrdRdpDeviceRedirection *grd_rdp_device_redirection_new (GrdSessionRdp *session_rdp, ++ HANDLE vcm); ++ ++GrdRdpSmartcard *grd_rdp_device_redirection_get_smartcard (GrdRdpDeviceRedirection *device_redirection); +diff --git a/src/grd-rdp-private.h b/src/grd-rdp-private.h +index 47057f1..1c280a8 100644 +--- a/src/grd-rdp-private.h ++++ b/src/grd-rdp-private.h +@@ -50,4 +50,5 @@ typedef struct _RdpPeerContext + GrdRdpDvcInput *input; + #endif + GrdRdpDvcTelemetry *telemetry; ++ GrdRdpDeviceRedirection *device_redirection; + } RdpPeerContext; +diff --git a/src/grd-rdp-smartcard.c b/src/grd-rdp-smartcard.c +new file mode 100644 +index 0000000..0cee1fe +--- /dev/null ++++ b/src/grd-rdp-smartcard.c +@@ -0,0 +1,171 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include "grd-rdp-smartcard.h" ++ ++#include "grd-context.h" ++#include "grd-rdp-device-redirection.h" ++#include "grd-session-rdp.h" ++ ++struct _GrdRdpSmartcard ++{ ++ GObject parent; ++ ++ GMutex shutdown_mutex; ++ gboolean in_shutdown; ++ ++ RdpdrServerContext *rdpdr_context; ++ GHashTable *smartcard_device_ids; ++}; ++ ++G_DEFINE_TYPE (GrdRdpSmartcard, grd_rdp_smartcard, G_TYPE_OBJECT) ++ ++static UINT ++on_smartcard_create (RdpdrServerContext *rdpdr_context, ++ const RdpdrDevice *device) ++{ ++ GrdRdpDeviceRedirection *device_redirection = rdpdr_context->data; ++ GrdRdpSmartcard *smartcard; ++ ++ smartcard = grd_rdp_device_redirection_get_smartcard (device_redirection); ++ if (!smartcard) ++ return CHANNEL_RC_OK; ++ ++ g_mutex_lock (&smartcard->shutdown_mutex); ++ if (smartcard->in_shutdown) ++ { ++ g_mutex_unlock (&smartcard->shutdown_mutex); ++ return CHANNEL_RC_OK; ++ } ++ g_mutex_unlock (&smartcard->shutdown_mutex); ++ ++ if (g_hash_table_contains (smartcard->smartcard_device_ids, ++ GUINT_TO_POINTER (device->DeviceId))) ++ { ++ g_warning ("[RDP.SMARTCARD] Smartcard device already exists: id=%u", ++ device->DeviceId); ++ return CHANNEL_RC_OK; ++ } ++ ++ g_debug ("[RDP.SMARTCARD] Smartcard device created: id=%u", device->DeviceId); ++ ++ g_hash_table_add (smartcard->smartcard_device_ids, ++ GUINT_TO_POINTER (device->DeviceId)); ++ ++ return CHANNEL_RC_OK; ++} ++ ++static UINT ++on_smartcard_delete (RdpdrServerContext *rdpdr_context, ++ uint32_t device_id) ++{ ++ GrdRdpDeviceRedirection *device_redirection = rdpdr_context->data; ++ GrdRdpSmartcard *smartcard; ++ ++ smartcard = grd_rdp_device_redirection_get_smartcard (device_redirection); ++ if (!smartcard) ++ return CHANNEL_RC_OK; ++ ++ g_mutex_lock (&smartcard->shutdown_mutex); ++ if (smartcard->in_shutdown) ++ { ++ g_mutex_unlock (&smartcard->shutdown_mutex); ++ return CHANNEL_RC_OK; ++ } ++ g_mutex_unlock (&smartcard->shutdown_mutex); ++ ++ if (!g_hash_table_remove (smartcard->smartcard_device_ids, ++ GUINT_TO_POINTER (device_id))) ++ return CHANNEL_RC_OK; ++ ++ g_debug ("[RDP.SMARTCARD] Smartcard device deleted: id=%u", device_id); ++ ++ return CHANNEL_RC_OK; ++} ++ ++void ++grd_rdp_smartcard_invoke_shutdown (GrdRdpSmartcard *smartcard) ++{ ++ g_mutex_lock (&smartcard->shutdown_mutex); ++ smartcard->in_shutdown = TRUE; ++ g_mutex_unlock (&smartcard->shutdown_mutex); ++} ++ ++GrdRdpSmartcard * ++grd_rdp_smartcard_new (GrdSessionRdp *session_rdp, ++ RdpdrServerContext *rdpdr_context) ++{ ++ GrdRdpSmartcard *smartcard; ++ GrdContext *context; ++ ++ context = grd_session_get_context (GRD_SESSION (session_rdp)); ++ g_assert (grd_context_get_runtime_mode (context) != GRD_RUNTIME_MODE_SYSTEM); ++ ++ smartcard = g_object_new (GRD_TYPE_RDP_SMARTCARD, NULL); ++ ++ smartcard->rdpdr_context = rdpdr_context; ++ ++ rdpdr_context->OnSmartcardCreate = on_smartcard_create; ++ rdpdr_context->OnSmartcardDelete = on_smartcard_delete; ++ ++ rdpdr_context->supported |= RDPDR_DTYP_SMARTCARD; ++ ++ return smartcard; ++} ++static void ++grd_rdp_smartcard_dispose (GObject *object) ++{ ++ GrdRdpSmartcard *smartcard = GRD_RDP_SMARTCARD (object); ++ ++ g_clear_pointer (&smartcard->smartcard_device_ids, g_hash_table_unref); ++ ++ G_OBJECT_CLASS (grd_rdp_smartcard_parent_class)->dispose (object); ++} ++ ++static void ++grd_rdp_smartcard_finalize (GObject *object) ++{ ++ GrdRdpSmartcard *smartcard = GRD_RDP_SMARTCARD (object); ++ ++ g_mutex_clear (&smartcard->shutdown_mutex); ++ ++ G_OBJECT_CLASS (grd_rdp_smartcard_parent_class)->finalize (object); ++} ++ ++static void ++grd_rdp_smartcard_init (GrdRdpSmartcard *smartcard) ++{ ++ g_mutex_init (&smartcard->shutdown_mutex); ++ ++ smartcard->smartcard_device_ids = g_hash_table_new (NULL, NULL); ++} ++ ++static void ++grd_rdp_smartcard_class_init (GrdRdpSmartcardClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->dispose = grd_rdp_smartcard_dispose; ++ object_class->finalize = grd_rdp_smartcard_finalize; ++} +diff --git a/src/grd-rdp-smartcard.h b/src/grd-rdp-smartcard.h +new file mode 100644 +index 0000000..cca248f +--- /dev/null ++++ b/src/grd-rdp-smartcard.h +@@ -0,0 +1,37 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#include "grd-types.h" ++ ++#define GRD_TYPE_RDP_SMARTCARD (grd_rdp_smartcard_get_type ()) ++G_DECLARE_FINAL_TYPE (GrdRdpSmartcard, grd_rdp_smartcard, ++ GRD, RDP_SMARTCARD, GObject) ++ ++GrdRdpSmartcard *grd_rdp_smartcard_new (GrdSessionRdp *session_rdp, ++ RdpdrServerContext *rdpdr_context); ++ ++void grd_rdp_smartcard_invoke_shutdown (GrdRdpSmartcard *smartcard); +diff --git a/src/grd-session-rdp.c b/src/grd-session-rdp.c +index 72125a9..6c963da 100644 +--- a/src/grd-session-rdp.c ++++ b/src/grd-session-rdp.c +@@ -22,6 +22,7 @@ + #include "grd-session-rdp.h" + + #include ++#include + #include + #include + #include +@@ -32,6 +33,7 @@ + #include "grd-clipboard-rdp.h" + #include "grd-context.h" + #include "grd-rdp-cursor-renderer.h" ++#include "grd-rdp-device-redirection.h" + #include "grd-rdp-dvc-audio-input.h" + #include "grd-rdp-dvc-audio-playback.h" + #include "grd-rdp-dvc-display-control.h" +@@ -1631,6 +1633,7 @@ grd_session_rdp_stop (GrdSession *session) + g_mutex_lock (&rdp_peer_context->channel_mutex); + g_clear_object (&rdp_peer_context->audio_input); + g_clear_object (&rdp_peer_context->clipboard_rdp); ++ g_clear_object (&rdp_peer_context->device_redirection); + g_clear_object (&rdp_peer_context->audio_playback); + g_clear_object (&rdp_peer_context->display_control); + #if 0 +@@ -1736,6 +1739,11 @@ initialize_remaining_virtual_channels (GrdSessionRdp *session_rdp) + rdp_peer_context->vcm, + get_max_monitor_count (session_rdp)); + } ++ if (WTSVirtualChannelManagerIsChannelJoined (vcm, RDPDR_SVC_CHANNEL_NAME)) ++ { ++ rdp_peer_context->device_redirection = ++ grd_rdp_device_redirection_new (session_rdp, vcm); ++ } + if (WTSVirtualChannelManagerIsChannelJoined (vcm, CLIPRDR_SVC_CHANNEL_NAME)) + { + uint32_t os_major_type = +diff --git a/src/grd-types.h b/src/grd-types.h +index f392c13..84af5f8 100644 +--- a/src/grd-types.h ++++ b/src/grd-types.h +@@ -47,6 +47,7 @@ typedef struct _GrdRdpBufferPool GrdRdpBufferPool; + typedef struct _GrdRdpConnectTimeAutodetection GrdRdpConnectTimeAutodetection; + typedef struct _GrdRdpCursorRenderer GrdRdpCursorRenderer; + typedef struct _GrdRdpDamageDetector GrdRdpDamageDetector; ++typedef struct _GrdRdpDeviceRedirection GrdRdpDeviceRedirection; + typedef struct _GrdRdpDvcAudioInput GrdRdpDvcAudioInput; + typedef struct _GrdRdpDvcAudioPlayback GrdRdpDvcAudioPlayback; + typedef struct _GrdRdpDvcDisplayControl GrdRdpDvcDisplayControl; +@@ -73,6 +74,7 @@ typedef struct _GrdRdpRenderer GrdRdpRenderer; + typedef struct _GrdRdpSAMFile GrdRdpSAMFile; + typedef struct _GrdRdpServer GrdRdpServer; + typedef struct _GrdRdpSessionMetrics GrdRdpSessionMetrics; ++typedef struct _GrdRdpSmartcard GrdRdpSmartcard; + typedef struct _GrdRdpStreamOwner GrdRdpStreamOwner; + typedef struct _GrdRdpSurface GrdRdpSurface; + typedef struct _GrdRdpSurfaceRenderer GrdRdpSurfaceRenderer; +diff --git a/src/meson.build b/src/meson.build +index 15a33a8..b2f6fd6 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -136,6 +136,8 @@ if have_rdp + 'grd-rdp-damage-detector-cuda.h', + 'grd-rdp-damage-detector-memcmp.c', + 'grd-rdp-damage-detector-memcmp.h', ++ 'grd-rdp-device-redirection.c', ++ 'grd-rdp-device-redirection.h', + 'grd-rdp-dsp.c', + 'grd-rdp-dsp.h', + 'grd-rdp-dvc.c', +@@ -198,6 +200,8 @@ if have_rdp + 'grd-rdp-server.h', + 'grd-rdp-session-metrics.c', + 'grd-rdp-session-metrics.h', ++ 'grd-rdp-smartcard.c', ++ 'grd-rdp-smartcard.h', + 'grd-rdp-stream-owner.c', + 'grd-rdp-stream-owner.h', + 'grd-rdp-surface.c', +From d1d5bee04dfd36cb44698a4d72e78b17f38fc25c Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 29 Jul 2026 17:49:07 +0200 +Subject: [PATCH 02/11] daemon-utils: Move session ID lookup from D-Bus sender + to shared utility + +This will be used later by grd-pcscd to identify the session +associated with a D-Bus caller. + +Part-of: +--- + src/grd-daemon-system.c | 52 ++++------------------------------------- + src/grd-daemon-utils.c | 44 ++++++++++++++++++++++++++++++++++ + src/grd-daemon-utils.h | 5 ++++ + 3 files changed, 53 insertions(+), 48 deletions(-) + +diff --git a/src/grd-daemon-system.c b/src/grd-daemon-system.c +index 67e94b58..527a677a 100644 +--- a/src/grd-daemon-system.c ++++ b/src/grd-daemon-system.c +@@ -231,50 +231,6 @@ get_routing_token_from_id (const char *id) + return g_strdup (id + strlen (REMOTE_DESKTOP_CLIENT_OBJECT_PATH "/")); + } + +-static char * +-get_session_id_of_sender (GDBusConnection *connection, +- const char *name, +- GCancellable *cancellable, +- GError **error) +-{ +- char *session_id = NULL; +- gboolean success; +- pid_t pid = 0; +- uid_t uid = 0; +- +- success = grd_get_pid_of_sender_sync (connection, +- name, +- &pid, +- cancellable, +- error); +- if (!success) +- return NULL; +- +- session_id = grd_get_session_id_from_pid (pid); +- if (session_id) +- return session_id; +- +- success = grd_get_uid_of_sender_sync (connection, +- name, +- &uid, +- cancellable, +- error); +- if (!success) +- return NULL; +- +- session_id = grd_get_session_id_from_uid (uid); +- if (!session_id) +- { +- g_set_error (error, +- G_IO_ERROR, +- G_IO_ERROR_NOT_FOUND, +- "Could not find a session for user %d", +- (int) uid); +- } +- +- return session_id; +-} +- + static char * + get_handover_object_path_for_call (GrdDaemonSystem *daemon_system, + GDBusMethodInvocation *invocation, +@@ -291,10 +247,10 @@ get_handover_object_path_for_call (GrdDaemonSystem *daemon_system, + sender = g_dbus_method_invocation_get_sender (invocation); + cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_system)); + +- session_id = get_session_id_of_sender (connection, +- sender, +- cancellable, +- error); ++ session_id = grd_get_session_id_of_sender (connection, ++ sender, ++ cancellable, ++ error); + if (!session_id) + return NULL; + +diff --git a/src/grd-daemon-utils.c b/src/grd-daemon-utils.c +index 87c95cdf..06c4fde2 100644 +--- a/src/grd-daemon-utils.c ++++ b/src/grd-daemon-utils.c +@@ -169,6 +169,50 @@ grd_get_session_id_from_uid (uid_t uid) + return g_strdup (session_id); + } + ++char * ++grd_get_session_id_of_sender (GDBusConnection *connection, ++ const char *name, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ char *session_id = NULL; ++ gboolean success; ++ pid_t pid = 0; ++ uid_t uid = 0; ++ ++ success = grd_get_pid_of_sender_sync (connection, ++ name, ++ &pid, ++ cancellable, ++ error); ++ if (!success) ++ return NULL; ++ ++ session_id = grd_get_session_id_from_pid (pid); ++ if (session_id) ++ return session_id; ++ ++ success = grd_get_uid_of_sender_sync (connection, ++ name, ++ &uid, ++ cancellable, ++ error); ++ if (!success) ++ return NULL; ++ ++ session_id = grd_get_session_id_from_uid (uid); ++ if (!session_id) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ G_IO_ERROR_NOT_FOUND, ++ "Could not find a session for user %d", ++ (int) uid); ++ } ++ ++ return session_id; ++} ++ + gboolean + grd_is_remote_login (void) + { +diff --git a/src/grd-daemon-utils.h b/src/grd-daemon-utils.h +index 174ef309..19fe3d66 100644 +--- a/src/grd-daemon-utils.h ++++ b/src/grd-daemon-utils.h +@@ -37,6 +37,11 @@ char *grd_get_session_id_from_pid (pid_t pid); + + char *grd_get_session_id_from_uid (uid_t uid); + ++char *grd_get_session_id_of_sender (GDBusConnection *connection, ++ const char *name, ++ GCancellable *cancellable, ++ GError **error); ++ + gboolean grd_is_remote_login (void); + + void grd_session_manager_call_logout_sync (void); +-- +2.51.0 + + +From ea08390a1c79e9afc0231a4ee76611b06fb3e10b Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Jul 2026 00:57:37 +0200 +Subject: [PATCH 03/11] rdp-smartcard: Add Pcscd D-Bus proxy + +Add the org.gnome.RemoteDesktop.Pcscd D-Bus interface definition and +generate bindings for it. GrdRdpSmartcard now retrieves this proxy +when is redirecting a smartcard device and will use it to connect local PC/SC +requests to the remote RDP client through grd-pcscd. + +In the next commit there'll be a dbus proxy to communicate with +grd-pcscd through a private dbus connection. + +Part-of: +--- + src/grd-private.h | 2 + + src/grd-rdp-smartcard.c | 75 +++++++++++++++++++++++++++ + src/meson.build | 6 +++ + src/org.gnome.RemoteDesktop.Pcscd.xml | 19 +++++++ + 4 files changed, 102 insertions(+) + create mode 100644 src/org.gnome.RemoteDesktop.Pcscd.xml + +diff --git a/src/grd-private.h b/src/grd-private.h +index 70b236b9..5b1c91c0 100644 +--- a/src/grd-private.h ++++ b/src/grd-private.h +@@ -39,6 +39,8 @@ + #define MUTTER_REMOTE_DESKTOP_OBJECT_PATH "/org/gnome/Mutter/RemoteDesktop" + #define MUTTER_SCREEN_CAST_BUS_NAME "org.gnome.Mutter.ScreenCast" + #define MUTTER_SCREEN_CAST_OBJECT_PATH "/org/gnome/Mutter/ScreenCast" ++#define REMOTE_DESKTOP_PCSCD_BUS_NAME "org.gnome.RemoteDesktop.Pcscd" ++#define REMOTE_DESKTOP_PCSCD_OBJECT_PATH "/org/gnome/RemoteDesktop/Pcscd" + #define GDM_BUS_NAME "org.gnome.DisplayManager" + #define GDM_REMOTE_DISPLAY_FACTORY_OBJECT_PATH "/org/gnome/DisplayManager/RemoteDisplayFactory" + #define GDM_OBJECT_MANAGER_OBJECT_PATH "/org/gnome/DisplayManager/Displays" +diff --git a/src/grd-rdp-smartcard.c b/src/grd-rdp-smartcard.c +index 0cee1fea..e418ef54 100644 +--- a/src/grd-rdp-smartcard.c ++++ b/src/grd-rdp-smartcard.c +@@ -25,6 +25,8 @@ + #include "grd-rdp-smartcard.h" + + #include "grd-context.h" ++#include "grd-dbus-pcscd.h" ++#include "grd-private.h" + #include "grd-rdp-device-redirection.h" + #include "grd-session-rdp.h" + +@@ -37,10 +39,75 @@ struct _GrdRdpSmartcard + + RdpdrServerContext *rdpdr_context; + GHashTable *smartcard_device_ids; ++ ++ /* System bus proxy to grd-pcscd */ ++ GCancellable *pcscd_proxy_cancellable; ++ GrdDBusPcscd *pcscd_proxy; + }; + + G_DEFINE_TYPE (GrdRdpSmartcard, grd_rdp_smartcard, G_TYPE_OBJECT) + ++static void ++on_pcscd_proxy_acquired (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ g_autoptr (GError) error = NULL; ++ GrdDBusPcscd *pcscd_proxy; ++ g_autofree char *name_owner = NULL; ++ ++ pcscd_proxy = grd_dbus_pcscd_proxy_new_for_bus_finish (result, &error); ++ if (!pcscd_proxy) ++ { ++ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ { ++ g_warning ("[RDP.SMARTCARD] Failed to create pcscd proxy: %s", ++ error->message); ++ } ++ return; ++ } ++ ++ smartcard->pcscd_proxy = pcscd_proxy; ++} ++ ++static void ++setup_private_proxy (GrdRdpSmartcard *smartcard) ++{ ++ g_assert (!smartcard->pcscd_proxy_cancellable); ++ ++ smartcard->pcscd_proxy_cancellable = g_cancellable_new (); ++ ++ grd_dbus_pcscd_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, ++ G_DBUS_PROXY_FLAGS_NONE, ++ REMOTE_DESKTOP_PCSCD_BUS_NAME, ++ REMOTE_DESKTOP_PCSCD_OBJECT_PATH, ++ smartcard->pcscd_proxy_cancellable, ++ on_pcscd_proxy_acquired, ++ smartcard); ++} ++ ++static void ++teardown_private_proxy (GrdRdpSmartcard *smartcard) ++{ ++ g_cancellable_cancel (smartcard->pcscd_proxy_cancellable); ++ g_clear_object (&smartcard->pcscd_proxy_cancellable); ++ ++ g_clear_object (&smartcard->pcscd_proxy); ++} ++ ++static void ++setup_pcscd_proxies (GrdRdpSmartcard *smartcard) ++{ ++ setup_private_proxy (smartcard); ++} ++ ++static void ++teardown_pcscd_proxies (GrdRdpSmartcard *smartcard) ++{ ++ teardown_private_proxy (smartcard); ++} ++ + static UINT + on_smartcard_create (RdpdrServerContext *rdpdr_context, + const RdpdrDevice *device) +@@ -70,6 +137,9 @@ on_smartcard_create (RdpdrServerContext *rdpdr_context, + + g_debug ("[RDP.SMARTCARD] Smartcard device created: id=%u", device->DeviceId); + ++ if (g_hash_table_size (smartcard->smartcard_device_ids) == 0) ++ setup_pcscd_proxies (smartcard); ++ + g_hash_table_add (smartcard->smartcard_device_ids, + GUINT_TO_POINTER (device->DeviceId)); + +@@ -101,6 +171,9 @@ on_smartcard_delete (RdpdrServerContext *rdpdr_context, + + g_debug ("[RDP.SMARTCARD] Smartcard device deleted: id=%u", device_id); + ++ if (g_hash_table_size (smartcard->smartcard_device_ids) == 0) ++ teardown_pcscd_proxies (smartcard); ++ + return CHANNEL_RC_OK; + } + +@@ -138,6 +211,8 @@ grd_rdp_smartcard_dispose (GObject *object) + { + GrdRdpSmartcard *smartcard = GRD_RDP_SMARTCARD (object); + ++ teardown_pcscd_proxies (smartcard); ++ + g_clear_pointer (&smartcard->smartcard_device_ids, g_hash_table_unref); + + G_OBJECT_CLASS (grd_rdp_smartcard_parent_class)->dispose (object); +diff --git a/src/meson.build b/src/meson.build +index 3343f6dc..1f1acda1 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -327,6 +327,12 @@ dbus_gen_remote_desktop = gnome.gdbus_codegen('grd-dbus-remote-desktop', + namespace: 'GrdDBusRemoteDesktop') + gen_daemon_sources += dbus_gen_remote_desktop + ++dbus_gen_pcscd = gnome.gdbus_codegen('grd-dbus-pcscd', ++ 'org.gnome.RemoteDesktop.Pcscd.xml', ++ interface_prefix: 'org.gnome.RemoteDesktop.', ++ namespace: 'GrdDBus') ++gen_daemon_sources += dbus_gen_pcscd ++ + if have_rdp + gen_daemon_sources += gnome.gdbus_codegen('grd-dbus-gdm', + 'org.gnome.DisplayManager.xml', +diff --git a/src/org.gnome.RemoteDesktop.Pcscd.xml b/src/org.gnome.RemoteDesktop.Pcscd.xml +new file mode 100644 +index 00000000..34c25302 +--- /dev/null ++++ b/src/org.gnome.RemoteDesktop.Pcscd.xml +@@ -0,0 +1,19 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +-- +2.51.0 + + +From a9c9b959f24fde92f24b3e2c0e24cad5b95e5cc8 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Jul 2026 01:40:51 +0200 +Subject: [PATCH 04/11] rdp-smartcard: Add grd-pcscd daemon and private D-Bus + connection + +Add the grd-pcscd daemon, a system-level service that bridges local +PC/SC clients with RDP smartcard sessions. + +When a smartcard device is announced by the RDP client, GrdRdpSmartcard +calls Connect() on grd-pcscd, passing a socketpair fd to establish a +private D-Bus connection shared between both sides. grd-pcscd creates +a GrdPcscdSession for each connection, which is the bridge between +local PC/SC requests and the remote smartcard. GrdRdpSmartcard exposes +the Pcscd.Session interface on the private D-Bus connection, and +GrdPcscdSession uses it to forward calls from local clients. + +GrdPcscdSession will export Pcscd.Session on the system bus in the +next commit. The Pcscd.Session interface is empty for now; actual +PC/SC method handlers will be added in subsequent commits. + +Also add the systemd service unit and its D-Bus bus policy. + +Part-of: +--- + data/gnome-remote-desktop-pcscd.service.in | 42 +++ + data/meson.build | 23 ++ + data/org.gnome.RemoteDesktop.Pcscd.conf.in | 23 ++ + data/org.gnome.RemoteDesktop.Pcscd.service.in | 5 + + src/grd-pcscd-session.c | 220 ++++++++++++++++ + src/grd-pcscd-session.h | 35 +++ + src/grd-pcscd.c | 246 ++++++++++++++++++ + src/grd-rdp-smartcard.c | 194 ++++++++++++++ + src/meson.build | 15 ++ + src/org.gnome.RemoteDesktop.Pcscd.xml | 40 +++ + 10 files changed, 843 insertions(+) + create mode 100644 data/gnome-remote-desktop-pcscd.service.in + create mode 100644 data/org.gnome.RemoteDesktop.Pcscd.conf.in + create mode 100644 data/org.gnome.RemoteDesktop.Pcscd.service.in + create mode 100644 src/grd-pcscd-session.c + create mode 100644 src/grd-pcscd-session.h + create mode 100644 src/grd-pcscd.c + +diff --git a/data/gnome-remote-desktop-pcscd.service.in b/data/gnome-remote-desktop-pcscd.service.in +new file mode 100644 +index 00000000..5f6c71a2 +--- /dev/null ++++ b/data/gnome-remote-desktop-pcscd.service.in +@@ -0,0 +1,42 @@ ++[Unit] ++Description=GNOME Remote Desktop PC/SC Daemon ++ ++[Service] ++Type=dbus ++BusName=org.gnome.RemoteDesktop.Pcscd ++User=@GRD_USERNAME@ ++ExecStart=@libexecdir@/grd-pcscd ++RuntimeDirectory=grd-pcscd ++ ++# Filesystem ++ProtectSystem=strict ++ProtectHome=yes ++PrivateTmp=yes ++PrivateDevices=yes ++ ++# Kernel ++ProtectKernelTunables=yes ++ProtectKernelModules=yes ++ProtectKernelLogs=yes ++ProtectControlGroups=yes ++ProtectClock=yes ++ProtectHostname=yes ++ ++# Privileges ++NoNewPrivileges=yes ++CapabilityBoundingSet= ++RestrictSUIDSGID=yes ++LockPersonality=yes ++ ++# Network ++RestrictAddressFamilies=AF_UNIX ++IPAddressDeny=any ++ ++# Misc ++RestrictRealtime=yes ++RestrictNamespaces=yes ++MemoryDenyWriteExecute=yes ++RemoveIPC=yes ++SystemCallArchitectures=native ++SystemCallFilter=@system-service ++SystemCallFilter=~@privileged @resources +diff --git a/data/meson.build b/data/meson.build +index e12c1d51..b81acf5c 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -71,6 +71,21 @@ if get_option('systemd') + }, + install_dir: userunitdir) + ++ configure_file(input: 'gnome-remote-desktop-pcscd.service.in', ++ output: 'gnome-remote-desktop-pcscd.service', ++ configuration: { ++ 'libexecdir': libexecdir, ++ 'GRD_USERNAME': grd_username, ++ }, ++ install_dir: systemunitdir) ++ ++ configure_file(input: 'org.gnome.RemoteDesktop.Pcscd.conf.in', ++ output: 'org.gnome.RemoteDesktop.Pcscd.conf', ++ configuration: { ++ 'GRD_USERNAME': grd_username, ++ }, ++ install_dir: dbus_sys_dir) ++ + configure_file(input: 'gnome-remote-desktop-configuration.service.in', + output: 'gnome-remote-desktop-configuration.service', + configuration: { +@@ -103,6 +118,14 @@ if get_option('systemd') + }, + install_dir: join_paths(datadir, 'polkit-1', 'actions')) + ++ configure_file(input: 'org.gnome.RemoteDesktop.Pcscd.service.in', ++ output: 'org.gnome.RemoteDesktop.Pcscd.service', ++ configuration: { ++ 'libexecdir': libexecdir, ++ 'GRD_USERNAME': grd_username, ++ }, ++ install_dir: dbus_system_services_dir) ++ + configure_file(input: 'org.gnome.RemoteDesktop.Configuration.service.in', + output: 'org.gnome.RemoteDesktop.Configuration.service', + configuration: { +diff --git a/data/org.gnome.RemoteDesktop.Pcscd.conf.in b/data/org.gnome.RemoteDesktop.Pcscd.conf.in +new file mode 100644 +index 00000000..410a4eb4 +--- /dev/null ++++ b/data/org.gnome.RemoteDesktop.Pcscd.conf.in +@@ -0,0 +1,23 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/data/org.gnome.RemoteDesktop.Pcscd.service.in b/data/org.gnome.RemoteDesktop.Pcscd.service.in +new file mode 100644 +index 00000000..d1d42139 +--- /dev/null ++++ b/data/org.gnome.RemoteDesktop.Pcscd.service.in +@@ -0,0 +1,5 @@ ++[D-BUS Service] ++Name=org.gnome.RemoteDesktop.Pcscd ++Exec=@libexecdir@/grd-pcscd ++User=@GRD_USERNAME@ ++SystemdService=gnome-remote-desktop-pcscd.service +diff --git a/src/grd-pcscd-session.c b/src/grd-pcscd-session.c +new file mode 100644 +index 00000000..26d76b71 +--- /dev/null ++++ b/src/grd-pcscd-session.c +@@ -0,0 +1,220 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include "grd-pcscd-session.h" ++ ++#include ++#include ++#include ++ ++#include "grd-dbus-pcscd.h" ++ ++struct _GrdPcscdSession ++{ ++ GObject parent; ++ ++ char *session_id; ++ ++ GCancellable *cancellable; ++ ++ GrdDBusPcscdSession *private_proxy; ++}; ++ ++enum ++{ ++ CLOSED, ++ ++ N_SIGNALS, ++}; ++ ++static guint signals[N_SIGNALS]; ++ ++G_DEFINE_TYPE (GrdPcscdSession, grd_pcscd_session, G_TYPE_OBJECT) ++ ++static void ++on_private_connection_closed (GDBusConnection *connection, ++ gboolean remote_peer_vanished, ++ GError *error, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ g_debug ("[PCSCD.SESSION %s] Private connection closed", session->session_id); ++ ++ g_signal_emit (session, signals[CLOSED], 0); ++} ++ ++static void ++on_private_proxy_ready (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GrdDBusPcscdSession) private_proxy = NULL; ++ g_autoptr (GError) error = NULL; ++ GrdPcscdSession *session = user_data; ++ GDBusConnection *private_connection; ++ ++ private_proxy = grd_dbus_pcscd_session_proxy_new_finish (result, &error); ++ if (!private_proxy) ++ { ++ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ { ++ g_warning ("[PCSCD.SESSION %s] Failed to create private proxy: %s", ++ session->session_id, error->message); ++ g_signal_emit (session, signals[CLOSED], 0); ++ } ++ return; ++ } ++ ++ private_connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (private_proxy)); ++ g_signal_connect_object (private_connection, "closed", ++ G_CALLBACK (on_private_connection_closed), ++ session, G_CONNECT_DEFAULT); ++ ++ session->private_proxy = g_steal_pointer (&private_proxy); ++ ++ g_debug ("[PCSCD.SESSION %s] Private D-Bus connection established", ++ session->session_id); ++} ++ ++static void ++on_private_connection_ready (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ g_autoptr (GDBusConnection) private_connection = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ private_connection = g_dbus_connection_new_finish (result, &error); ++ if (!private_connection) ++ { ++ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ { ++ g_warning ("[PCSCD.SESSION %s] Failed to create private connection: %s", ++ session->session_id, error->message); ++ g_signal_emit (session, signals[CLOSED], 0); ++ } ++ return; ++ } ++ ++ grd_dbus_pcscd_session_proxy_new (private_connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ NULL, ++ "/", ++ session->cancellable, ++ on_private_proxy_ready, ++ session); ++} ++ ++static void ++create_private_proxy (GrdPcscdSession *session, ++ int fd) ++{ ++ g_autoptr (GInputStream) input_stream = NULL; ++ g_autoptr (GOutputStream) output_stream = NULL; ++ g_autoptr (GIOStream) io_stream = NULL; ++ g_autofree char *guid = NULL; ++ ++ /* fd ownership: fd -> input_stream -> io_stream -> private_connection -> proxy */ ++ input_stream = g_unix_input_stream_new (fd, TRUE); ++ output_stream = g_unix_output_stream_new (fd, FALSE); ++ io_stream = g_simple_io_stream_new (input_stream, output_stream); ++ ++ guid = g_dbus_generate_guid (); ++ g_dbus_connection_new (io_stream, ++ guid, ++ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER | ++ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS, ++ NULL, ++ session->cancellable, ++ on_private_connection_ready, ++ session); ++} ++ ++const char * ++grd_pcscd_session_get_session_id (GrdPcscdSession *session) ++{ ++ return session->session_id; ++} ++ ++GrdPcscdSession * ++grd_pcscd_session_new (const char *session_id, ++ int fd, ++ GDBusConnection *system_connection) ++{ ++ g_autoptr (GrdPcscdSession) session = NULL; ++ ++ session = g_object_new (GRD_TYPE_PCSCD_SESSION, NULL); ++ session->session_id = g_strdup (session_id); ++ session->cancellable = g_cancellable_new (); ++ ++ create_private_proxy (session, fd); ++ ++ return g_steal_pointer (&session); ++} ++ ++static void ++grd_pcscd_session_dispose (GObject *object) ++{ ++ GrdPcscdSession *session = GRD_PCSCD_SESSION (object); ++ ++ g_cancellable_cancel (session->cancellable); ++ g_clear_object (&session->cancellable); ++ ++ if (session->private_proxy) ++ { ++ GDBusConnection *private_connection = ++ g_dbus_proxy_get_connection (G_DBUS_PROXY (session->private_proxy)); ++ ++ if (!g_dbus_connection_is_closed (private_connection)) ++ g_dbus_connection_close (private_connection, NULL, NULL, NULL); ++ ++ g_clear_object (&session->private_proxy); ++ } ++ ++ g_clear_pointer (&session->session_id, g_free); ++ ++ G_OBJECT_CLASS (grd_pcscd_session_parent_class)->dispose (object); ++} ++ ++static void ++grd_pcscd_session_init (GrdPcscdSession *session) ++{ ++} ++ ++static void ++grd_pcscd_session_class_init (GrdPcscdSessionClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->dispose = grd_pcscd_session_dispose; ++ ++ signals[CLOSED] = ++ g_signal_new ("closed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, NULL, NULL, NULL, ++ G_TYPE_NONE, 0); ++} +diff --git a/src/grd-pcscd-session.h b/src/grd-pcscd-session.h +new file mode 100644 +index 00000000..bafcb1ef +--- /dev/null ++++ b/src/grd-pcscd-session.h +@@ -0,0 +1,35 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#pragma once ++ ++#include ++ ++#define GRD_TYPE_PCSCD_SESSION (grd_pcscd_session_get_type ()) ++G_DECLARE_FINAL_TYPE (GrdPcscdSession, grd_pcscd_session, ++ GRD, PCSCD_SESSION, GObject) ++ ++GrdPcscdSession *grd_pcscd_session_new (const char *session_id, ++ int fd, ++ GDBusConnection *system_connection); ++ ++const char *grd_pcscd_session_get_session_id (GrdPcscdSession *session); +diff --git a/src/grd-pcscd.c b/src/grd-pcscd.c +new file mode 100644 +index 00000000..a70ce574 +--- /dev/null ++++ b/src/grd-pcscd.c +@@ -0,0 +1,246 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++ ++#include "grd-daemon-utils.h" ++#include "grd-dbus-pcscd.h" ++#include "grd-pcscd-session.h" ++ ++#define PCSCD_BUS_NAME "org.gnome.RemoteDesktop.Pcscd" ++#define PCSCD_OBJECT_PATH "/org/gnome/RemoteDesktop/Pcscd" ++ ++typedef struct _GrdPcscd ++{ ++ GMainLoop *loop; ++ guint sigint_source_id; ++ guint sigterm_source_id; ++ ++ guint own_name_id; ++ GrdDBusPcscd *skeleton; ++ GDBusConnection *connection; ++ ++ GHashTable *sessions; ++} GrdPcscd; ++ ++static void ++grd_pcscd_free (GrdPcscd *pcscd) ++{ ++ g_clear_pointer (&pcscd->sessions, g_hash_table_unref); ++ ++ g_clear_handle_id (&pcscd->sigint_source_id, g_source_remove); ++ g_clear_handle_id (&pcscd->sigterm_source_id, g_source_remove); ++ ++ g_clear_handle_id (&pcscd->own_name_id, g_bus_unown_name); ++ ++ if (pcscd->skeleton) ++ { ++ g_dbus_interface_skeleton_unexport ( ++ G_DBUS_INTERFACE_SKELETON (pcscd->skeleton)); ++ g_clear_object (&pcscd->skeleton); ++ } ++ ++ g_clear_object (&pcscd->connection); ++ g_clear_pointer (&pcscd->loop, g_main_loop_unref); ++} ++ ++G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (GrdPcscd, grd_pcscd_free) ++ ++static void ++on_session_closed (GrdPcscdSession *session, ++ gpointer user_data) ++{ ++ GrdPcscd *pcscd = user_data; ++ const char *session_id; ++ ++ session_id = grd_pcscd_session_get_session_id (session); ++ ++ g_message ("Closing smartcard session %s", session_id); ++ ++ g_hash_table_remove (pcscd->sessions, session_id); ++} ++ ++static gboolean ++on_handle_connect (GrdDBusPcscd *skeleton, ++ GDBusMethodInvocation *invocation, ++ GUnixFDList *fd_list, ++ GVariant *arg_rdp_fd, ++ gpointer user_data) ++{ ++ g_autofree char *session_id = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autofd int fd = -1; ++ GrdPcscd *pcscd = user_data; ++ GDBusConnection *connection; ++ GrdPcscdSession *session; ++ const char *sender; ++ int fd_idx; ++ ++ if (!G_IS_UNIX_FD_LIST (fd_list)) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "No fd list received"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ fd_idx = g_variant_get_handle (arg_rdp_fd); ++ fd = g_unix_fd_list_get (fd_list, fd_idx, &error); ++ if (fd < 0) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "Failed to get fd: %s", ++ error->message); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ connection = g_dbus_method_invocation_get_connection (invocation); ++ sender = g_dbus_method_invocation_get_sender (invocation); ++ ++ session_id = grd_get_session_id_of_sender (connection, sender, NULL, &error); ++ if (!session_id) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Failed to get session of " ++ "sender: %s", ++ error->message); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (g_hash_table_contains (pcscd->sessions, session_id)) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Session %s already exists", ++ session_id); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ session = grd_pcscd_session_new (session_id, ++ g_steal_fd (&fd), ++ pcscd->connection); ++ ++ g_signal_connect (session, "closed", ++ G_CALLBACK (on_session_closed), ++ pcscd); ++ ++ g_hash_table_insert (pcscd->sessions, g_strdup (session_id), session); ++ ++ g_message ("New smartcard session %s", session_id); ++ ++ grd_dbus_pcscd_complete_connect (skeleton, invocation, NULL); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_bus_acquired (GDBusConnection *connection, ++ const char *name, ++ gpointer user_data) ++{ ++ g_autoptr (GrdDBusPcscd) skeleton = NULL; ++ g_autoptr (GError) error = NULL; ++ GrdPcscd *pcscd = user_data; ++ ++ skeleton = grd_dbus_pcscd_skeleton_new (); ++ g_signal_connect (skeleton, "handle-connect", ++ G_CALLBACK (on_handle_connect), ++ pcscd); ++ ++ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), ++ connection, ++ PCSCD_OBJECT_PATH, ++ &error)) ++ { ++ g_warning ("Failed to export Pcscd interface: %s", error->message); ++ g_main_loop_quit (pcscd->loop); ++ return; ++ } ++ ++ g_set_object (&pcscd->skeleton, skeleton); ++ g_set_object (&pcscd->connection, connection); ++} ++ ++static void ++on_name_lost (GDBusConnection *connection, ++ const char *name, ++ gpointer user_data) ++{ ++ GrdPcscd *pcscd = user_data; ++ ++ g_warning ("Lost bus name %s", name); ++ ++ g_main_loop_quit (pcscd->loop); ++} ++ ++static gboolean ++on_signal_quit (gpointer user_data) ++{ ++ GrdPcscd *pcscd = user_data; ++ ++ g_message ("Received signal, shutting down"); ++ ++ g_clear_handle_id (&pcscd->sigint_source_id, g_source_remove); ++ g_clear_handle_id (&pcscd->sigterm_source_id, g_source_remove); ++ ++ g_main_loop_quit (pcscd->loop); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++int ++main (int argc, ++ char **argv) ++{ ++ g_auto (GrdPcscd) pcscd = {}; ++ ++ pcscd.loop = g_main_loop_new (NULL, FALSE); ++ pcscd.sessions = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, g_object_unref); ++ ++ pcscd.own_name_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, ++ PCSCD_BUS_NAME, ++ G_BUS_NAME_OWNER_FLAGS_NONE, ++ on_bus_acquired, ++ NULL, ++ on_name_lost, ++ &pcscd, ++ NULL); ++ ++ pcscd.sigint_source_id = g_unix_signal_add (SIGINT, on_signal_quit, &pcscd); ++ pcscd.sigterm_source_id = g_unix_signal_add (SIGTERM, on_signal_quit, &pcscd); ++ ++ g_main_loop_run (pcscd.loop); ++ ++ return EXIT_SUCCESS; ++} +diff --git a/src/grd-rdp-smartcard.c b/src/grd-rdp-smartcard.c +index e418ef54..a674fc0e 100644 +--- a/src/grd-rdp-smartcard.c ++++ b/src/grd-rdp-smartcard.c +@@ -24,12 +24,22 @@ + + #include "grd-rdp-smartcard.h" + ++#include ++#include ++#include ++ + #include "grd-context.h" + #include "grd-dbus-pcscd.h" + #include "grd-private.h" + #include "grd-rdp-device-redirection.h" + #include "grd-session-rdp.h" + ++typedef struct ++{ ++ GrdRdpSmartcard *smartcard; ++ int fd; ++} PcscdConnectData; ++ + struct _GrdRdpSmartcard + { + GObject parent; +@@ -42,11 +52,181 @@ struct _GrdRdpSmartcard + + /* System bus proxy to grd-pcscd */ + GCancellable *pcscd_proxy_cancellable; ++ unsigned long name_owner_changed_id; + GrdDBusPcscd *pcscd_proxy; ++ ++ /* Private peer-to-peer proxy for system-level clients (via grd-pcscd) */ ++ GCancellable *private_proxy_cancellable; ++ GrdDBusPcscdSession *private_proxy; + }; + + G_DEFINE_TYPE (GrdRdpSmartcard, grd_rdp_smartcard, G_TYPE_OBJECT) + ++static void ++on_private_connection_ready (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusConnection) private_connection = NULL; ++ g_autoptr (GrdDBusPcscdSession) private_proxy = NULL; ++ g_autoptr (GError) error = NULL; ++ GrdRdpSmartcard *smartcard = user_data; ++ ++ private_connection = g_dbus_connection_new_finish (result, &error); ++ if (!private_connection) ++ { ++ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ { ++ g_warning ("[RDP.SMARTCARD] Failed to create private connection: %s", ++ error->message); ++ } ++ return; ++ } ++ ++ private_proxy = grd_dbus_pcscd_session_skeleton_new (); ++ if (!g_dbus_interface_skeleton_export ( ++ G_DBUS_INTERFACE_SKELETON (private_proxy), ++ private_connection, ++ "/", ++ &error)) ++ { ++ g_warning ("[RDP.SMARTCARD] Failed to export session skeleton: %s", ++ error->message); ++ return; ++ } ++ ++ smartcard->private_proxy = g_steal_pointer (&private_proxy); ++ ++ g_debug ("[RDP.SMARTCARD] Private D-Bus connection established"); ++} ++ ++static void ++create_private_proxy (GrdRdpSmartcard *smartcard, ++ int fd) ++{ ++ g_autoptr (GOutputStream) output_stream = NULL; ++ g_autoptr (GInputStream) input_stream = NULL; ++ g_autoptr (GIOStream) io_stream = NULL; ++ ++ /* fd ownership: fd -> output_stream -> io_stream -> private_connection -> proxy */ ++ output_stream = g_unix_output_stream_new (fd, TRUE); ++ input_stream = g_unix_input_stream_new (fd, FALSE); ++ io_stream = g_simple_io_stream_new (input_stream, output_stream); ++ ++ g_dbus_connection_new (io_stream, ++ NULL, ++ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, ++ NULL, ++ smartcard->private_proxy_cancellable, ++ on_private_connection_ready, ++ smartcard); ++} ++ ++static void ++on_connect_to_grd_pcscd_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ GrdDBusPcscd *pcscd_proxy = GRD_DBUS_PCSCD (source_object); ++ PcscdConnectData *data = user_data; ++ GrdRdpSmartcard *smartcard = data->smartcard; ++ g_autofd int fd = g_steal_fd (&data->fd); ++ g_autoptr (GError) error = NULL; ++ ++ g_free (data); ++ ++ if (!grd_dbus_pcscd_call_connect_finish (pcscd_proxy, ++ NULL, ++ result, ++ &error)) ++ { ++ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) ++ { ++ g_warning ("[RDP.SMARTCARD] Failed to connect to pcscd: %s", ++ error->message); ++ } ++ return; ++ } ++ ++ g_debug ("[RDP.SMARTCARD] Connected to pcscd, setting up private D-Bus"); ++ ++ create_private_proxy (smartcard, g_steal_fd (&fd)); ++} ++ ++static void ++connect_to_grd_pcscd (GrdRdpSmartcard *smartcard) ++{ ++ g_autoptr (GUnixFDList) fd_list = NULL; ++ g_autoptr (GError) error = NULL; ++ PcscdConnectData *data; ++ g_autofd int local_fd = -1; ++ int fds[2]; ++ int fd_idx; ++ ++ g_assert (!smartcard->private_proxy_cancellable); ++ ++ smartcard->private_proxy_cancellable = g_cancellable_new (); ++ ++ if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) < 0) ++ { ++ g_warning ("[RDP.SMARTCARD] Failed to create socketpair: %s", ++ g_strerror (errno)); ++ return; ++ } ++ ++ local_fd = fds[0]; ++ ++ fd_list = g_unix_fd_list_new (); ++ fd_idx = g_unix_fd_list_append (fd_list, fds[1], &error); ++ close (fds[1]); ++ ++ if (fd_idx < 0) ++ { ++ g_warning ("[RDP.SMARTCARD] Failed to append fd to list: %s", ++ error->message); ++ return; ++ } ++ ++ data = g_new0 (PcscdConnectData, 1); ++ data->smartcard = smartcard; ++ data->fd = g_steal_fd (&local_fd); ++ ++ grd_dbus_pcscd_call_connect (smartcard->pcscd_proxy, ++ g_variant_new_handle (fd_idx), ++ fd_list, ++ smartcard->private_proxy_cancellable, ++ on_connect_to_grd_pcscd_finished, ++ data); ++} ++ ++static void ++disconnect_from_grd_pcscd (GrdRdpSmartcard *smartcard) ++{ ++ g_cancellable_cancel (smartcard->private_proxy_cancellable); ++ g_clear_object (&smartcard->private_proxy_cancellable); ++ ++ if (smartcard->private_proxy) ++ { ++ g_dbus_interface_skeleton_unexport ( ++ G_DBUS_INTERFACE_SKELETON (smartcard->private_proxy)); ++ g_clear_object (&smartcard->private_proxy); ++ } ++} ++ ++static void ++on_grd_pcscd_name_owner_changed (GrdRdpSmartcard *smartcard) ++{ ++ GrdDBusPcscd *pcscd_proxy = smartcard->pcscd_proxy; ++ g_autofree char *name_owner = NULL; ++ ++ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (pcscd_proxy)); ++ ++ disconnect_from_grd_pcscd (smartcard); ++ ++ if (name_owner) ++ connect_to_grd_pcscd (smartcard); ++} ++ + static void + on_pcscd_proxy_acquired (GObject *source_object, + GAsyncResult *result, +@@ -69,6 +249,15 @@ on_pcscd_proxy_acquired (GObject *source_object, + } + + smartcard->pcscd_proxy = pcscd_proxy; ++ ++ smartcard->name_owner_changed_id = ++ g_signal_connect_swapped (pcscd_proxy, "notify::g-name-owner", ++ G_CALLBACK (on_grd_pcscd_name_owner_changed), ++ smartcard); ++ ++ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (pcscd_proxy)); ++ if (name_owner) ++ connect_to_grd_pcscd (smartcard); + } + + static void +@@ -93,6 +282,11 @@ teardown_private_proxy (GrdRdpSmartcard *smartcard) + g_cancellable_cancel (smartcard->pcscd_proxy_cancellable); + g_clear_object (&smartcard->pcscd_proxy_cancellable); + ++ g_clear_signal_handler (&smartcard->name_owner_changed_id, ++ smartcard->pcscd_proxy); ++ ++ disconnect_from_grd_pcscd (smartcard); ++ + g_clear_object (&smartcard->pcscd_proxy); + } + +diff --git a/src/meson.build b/src/meson.build +index 1f1acda1..b6102a9a 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -440,6 +440,21 @@ if have_rdp + include_directories: [configinc], + install: true, + install_dir: libexecdir) ++ ++ executable('grd-pcscd', ++ 'grd-pcscd.c', ++ 'grd-pcscd-session.c', ++ 'grd-pcscd-session.h', ++ 'grd-daemon-utils.c', ++ 'grd-daemon-utils.h', ++ dbus_gen_pcscd, ++ dependencies: [glib_dep, ++ gio_dep, ++ gio_unix_dep, ++ libsystemd_dep], ++ include_directories: [configinc], ++ install: true, ++ install_dir: libexecdir) + endif + + custom_target('gsettings-enums', +diff --git a/src/org.gnome.RemoteDesktop.Pcscd.xml b/src/org.gnome.RemoteDesktop.Pcscd.xml +index 34c25302..34e98a9c 100644 +--- a/src/org.gnome.RemoteDesktop.Pcscd.xml ++++ b/src/org.gnome.RemoteDesktop.Pcscd.xml +@@ -14,6 +14,46 @@ + --> + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +-- +2.51.0 + + +From 50fb2998a20e63e73b1533836091eb89cdf27807 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Jul 2026 02:02:01 +0200 +Subject: [PATCH 05/11] rdp-smartcard: Add PC/SC commands and libgrdpcsc + library + +Populate the Pcscd.Session D-Bus interface with all winscard.h PC/SC +methods: EstablishContext, ReleaseContext, Connect, Disconnect, +Transmit, Status, GetStatusChange, ListReaders, and others. + +Add GrdPcscdSession handlers that export the Pcscd.Session interface +on the system bus at /org/gnome/RemoteDesktop/Pcscd/{session_id} and +forward incoming calls over the private D-Bus connection to +GrdRdpSmartcard. GrdRdpSmartcard handles them by dispatching through +FreeRDP's RDPDR smartcard callbacks to the remote RDP client, and +returns the results back. + +Add libgrdpcsc (libgrdpcsc.so.0), a drop-in replacement for +libpcsclite that redirects PC/SC calls to grd-pcscd instead of the +local pcscd socket. It determines which session to target using +GDM_AUTH_SESSION_ID (set by GDM during PAM authentication) or +XDG_SESSION_ID (for applications inside the remote session), and +connects to the corresponding Pcscd.Session object on the system bus. +This library is intended to be configured as the provider_library in +opensc.conf so that clients like sssd or OpenSC transparently use the +redirected smartcard. + +Part-of: +--- + meson.build | 2 +- + src/grd-pcsc-lib-backend-grd-pcscd-proxy.c | 920 ++++++++++++ + src/grd-pcsc-lib-backend.c | 57 + + src/grd-pcsc-lib-backend.h | 137 ++ + src/grd-pcsc-lib.c | 297 ++++ + src/grd-pcscd-session.c | 917 +++++++++++- + src/grd-pcscd-session.h | 7 +- + src/grd-pcscd.c | 13 +- + src/grd-rdp-smartcard.c | 1565 +++++++++++++++++++- + src/meson.build | 17 + + src/org.gnome.RemoteDesktop.Pcscd.xml | 204 +++ + 11 files changed, 4122 insertions(+), 14 deletions(-) + create mode 100644 src/grd-pcsc-lib-backend-grd-pcscd-proxy.c + create mode 100644 src/grd-pcsc-lib-backend.c + create mode 100644 src/grd-pcsc-lib-backend.h + create mode 100644 src/grd-pcsc-lib.c + +diff --git a/meson.build b/meson.build +index 2419627..5e91d25 100644 +--- a/meson.build ++++ b/meson.build +@@ -6,7 +6,7 @@ project('gnome-remote-desktop', 'c', + + cuda_req = '>= 11.1.5.0' + epoxy_req = '>= 1.4' +-freerdp_req = '>= 3.10.0' ++freerdp_req = '>= 3.28.0' + fuse_req = '>= 3.9.1' + polkit_req = '>= 122' + vulkan_req = '>= 1.2.0' +diff --git a/src/grd-pcsc-lib-backend-grd-pcscd-proxy.c b/src/grd-pcsc-lib-backend-grd-pcscd-proxy.c +new file mode 100644 +index 0000000..7c8b144 +--- /dev/null ++++ b/src/grd-pcsc-lib-backend-grd-pcscd-proxy.c +@@ -0,0 +1,920 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include "grd-pcsc-lib-backend.h" ++ ++#include ++#include ++ ++#include "grd-dbus-pcscd.h" ++ ++#define PCSCD_BUS_NAME "org.gnome.RemoteDesktop.Pcscd" ++ ++static GrdDBusPcscdSession *session_proxy; ++ ++static LONG ++grd_pcscd_proxy_establish_context (DWORD dwScope, ++ LPCVOID pvReserved1, ++ LPCVOID pvReserved2, ++ LPSCARDCONTEXT phContext) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ int64_t context = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(t)", (uint64_t) dwScope); ++ ++ if (!grd_dbus_pcscd_session_call_establish_context_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] EstablishContext D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(xx)", ++ &return_value, ++ &context); ++ ++ if (phContext) ++ *phContext = context; ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_release_context (SCARDCONTEXT hContext) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(x)", (int64_t) hContext); ++ ++ if (!grd_dbus_pcscd_session_call_release_context_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] ReleaseContext D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x)", &return_value); ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_is_valid_context (SCARDCONTEXT hContext) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(x)", (int64_t) hContext); ++ ++ if (!grd_dbus_pcscd_session_call_is_valid_context_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] IsValidContext D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x)", &return_value); ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_connect (SCARDCONTEXT hContext, ++ LPCSTR szReader, ++ DWORD dwShareMode, ++ DWORD dwPreferredProtocols, ++ LPSCARDHANDLE phCard, ++ LPDWORD pdwActiveProtocol) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ int64_t out_context = 0; ++ int64_t out_card = 0; ++ uint64_t active_protocol = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(xstt)", ++ (int64_t) hContext, ++ szReader, ++ (uint64_t) dwShareMode, ++ (uint64_t) dwPreferredProtocols); ++ ++ if (!grd_dbus_pcscd_session_call_connect_card_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] Connect D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(xxxt)", ++ &return_value, ++ &out_context, ++ &out_card, ++ &active_protocol); ++ ++ if (phCard) ++ *phCard = out_card; ++ if (pdwActiveProtocol) ++ *pdwActiveProtocol = active_protocol; ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_reconnect (SCARDHANDLE hCard, ++ DWORD dwShareMode, ++ DWORD dwPreferredProtocols, ++ DWORD dwInitialization, ++ LPDWORD pdwActiveProtocol) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ uint64_t active_protocol = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(xttt)", ++ (int64_t) hCard, ++ (uint64_t) dwShareMode, ++ (uint64_t) dwPreferredProtocols, ++ (uint64_t) dwInitialization); ++ ++ if (!grd_dbus_pcscd_session_call_reconnect_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] Reconnect D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(xt)", ++ &return_value, ++ &active_protocol); ++ ++ if (pdwActiveProtocol) ++ *pdwActiveProtocol = active_protocol; ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_disconnect (SCARDHANDLE hCard, ++ DWORD dwDisposition) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(xt)", ++ (int64_t) hCard, ++ (uint64_t) dwDisposition); ++ ++ if (!grd_dbus_pcscd_session_call_disconnect_card_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] Disconnect D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x)", &return_value); ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_begin_transaction (SCARDHANDLE hCard) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(x)", (int64_t) hCard); ++ ++ if (!grd_dbus_pcscd_session_call_begin_transaction_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] BeginTransaction D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x)", &return_value); ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_end_transaction (SCARDHANDLE hCard, ++ DWORD dwDisposition) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(xt)", ++ (int64_t) hCard, ++ (uint64_t) dwDisposition); ++ ++ if (!grd_dbus_pcscd_session_call_end_transaction_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] EndTransaction D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x)", &return_value); ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_status (SCARDHANDLE hCard, ++ LPSTR mszReaderName, ++ LPDWORD pcchReaderLen, ++ LPDWORD pdwState, ++ LPDWORD pdwProtocol, ++ LPBYTE pbAtr, ++ LPDWORD pcbAtrLen) ++{ ++ g_autoptr (GVariant) atr_variant = NULL; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ const char *reader_name = NULL; ++ int64_t return_value = 0; ++ uint64_t state = 0; ++ uint64_t protocol = 0; ++ const uint8_t *atr_data; ++ size_t atr_size; ++ size_t reader_len; ++ GVariant *call; ++ ++ call = g_variant_new ("(xtt)", ++ (int64_t) hCard, ++ (uint64_t) (pcchReaderLen ? *pcchReaderLen : 0), ++ (uint64_t) (pcbAtrLen ? *pcbAtrLen : 0)); ++ ++ if (!grd_dbus_pcscd_session_call_status_card_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] Status D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x&stt@ay)", ++ &return_value, ++ &reader_name, ++ &state, ++ &protocol, ++ &atr_variant); ++ ++ if (pdwState) ++ *pdwState = state; ++ if (pdwProtocol) ++ *pdwProtocol = protocol; ++ ++ reader_len = strlen (reader_name) + 1; ++ atr_data = g_variant_get_fixed_array (atr_variant, &atr_size, ++ sizeof (uint8_t)); ++ ++ if (pcchReaderLen) ++ { ++ if (mszReaderName) ++ { ++ if (reader_len > *pcchReaderLen) ++ return_value = SCARD_E_INSUFFICIENT_BUFFER; ++ ++ strncpy (mszReaderName, reader_name, *pcchReaderLen); ++ } ++ *pcchReaderLen = reader_len; ++ } ++ ++ if (pcbAtrLen) ++ { ++ if (pbAtr) ++ { ++ if (atr_size > *pcbAtrLen) ++ return_value = SCARD_E_INSUFFICIENT_BUFFER; ++ ++ memcpy (pbAtr, atr_data, MIN (atr_size, *pcbAtrLen)); ++ } ++ *pcbAtrLen = atr_size; ++ } ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_get_status_change (SCARDCONTEXT hContext, ++ DWORD dwTimeout, ++ SCARD_READERSTATE *rgReaderStates, ++ DWORD cReaders) ++{ ++ g_autoptr (GVariant) reader_states_out = NULL; ++ g_autoptr (GVariant) ret = NULL; ++ GVariantBuilder *builder; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ uint64_t atr_event_state; ++ uint64_t atr_length; ++ GVariant *atr_variant; ++ GVariantIter iter; ++ unsigned int i; ++ GVariant *call; ++ ++ builder = g_variant_builder_new (G_VARIANT_TYPE ("a(st)")); ++ for (i = 0; i < cReaders; i++) ++ { ++ g_variant_builder_add (builder, "(st)", ++ rgReaderStates[i].szReader, ++ rgReaderStates[i].dwCurrentState); ++ } ++ ++ call = g_variant_new ("(xt@a(st))", ++ (int64_t) hContext, ++ (uint64_t) dwTimeout, ++ g_variant_builder_end (builder)); ++ ++ if (!grd_dbus_pcscd_session_call_get_status_change_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] GetStatusChange D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x@a(ttay))", ++ &return_value, ++ &reader_states_out); ++ ++ i = 0; ++ g_variant_iter_init (&iter, reader_states_out); ++ while (g_variant_iter_loop (&iter, "(tt@ay)", ++ &atr_event_state, ++ &atr_length, ++ &atr_variant)) ++ { ++ if (i < cReaders) ++ { ++ const unsigned char *atr_data; ++ size_t atr_size; ++ ++ rgReaderStates[i].dwEventState = atr_event_state; ++ rgReaderStates[i].cbAtr = atr_length; ++ ++ atr_data = g_variant_get_fixed_array (atr_variant, ++ &atr_size, ++ sizeof (unsigned char)); ++ if (atr_size > MAX_ATR_SIZE) ++ atr_size = MAX_ATR_SIZE; ++ memcpy (rgReaderStates[i].rgbAtr, atr_data, atr_size); ++ } ++ i++; ++ } ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_control (SCARDHANDLE hCard, ++ DWORD dwControlCode, ++ LPCVOID pbSendBuffer, ++ DWORD cbSendLength, ++ LPVOID pbRecvBuffer, ++ DWORD cbRecvLength, ++ LPDWORD lpBytesReturned) ++{ ++ g_autoptr (GVariant) recv_variant = NULL; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ const uint8_t *recv_data; ++ size_t recv_size; ++ GVariant *call; ++ ++ call = g_variant_new ("(xt@aytt)", ++ (int64_t) hCard, ++ (uint64_t) dwControlCode, ++ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ++ pbSendBuffer, ++ cbSendLength, ++ sizeof (uint8_t)), ++ (uint64_t) cbSendLength, ++ (uint64_t) cbRecvLength); ++ ++ if (!grd_dbus_pcscd_session_call_control_card_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] Control D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x@ay)", ++ &return_value, ++ &recv_variant); ++ ++ recv_data = g_variant_get_fixed_array (recv_variant, &recv_size, ++ sizeof (uint8_t)); ++ if (recv_size > cbRecvLength) ++ return_value = SCARD_E_INSUFFICIENT_BUFFER; ++ ++ if (pbRecvBuffer) ++ memcpy (pbRecvBuffer, recv_data, MIN (recv_size, cbRecvLength)); ++ if (lpBytesReturned) ++ *lpBytesReturned = recv_size; ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_transmit (SCARDHANDLE hCard, ++ const SCARD_IO_REQUEST *pioSendPci, ++ LPCBYTE pbSendBuffer, ++ DWORD cbSendLength, ++ SCARD_IO_REQUEST *pioRecvPci, ++ LPBYTE pbRecvBuffer, ++ LPDWORD pcbRecvLength) ++{ ++ g_autoptr (GVariant) recv_variant = NULL; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ uint64_t recv_pci_protocol = 0; ++ const uint8_t *recv_data; ++ size_t recv_size; ++ size_t copy_size; ++ GVariant *call; ++ ++ call = g_variant_new ("(xt@aytt)", ++ (int64_t) hCard, ++ (uint64_t) (pioSendPci ? pioSendPci->dwProtocol : 0), ++ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ++ pbSendBuffer, ++ cbSendLength, ++ sizeof (uint8_t)), ++ (uint64_t) (pioRecvPci ? pioRecvPci->dwProtocol : 0), ++ (uint64_t) (pcbRecvLength ? *pcbRecvLength : 0)); ++ ++ if (!grd_dbus_pcscd_session_call_transmit_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] Transmit D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(xt@ay)", ++ &return_value, ++ &recv_pci_protocol, ++ &recv_variant); ++ ++ recv_data = g_variant_get_fixed_array (recv_variant, &recv_size, ++ sizeof (uint8_t)); ++ copy_size = recv_size; ++ if (pcbRecvLength && recv_size > *pcbRecvLength) ++ { ++ copy_size = *pcbRecvLength; ++ return_value = SCARD_E_INSUFFICIENT_BUFFER; ++ } ++ ++ if (pbRecvBuffer) ++ memcpy (pbRecvBuffer, recv_data, copy_size); ++ if (pcbRecvLength) ++ *pcbRecvLength = recv_size; ++ ++ if (pioRecvPci) ++ { ++ pioRecvPci->dwProtocol = recv_pci_protocol; ++ pioRecvPci->cbPciLength = sizeof (SCARD_IO_REQUEST); ++ } ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_list_reader_groups (SCARDCONTEXT hContext, ++ LPSTR mszGroups, ++ LPDWORD pcchGroups) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ g_auto (GStrv) groups = NULL; ++ int64_t return_value = 0; ++ size_t groups_size = 0; ++ unsigned int i; ++ GVariant *call; ++ ++ call = g_variant_new ("(x)", (int64_t) hContext); ++ ++ if (!grd_dbus_pcscd_session_call_list_reader_groups_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] ListReaderGroups D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x^as)", ++ &return_value, ++ &groups); ++ ++ if (return_value != SCARD_S_SUCCESS) ++ return return_value; ++ ++ for (i = 0; groups[i]; i++) ++ groups_size += strlen (groups[i]) + 1; ++ groups_size++; ++ ++ if (!mszGroups) ++ { ++ if (pcchGroups) ++ *pcchGroups = groups_size; ++ return SCARD_S_SUCCESS; ++ } ++ ++ if (pcchGroups && *pcchGroups < groups_size) ++ { ++ *pcchGroups = groups_size; ++ return SCARD_E_INSUFFICIENT_BUFFER; ++ } ++ ++ if (pcchGroups) ++ *pcchGroups = groups_size; ++ for (i = 0; groups[i]; i++) ++ { ++ size_t len = strlen (groups[i]) + 1; ++ memcpy (mszGroups, groups[i], len); ++ mszGroups += len; ++ } ++ *mszGroups = '\0'; ++ ++ return SCARD_S_SUCCESS; ++} ++ ++static LONG ++grd_pcscd_proxy_list_readers (SCARDCONTEXT hContext, ++ LPCSTR mszGroups, ++ LPSTR mszReaders, ++ LPDWORD pcchReaders) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ g_auto (GStrv) readers = NULL; ++ int64_t return_value = 0; ++ size_t readers_size = 0; ++ unsigned int i; ++ GVariant *call; ++ ++ call = g_variant_new ("(x)", (int64_t) hContext); ++ ++ if (!grd_dbus_pcscd_session_call_list_readers_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] ListReaders D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x^as)", ++ &return_value, ++ &readers); ++ ++ if (return_value != SCARD_S_SUCCESS) ++ return return_value; ++ ++ for (i = 0; readers[i]; i++) ++ readers_size += strlen (readers[i]) + 1; ++ readers_size++; ++ ++ if (!mszReaders) ++ { ++ if (pcchReaders) ++ *pcchReaders = readers_size; ++ return SCARD_S_SUCCESS; ++ } ++ ++ if (pcchReaders && *pcchReaders < readers_size) ++ { ++ *pcchReaders = readers_size; ++ return SCARD_E_INSUFFICIENT_BUFFER; ++ } ++ ++ if (pcchReaders) ++ *pcchReaders = readers_size; ++ for (i = 0; readers[i]; i++) ++ { ++ size_t len = strlen (readers[i]) + 1; ++ memcpy (mszReaders, readers[i], len); ++ mszReaders += len; ++ } ++ *mszReaders = '\0'; ++ ++ return SCARD_S_SUCCESS; ++} ++ ++static LONG ++grd_pcscd_proxy_free_memory (SCARDCONTEXT hContext, ++ LPCVOID pvMem) ++{ ++ return SCARD_S_SUCCESS; ++} ++ ++static LONG ++grd_pcscd_proxy_cancel (SCARDCONTEXT hContext) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(x)", (int64_t) hContext); ++ ++ if (!grd_dbus_pcscd_session_call_cancel_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] Cancel D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x)", &return_value); ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_get_attrib (SCARDHANDLE hCard, ++ DWORD dwAttrId, ++ LPBYTE pbAttr, ++ LPDWORD pcbAttrLen) ++{ ++ g_autoptr (GVariant) attr_variant = NULL; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ const uint8_t *attr_data; ++ size_t attr_size; ++ size_t copy_size; ++ GVariant *call; ++ ++ call = g_variant_new ("(xtt)", ++ (int64_t) hCard, ++ (uint64_t) dwAttrId, ++ (uint64_t) (pcbAttrLen ? *pcbAttrLen : 0)); ++ ++ if (!grd_dbus_pcscd_session_call_get_attrib_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] GetAttrib D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x@ay)", ++ &return_value, ++ &attr_variant); ++ ++ attr_data = g_variant_get_fixed_array (attr_variant, &attr_size, ++ sizeof (uint8_t)); ++ ++ if (!pbAttr) ++ { ++ if (pcbAttrLen) ++ *pcbAttrLen = attr_size; ++ return return_value; ++ } ++ ++ copy_size = attr_size; ++ if (pcbAttrLen && attr_size > *pcbAttrLen) ++ { ++ copy_size = *pcbAttrLen; ++ return_value = SCARD_E_INSUFFICIENT_BUFFER; ++ } ++ ++ memcpy (pbAttr, attr_data, copy_size); ++ if (pcbAttrLen) ++ *pcbAttrLen = attr_size; ++ ++ return return_value; ++} ++ ++static LONG ++grd_pcscd_proxy_set_attrib (SCARDHANDLE hCard, ++ DWORD dwAttrId, ++ LPCBYTE pbAttr, ++ DWORD cbAttrLen) ++{ ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ int64_t return_value = 0; ++ GVariant *call; ++ ++ call = g_variant_new ("(xt@ay)", ++ (int64_t) hCard, ++ (uint64_t) dwAttrId, ++ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ++ pbAttr, ++ cbAttrLen, ++ sizeof (uint8_t))); ++ ++ if (!grd_dbus_pcscd_session_call_set_attrib_sync (session_proxy, ++ call, ++ &ret, ++ NULL, ++ &error)) ++ { ++ g_warning ("[PCSC] SetAttrib D-Bus call failed: %s", ++ error->message); ++ return SCARD_F_INTERNAL_ERROR; ++ } ++ ++ g_variant_get (ret, "(x)", &return_value); ++ ++ return return_value; ++} ++ ++static char * ++get_session_id (void) ++{ ++ const char *session_id; ++ char *logind_session_id = NULL; ++ ++ session_id = g_getenv ("GDM_AUTH_SESSION_ID"); ++ if (session_id && g_strcmp0 (session_id, "") != 0) ++ return g_strdup (session_id); ++ ++ if (sd_pid_get_session (0, &logind_session_id) < 0) ++ return NULL; ++ ++ return logind_session_id; ++} ++ ++static gboolean ++dbus_interface_exists (GDBusProxy *proxy) ++{ ++ g_autoptr (GVariant) result = NULL; ++ ++ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (proxy), ++ g_dbus_proxy_get_name (proxy), ++ g_dbus_proxy_get_object_path (proxy), ++ "org.freedesktop.DBus.Properties", ++ "GetAll", ++ g_variant_new ("(s)", ++ g_dbus_proxy_get_interface_name (proxy)), ++ NULL, ++ G_DBUS_CALL_FLAGS_NONE, ++ -1, ++ NULL, ++ NULL); ++ return result != NULL; ++} ++ ++gboolean ++grd_pcsc_lib_backend_init_grd_pcscd_proxy (GrdPcscLibBackend *backend) ++{ ++ g_autofree char *session_id = NULL; ++ g_autofree char *object_path = NULL; ++ g_autofree char *name_owner = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ session_id = get_session_id (); ++ if (!session_id) ++ return FALSE; ++ ++ object_path = g_strdup_printf ("/org/gnome/RemoteDesktop/Pcscd/%s", session_id); ++ session_proxy = grd_dbus_pcscd_session_proxy_new_for_bus_sync ( ++ G_BUS_TYPE_SYSTEM, ++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, ++ PCSCD_BUS_NAME, ++ object_path, ++ NULL, ++ &error); ++ if (!session_proxy) ++ return FALSE; ++ ++ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (session_proxy)); ++ if (!name_owner) ++ return FALSE; ++ ++ if (!dbus_interface_exists (G_DBUS_PROXY (session_proxy))) ++ { ++ g_clear_object (&session_proxy); ++ return FALSE; ++ } ++ ++ backend->SCardEstablishContext = grd_pcscd_proxy_establish_context; ++ backend->SCardReleaseContext = grd_pcscd_proxy_release_context; ++ backend->SCardIsValidContext = grd_pcscd_proxy_is_valid_context; ++ backend->SCardConnect = grd_pcscd_proxy_connect; ++ backend->SCardReconnect = grd_pcscd_proxy_reconnect; ++ backend->SCardDisconnect = grd_pcscd_proxy_disconnect; ++ backend->SCardBeginTransaction = grd_pcscd_proxy_begin_transaction; ++ backend->SCardEndTransaction = grd_pcscd_proxy_end_transaction; ++ backend->SCardStatus = grd_pcscd_proxy_status; ++ backend->SCardGetStatusChange = grd_pcscd_proxy_get_status_change; ++ backend->SCardControl = grd_pcscd_proxy_control; ++ backend->SCardTransmit = grd_pcscd_proxy_transmit; ++ backend->SCardListReaderGroups = grd_pcscd_proxy_list_reader_groups; ++ backend->SCardListReaders = grd_pcscd_proxy_list_readers; ++ backend->SCardFreeMemory = grd_pcscd_proxy_free_memory; ++ backend->SCardCancel = grd_pcscd_proxy_cancel; ++ backend->SCardGetAttrib = grd_pcscd_proxy_get_attrib; ++ backend->SCardSetAttrib = grd_pcscd_proxy_set_attrib; ++ ++ return TRUE; ++} ++ ++void ++grd_pcsc_lib_backend_cleanup_grd_pcscd_proxy (void) ++{ ++ g_clear_object (&session_proxy); ++} +diff --git a/src/grd-pcsc-lib-backend.c b/src/grd-pcsc-lib-backend.c +new file mode 100644 +index 0000000..16d18a4 +--- /dev/null ++++ b/src/grd-pcsc-lib-backend.c +@@ -0,0 +1,57 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include "grd-pcsc-lib-backend.h" ++ ++#define SET_DEFAULT(backend, member, func) \ ++ (backend)->member = (typeof ((backend)->member)) (func) ++ ++static LONG ++internal_error (void) ++{ ++ return SCARD_F_INTERNAL_ERROR; ++} ++ ++void ++grd_pcsc_lib_backend_set_default (GrdPcscLibBackend *backend) ++{ ++ SET_DEFAULT (backend, SCardEstablishContext, internal_error); ++ SET_DEFAULT (backend, SCardReleaseContext, internal_error); ++ SET_DEFAULT (backend, SCardIsValidContext, internal_error); ++ SET_DEFAULT (backend, SCardConnect, internal_error); ++ SET_DEFAULT (backend, SCardReconnect, internal_error); ++ SET_DEFAULT (backend, SCardDisconnect, internal_error); ++ SET_DEFAULT (backend, SCardBeginTransaction, internal_error); ++ SET_DEFAULT (backend, SCardEndTransaction, internal_error); ++ SET_DEFAULT (backend, SCardStatus, internal_error); ++ SET_DEFAULT (backend, SCardGetStatusChange, internal_error); ++ SET_DEFAULT (backend, SCardControl, internal_error); ++ SET_DEFAULT (backend, SCardTransmit, internal_error); ++ SET_DEFAULT (backend, SCardListReaderGroups, internal_error); ++ SET_DEFAULT (backend, SCardListReaders, internal_error); ++ SET_DEFAULT (backend, SCardFreeMemory, internal_error); ++ SET_DEFAULT (backend, SCardCancel, internal_error); ++ SET_DEFAULT (backend, SCardGetAttrib, internal_error); ++ SET_DEFAULT (backend, SCardSetAttrib, internal_error); ++} +diff --git a/src/grd-pcsc-lib-backend.h b/src/grd-pcsc-lib-backend.h +new file mode 100644 +index 0000000..47c6a13 +--- /dev/null ++++ b/src/grd-pcsc-lib-backend.h +@@ -0,0 +1,137 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#pragma once ++ ++#include ++ ++typedef unsigned long DWORD; ++typedef DWORD *LPDWORD; ++typedef long LONG; ++typedef LONG SCARDCONTEXT; ++typedef SCARDCONTEXT *LPSCARDCONTEXT; ++typedef LONG SCARDHANDLE; ++typedef SCARDHANDLE *LPSCARDHANDLE; ++typedef const void *LPCVOID; ++typedef void *LPVOID; ++typedef unsigned char BYTE; ++typedef BYTE *LPBYTE; ++typedef const BYTE *LPCBYTE; ++typedef const char *LPCSTR; ++typedef char *LPSTR; ++ ++#define MAX_ATR_SIZE 33 ++ ++typedef struct ++{ ++ const char *szReader; ++ void *pvUserData; ++ DWORD dwCurrentState; ++ DWORD dwEventState; ++ DWORD cbAtr; ++ unsigned char rgbAtr[MAX_ATR_SIZE]; ++} SCARD_READERSTATE; ++ ++typedef struct ++{ ++ unsigned long dwProtocol; ++ unsigned long cbPciLength; ++} SCARD_IO_REQUEST; ++ ++#define SCARD_S_SUCCESS ((LONG) 0x00000000) ++#define SCARD_F_INTERNAL_ERROR ((LONG) 0x80100001) ++#define SCARD_E_INSUFFICIENT_BUFFER ((LONG) 0x80100008) ++ ++typedef struct ++{ ++ LONG (*SCardEstablishContext) (DWORD, ++ LPCVOID, ++ LPCVOID, ++ LPSCARDCONTEXT); ++ LONG (*SCardReleaseContext) (SCARDCONTEXT); ++ LONG (*SCardIsValidContext) (SCARDCONTEXT); ++ LONG (*SCardConnect) (SCARDCONTEXT, ++ LPCSTR, ++ DWORD, ++ DWORD, ++ LPSCARDHANDLE, ++ LPDWORD); ++ LONG (*SCardReconnect) (SCARDHANDLE, ++ DWORD, ++ DWORD, ++ DWORD, ++ LPDWORD); ++ LONG (*SCardDisconnect) (SCARDHANDLE, ++ DWORD); ++ LONG (*SCardBeginTransaction) (SCARDHANDLE); ++ LONG (*SCardEndTransaction) (SCARDHANDLE, ++ DWORD); ++ LONG (*SCardStatus) (SCARDHANDLE, ++ LPSTR, ++ LPDWORD, ++ LPDWORD, ++ LPDWORD, ++ LPBYTE, ++ LPDWORD); ++ LONG (*SCardGetStatusChange) (SCARDCONTEXT, ++ DWORD, ++ SCARD_READERSTATE *, ++ DWORD); ++ LONG (*SCardControl) (SCARDHANDLE, ++ DWORD, ++ LPCVOID, ++ DWORD, ++ LPVOID, ++ DWORD, ++ LPDWORD); ++ LONG (*SCardTransmit) (SCARDHANDLE, ++ const SCARD_IO_REQUEST *, ++ LPCBYTE, ++ DWORD, ++ SCARD_IO_REQUEST *, ++ LPBYTE, ++ LPDWORD); ++ LONG (*SCardListReaderGroups) (SCARDCONTEXT, ++ LPSTR, ++ LPDWORD); ++ LONG (*SCardListReaders) (SCARDCONTEXT, ++ LPCSTR, ++ LPSTR, ++ LPDWORD); ++ LONG (*SCardFreeMemory) (SCARDCONTEXT, ++ LPCVOID); ++ LONG (*SCardCancel) (SCARDCONTEXT); ++ LONG (*SCardGetAttrib) (SCARDHANDLE, ++ DWORD, ++ LPBYTE, ++ LPDWORD); ++ LONG (*SCardSetAttrib) (SCARDHANDLE, ++ DWORD, ++ LPCBYTE, ++ DWORD); ++} GrdPcscLibBackend; ++ ++void grd_pcsc_lib_backend_set_default (GrdPcscLibBackend *backend); ++ ++gboolean grd_pcsc_lib_backend_init_grd_pcscd_proxy (GrdPcscLibBackend *backend); ++ ++void grd_pcsc_lib_backend_cleanup_grd_pcscd_proxy (void); +diff --git a/src/grd-pcsc-lib.c b/src/grd-pcsc-lib.c +new file mode 100644 +index 0000000..94ae36c +--- /dev/null ++++ b/src/grd-pcsc-lib.c +@@ -0,0 +1,297 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include "grd-pcsc-lib-backend.h" ++ ++#define PCSC_API __attribute__((visibility ("default"))) ++#define PCSC_DESTRUCTOR __attribute__ ((destructor)) ++ ++static GrdPcscLibBackend backend; ++ ++PCSC_DESTRUCTOR static void ++on_unload (void) ++{ ++ grd_pcsc_lib_backend_cleanup_grd_pcscd_proxy (); ++} ++ ++static void ++init_grd_pcsc_backend (void) ++{ ++ grd_pcsc_lib_backend_set_default (&backend); ++ ++ if (grd_pcsc_lib_backend_init_grd_pcscd_proxy (&backend)) ++ return; ++ ++ g_warning ("[PCSC] No backend available"); ++} ++ ++static void ++ensure_initialized (void) ++{ ++ static gsize initialized = 0; ++ ++ if (g_once_init_enter (&initialized)) ++ { ++ init_grd_pcsc_backend (); ++ g_once_init_leave (&initialized, 1); ++ } ++} ++ ++PCSC_API LONG ++SCardEstablishContext (DWORD dwScope, ++ LPCVOID pvReserved1, ++ LPCVOID pvReserved2, ++ LPSCARDCONTEXT phContext) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardEstablishContext (dwScope, ++ pvReserved1, ++ pvReserved2, ++ phContext); ++} ++ ++PCSC_API LONG ++SCardReleaseContext (SCARDCONTEXT hContext) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardReleaseContext (hContext); ++} ++ ++PCSC_API LONG ++SCardIsValidContext (SCARDCONTEXT hContext) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardIsValidContext (hContext); ++} ++ ++PCSC_API LONG ++SCardConnect (SCARDCONTEXT hContext, ++ LPCSTR szReader, ++ DWORD dwShareMode, ++ DWORD dwPreferredProtocols, ++ LPSCARDHANDLE phCard, ++ LPDWORD pdwActiveProtocol) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardConnect (hContext, ++ szReader, ++ dwShareMode, ++ dwPreferredProtocols, ++ phCard, ++ pdwActiveProtocol); ++} ++ ++PCSC_API LONG ++SCardReconnect (SCARDHANDLE hCard, ++ DWORD dwShareMode, ++ DWORD dwPreferredProtocols, ++ DWORD dwInitialization, ++ LPDWORD pdwActiveProtocol) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardReconnect (hCard, ++ dwShareMode, ++ dwPreferredProtocols, ++ dwInitialization, ++ pdwActiveProtocol); ++} ++ ++PCSC_API LONG ++SCardDisconnect (SCARDHANDLE hCard, ++ DWORD dwDisposition) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardDisconnect (hCard, ++ dwDisposition); ++} ++ ++PCSC_API LONG ++SCardBeginTransaction (SCARDHANDLE hCard) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardBeginTransaction (hCard); ++} ++ ++PCSC_API LONG ++SCardEndTransaction (SCARDHANDLE hCard, ++ DWORD dwDisposition) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardEndTransaction (hCard, ++ dwDisposition); ++} ++ ++PCSC_API LONG ++SCardStatus (SCARDHANDLE hCard, ++ LPSTR mszReaderName, ++ LPDWORD pcchReaderLen, ++ LPDWORD pdwState, ++ LPDWORD pdwProtocol, ++ LPBYTE pbAtr, ++ LPDWORD pcbAtrLen) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardStatus (hCard, ++ mszReaderName, ++ pcchReaderLen, ++ pdwState, ++ pdwProtocol, ++ pbAtr, ++ pcbAtrLen); ++} ++ ++PCSC_API LONG ++SCardGetStatusChange (SCARDCONTEXT hContext, ++ DWORD dwTimeout, ++ SCARD_READERSTATE *rgReaderStates, ++ DWORD cReaders) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardGetStatusChange (hContext, ++ dwTimeout, ++ rgReaderStates, ++ cReaders); ++} ++ ++PCSC_API LONG ++SCardControl (SCARDHANDLE hCard, ++ DWORD dwControlCode, ++ LPCVOID pbSendBuffer, ++ DWORD cbSendLength, ++ LPVOID pbRecvBuffer, ++ DWORD cbRecvLength, ++ LPDWORD lpBytesReturned) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardControl (hCard, ++ dwControlCode, ++ pbSendBuffer, ++ cbSendLength, ++ pbRecvBuffer, ++ cbRecvLength, ++ lpBytesReturned); ++} ++ ++PCSC_API LONG ++SCardTransmit (SCARDHANDLE hCard, ++ const SCARD_IO_REQUEST *pioSendPci, ++ LPCBYTE pbSendBuffer, ++ DWORD cbSendLength, ++ SCARD_IO_REQUEST *pioRecvPci, ++ LPBYTE pbRecvBuffer, ++ LPDWORD pcbRecvLength) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardTransmit (hCard, ++ pioSendPci, ++ pbSendBuffer, ++ cbSendLength, ++ pioRecvPci, ++ pbRecvBuffer, ++ pcbRecvLength); ++} ++ ++PCSC_API LONG ++SCardListReaderGroups (SCARDCONTEXT hContext, ++ LPSTR mszGroups, ++ LPDWORD pcchGroups) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardListReaderGroups (hContext, ++ mszGroups, ++ pcchGroups); ++} ++ ++PCSC_API LONG ++SCardListReaders (SCARDCONTEXT hContext, ++ LPCSTR mszGroups, ++ LPSTR mszReaders, ++ LPDWORD pcchReaders) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardListReaders (hContext, ++ mszGroups, ++ mszReaders, ++ pcchReaders); ++} ++ ++PCSC_API LONG ++SCardFreeMemory (SCARDCONTEXT hContext, ++ LPCVOID pvMem) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardFreeMemory (hContext, ++ pvMem); ++} ++ ++PCSC_API LONG ++SCardCancel (SCARDCONTEXT hContext) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardCancel (hContext); ++} ++ ++PCSC_API LONG ++SCardGetAttrib (SCARDHANDLE hCard, ++ DWORD dwAttrId, ++ LPBYTE pbAttr, ++ LPDWORD pcbAttrLen) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardGetAttrib (hCard, ++ dwAttrId, ++ pbAttr, ++ pcbAttrLen); ++} ++ ++PCSC_API LONG ++SCardSetAttrib (SCARDHANDLE hCard, ++ DWORD dwAttrId, ++ LPCBYTE pbAttr, ++ DWORD cbAttrLen) ++{ ++ ensure_initialized (); ++ ++ return backend.SCardSetAttrib (hCard, ++ dwAttrId, ++ pbAttr, ++ cbAttrLen); ++} +diff --git a/src/grd-pcscd-session.c b/src/grd-pcscd-session.c +index 26d76b7..aa45ac2 100644 +--- a/src/grd-pcscd-session.c ++++ b/src/grd-pcscd-session.c +@@ -30,6 +30,8 @@ + + #include "grd-dbus-pcscd.h" + ++#define PCSCD_OBJECT_PATH_PREFIX "/org/gnome/RemoteDesktop/Pcscd" ++ + struct _GrdPcscdSession + { + GObject parent; +@@ -39,6 +41,7 @@ struct _GrdPcscdSession + GCancellable *cancellable; + + GrdDBusPcscdSession *private_proxy; ++ GrdDBusPcscdSession *system_proxy; + }; + + enum +@@ -52,6 +55,822 @@ static guint signals[N_SIGNALS]; + + G_DEFINE_TYPE (GrdPcscdSession, grd_pcscd_session, G_TYPE_OBJECT) + ++static void ++on_establish_context_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_establish_context_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xx))", ++ ret)); ++} ++ ++static gboolean ++on_handle_establish_context (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_establish_context (session->private_proxy, ++ call, ++ session->cancellable, ++ on_establish_context_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_release_context_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_release_context_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(x))", ++ ret)); ++} ++ ++static gboolean ++on_handle_release_context (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_release_context (session->private_proxy, ++ call, ++ session->cancellable, ++ on_release_context_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_is_valid_context_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_is_valid_context_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(x))", ++ ret)); ++} ++ ++static gboolean ++on_handle_is_valid_context (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_is_valid_context (session->private_proxy, ++ call, ++ session->cancellable, ++ on_is_valid_context_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_connect_card_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_connect_card_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xxxt))", ++ ret)); ++} ++ ++static gboolean ++on_handle_connect_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_connect_card (session->private_proxy, ++ call, ++ session->cancellable, ++ on_connect_card_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_reconnect_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_reconnect_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xt))", ++ ret)); ++} ++ ++static gboolean ++on_handle_reconnect (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_reconnect (session->private_proxy, ++ call, ++ session->cancellable, ++ on_reconnect_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_disconnect_card_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_disconnect_card_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(x))", ++ ret)); ++} ++ ++static gboolean ++on_handle_disconnect_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_disconnect_card (session->private_proxy, ++ call, ++ session->cancellable, ++ on_disconnect_card_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_begin_transaction_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_begin_transaction_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(x))", ++ ret)); ++} ++ ++static gboolean ++on_handle_begin_transaction (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_begin_transaction (session->private_proxy, ++ call, ++ session->cancellable, ++ on_begin_transaction_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_end_transaction_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_end_transaction_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(x))", ++ ret)); ++} ++ ++static gboolean ++on_handle_end_transaction (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_end_transaction (session->private_proxy, ++ call, ++ session->cancellable, ++ on_end_transaction_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_status_card_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_status_card_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xsttay))", ++ ret)); ++} ++ ++static gboolean ++on_handle_status_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_status_card (session->private_proxy, ++ call, ++ session->cancellable, ++ on_status_card_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_get_status_change_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_get_status_change_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xa(ttay)))", ++ ret)); ++} ++ ++static gboolean ++on_handle_get_status_change (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_get_status_change (session->private_proxy, ++ call, ++ session->cancellable, ++ on_get_status_change_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_control_card_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_control_card_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xay))", ++ ret)); ++} ++ ++static gboolean ++on_handle_control_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_control_card (session->private_proxy, ++ call, ++ session->cancellable, ++ on_control_card_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_transmit_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_transmit_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xtay))", ++ ret)); ++} ++ ++static gboolean ++on_handle_transmit (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_transmit (session->private_proxy, ++ call, ++ session->cancellable, ++ on_transmit_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_list_reader_groups_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_list_reader_groups_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xas))", ++ ret)); ++} ++ ++static gboolean ++on_handle_list_reader_groups (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_list_reader_groups (session->private_proxy, ++ call, ++ session->cancellable, ++ on_list_reader_groups_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_list_readers_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_list_readers_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xas))", ++ ret)); ++} ++ ++static gboolean ++on_handle_list_readers (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_list_readers (session->private_proxy, ++ call, ++ session->cancellable, ++ on_list_readers_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_cancel_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_cancel_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(x))", ++ ret)); ++} ++ ++static gboolean ++on_handle_cancel (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_cancel (session->private_proxy, ++ call, ++ session->cancellable, ++ on_cancel_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_get_attrib_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_get_attrib_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(xay))", ++ ret)); ++} ++ ++static gboolean ++on_handle_get_attrib (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_get_attrib (session->private_proxy, ++ call, ++ session->cancellable, ++ on_get_attrib_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_set_attrib_finished (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr (GDBusMethodInvocation) invocation = user_data; ++ g_autoptr (GVariant) ret = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!grd_dbus_pcscd_session_call_set_attrib_finish ( ++ GRD_DBUS_PCSCD_SESSION (source_object), ++ &ret, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return; ++ } ++ ++ g_dbus_method_invocation_return_value (invocation, ++ g_variant_new ("(@(x))", ++ ret)); ++} ++ ++static gboolean ++on_handle_set_attrib (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ if (!session->private_proxy) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "RDP Smartcard not connected"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ grd_dbus_pcscd_session_call_set_attrib (session->private_proxy, ++ call, ++ session->cancellable, ++ on_set_attrib_finished, ++ g_object_ref (invocation)); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ + static void + on_private_connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, +@@ -128,6 +947,84 @@ on_private_connection_ready (GObject *source_object, + session); + } + ++static gboolean ++create_system_proxy (GrdPcscdSession *session, ++ GDBusConnection *system_connection, ++ GError **error) ++{ ++ g_autoptr (GrdDBusPcscdSession) system_proxy = NULL; ++ g_autofree char *object_path = NULL; ++ ++ object_path = ++ g_strdup_printf ("%s/%s", PCSCD_OBJECT_PATH_PREFIX, session->session_id); ++ ++ system_proxy = grd_dbus_pcscd_session_skeleton_new (); ++ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (system_proxy), ++ system_connection, ++ object_path, ++ error)) ++ return FALSE; ++ ++ g_signal_connect_object (system_proxy, "handle-establish-context", ++ G_CALLBACK (on_handle_establish_context), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-release-context", ++ G_CALLBACK (on_handle_release_context), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-is-valid-context", ++ G_CALLBACK (on_handle_is_valid_context), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-connect-card", ++ G_CALLBACK (on_handle_connect_card), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-reconnect", ++ G_CALLBACK (on_handle_reconnect), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-disconnect-card", ++ G_CALLBACK (on_handle_disconnect_card), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-begin-transaction", ++ G_CALLBACK (on_handle_begin_transaction), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-end-transaction", ++ G_CALLBACK (on_handle_end_transaction), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-status-card", ++ G_CALLBACK (on_handle_status_card), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-get-status-change", ++ G_CALLBACK (on_handle_get_status_change), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-control-card", ++ G_CALLBACK (on_handle_control_card), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-transmit", ++ G_CALLBACK (on_handle_transmit), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-list-reader-groups", ++ G_CALLBACK (on_handle_list_reader_groups), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-list-readers", ++ G_CALLBACK (on_handle_list_readers), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-cancel", ++ G_CALLBACK (on_handle_cancel), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-get-attrib", ++ G_CALLBACK (on_handle_get_attrib), ++ session, G_CONNECT_DEFAULT); ++ g_signal_connect_object (system_proxy, "handle-set-attrib", ++ G_CALLBACK (on_handle_set_attrib), ++ session, G_CONNECT_DEFAULT); ++ ++ session->system_proxy = g_steal_pointer (&system_proxy); ++ ++ g_debug ("[PCSCD.SESSION %s] Exported on system bus at %s", ++ session->session_id, object_path); ++ ++ return TRUE; ++} ++ + static void + create_private_proxy (GrdPcscdSession *session, + int fd) +@@ -160,9 +1057,10 @@ grd_pcscd_session_get_session_id (GrdPcscdSession *session) + } + + GrdPcscdSession * +-grd_pcscd_session_new (const char *session_id, +- int fd, +- GDBusConnection *system_connection) ++grd_pcscd_session_new (const char *session_id, ++ int fd, ++ GDBusConnection *system_connection, ++ GError **error) + { + g_autoptr (GrdPcscdSession) session = NULL; + +@@ -170,6 +1068,12 @@ grd_pcscd_session_new (const char *session_id, + session->session_id = g_strdup (session_id); + session->cancellable = g_cancellable_new (); + ++ if (!create_system_proxy (session, system_connection, error)) ++ { ++ close (fd); ++ return NULL; ++ } ++ + create_private_proxy (session, fd); + + return g_steal_pointer (&session); +@@ -194,6 +1098,13 @@ grd_pcscd_session_dispose (GObject *object) + g_clear_object (&session->private_proxy); + } + ++ if (session->system_proxy) ++ { ++ g_dbus_interface_skeleton_unexport ( ++ G_DBUS_INTERFACE_SKELETON (session->system_proxy)); ++ g_clear_object (&session->system_proxy); ++ } ++ + g_clear_pointer (&session->session_id, g_free); + + G_OBJECT_CLASS (grd_pcscd_session_parent_class)->dispose (object); +diff --git a/src/grd-pcscd-session.h b/src/grd-pcscd-session.h +index bafcb1e..1a0cce2 100644 +--- a/src/grd-pcscd-session.h ++++ b/src/grd-pcscd-session.h +@@ -28,8 +28,9 @@ + G_DECLARE_FINAL_TYPE (GrdPcscdSession, grd_pcscd_session, + GRD, PCSCD_SESSION, GObject) + +-GrdPcscdSession *grd_pcscd_session_new (const char *session_id, +- int fd, +- GDBusConnection *system_connection); ++GrdPcscdSession *grd_pcscd_session_new (const char *session_id, ++ int fd, ++ GDBusConnection *system_connection, ++ GError **error); + + const char *grd_pcscd_session_get_session_id (GrdPcscdSession *session); +diff --git a/src/grd-pcscd.c b/src/grd-pcscd.c +index a70ce57..781f290 100644 +--- a/src/grd-pcscd.c ++++ b/src/grd-pcscd.c +@@ -148,7 +148,18 @@ on_handle_connect (GrdDBusPcscd *skeleton, + + session = grd_pcscd_session_new (session_id, + g_steal_fd (&fd), +- pcscd->connection); ++ pcscd->connection, ++ &error); ++ if (!session) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Failed to create session %s: %s", ++ session_id, ++ error->message); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } + + g_signal_connect (session, "closed", + G_CALLBACK (on_session_closed), +diff --git a/src/grd-rdp-smartcard.c b/src/grd-rdp-smartcard.c +index a674fc0..746a7ab 100644 +--- a/src/grd-rdp-smartcard.c ++++ b/src/grd-rdp-smartcard.c +@@ -24,6 +24,7 @@ + + #include "grd-rdp-smartcard.h" + ++#include + #include + #include + #include +@@ -34,6 +35,37 @@ + #include "grd-rdp-device-redirection.h" + #include "grd-session-rdp.h" + ++#define EXTEND_32_TO_64(val) \ ++ (G_STATIC_ASSERT_EXPR (sizeof (val) == sizeof (uint32_t)), \ ++ (uint64_t) (uint32_t) (val)) ++ ++#define NARROW_64_TO_32(val) \ ++ (G_STATIC_ASSERT_EXPR (sizeof (val) == sizeof (uint64_t)), \ ++ ((uint64_t) (val) > UINT32_MAX \ ++ ? (g_warning ("Narrowing 64-to-32 lost data in '"#val"': 0x%lx", \ ++ (uint64_t) (val)), \ ++ (uint32_t) (val)) \ ++ : (uint32_t) (val))) ++ ++/* ++ * pcsc-lite dwState bitmask values. These differ from the WinSCard ++ * sequential values (0-6) defined by FreeRDP/WinPR in winpr/smartcard.h. ++ */ ++#define PCSC_SCARD_UNKNOWN 0x0001 ++#define PCSC_SCARD_ABSENT 0x0002 ++#define PCSC_SCARD_PRESENT 0x0004 ++#define PCSC_SCARD_SWALLOWED 0x0008 ++#define PCSC_SCARD_POWERED 0x0010 ++#define PCSC_SCARD_NEGOTIABLE 0x0020 ++ ++/* ++ * pcsc-lite and WinSCard use different values for SCARD_PROTOCOL_RAW. ++ * SCARD_PROTOCOL_T15 is not in WinSCard. ++ * T=0 (0x01) and T=1 (0x02) are the same in both. ++ */ ++#define PCSC_SCARD_PROTOCOL_RAW 0x0004 ++#define PCSC_SCARD_PROTOCOL_T15 0x0008 ++ + typedef struct + { + GrdRdpSmartcard *smartcard; +@@ -49,6 +81,7 @@ struct _GrdRdpSmartcard + + RdpdrServerContext *rdpdr_context; + GHashTable *smartcard_device_ids; ++ GHashTable *pending_invocations; + + /* System bus proxy to grd-pcscd */ + GCancellable *pcscd_proxy_cancellable; +@@ -62,6 +95,1454 @@ struct _GrdRdpSmartcard + + G_DEFINE_TYPE (GrdRdpSmartcard, grd_rdp_smartcard, G_TYPE_OBJECT) + ++static uint32_t ++convert_card_state_to_pcsc (uint32_t card_reader_state) ++{ ++ switch (card_reader_state) ++ { ++ case SCARD_UNKNOWN: ++ return PCSC_SCARD_UNKNOWN; ++ case SCARD_ABSENT: ++ return PCSC_SCARD_ABSENT; ++ case SCARD_PRESENT: ++ return PCSC_SCARD_PRESENT; ++ case SCARD_SWALLOWED: ++ return PCSC_SCARD_PRESENT | PCSC_SCARD_SWALLOWED; ++ case SCARD_POWERED: ++ return PCSC_SCARD_PRESENT | PCSC_SCARD_POWERED; ++ case SCARD_NEGOTIABLE: ++ case SCARD_SPECIFIC: ++ return PCSC_SCARD_PRESENT | PCSC_SCARD_POWERED | PCSC_SCARD_NEGOTIABLE; ++ default: ++ g_warning ("[RDP.SMARTCARD] Unknown CardReaderState: %u", card_reader_state); ++ return PCSC_SCARD_UNKNOWN; ++ } ++} ++ ++static uint32_t ++convert_protocol_to_pcsc (uint32_t dwProtocols) ++{ ++ if (dwProtocols & SCARD_PROTOCOL_RAW) ++ { ++ dwProtocols &= ~SCARD_PROTOCOL_RAW; ++ dwProtocols |= PCSC_SCARD_PROTOCOL_RAW; ++ } ++ ++ if (dwProtocols & SCARD_PROTOCOL_DEFAULT) ++ { ++ dwProtocols &= ~SCARD_PROTOCOL_DEFAULT; ++ } ++ ++ if (dwProtocols == SCARD_PROTOCOL_UNDEFINED) ++ { ++ dwProtocols = SCARD_PROTOCOL_Tx; ++ } ++ ++ return dwProtocols; ++} ++ ++static uint32_t ++convert_protocol_to_winscard (uint32_t dwProtocols) ++{ ++ if (dwProtocols & PCSC_SCARD_PROTOCOL_RAW) ++ { ++ dwProtocols &= ~PCSC_SCARD_PROTOCOL_RAW; ++ dwProtocols |= SCARD_PROTOCOL_RAW; ++ } ++ ++ if (dwProtocols & PCSC_SCARD_PROTOCOL_T15) ++ { ++ dwProtocols &= ~PCSC_SCARD_PROTOCOL_T15; ++ } ++ ++ return dwProtocols; ++} ++ ++static gboolean ++is_device_unavailable (GrdRdpSmartcard *smartcard, ++ GDBusMethodInvocation *invocation) ++{ ++ g_mutex_lock (&smartcard->shutdown_mutex); ++ if (!smartcard->in_shutdown && ++ g_hash_table_size (smartcard->smartcard_device_ids) > 0) ++ { ++ g_mutex_unlock (&smartcard->shutdown_mutex); ++ return FALSE; ++ } ++ g_mutex_unlock (&smartcard->shutdown_mutex); ++ ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Smartcard device not available"); ++ return TRUE; ++} ++ ++static void ++remove_pending_invocation (RdpdrServerContext *rdpdr_context, ++ GDBusMethodInvocation *invocation) ++{ ++ GrdRdpDeviceRedirection *device_redirection = rdpdr_context->data; ++ GrdRdpSmartcard *smartcard; ++ ++ smartcard = grd_rdp_device_redirection_get_smartcard (device_redirection); ++ if (!smartcard) ++ return; ++ ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++} ++ ++static void ++on_smartcard_establish_context_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const EstablishContext_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ int64_t context = 0; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] EstablishContext complete: return_code=%i", ++ return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ if (ret) ++ context = (int64_t) smartcard_scard_context_native_from_redir (&ret->hContext); ++ ++ ret_variant = g_variant_new ("((xx))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ context); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_establish_context (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ uint64_t scope; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] EstablishContext"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(t)", &scope); ++ status = rdpdr_context->SmartcardEstablishContext (rdpdr_context, ++ invocation, ++ NARROW_64_TO_32 (scope), ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardEstablishContext failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_release_context_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] ReleaseContext complete: return_code=%i", ++ return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ ret_variant = g_variant_new ("((x))", ++ (int64_t) EXTEND_32_TO_64 (return_code)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_release_context (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ REDIR_SCARDCONTEXT redir_context = {}; ++ int64_t context; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] ReleaseContext"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(x)", &context); ++ smartcard_scard_context_native_to_redir (&redir_context, context); ++ ++ status = rdpdr_context->SmartcardReleaseContext (rdpdr_context, ++ invocation, ++ &redir_context, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardReleaseContext failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_is_valid_context_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] IsValidContext complete: return_code=%i", ++ return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ ret_variant = g_variant_new ("((x))", ++ (int64_t) EXTEND_32_TO_64 (return_code)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_is_valid_context (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ REDIR_SCARDCONTEXT redir_context = {}; ++ int64_t context; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] IsValidContext"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(x)", &context); ++ smartcard_scard_context_native_to_redir (&redir_context, context); ++ ++ status = rdpdr_context->SmartcardIsValidContext (rdpdr_context, ++ invocation, ++ &redir_context, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardIsValidContext failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_connect_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const Connect_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ int64_t out_context = 0; ++ int64_t card = 0; ++ uint64_t active_protocol = 0; ++ ++ g_debug ("[RDP.SMARTCARD] Connect complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ if (ret) ++ { ++ out_context = ++ (int64_t) smartcard_scard_context_native_from_redir (&ret->hContext); ++ card = ++ (int64_t) smartcard_scard_handle_native_from_redir (&ret->hCard); ++ active_protocol = ++ EXTEND_32_TO_64 (convert_protocol_to_pcsc (ret->dwActiveProtocol)); ++ } ++ ++ ret_variant = g_variant_new ("((xxxt))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ out_context, card, active_protocol); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_connect_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *variant_call, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ ConnectA_Call call = {}; ++ int64_t context; ++ const char *reader; ++ uint64_t share_mode; ++ uint64_t preferred_protocols; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] Connect"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (variant_call, "(x&stt)", ++ &context, ++ &reader, ++ &share_mode, ++ &preferred_protocols); ++ ++ smartcard_scard_context_native_to_redir (&call.Common.handles.hContext, ++ context); ++ call.Common.dwShareMode = NARROW_64_TO_32 (share_mode); ++ call.szReader = (char *) reader; ++ call.Common.dwPreferredProtocols = ++ convert_protocol_to_winscard (NARROW_64_TO_32 (preferred_protocols)); ++ ++ status = rdpdr_context->SmartcardConnectA (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardConnectA failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_reconnect_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const Reconnect_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ uint64_t active_protocol = 0; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] Reconnect complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ if (ret) ++ { ++ active_protocol = ++ EXTEND_32_TO_64 (convert_protocol_to_pcsc (ret->dwActiveProtocol)); ++ } ++ ++ ret_variant = g_variant_new ("((xt))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ active_protocol); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_reconnect (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ Reconnect_Call call = {}; ++ int64_t card; ++ uint64_t share_mode; ++ uint64_t preferred_protocols; ++ uint64_t initialization; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] Reconnect"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xttt)", ++ &card, ++ &share_mode, ++ &preferred_protocols, ++ &initialization); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.dwShareMode = NARROW_64_TO_32 (share_mode); ++ call.dwPreferredProtocols = ++ convert_protocol_to_winscard (NARROW_64_TO_32 (preferred_protocols)); ++ call.dwInitialization = NARROW_64_TO_32 (initialization); ++ ++ status = rdpdr_context->SmartcardReconnect (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardReconnect failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_disconnect_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] Disconnect complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ ret_variant = g_variant_new ("((x))", ++ (int64_t) EXTEND_32_TO_64 (return_code)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_disconnect_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ HCardAndDisposition_Call call = {}; ++ int64_t card; ++ uint64_t disposition; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] Disconnect"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xt)", &card, &disposition); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.dwDisposition = NARROW_64_TO_32 (disposition); ++ ++ status = rdpdr_context->SmartcardDisconnect (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardDisconnect failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_begin_transaction_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] BeginTransaction complete: return_code=%i", ++ return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ ret_variant = g_variant_new ("((x))", ++ (int64_t) EXTEND_32_TO_64 (return_code)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_begin_transaction (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ HCardAndDisposition_Call call = {}; ++ int64_t card; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] BeginTransaction"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(x)", &card); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ ++ status = rdpdr_context->SmartcardBeginTransaction (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardBeginTransaction failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_end_transaction_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] EndTransaction complete: return_code=%i", ++ return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ ret_variant = g_variant_new ("((x))", ++ (int64_t) EXTEND_32_TO_64 (return_code)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_end_transaction (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ HCardAndDisposition_Call call = {}; ++ int64_t card; ++ uint64_t disposition; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] EndTransaction"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xt)", &card, &disposition); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.dwDisposition = NARROW_64_TO_32 (disposition); ++ ++ status = rdpdr_context->SmartcardEndTransaction (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardEndTransaction failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_status_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const Status_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ const char *reader_name = ""; ++ uint64_t state = 0; ++ uint64_t protocol = 0; ++ const uint8_t *atr = NULL; ++ size_t atr_len = 0; ++ ++ g_debug ("[RDP.SMARTCARD] Status complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ if (ret) ++ { ++ if (ret->mszReaderNames) ++ reader_name = (const char *) ret->mszReaderNames; ++ state = EXTEND_32_TO_64 (convert_card_state_to_pcsc (ret->dwState)); ++ protocol = EXTEND_32_TO_64 (convert_protocol_to_pcsc (ret->dwProtocol)); ++ atr = ret->pbAtr; ++ atr_len = ret->cbAtrLen; ++ } ++ ++ ret_variant = g_variant_new ("((xstt@ay))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ reader_name, state, protocol, ++ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ++ atr, atr_len, ++ sizeof (uint8_t))); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_status_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ Status_Call call = {}; ++ int64_t card; ++ uint64_t reader_len; ++ uint64_t atr_len; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] Status"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xtt)", &card, &reader_len, &atr_len); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.cchReaderLen = NARROW_64_TO_32 (reader_len); ++ call.cbAtrLen = NARROW_64_TO_32 (atr_len); ++ ++ status = rdpdr_context->SmartcardStatusA (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardStatusA failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_get_status_change_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const GetStatusChange_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariantBuilder *builder; ++ GVariant *ret_variant; ++ int i; ++ ++ g_debug ("[RDP.SMARTCARD] GetStatusChange complete: return_code=%i", ++ return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ttay)")); ++ ++ if (ret) ++ { ++ for (i = 0; i < ret->cReaders; i++) ++ { ++ g_variant_builder_add (builder, "(tt@ay)", ++ EXTEND_32_TO_64 (ret->rgReaderStates[i].dwEventState), ++ EXTEND_32_TO_64 (ret->rgReaderStates[i].cbAtr), ++ g_variant_new_fixed_array ( ++ G_VARIANT_TYPE_BYTE, ++ ret->rgReaderStates[i].rgbAtr, ++ ret->rgReaderStates[i].cbAtr, ++ sizeof (uint8_t))); ++ } ++ } ++ ++ ret_variant = g_variant_new ("((x@a(ttay)))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ g_variant_builder_end (builder)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_get_status_change (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ g_autoptr (GVariant) reader_states = NULL; ++ g_autofree SCARD_READERSTATEA *reader_states_a = NULL; ++ GetStatusChangeA_Call call = {}; ++ int64_t context; ++ uint64_t timeout; ++ char *reader_name; ++ uint64_t current_state; ++ uint32_t completion_id; ++ GVariantIter iter; ++ size_t n_readers; ++ uint32_t status; ++ int i; ++ ++ g_debug ("[RDP.SMARTCARD] GetStatusChange"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xt@a(st))", ++ &context, ++ &timeout, ++ &reader_states); ++ ++ n_readers = g_variant_n_children (reader_states); ++ reader_states_a = g_new0 (SCARD_READERSTATEA, n_readers); ++ ++ i = 0; ++ g_variant_iter_init (&iter, reader_states); ++ while (g_variant_iter_next (&iter, "(&st)", &reader_name, ¤t_state)) ++ { ++ reader_states_a[i].szReader = reader_name; ++ reader_states_a[i].dwCurrentState = NARROW_64_TO_32 (current_state); ++ i++; ++ } ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); ++ call.dwTimeOut = NARROW_64_TO_32 (timeout); ++ call.cReaders = NARROW_64_TO_32 (n_readers); ++ call.rgReaderStates = reader_states_a; ++ ++ status = rdpdr_context->SmartcardGetStatusChangeA (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardGetStatusChange failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_control_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const Control_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ const uint8_t *out_buffer = NULL; ++ size_t out_buffer_size = 0; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] Control complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ if (ret) ++ { ++ out_buffer = ret->pvOutBuffer; ++ out_buffer_size = ret->cbOutBufferSize; ++ } ++ ++ ret_variant = g_variant_new ("((x@ay))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ++ out_buffer, ++ out_buffer_size, ++ sizeof (uint8_t))); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_control_card (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ g_autoptr (GVariant) send_buffer = NULL; ++ Control_Call call = {}; ++ int64_t card; ++ uint64_t control_code; ++ uint64_t send_buffer_size; ++ uint64_t recv_buffer_size; ++ const uint8_t *send_data; ++ size_t send_size; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] Control"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xt@aytt)", ++ &card, ++ &control_code, ++ &send_buffer, ++ &send_buffer_size, ++ &recv_buffer_size); ++ ++ send_data = g_variant_get_fixed_array (send_buffer, &send_size, ++ sizeof (uint8_t)); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.dwControlCode = NARROW_64_TO_32 (control_code); ++ call.cbInBufferSize = NARROW_64_TO_32 (send_buffer_size); ++ call.pvInBuffer = (uint8_t *) send_data; ++ call.cbOutBufferSize = NARROW_64_TO_32 (recv_buffer_size); ++ ++ status = rdpdr_context->SmartcardControl (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardControl failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_transmit_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const Transmit_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ uint64_t recv_pci_protocol = 0; ++ const uint8_t *recv_buffer = NULL; ++ size_t recv_length = 0; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] Transmit complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ if (ret) ++ { ++ if (ret->pioRecvPci) ++ { ++ recv_pci_protocol = ++ EXTEND_32_TO_64 (convert_protocol_to_pcsc (ret->pioRecvPci->dwProtocol)); ++ } ++ recv_buffer = ret->pbRecvBuffer; ++ recv_length = ret->cbRecvLength; ++ } ++ ++ ret_variant = g_variant_new ("((xt@ay))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ recv_pci_protocol, ++ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ++ recv_buffer, ++ recv_length, ++ sizeof (uint8_t))); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_transmit (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ g_autoptr (GVariant) send_buffer = NULL; ++ SCARD_IO_REQUEST send_pci = {}; ++ SCARD_IO_REQUEST recv_pci = {}; ++ Transmit_Call call = {}; ++ int64_t card; ++ uint64_t send_pci_protocol; ++ uint64_t recv_pci_protocol_in; ++ uint64_t recv_buffer_size; ++ const uint8_t *send_data; ++ size_t send_size; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] Transmit"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xt@aytt)", ++ &card, ++ &send_pci_protocol, ++ &send_buffer, ++ &recv_pci_protocol_in, ++ &recv_buffer_size); ++ ++ send_data = g_variant_get_fixed_array (send_buffer, &send_size, ++ sizeof (uint8_t)); ++ ++ send_pci.dwProtocol = ++ convert_protocol_to_winscard (NARROW_64_TO_32 (send_pci_protocol)); ++ send_pci.cbPciLength = sizeof (SCARD_IO_REQUEST); ++ ++ recv_pci.dwProtocol = ++ convert_protocol_to_winscard (NARROW_64_TO_32 (recv_pci_protocol_in)); ++ recv_pci.cbPciLength = sizeof (SCARD_IO_REQUEST); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.pioSendPci = &send_pci; ++ call.cbSendLength = NARROW_64_TO_32 (send_size); ++ call.pbSendBuffer = (uint8_t *) send_data; ++ call.pioRecvPci = &recv_pci; ++ call.cbRecvLength = NARROW_64_TO_32 (recv_buffer_size); ++ ++ status = rdpdr_context->SmartcardTransmit (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardTransmit failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_list_reader_groups_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const ListReaderGroups_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariantBuilder *builder; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] ListReaderGroups complete: return_code=%i", ++ return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); ++ ++ if (ret && ret->msz && return_code == SCARD_S_SUCCESS) ++ { ++ const char *p = (const char *) ret->msz; ++ const char *end = (const char *) (ret->msz + ret->cBytes); ++ ++ while (p < end && *p) ++ { ++ g_variant_builder_add (builder, "s", p); ++ p += strlen (p) + 1; ++ } ++ } ++ ++ ret_variant = g_variant_new ("((x@as))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ g_variant_builder_end (builder)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_list_reader_groups (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ ListReaderGroups_Call call = {}; ++ int64_t context; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] ListReaderGroups"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(x)", &context); ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); ++ call.cchGroups = SCARD_AUTOALLOCATE; ++ ++ status = rdpdr_context->SmartcardListReaderGroupsA (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardListReaderGroups failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_list_readers_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const ListReaders_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariantBuilder *builder; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] ListReaders complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); ++ ++ if (ret && ret->msz && return_code == SCARD_S_SUCCESS) ++ { ++ const char *p = (const char *) ret->msz; ++ const char *end = (const char *) (ret->msz + ret->cBytes); ++ ++ while (p < end && *p) ++ { ++ g_variant_builder_add (builder, "s", p); ++ p += strlen (p) + 1; ++ } ++ } ++ ++ ret_variant = g_variant_new ("((x@as))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ g_variant_builder_end (builder)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_list_readers (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ ListReaders_Call call = {}; ++ int64_t context; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] ListReaders"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(x)", &context); ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); ++ call.cchReaders = SCARD_AUTOALLOCATE; ++ ++ status = rdpdr_context->SmartcardListReadersA (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardListReaders failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_cancel_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] Cancel complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ ret_variant = g_variant_new ("((x))", ++ (int64_t) EXTEND_32_TO_64 (return_code)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_cancel (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ REDIR_SCARDCONTEXT redir_context = {}; ++ int64_t context; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] Cancel"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(x)", &context); ++ smartcard_scard_context_native_to_redir (&redir_context, context); ++ ++ status = rdpdr_context->SmartcardCancel (rdpdr_context, ++ invocation, ++ &redir_context, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardCancel failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_get_attrib_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code, ++ const GetAttrib_Return *ret) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ const uint8_t *attr = NULL; ++ size_t attr_len = 0; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] GetAttrib complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ if (ret) ++ { ++ attr = ret->pbAttr; ++ attr_len = ret->cbAttrLen; ++ } ++ ++ ret_variant = g_variant_new ("((x@ay))", ++ (int64_t) EXTEND_32_TO_64 (return_code), ++ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ++ attr, attr_len, ++ sizeof (uint8_t))); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_get_attrib (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ GetAttrib_Call call = {}; ++ int64_t card; ++ uint64_t attr_id; ++ uint64_t attr_len; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] GetAttrib"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xtt)", &card, &attr_id, &attr_len); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.dwAttrId = NARROW_64_TO_32 (attr_id); ++ call.cbAttrLen = NARROW_64_TO_32 (attr_len); ++ ++ status = rdpdr_context->SmartcardGetAttrib (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardGetAttrib failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++on_smartcard_set_attrib_complete (RdpdrServerContext *rdpdr_context, ++ void *callback_data, ++ uint32_t io_status, ++ int32_t return_code) ++{ ++ GDBusMethodInvocation *invocation = callback_data; ++ GVariant *ret_variant; ++ ++ g_debug ("[RDP.SMARTCARD] SetAttrib complete: return_code=%i", return_code); ++ ++ remove_pending_invocation (rdpdr_context, invocation); ++ ++ ret_variant = g_variant_new ("((x))", ++ (int64_t) EXTEND_32_TO_64 (return_code)); ++ g_dbus_method_invocation_return_value (invocation, ret_variant); ++} ++ ++static gboolean ++on_handle_set_attrib (GrdDBusPcscdSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GVariant *call_variant, ++ gpointer user_data) ++{ ++ GrdRdpSmartcard *smartcard = user_data; ++ RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; ++ g_autoptr (GVariant) attr_buffer = NULL; ++ SetAttrib_Call call = {}; ++ int64_t card; ++ uint64_t attr_id; ++ const uint8_t *attr_data; ++ size_t attr_size; ++ uint32_t completion_id; ++ uint32_t status; ++ ++ g_debug ("[RDP.SMARTCARD] SetAttrib"); ++ ++ if (is_device_unavailable (smartcard, invocation)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ g_variant_get (call_variant, "(xt@ay)", ++ &card, ++ &attr_id, ++ &attr_buffer); ++ ++ attr_data = g_variant_get_fixed_array (attr_buffer, &attr_size, ++ sizeof (uint8_t)); ++ ++ smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); ++ call.dwAttrId = NARROW_64_TO_32 (attr_id); ++ call.cbAttrLen = attr_size; ++ call.pbAttr = (uint8_t *) attr_data; ++ ++ status = rdpdr_context->SmartcardSetAttrib (rdpdr_context, ++ invocation, ++ &call, ++ &completion_id); ++ if (status != CHANNEL_RC_OK) ++ { ++ g_hash_table_remove (smartcard->pending_invocations, invocation); ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "SmartcardSetAttrib failed: %u", ++ status); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static void ++connect_pcscd_session_handlers (GrdDBusPcscdSession *skeleton, ++ GrdRdpSmartcard *smartcard) ++{ ++ g_signal_connect_object (skeleton, "handle-establish-context", ++ G_CALLBACK (on_handle_establish_context), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-release-context", ++ G_CALLBACK (on_handle_release_context), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-is-valid-context", ++ G_CALLBACK (on_handle_is_valid_context), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-connect-card", ++ G_CALLBACK (on_handle_connect_card), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-reconnect", ++ G_CALLBACK (on_handle_reconnect), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-disconnect-card", ++ G_CALLBACK (on_handle_disconnect_card), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-begin-transaction", ++ G_CALLBACK (on_handle_begin_transaction), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-end-transaction", ++ G_CALLBACK (on_handle_end_transaction), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-status-card", ++ G_CALLBACK (on_handle_status_card), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-get-status-change", ++ G_CALLBACK (on_handle_get_status_change), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-control-card", ++ G_CALLBACK (on_handle_control_card), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-transmit", ++ G_CALLBACK (on_handle_transmit), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-list-reader-groups", ++ G_CALLBACK (on_handle_list_reader_groups), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-list-readers", ++ G_CALLBACK (on_handle_list_readers), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-cancel", ++ G_CALLBACK (on_handle_cancel), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-get-attrib", ++ G_CALLBACK (on_handle_get_attrib), ++ smartcard, G_CONNECT_DEFAULT); ++ g_signal_connect_object (skeleton, "handle-set-attrib", ++ G_CALLBACK (on_handle_set_attrib), ++ smartcard, G_CONNECT_DEFAULT); ++} ++ + static void + on_private_connection_ready (GObject *source_object, + GAsyncResult *result, +@@ -95,6 +1576,8 @@ on_private_connection_ready (GObject *source_object, + return; + } + ++ connect_pcscd_session_handlers (private_proxy, smartcard); ++ + smartcard->private_proxy = g_steal_pointer (&private_proxy); + + g_debug ("[RDP.SMARTCARD] Private D-Bus connection established"); +@@ -199,18 +1682,52 @@ connect_to_grd_pcscd (GrdRdpSmartcard *smartcard) + data); + } + ++static gboolean ++fail_pending_invocation_by_connection (gpointer key, ++ gpointer value, ++ gpointer user_data) ++{ ++ GDBusMethodInvocation *invocation = key; ++ GDBusConnection *connection = user_data; ++ ++ if (g_dbus_method_invocation_get_connection (invocation) != connection) ++ return FALSE; ++ ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Smartcard device disconnected"); ++ return TRUE; ++} ++ ++static void ++unexport_and_fail_pending_invocations (GrdRdpSmartcard *smartcard, ++ GrdDBusPcscdSession **proxy) ++{ ++ GDBusInterfaceSkeleton *skeleton; ++ GDBusConnection *connection; ++ ++ if (!*proxy) ++ return; ++ ++ skeleton = G_DBUS_INTERFACE_SKELETON (*proxy); ++ connection = g_dbus_interface_skeleton_get_connection (skeleton); ++ ++ g_dbus_interface_skeleton_unexport (skeleton); ++ g_clear_object (proxy); ++ ++ g_hash_table_foreach_remove (smartcard->pending_invocations, ++ fail_pending_invocation_by_connection, ++ connection); ++} ++ + static void + disconnect_from_grd_pcscd (GrdRdpSmartcard *smartcard) + { + g_cancellable_cancel (smartcard->private_proxy_cancellable); + g_clear_object (&smartcard->private_proxy_cancellable); + +- if (smartcard->private_proxy) +- { +- g_dbus_interface_skeleton_unexport ( +- G_DBUS_INTERFACE_SKELETON (smartcard->private_proxy)); +- g_clear_object (&smartcard->private_proxy); +- } ++ unexport_and_fail_pending_invocations (smartcard, &smartcard->private_proxy); + } + + static void +@@ -395,6 +1912,40 @@ grd_rdp_smartcard_new (GrdSessionRdp *session_rdp, + + rdpdr_context->OnSmartcardCreate = on_smartcard_create; + rdpdr_context->OnSmartcardDelete = on_smartcard_delete; ++ rdpdr_context->OnSmartcardEstablishContextComplete = ++ on_smartcard_establish_context_complete; ++ rdpdr_context->OnSmartcardReleaseContextComplete = ++ on_smartcard_release_context_complete; ++ rdpdr_context->OnSmartcardIsValidContextComplete = ++ on_smartcard_is_valid_context_complete; ++ rdpdr_context->OnSmartcardConnectComplete = ++ on_smartcard_connect_complete; ++ rdpdr_context->OnSmartcardReconnectComplete = ++ on_smartcard_reconnect_complete; ++ rdpdr_context->OnSmartcardDisconnectComplete = ++ on_smartcard_disconnect_complete; ++ rdpdr_context->OnSmartcardBeginTransactionComplete = ++ on_smartcard_begin_transaction_complete; ++ rdpdr_context->OnSmartcardEndTransactionComplete = ++ on_smartcard_end_transaction_complete; ++ rdpdr_context->OnSmartcardStatusComplete = ++ on_smartcard_status_complete; ++ rdpdr_context->OnSmartcardGetStatusChangeComplete = ++ on_smartcard_get_status_change_complete; ++ rdpdr_context->OnSmartcardControlComplete = ++ on_smartcard_control_complete; ++ rdpdr_context->OnSmartcardTransmitComplete = ++ on_smartcard_transmit_complete; ++ rdpdr_context->OnSmartcardListReaderGroupsComplete = ++ on_smartcard_list_reader_groups_complete; ++ rdpdr_context->OnSmartcardListReadersComplete = ++ on_smartcard_list_readers_complete; ++ rdpdr_context->OnSmartcardCancelComplete = ++ on_smartcard_cancel_complete; ++ rdpdr_context->OnSmartcardGetAttribComplete = ++ on_smartcard_get_attrib_complete; ++ rdpdr_context->OnSmartcardSetAttribComplete = ++ on_smartcard_set_attrib_complete; + + rdpdr_context->supported |= RDPDR_DTYP_SMARTCARD; + +@@ -408,6 +1959,7 @@ grd_rdp_smartcard_dispose (GObject *object) + teardown_pcscd_proxies (smartcard); + + g_clear_pointer (&smartcard->smartcard_device_ids, g_hash_table_unref); ++ g_clear_pointer (&smartcard->pending_invocations, g_hash_table_unref); + + G_OBJECT_CLASS (grd_rdp_smartcard_parent_class)->dispose (object); + } +@@ -428,6 +1980,7 @@ grd_rdp_smartcard_init (GrdRdpSmartcard *smartcard) + g_mutex_init (&smartcard->shutdown_mutex); + + smartcard->smartcard_device_ids = g_hash_table_new (NULL, NULL); ++ smartcard->pending_invocations = g_hash_table_new (NULL, NULL); + } + + static void +diff --git a/src/meson.build b/src/meson.build +index 62e32a5..73c59cd 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -437,6 +437,23 @@ if have_rdp + include_directories: [configinc], + install: true, + install_dir: libexecdir) ++ ++ shared_library('grdpcsc', ++ 'grd-pcsc-lib.c', ++ 'grd-pcsc-lib-backend.c', ++ 'grd-pcsc-lib-backend-grd-pcscd-proxy.c', ++ dbus_gen_pcscd, ++ dependencies: [glib_dep, ++ gio_dep, ++ dl_dep, ++ libsystemd_dep], ++ include_directories: [configinc], ++ # GLib's GType system cannot survive dlclose/dlopen cycles ++ link_args: ['-Wl,-z,nodelete'], ++ version: '0.0.0', ++ soversion: 0, ++ gnu_symbol_visibility: 'hidden', ++ install: true) + endif + + custom_target('gsettings-enums', +diff --git a/src/org.gnome.RemoteDesktop.Pcscd.xml b/src/org.gnome.RemoteDesktop.Pcscd.xml +index 34e98a9..d4042e4 100644 +--- a/src/org.gnome.RemoteDesktop.Pcscd.xml ++++ b/src/org.gnome.RemoteDesktop.Pcscd.xml +@@ -54,6 +54,210 @@ + --> + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +-- +2.51.0 + +From de2047feab4712684bf933de04ba8e9c4d75bb5a Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 3 Aug 2026 19:38:51 +0200 +Subject: [PATCH 06/11] rdp-smartcard: Add session bus proxy for direct PC/SC + access + +Add a session-level D-Bus skeleton to GrdRdpSmartcard so that processes +running inside the user session can issue PC/SC commands directly to the +RDP client, without depending on grd-pcscd on the system bus. + +When a smartcard device is created, GrdRdpSmartcard now owns the +org.gnome.RemoteDesktop.Pcscd bus name on the session bus and exports +a PcscdSession skeleton at /org/gnome/RemoteDesktop/Pcscd/{session_id}. +This session proxy is fully independent from the private peer-to-peer +proxy used by grd-pcscd: each has its own pending invocations table, and +a grd-pcscd name owner change only affects the private path. + +libgrdpcsc first tries to connect to the session bus object, falling +back to the system bus object if it is not available. + +Part-of: +--- + src/grd-pcsc-lib-backend-grd-pcscd-proxy.c | 130 +++++++++++++++------ + src/grd-rdp-smartcard.c | 104 +++++++++++++++++ + 2 files changed, 199 insertions(+), 35 deletions(-) + +diff --git a/src/grd-pcsc-lib-backend-grd-pcscd-proxy.c b/src/grd-pcsc-lib-backend-grd-pcscd-proxy.c +index 7c8b1442..779e1586 100644 +--- a/src/grd-pcsc-lib-backend-grd-pcscd-proxy.c ++++ b/src/grd-pcsc-lib-backend-grd-pcscd-proxy.c +@@ -822,6 +822,40 @@ grd_pcscd_proxy_set_attrib (SCARDHANDLE hCard, + return return_value; + } + ++static char * ++get_session_id_from_uid (uid_t uid) ++{ ++ const char * const graphical_types[] = { "wayland", "x11", NULL }; ++ const char * const active_states[] = { "active", "online", NULL }; ++ g_auto (GStrv) sessions = NULL; ++ int n_sessions; ++ int i; ++ ++ n_sessions = sd_uid_get_sessions (uid, 0, &sessions); ++ ++ for (i = n_sessions - 1; i >= 0; i--) ++ { ++ g_autofree char *type = NULL; ++ g_autofree char *state = NULL; ++ ++ if (sd_session_get_type (sessions[i], &type) < 0) ++ continue; ++ ++ if (!g_strv_contains (graphical_types, type)) ++ continue; ++ ++ if (sd_session_get_state (sessions[i], &state) < 0) ++ continue; ++ ++ if (!g_strv_contains (active_states, state)) ++ continue; ++ ++ return g_strdup (sessions[i]); ++ } ++ ++ return NULL; ++} ++ + static char * + get_session_id (void) + { +@@ -832,10 +866,14 @@ get_session_id (void) + if (session_id && g_strcmp0 (session_id, "") != 0) + return g_strdup (session_id); + +- if (sd_pid_get_session (0, &logind_session_id) < 0) +- return NULL; ++ session_id = g_getenv ("XDG_SESSION_ID"); ++ if (session_id && session_id[0] != '\0') ++ return g_strdup (session_id); ++ ++ if (sd_pid_get_session (0, &logind_session_id) >= 0) ++ return logind_session_id; + +- return logind_session_id; ++ return get_session_id_from_uid (getuid ()); + } + + static gboolean +@@ -858,39 +896,9 @@ dbus_interface_exists (GDBusProxy *proxy) + return result != NULL; + } + +-gboolean +-grd_pcsc_lib_backend_init_grd_pcscd_proxy (GrdPcscLibBackend *backend) ++static void ++set_backend_functions (GrdPcscLibBackend *backend) + { +- g_autofree char *session_id = NULL; +- g_autofree char *object_path = NULL; +- g_autofree char *name_owner = NULL; +- g_autoptr (GError) error = NULL; +- +- session_id = get_session_id (); +- if (!session_id) +- return FALSE; +- +- object_path = g_strdup_printf ("/org/gnome/RemoteDesktop/Pcscd/%s", session_id); +- session_proxy = grd_dbus_pcscd_session_proxy_new_for_bus_sync ( +- G_BUS_TYPE_SYSTEM, +- G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, +- PCSCD_BUS_NAME, +- object_path, +- NULL, +- &error); +- if (!session_proxy) +- return FALSE; +- +- name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (session_proxy)); +- if (!name_owner) +- return FALSE; +- +- if (!dbus_interface_exists (G_DBUS_PROXY (session_proxy))) +- { +- g_clear_object (&session_proxy); +- return FALSE; +- } +- + backend->SCardEstablishContext = grd_pcscd_proxy_establish_context; + backend->SCardReleaseContext = grd_pcscd_proxy_release_context; + backend->SCardIsValidContext = grd_pcscd_proxy_is_valid_context; +@@ -909,10 +917,62 @@ grd_pcsc_lib_backend_init_grd_pcscd_proxy (GrdPcscLibBackend *backend) + backend->SCardCancel = grd_pcscd_proxy_cancel; + backend->SCardGetAttrib = grd_pcscd_proxy_get_attrib; + backend->SCardSetAttrib = grd_pcscd_proxy_set_attrib; ++} ++ ++static gboolean ++try_bus_type (GBusType bus_type, ++ const char *object_path) ++{ ++ g_autofree char *name_owner = NULL; ++ ++ session_proxy = grd_dbus_pcscd_session_proxy_new_for_bus_sync ( ++ bus_type, ++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, ++ PCSCD_BUS_NAME, ++ object_path, ++ NULL, ++ NULL); ++ if (!session_proxy) ++ return FALSE; ++ ++ name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (session_proxy)); ++ if (!name_owner) ++ { ++ g_clear_object (&session_proxy); ++ return FALSE; ++ } ++ ++ if (!dbus_interface_exists (G_DBUS_PROXY (session_proxy))) ++ { ++ g_clear_object (&session_proxy); ++ return FALSE; ++ } + + return TRUE; + } + ++gboolean ++grd_pcsc_lib_backend_init_grd_pcscd_proxy (GrdPcscLibBackend *backend) ++{ ++ g_autofree char *session_id = NULL; ++ g_autofree char *object_path = NULL; ++ ++ session_id = get_session_id (); ++ if (!session_id) ++ return FALSE; ++ ++ object_path = g_strdup_printf ("/org/gnome/RemoteDesktop/Pcscd/%s", session_id); ++ ++ if (try_bus_type (G_BUS_TYPE_SESSION, object_path) || ++ try_bus_type (G_BUS_TYPE_SYSTEM, object_path)) ++ { ++ set_backend_functions (backend); ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ + void + grd_pcsc_lib_backend_cleanup_grd_pcscd_proxy (void) + { +diff --git a/src/grd-rdp-smartcard.c b/src/grd-rdp-smartcard.c +index 746a7abb..c73858ad 100644 +--- a/src/grd-rdp-smartcard.c ++++ b/src/grd-rdp-smartcard.c +@@ -30,6 +30,7 @@ + #include + + #include "grd-context.h" ++#include "grd-daemon-utils.h" + #include "grd-dbus-pcscd.h" + #include "grd-private.h" + #include "grd-rdp-device-redirection.h" +@@ -91,6 +92,10 @@ struct _GrdRdpSmartcard + /* Private peer-to-peer proxy for system-level clients (via grd-pcscd) */ + GCancellable *private_proxy_cancellable; + GrdDBusPcscdSession *private_proxy; ++ ++ /* Session proxy for session-level clients (direct access) */ ++ GrdDBusPcscdSession *session_proxy; ++ unsigned int session_bus_name_id; + }; + + G_DEFINE_TYPE (GrdRdpSmartcard, grd_rdp_smartcard, G_TYPE_OBJECT) +@@ -1807,16 +1812,115 @@ teardown_private_proxy (GrdRdpSmartcard *smartcard) + g_clear_object (&smartcard->pcscd_proxy); + } + ++static char * ++get_session_id (void) ++{ ++ const char *session_id; ++ char *logind_session_id = NULL; ++ ++ session_id = g_getenv ("XDG_SESSION_ID"); ++ if (session_id && session_id[0] != '\0') ++ return g_strdup (session_id); ++ ++ logind_session_id = grd_get_session_id_from_pid (getpid ()); ++ if (logind_session_id) ++ return logind_session_id; ++ ++ logind_session_id = grd_get_session_id_from_uid (getuid ()); ++ if (!logind_session_id) ++ g_warning ("[RDP.SMARTCARD] Failed to get session ID"); ++ ++ return logind_session_id; ++} ++ ++static void ++on_session_bus_acquired (GDBusConnection *connection, ++ const char *name, ++ gpointer user_data) ++{ ++ g_autoptr (GrdDBusPcscdSession) session_proxy = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autofree char *session_id = NULL; ++ g_autofree char *object_path = NULL; ++ GrdRdpSmartcard *smartcard = user_data; ++ ++ session_id = get_session_id (); ++ if (!session_id) ++ return; ++ ++ object_path = ++ g_strdup_printf (REMOTE_DESKTOP_PCSCD_OBJECT_PATH "/%s", session_id); ++ ++ session_proxy = grd_dbus_pcscd_session_skeleton_new (); ++ if (!g_dbus_interface_skeleton_export ( ++ G_DBUS_INTERFACE_SKELETON (session_proxy), ++ connection, ++ object_path, ++ &error)) ++ { ++ g_warning ("[RDP.SMARTCARD] Failed to export session proxy: %s", ++ error->message); ++ return; ++ } ++ ++ connect_pcscd_session_handlers (session_proxy, smartcard); ++ ++ smartcard->session_proxy = g_steal_pointer (&session_proxy); ++ ++ g_debug ("[RDP.SMARTCARD] Session bus proxy established at %s", object_path); ++} ++ ++static void ++on_session_bus_name_acquired (GDBusConnection *connection, ++ const char *name, ++ gpointer user_data) ++{ ++ g_debug ("[RDP.SMARTCARD] Acquired session bus name %s", name); ++} ++ ++static void ++on_session_bus_name_lost (GDBusConnection *connection, ++ const char *name, ++ gpointer user_data) ++{ ++ g_debug ("[RDP.SMARTCARD] Lost session bus name %s", name); ++} ++ ++static void ++setup_session_proxy (GrdRdpSmartcard *smartcard) ++{ ++ g_assert (!smartcard->session_bus_name_id); ++ ++ smartcard->session_bus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION, ++ REMOTE_DESKTOP_PCSCD_BUS_NAME, ++ G_BUS_NAME_OWNER_FLAGS_NONE, ++ on_session_bus_acquired, ++ on_session_bus_name_acquired, ++ on_session_bus_name_lost, ++ smartcard, ++ NULL); ++} ++ ++static void ++teardown_session_proxy (GrdRdpSmartcard *smartcard) ++{ ++ g_clear_handle_id (&smartcard->session_bus_name_id, g_bus_unown_name); ++ ++ unexport_and_fail_pending_invocations (smartcard, &smartcard->session_proxy); ++} ++ + static void + setup_pcscd_proxies (GrdRdpSmartcard *smartcard) + { + setup_private_proxy (smartcard); ++ setup_session_proxy (smartcard); + } + + static void + teardown_pcscd_proxies (GrdRdpSmartcard *smartcard) + { + teardown_private_proxy (smartcard); ++ teardown_session_proxy (smartcard); + } + + static UINT +-- +2.51.0 + + +From 50454a9bb9b81c269b72c2479ab144279f408631 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Jul 2026 02:04:51 +0200 +Subject: [PATCH 07/11] rdp-smartcard: Add libpcsclite fallback backend to + libgrdpcsc + +When libgrdpcsc cannot connect to grd-pcscd (e.g. no smartcard is +being redirected), fall back to loading the real libpcsclite.so.1 via +dlopen and forwarding all PC/SC calls to it. This allows libgrdpcsc to +be configured as the default provider_library without breaking local +smartcard usage. + +Part-of: +--- + src/grd-pcsc-lib-backend-libpcsclite.c | 78 ++++++++++++++++++++++++++ + src/grd-pcsc-lib-backend.h | 4 ++ + src/grd-pcsc-lib.c | 4 ++ + src/meson.build | 1 + + 4 files changed, 87 insertions(+) + create mode 100644 src/grd-pcsc-lib-backend-libpcsclite.c + +diff --git a/src/grd-pcsc-lib-backend-libpcsclite.c b/src/grd-pcsc-lib-backend-libpcsclite.c +new file mode 100644 +index 00000000..624cece9 +--- /dev/null ++++ b/src/grd-pcsc-lib-backend-libpcsclite.c +@@ -0,0 +1,78 @@ ++/* ++ * Copyright (C) 2026 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ * Written by: ++ * Joan Torres Lopez ++ */ ++ ++#include "config.h" ++ ++#include "grd-pcsc-lib-backend.h" ++ ++#include ++ ++#define LIBPCSCLITE "libpcsclite.so.1" ++ ++#define LOAD_SYMBOL(backend, member, handle) \ ++ do \ ++ { \ ++ (backend)->member = dlsym ((handle), #member); \ ++ if (!(backend)->member) \ ++ { \ ++ g_clear_pointer (&(handle), dlclose); \ ++ return FALSE; \ ++ } \ ++ } \ ++ while (0) ++ ++static void *lib_handle; ++ ++gboolean ++grd_pcsc_lib_backend_init_libpcsclite (GrdPcscLibBackend *backend) ++{ ++ lib_handle = dlopen (LIBPCSCLITE, RTLD_LAZY); ++ if (!lib_handle) ++ return FALSE; ++ ++ LOAD_SYMBOL (backend, SCardEstablishContext, lib_handle); ++ LOAD_SYMBOL (backend, SCardReleaseContext, lib_handle); ++ LOAD_SYMBOL (backend, SCardIsValidContext, lib_handle); ++ LOAD_SYMBOL (backend, SCardConnect, lib_handle); ++ LOAD_SYMBOL (backend, SCardReconnect, lib_handle); ++ LOAD_SYMBOL (backend, SCardDisconnect, lib_handle); ++ LOAD_SYMBOL (backend, SCardBeginTransaction, lib_handle); ++ LOAD_SYMBOL (backend, SCardEndTransaction, lib_handle); ++ LOAD_SYMBOL (backend, SCardStatus, lib_handle); ++ LOAD_SYMBOL (backend, SCardGetStatusChange, lib_handle); ++ LOAD_SYMBOL (backend, SCardControl, lib_handle); ++ LOAD_SYMBOL (backend, SCardTransmit, lib_handle); ++ LOAD_SYMBOL (backend, SCardListReaderGroups, lib_handle); ++ LOAD_SYMBOL (backend, SCardListReaders, lib_handle); ++ LOAD_SYMBOL (backend, SCardFreeMemory, lib_handle); ++ LOAD_SYMBOL (backend, SCardCancel, lib_handle); ++ LOAD_SYMBOL (backend, SCardGetAttrib, lib_handle); ++ LOAD_SYMBOL (backend, SCardSetAttrib, lib_handle); ++ ++ return TRUE; ++} ++ ++void ++grd_pcsc_lib_backend_cleanup_libpcsclite (void) ++{ ++ g_clear_pointer (&lib_handle, dlclose); ++} +diff --git a/src/grd-pcsc-lib-backend.h b/src/grd-pcsc-lib-backend.h +index 47c6a13a..0fa01551 100644 +--- a/src/grd-pcsc-lib-backend.h ++++ b/src/grd-pcsc-lib-backend.h +@@ -135,3 +135,7 @@ void grd_pcsc_lib_backend_set_default (GrdPcscLibBackend *backend); + gboolean grd_pcsc_lib_backend_init_grd_pcscd_proxy (GrdPcscLibBackend *backend); + + void grd_pcsc_lib_backend_cleanup_grd_pcscd_proxy (void); ++ ++gboolean grd_pcsc_lib_backend_init_libpcsclite (GrdPcscLibBackend *backend); ++ ++void grd_pcsc_lib_backend_cleanup_libpcsclite (void); +diff --git a/src/grd-pcsc-lib.c b/src/grd-pcsc-lib.c +index 94ae36c3..7dda0627 100644 +--- a/src/grd-pcsc-lib.c ++++ b/src/grd-pcsc-lib.c +@@ -33,6 +33,7 @@ PCSC_DESTRUCTOR static void + on_unload (void) + { + grd_pcsc_lib_backend_cleanup_grd_pcscd_proxy (); ++ grd_pcsc_lib_backend_cleanup_libpcsclite (); + } + + static void +@@ -43,6 +44,9 @@ init_grd_pcsc_backend (void) + if (grd_pcsc_lib_backend_init_grd_pcscd_proxy (&backend)) + return; + ++ if (grd_pcsc_lib_backend_init_libpcsclite (&backend)) ++ return; ++ + g_warning ("[PCSC] No backend available"); + } + +diff --git a/src/meson.build b/src/meson.build +index f3087945..890ebca4 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -460,6 +460,7 @@ if have_rdp + 'grd-pcsc-lib.c', + 'grd-pcsc-lib-backend.c', + 'grd-pcsc-lib-backend-grd-pcscd-proxy.c', ++ 'grd-pcsc-lib-backend-libpcsclite.c', + dbus_gen_pcscd, + dependencies: [glib_dep, + gio_dep, +-- +2.51.0 + + +From fb42f2fa75469def41b47e0ac6afccaa897de619 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Jul 2026 02:09:43 +0200 +Subject: [PATCH 08/11] pcscd-session: Add D-Bus method authorization + +Restrict access to the Pcscd.Session interface exported on the system +bus. A caller is authorized if its UID matches the session owner or +root, or if it holds the org.gnome.remotedesktop.use-grd-pcscd polkit +action. The latter allows services like sssd that run under a different +UID to access the redirected smartcard by adding a polkit rule. + +Authorized senders are cached per session to avoid the polkit roundtrip +on every PC/SC call, since the high volume of operations would +otherwise add significant latency. The cache is cleared when polkit +signals that its rules have changed. + +Part-of: +--- + data/meson.build | 7 ++ + ...nome.remotedesktop.use-grd-pcscd.policy.in | 21 ++++ + src/grd-pcscd-session.c | 112 ++++++++++++++++++ + src/meson.build | 3 +- + 4 files changed, 142 insertions(+), 1 deletion(-) + create mode 100644 data/org.gnome.remotedesktop.use-grd-pcscd.policy.in + +diff --git a/data/meson.build b/data/meson.build +index b81acf5c..4c16d890 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -118,6 +118,13 @@ if get_option('systemd') + }, + install_dir: join_paths(datadir, 'polkit-1', 'actions')) + ++ configure_file(input: 'org.gnome.remotedesktop.use-grd-pcscd.policy.in', ++ output: 'org.gnome.remotedesktop.use-grd-pcscd.policy', ++ configuration: { ++ 'GRD_USERNAME': grd_username, ++ }, ++ install_dir: join_paths(datadir, 'polkit-1', 'actions')) ++ + configure_file(input: 'org.gnome.RemoteDesktop.Pcscd.service.in', + output: 'org.gnome.RemoteDesktop.Pcscd.service', + configuration: { +diff --git a/data/org.gnome.remotedesktop.use-grd-pcscd.policy.in b/data/org.gnome.remotedesktop.use-grd-pcscd.policy.in +new file mode 100644 +index 00000000..d4efe1c9 +--- /dev/null ++++ b/data/org.gnome.remotedesktop.use-grd-pcscd.policy.in +@@ -0,0 +1,21 @@ ++ ++ ++ ++ ++ The GNOME Project ++ http://www.gnome.org/ ++ ++ ++ Allow to use gnome-remote-desktop's PC/SC smart card session ++ Authentication is required to use gnome-remote-desktop's PC/SC smart card session ++ ++ auth_admin ++ auth_admin ++ auth_admin ++ ++ unix-user:@GRD_USERNAME@ ++ ++ ++ +diff --git a/src/grd-pcscd-session.c b/src/grd-pcscd-session.c +index aa45ac2f..0cc3e942 100644 +--- a/src/grd-pcscd-session.c ++++ b/src/grd-pcscd-session.c +@@ -27,10 +27,12 @@ + #include + #include + #include ++#include + + #include "grd-dbus-pcscd.h" + + #define PCSCD_OBJECT_PATH_PREFIX "/org/gnome/RemoteDesktop/Pcscd" ++#define GRD_USE_GRD_PCSCD_POLKIT_ACTION "org.gnome.remotedesktop.use-grd-pcscd" + + struct _GrdPcscdSession + { +@@ -38,6 +40,9 @@ struct _GrdPcscdSession + + char *session_id; + ++ PolkitAuthority *authority; ++ GHashTable *authorized_senders; ++ + GCancellable *cancellable; + + GrdDBusPcscdSession *private_proxy; +@@ -55,6 +60,105 @@ static guint signals[N_SIGNALS]; + + G_DEFINE_TYPE (GrdPcscdSession, grd_pcscd_session, G_TYPE_OBJECT) + ++static void ++on_polkit_authority_changed (PolkitAuthority *authority, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ ++ g_debug ("[PCSCD.SESSION %s] Polkit rules changed, clearing authorization cache", ++ session->session_id); ++ g_hash_table_remove_all (session->authorized_senders); ++} ++ ++static gboolean ++ensure_polkit_authority (GrdPcscdSession *session) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ if (session->authority) ++ return TRUE; ++ ++ session->authority = polkit_authority_get_sync (session->cancellable, &error); ++ if (!session->authority) ++ { ++ g_warning ("[PCSCD.SESSION %s] Failed to get polkit authority: %s", ++ session->session_id, error->message); ++ return FALSE; ++ } ++ ++ g_signal_connect_object (session->authority, "changed", ++ G_CALLBACK (on_polkit_authority_changed), ++ session, G_CONNECT_DEFAULT); ++ ++ return TRUE; ++} ++ ++static gboolean ++check_polkit_action (GrdPcscdSession *session, ++ const char *sender, ++ const char *action) ++{ ++ g_autoptr (PolkitAuthorizationResult) result = NULL; ++ g_autoptr (PolkitSubject) subject = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ subject = polkit_system_bus_name_new (sender); ++ result = polkit_authority_check_authorization_sync (session->authority, ++ subject, ++ action, ++ NULL, ++ POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE, ++ session->cancellable, ++ &error); ++ if (!result) ++ { ++ g_warning ("[PCSCD.SESSION %s] Failed to check authorization for %s: %s", ++ session->session_id, action, error->message); ++ return FALSE; ++ } ++ ++ return polkit_authorization_result_get_is_authorized (result); ++} ++ ++static gboolean ++check_polkit (GrdPcscdSession *session, ++ const char *sender) ++{ ++ if (!ensure_polkit_authority (session)) ++ return FALSE; ++ ++ return check_polkit_action (session, sender, ++ GRD_USE_GRD_PCSCD_POLKIT_ACTION); ++} ++ ++static gboolean ++on_authorize_method (GDBusInterfaceSkeleton *interface, ++ GDBusMethodInvocation *invocation, ++ gpointer user_data) ++{ ++ GrdPcscdSession *session = user_data; ++ const char *sender; ++ ++ sender = g_dbus_method_invocation_get_sender (invocation); ++ ++ if (g_hash_table_contains (session->authorized_senders, sender)) ++ return TRUE; ++ ++ if (!check_polkit (session, sender)) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Not authorized"); ++ return FALSE; ++ } ++ ++ g_hash_table_add (session->authorized_senders, g_strdup (sender)); ++ ++ return TRUE; ++} ++ + static void + on_establish_context_finished (GObject *source_object, + GAsyncResult *result, +@@ -965,6 +1069,9 @@ create_system_proxy (GrdPcscdSession *session, + error)) + return FALSE; + ++ g_signal_connect_object (system_proxy, "g-authorize-method", ++ G_CALLBACK (on_authorize_method), ++ session, G_CONNECT_DEFAULT); + g_signal_connect_object (system_proxy, "handle-establish-context", + G_CALLBACK (on_handle_establish_context), + session, G_CONNECT_DEFAULT); +@@ -1066,6 +1173,8 @@ grd_pcscd_session_new (const char *session_id, + + session = g_object_new (GRD_TYPE_PCSCD_SESSION, NULL); + session->session_id = g_strdup (session_id); ++ session->authorized_senders = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, NULL); + session->cancellable = g_cancellable_new (); + + if (!create_system_proxy (session, system_connection, error)) +@@ -1087,6 +1196,9 @@ grd_pcscd_session_dispose (GObject *object) + g_cancellable_cancel (session->cancellable); + g_clear_object (&session->cancellable); + ++ g_clear_object (&session->authority); ++ g_clear_pointer (&session->authorized_senders, g_hash_table_destroy); ++ + if (session->private_proxy) + { + GDBusConnection *private_connection = +diff --git a/src/meson.build b/src/meson.build +index 890ebca4..e665770a 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -451,7 +451,8 @@ if have_rdp + dependencies: [glib_dep, + gio_dep, + gio_unix_dep, +- libsystemd_dep], ++ libsystemd_dep, ++ polkit_dep], + include_directories: [configinc], + install: true, + install_dir: libexecdir) +-- +2.51.0 + + +From 419a1fe61128e46c749d7e0660fbbd717a8030d2 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Wed, 8 Jul 2026 03:04:10 +0200 +Subject: [PATCH 09/11] ci: Add WITHOUT_WINPR_3x_DEPRECATED flag + +Build WinPR without 3.x deprecated symbols to avoid deprecation +warnings during compilation. + +Part-of: +--- + config.h.meson | 1 + + meson.build | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/config.h.meson b/config.h.meson +index cafba4e..d77b696 100644 +--- a/config.h.meson ++++ b/config.h.meson +@@ -35,3 +35,4 @@ + + /* Workaround for https://github.com/FreeRDP/FreeRDP/issues/11412 */ + #mesondefine WITHOUT_FREERDP_3x_DEPRECATED ++#mesondefine WITHOUT_WINPR_3x_DEPRECATED +diff --git a/meson.build b/meson.build +index 5e91d25..3b02e28 100644 +--- a/meson.build ++++ b/meson.build +@@ -117,6 +117,7 @@ cdata.set_quoted('GRD_SHADER_DIR', grd_shaderdir) + + # Workaround for https://github.com/FreeRDP/FreeRDP/issues/11412 + cdata.set('WITHOUT_FREERDP_3x_DEPRECATED', true) ++cdata.set('WITHOUT_WINPR_3x_DEPRECATED', true) + + configure_file(input: 'config.h.meson', + output: 'config.h', +From e816e9773d1f0fc70c08a5276cfd49e4824dbd00 Mon Sep 17 00:00:00 2001 +From: Joan Torres Lopez +Date: Mon, 17 Aug 2026 22:43:59 +0200 +Subject: [PATCH 10/11] rdp-smartcard: Populate hContext for card-handle + operations + +PC/SC APIs like SCardControl only take a card handle since the +context is implicit, but MS-RDPESC requires every card-handle +operation to send both hContext and hCard explicitly. All nine +operations only set hCard, causing mstsc to disconnect. Track each +card's context in a new card_to_context hash table, populated on +Connect and purged on Disconnect/ReleaseContext. + +Part-of: +--- + src/grd-rdp-smartcard.c | 158 +++++++++++++++++++++++++++++++++++----- + 1 file changed, 141 insertions(+), 17 deletions(-) + +diff --git a/src/grd-rdp-smartcard.c b/src/grd-rdp-smartcard.c +index c73858ad..4abda0a9 100644 +--- a/src/grd-rdp-smartcard.c ++++ b/src/grd-rdp-smartcard.c +@@ -48,6 +48,14 @@ + (uint32_t) (val)) \ + : (uint32_t) (val))) + ++#define INT64_TO_POINTER(val) \ ++ (G_STATIC_ASSERT_EXPR (sizeof (val) == sizeof (gpointer)), \ ++ (gpointer) (val)) ++ ++#define POINTER_TO_INT64(ptr) \ ++ (G_STATIC_ASSERT_EXPR (sizeof (ptr) == sizeof (int64_t)), \ ++ (int64_t) (ptr)) ++ + /* + * pcsc-lite dwState bitmask values. These differ from the WinSCard + * sequential values (0-6) defined by FreeRDP/WinPR in winpr/smartcard.h. +@@ -96,6 +104,8 @@ struct _GrdRdpSmartcard + /* Session proxy for session-level clients (direct access) */ + GrdDBusPcscdSession *session_proxy; + unsigned int session_bus_name_id; ++ ++ GHashTable *card_to_context; + }; + + G_DEFINE_TYPE (GrdRdpSmartcard, grd_rdp_smartcard, G_TYPE_OBJECT) +@@ -279,6 +289,14 @@ on_smartcard_release_context_complete (RdpdrServerContext *rdpdr_context, + g_dbus_method_invocation_return_value (invocation, ret_variant); + } + ++static gboolean ++remove_cards_for_context (gpointer key, ++ gpointer card_context, ++ gpointer context) ++{ ++ return POINTER_TO_INT64 (card_context) == POINTER_TO_INT64 (context); ++} ++ + static gboolean + on_handle_release_context (GrdDBusPcscdSession *skeleton, + GDBusMethodInvocation *invocation, +@@ -300,6 +318,11 @@ on_handle_release_context (GrdDBusPcscdSession *skeleton, + g_hash_table_add (smartcard->pending_invocations, invocation); + + g_variant_get (call_variant, "(x)", &context); ++ ++ g_hash_table_foreach_remove (smartcard->card_to_context, ++ remove_cards_for_context, ++ INT64_TO_POINTER (context)); ++ + smartcard_scard_context_native_to_redir (&redir_context, context); + + status = rdpdr_context->SmartcardReleaseContext (rdpdr_context, +@@ -380,6 +403,30 @@ on_handle_is_valid_context (GrdDBusPcscdSession *skeleton, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++static void ++track_card_context (RdpdrServerContext *rdpdr_context, ++ int64_t card, ++ int64_t context) ++{ ++ GrdRdpDeviceRedirection *device_redirection = rdpdr_context->data; ++ GrdRdpSmartcard *smartcard; ++ ++ smartcard = grd_rdp_device_redirection_get_smartcard (device_redirection); ++ if (smartcard) ++ { ++ g_hash_table_insert (smartcard->card_to_context, ++ INT64_TO_POINTER (card), ++ INT64_TO_POINTER (context)); ++ } ++} ++ ++static void ++untrack_card_context (GrdRdpSmartcard *smartcard, ++ int64_t card) ++{ ++ g_hash_table_remove (smartcard->card_to_context, INT64_TO_POINTER (card)); ++} ++ + static void + on_smartcard_connect_complete (RdpdrServerContext *rdpdr_context, + void *callback_data, +@@ -405,6 +452,9 @@ on_smartcard_connect_complete (RdpdrServerContext *rdpdr_context, + (int64_t) smartcard_scard_handle_native_from_redir (&ret->hCard); + active_protocol = + EXTEND_32_TO_64 (convert_protocol_to_pcsc (ret->dwActiveProtocol)); ++ ++ if (card != 0) ++ track_card_context (rdpdr_context, card, out_context); + } + + ret_variant = g_variant_new ("((xxxt))", +@@ -494,6 +544,30 @@ on_smartcard_reconnect_complete (RdpdrServerContext *rdpdr_context, + g_dbus_method_invocation_return_value (invocation, ret_variant); + } + ++static gboolean ++resolve_context_for_card (GrdRdpSmartcard *smartcard, ++ GDBusMethodInvocation *invocation, ++ int64_t card, ++ int64_t *out_context) ++{ ++ gpointer value; ++ ++ if (!g_hash_table_lookup_extended (smartcard->card_to_context, ++ INT64_TO_POINTER (card), ++ NULL, ++ &value)) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "No context found for card handle"); ++ return FALSE; ++ } ++ ++ *out_context = POINTER_TO_INT64 (value); ++ return TRUE; ++} ++ + static gboolean + on_handle_reconnect (GrdDBusPcscdSession *skeleton, + GDBusMethodInvocation *invocation, +@@ -504,6 +578,7 @@ on_handle_reconnect (GrdDBusPcscdSession *skeleton, + RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; + Reconnect_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t share_mode; + uint64_t preferred_protocols; + uint64_t initialization; +@@ -515,14 +590,18 @@ on_handle_reconnect (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(xttt)", + &card, + &share_mode, + &preferred_protocols, + &initialization); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.dwShareMode = NARROW_64_TO_32 (share_mode); + call.dwPreferredProtocols = +@@ -575,6 +654,7 @@ on_handle_disconnect_card (GrdDBusPcscdSession *skeleton, + RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; + HCardAndDisposition_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t disposition; + uint32_t completion_id; + uint32_t status; +@@ -584,10 +664,16 @@ on_handle_disconnect_card (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + ++ g_variant_get (call_variant, "(xt)", &card, &disposition); ++ ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ + g_hash_table_add (smartcard->pending_invocations, invocation); + +- g_variant_get (call_variant, "(xt)", &card, &disposition); ++ untrack_card_context (smartcard, card); + ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.dwDisposition = NARROW_64_TO_32 (disposition); + +@@ -638,6 +724,7 @@ on_handle_begin_transaction (GrdDBusPcscdSession *skeleton, + RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; + HCardAndDisposition_Call call = {}; + int64_t card; ++ int64_t context; + uint32_t completion_id; + uint32_t status; + +@@ -646,10 +733,14 @@ on_handle_begin_transaction (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(x)", &card); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + + status = rdpdr_context->SmartcardBeginTransaction (rdpdr_context, +@@ -699,6 +790,7 @@ on_handle_end_transaction (GrdDBusPcscdSession *skeleton, + RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; + HCardAndDisposition_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t disposition; + uint32_t completion_id; + uint32_t status; +@@ -708,10 +800,14 @@ on_handle_end_transaction (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(xt)", &card, &disposition); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.dwDisposition = NARROW_64_TO_32 (disposition); + +@@ -781,6 +877,7 @@ on_handle_status_card (GrdDBusPcscdSession *skeleton, + RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; + Status_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t reader_len; + uint64_t atr_len; + uint32_t completion_id; +@@ -791,10 +888,14 @@ on_handle_status_card (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(xtt)", &card, &reader_len, &atr_len); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.cchReaderLen = NARROW_64_TO_32 (reader_len); + call.cbAtrLen = NARROW_64_TO_32 (atr_len); +@@ -968,6 +1069,7 @@ on_handle_control_card (GrdDBusPcscdSession *skeleton, + g_autoptr (GVariant) send_buffer = NULL; + Control_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t control_code; + uint64_t send_buffer_size; + uint64_t recv_buffer_size; +@@ -981,8 +1083,6 @@ on_handle_control_card (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(xt@aytt)", + &card, + &control_code, +@@ -990,9 +1090,15 @@ on_handle_control_card (GrdDBusPcscdSession *skeleton, + &send_buffer_size, + &recv_buffer_size); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ + send_data = g_variant_get_fixed_array (send_buffer, &send_size, + sizeof (uint8_t)); + ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.dwControlCode = NARROW_64_TO_32 (control_code); + call.cbInBufferSize = NARROW_64_TO_32 (send_buffer_size); +@@ -1068,6 +1174,7 @@ on_handle_transmit (GrdDBusPcscdSession *skeleton, + SCARD_IO_REQUEST recv_pci = {}; + Transmit_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t send_pci_protocol; + uint64_t recv_pci_protocol_in; + uint64_t recv_buffer_size; +@@ -1081,8 +1188,6 @@ on_handle_transmit (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(xt@aytt)", + &card, + &send_pci_protocol, +@@ -1090,6 +1195,11 @@ on_handle_transmit (GrdDBusPcscdSession *skeleton, + &recv_pci_protocol_in, + &recv_buffer_size); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ + send_data = g_variant_get_fixed_array (send_buffer, &send_size, + sizeof (uint8_t)); + +@@ -1101,6 +1211,7 @@ on_handle_transmit (GrdDBusPcscdSession *skeleton, + convert_protocol_to_winscard (NARROW_64_TO_32 (recv_pci_protocol_in)); + recv_pci.cbPciLength = sizeof (SCARD_IO_REQUEST); + ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.pioSendPci = &send_pci; + call.cbSendLength = NARROW_64_TO_32 (send_size); +@@ -1382,6 +1493,7 @@ on_handle_get_attrib (GrdDBusPcscdSession *skeleton, + RdpdrServerContext *rdpdr_context = smartcard->rdpdr_context; + GetAttrib_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t attr_id; + uint64_t attr_len; + uint32_t completion_id; +@@ -1392,10 +1504,14 @@ on_handle_get_attrib (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(xtt)", &card, &attr_id, &attr_len); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.dwAttrId = NARROW_64_TO_32 (attr_id); + call.cbAttrLen = NARROW_64_TO_32 (attr_len); +@@ -1447,6 +1563,7 @@ on_handle_set_attrib (GrdDBusPcscdSession *skeleton, + g_autoptr (GVariant) attr_buffer = NULL; + SetAttrib_Call call = {}; + int64_t card; ++ int64_t context; + uint64_t attr_id; + const uint8_t *attr_data; + size_t attr_size; +@@ -1458,16 +1575,20 @@ on_handle_set_attrib (GrdDBusPcscdSession *skeleton, + if (is_device_unavailable (smartcard, invocation)) + return G_DBUS_METHOD_INVOCATION_HANDLED; + +- g_hash_table_add (smartcard->pending_invocations, invocation); +- + g_variant_get (call_variant, "(xt@ay)", + &card, + &attr_id, + &attr_buffer); + ++ if (!resolve_context_for_card (smartcard, invocation, card, &context)) ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++ g_hash_table_add (smartcard->pending_invocations, invocation); ++ + attr_data = g_variant_get_fixed_array (attr_buffer, &attr_size, + sizeof (uint8_t)); + ++ smartcard_scard_context_native_to_redir (&call.handles.hContext, context); + smartcard_scard_handle_native_to_redir (&call.handles.hCard, card); + call.dwAttrId = NARROW_64_TO_32 (attr_id); + call.cbAttrLen = attr_size; +@@ -1921,6 +2042,7 @@ teardown_pcscd_proxies (GrdRdpSmartcard *smartcard) + { + teardown_private_proxy (smartcard); + teardown_session_proxy (smartcard); ++ g_hash_table_remove_all (smartcard->card_to_context); + } + + static UINT +@@ -2064,6 +2186,7 @@ grd_rdp_smartcard_dispose (GObject *object) + + g_clear_pointer (&smartcard->smartcard_device_ids, g_hash_table_unref); + g_clear_pointer (&smartcard->pending_invocations, g_hash_table_unref); ++ g_clear_pointer (&smartcard->card_to_context, g_hash_table_unref); + + G_OBJECT_CLASS (grd_rdp_smartcard_parent_class)->dispose (object); + } +@@ -2085,6 +2208,7 @@ grd_rdp_smartcard_init (GrdRdpSmartcard *smartcard) + + smartcard->smartcard_device_ids = g_hash_table_new (NULL, NULL); + smartcard->pending_invocations = g_hash_table_new (NULL, NULL); ++ smartcard->card_to_context = g_hash_table_new (NULL, NULL); + } + + static void +-- +2.51.0 + + +From 2afaced792b91874eb71dc0d0338d295f844601c Mon Sep 17 00:00:00 2001 +From: Pascal Nowack +Date: Sun, 10 May 2026 19:14:08 +0200 +Subject: [PATCH 11/11] build: Add support for FreeRDP 3.25.0 + +FreeRDP 3.25.0 introduced an API break on the client side, requiring the +test client to exchange the old authenticate callback with a revised +version. Since it is not possible to increase dependencies on the stable +branch, simply check the installed FreeRDP version at build time and use +the correct API depending on it. +As the API change only affects the test client, it should not matter if +a distribution decides to update FreeRDP to 3.25 or later when +gnome-remote-desktop was already built. + +Closes: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/work_items/326 +Part-of: +--- + tests/test-client-rdp.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/tests/test-client-rdp.c b/tests/test-client-rdp.c +index a06a1879..b97e6fa3 100644 +--- a/tests/test-client-rdp.c ++++ b/tests/test-client-rdp.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #include + + #define TEST_1_WIDTH 800 +@@ -246,10 +247,18 @@ rdp_client_post_disconnect (freerdp *instance) + } + + static BOOL ++#if FREERDP_VERSION_MAJOR == 3 && FREERDP_VERSION_MINOR >= 25 ++rdp_client_authenticate_ex (freerdp *instance, ++ char **username, ++ char **password, ++ char **domain, ++ rdp_auth_reason reason) ++#else + rdp_client_authenticate (freerdp *instance, + char **username, + char **password, + char **domain) ++#endif /* FREERDP_VERSION_MAJOR == 3 && FREERDP_VERSION_MINOR >= 25 */ + { + /* Credentials were already parsed from the command line */ + +@@ -307,7 +316,11 @@ rdp_client_new (freerdp *instance, + instance->PreConnect = rdp_client_pre_connect; + instance->PostConnect = rdp_client_post_connect; + instance->PostDisconnect = rdp_client_post_disconnect; ++#if FREERDP_VERSION_MAJOR == 3 && FREERDP_VERSION_MINOR >= 25 ++ instance->AuthenticateEx = rdp_client_authenticate_ex; ++#else + instance->Authenticate = rdp_client_authenticate; ++#endif /* FREERDP_VERSION_MAJOR == 3 && FREERDP_VERSION_MINOR >= 25 */ + instance->VerifyCertificateEx = rdp_client_verify_certificate_ex; + instance->VerifyChangedCertificateEx = rdp_client_verify_changed_certificate_ex; + +-- +2.51.0 + diff --git a/gnome-remote-desktop.spec b/gnome-remote-desktop.spec index db93fa9..37fc084 100644 --- a/gnome-remote-desktop.spec +++ b/gnome-remote-desktop.spec @@ -34,6 +34,10 @@ Patch3: 0001-daemon-system-Use-timeout_add_seconds_once-on-abort_.patch Patch4: 0001-daemon-handover-Move-callback-definitions-earlier-in.patch Patch5: 0001-daemon-system-Respect-existing-remote_id-from-third-.patch +# Add smartcard redirection support +# https://gitlab.gnome.org/GNOME/gnome-remote-desktop/-/merge_requests/406 +Patch6: add-smartcard-redirection-support.patch + BuildRequires: asciidoc BuildRequires: gcc @@ -46,7 +50,7 @@ BuildRequires: pkgconfig(dbus-1) BuildRequires: pkgconfig(ffnvcodec) >= 11.1.5.0 %if %{with rdp} BuildRequires: pkgconfig(fdk-aac) -BuildRequires: pkgconfig(freerdp3) >= 3.10.0 +BuildRequires: pkgconfig(freerdp3) >= 3.28.0 BuildRequires: pkgconfig(fuse3) >= 3.9.1 BuildRequires: pkgconfig(opus) BuildRequires: pkgconfig(polkit-gobject-1) >= 122 @@ -115,6 +119,7 @@ GNOME desktop environment. %post %systemd_post %{systemd_unit_system} +%systemd_post gnome-remote-desktop-pcscd.service %systemd_user_post %{systemd_unit_handover} %systemd_user_post %{systemd_unit_headless} %systemd_user_post %{systemd_unit_user} @@ -122,6 +127,7 @@ GNOME desktop environment. %preun %systemd_preun %{systemd_unit_system} +%systemd_preun gnome-remote-desktop-pcscd.service %systemd_user_preun %{systemd_unit_handover} %systemd_user_preun %{systemd_unit_headless} %systemd_user_preun %{systemd_unit_user} @@ -129,6 +135,7 @@ GNOME desktop environment. %postun %systemd_postun_with_restart %{systemd_unit_system} +%systemd_postun_with_restart gnome-remote-desktop-pcscd.service %systemd_user_postun_with_restart %{systemd_unit_handover} %systemd_user_postun_with_restart %{systemd_unit_headless} %systemd_user_postun_with_restart %{systemd_unit_user} @@ -141,18 +148,25 @@ GNOME desktop environment. %{_libexecdir}/gnome-remote-desktop-daemon %{_libexecdir}/gnome-remote-desktop-enable-service %{_libexecdir}/gnome-remote-desktop-configuration-daemon +%{_libexecdir}/grd-pcscd +%{_libdir}/libgrdpcsc.so +%{_libdir}/libgrdpcsc.so.0* %{_userunitdir}/%{systemd_unit_user} %{_userunitdir}/%{systemd_unit_headless} %{_userunitdir}/%{systemd_unit_handover} %{_unitdir}/%{systemd_unit_system} %{_unitdir}/gnome-remote-desktop-configuration.service +%{_unitdir}/gnome-remote-desktop-pcscd.service %{_datadir}/applications/org.gnome.RemoteDesktop.Handover.desktop %{_datadir}/dbus-1/system-services/org.gnome.RemoteDesktop.Configuration.service +%{_datadir}/dbus-1/system-services/org.gnome.RemoteDesktop.Pcscd.service %{_datadir}/dbus-1/system.d/org.gnome.RemoteDesktop.conf +%{_datadir}/dbus-1/system.d/org.gnome.RemoteDesktop.Pcscd.conf %{_datadir}/glib-2.0/schemas/org.gnome.desktop.remote-desktop.gschema.xml %{_datadir}/glib-2.0/schemas/org.gnome.desktop.remote-desktop.enums.xml %{_datadir}/polkit-1/actions/org.gnome.remotedesktop.configure-system-daemon.policy %{_datadir}/polkit-1/actions/org.gnome.remotedesktop.enable-system-daemon.policy +%{_datadir}/polkit-1/actions/org.gnome.remotedesktop.use-grd-pcscd.policy %{_datadir}/polkit-1/rules.d/20-gnome-remote-desktop.rules %{_sysusersdir}/gnome-remote-desktop-sysusers.conf %{_tmpfilesdir}/gnome-remote-desktop-tmpfiles.conf