From d99743349ca45972868aae427f25c8e4d25c0c4f Mon Sep 17 00:00:00 2001 From: daidai_is_here Date: Fri, 13 Dec 2019 14:46:07 +0800 Subject: [PATCH] package init --- ...d-Bump-pipewire-requirement-to-0.2.2.patch | 25 + ...vnc-Don-t-requeue-close-session-idle.patch | 84 ++ ...Add-anonymous-TLS-encryption-support.patch | 953 ++++++++++++++++++ ...ream-Close-session-when-disconnected.patch | 28 + gnome-remote-desktop-0.1.6.tar.xz | Bin 0 -> 24612 bytes gnome-remote-desktop.spec | 53 + 6 files changed, 1143 insertions(+) create mode 100644 0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch create mode 100644 0001-session-vnc-Don-t-requeue-close-session-idle.patch create mode 100644 0001-vnc-Add-anonymous-TLS-encryption-support.patch create mode 100644 0002-vnc-pipewire-stream-Close-session-when-disconnected.patch create mode 100644 gnome-remote-desktop-0.1.6.tar.xz create mode 100644 gnome-remote-desktop.spec diff --git a/0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch b/0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch new file mode 100644 index 0000000..62b84b2 --- /dev/null +++ b/0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch @@ -0,0 +1,25 @@ +From 8f760d73df6011330cd09da7ca7b8a3f40c9a3ef Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 7 Aug 2018 13:35:43 +0200 +Subject: [PATCH] meson.build: Bump pipewire requirement to 0.2.2 + +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 6951b89..34ec5ea 100644 +--- a/meson.build ++++ b/meson.build +@@ -10,7 +10,7 @@ gnome = import('gnome') + glib_dep = dependency('glib-2.0') + gio_dep = dependency('gio-2.0') + gio_unix_dep = dependency('gio-unix-2.0') +-pipewire_dep = dependency('libpipewire-0.1') ++pipewire_dep = dependency('libpipewire-0.2', version: '>= 0.2.2') + systemd_dep = dependency('systemd') + libvncserver_dep = dependency('libvncserver') + libsecret_dep = dependency('libsecret-1') +-- +2.17.1 + diff --git a/0001-session-vnc-Don-t-requeue-close-session-idle.patch b/0001-session-vnc-Don-t-requeue-close-session-idle.patch new file mode 100644 index 0000000..20a3f56 --- /dev/null +++ b/0001-session-vnc-Don-t-requeue-close-session-idle.patch @@ -0,0 +1,84 @@ +From add0ea34fd1d6835c99aebeb4e56b805b38e53ec Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 1 Oct 2018 18:02:39 +0200 +Subject: [PATCH 1/2] session/vnc: Don't requeue close session idle + +If being closed due to a PipeWire error, RFB will still process state +and invoke callbacks when cleaning up the RFB screen, meaning we'd +requeue the close session idle handler. Avoid this by avoiding +requeueing if there is already one queued, and don't mark is as unqueued +until after actually stopping the session. +--- + src/grd-session-vnc.c | 28 ++++++++++++++++++---------- + 1 file changed, 18 insertions(+), 10 deletions(-) + +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index ce4dd29..3c98eeb 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -165,6 +165,16 @@ grd_session_vnc_draw_buffer (GrdSessionVnc *session_vnc, + rfbProcessEvents (session_vnc->rfb_screen, 0); + } + ++static void ++maybe_queue_close_session_idle (GrdSessionVnc *session_vnc) ++{ ++ if (session_vnc->close_session_idle_id) ++ return; ++ ++ session_vnc->close_session_idle_id = ++ g_idle_add (close_session_idle, session_vnc); ++} ++ + static void + handle_client_gone (rfbClientPtr rfb_client) + { +@@ -172,8 +182,7 @@ handle_client_gone (rfbClientPtr rfb_client) + + g_debug ("VNC client gone"); + +- session_vnc->close_session_idle_id = +- g_idle_add (close_session_idle, session_vnc); ++ maybe_queue_close_session_idle (session_vnc); + } + + static void +@@ -670,12 +679,6 @@ grd_session_vnc_stop (GrdSession *session) + + g_debug ("Stopping VNC session"); + +- if (session_vnc->close_session_idle_id) +- { +- g_source_remove (session_vnc->close_session_idle_id); +- session_vnc->close_session_idle_id = 0; +- } +- + g_clear_object (&session_vnc->pipewire_stream); + + grd_session_vnc_detach_source (session_vnc); +@@ -683,6 +686,12 @@ grd_session_vnc_stop (GrdSession *session) + g_clear_object (&session_vnc->connection); + g_clear_pointer (&session_vnc->rfb_screen->frameBuffer, g_free); + g_clear_pointer (&session_vnc->rfb_screen, (GDestroyNotify) rfbScreenCleanup); ++ ++ if (session_vnc->close_session_idle_id) ++ { ++ g_source_remove (session_vnc->close_session_idle_id); ++ session_vnc->close_session_idle_id = 0; ++ } + } + + static gboolean +@@ -703,8 +712,7 @@ on_pipwire_stream_closed (GrdVncPipeWireStream *stream, + { + g_warning ("PipeWire stream closed, closing client"); + +- session_vnc->close_session_idle_id = +- g_idle_add (close_session_idle, session_vnc); ++ maybe_queue_close_session_idle (session_vnc); + } + + static void +-- +2.17.1 + diff --git a/0001-vnc-Add-anonymous-TLS-encryption-support.patch b/0001-vnc-Add-anonymous-TLS-encryption-support.patch new file mode 100644 index 0000000..fe25694 --- /dev/null +++ b/0001-vnc-Add-anonymous-TLS-encryption-support.patch @@ -0,0 +1,953 @@ +From fcfef86768d3dc63a2e7da799beb011800dff2ad Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 14 Jun 2018 12:21:37 +0200 +Subject: [PATCH] vnc: Add anonymous TLS encryption support + +Add support for encrypting the VNC connection using anonymous TLS. In +effect this means that the channel is encrypted using TLS but that no +authentication of the peers are done. This means the connection is still +vulnerable to man-in-the-middle attacks where an attacker proxies the +VNC connection. +--- + meson.build | 1 + + src/grd-enums.h | 6 + + src/grd-session-vnc.c | 98 +++- + src/grd-session-vnc.h | 16 + + src/grd-settings.c | 27 ++ + src/grd-settings.h | 2 + + src/grd-vnc-server.c | 45 ++ + src/grd-vnc-tls.c | 444 ++++++++++++++++++ + src/grd-vnc-tls.h | 28 ++ + src/meson.build | 5 +- + ...g.gnome.desktop.remote-desktop.gschema.xml | 10 + + 11 files changed, 666 insertions(+), 16 deletions(-) + create mode 100644 src/grd-vnc-tls.c + create mode 100644 src/grd-vnc-tls.h + +diff --git a/meson.build b/meson.build +index d8e20d2..f8c8cee 100644 +--- a/meson.build ++++ b/meson.build +@@ -15,6 +15,7 @@ systemd_dep = dependency('systemd') + libvncserver_dep = dependency('libvncserver') + libsecret_dep = dependency('libsecret-1') + libnotify_dep = dependency('libnotify') ++gnutls_dep = dependency('gnutls') + + cdata = configuration_data() + cdata.set_quoted('GETTEXT_PACKAGE', 'gnome-remote-desktop') +diff --git a/src/grd-enums.h b/src/grd-enums.h +index ffab821..4333863 100644 +--- a/src/grd-enums.h ++++ b/src/grd-enums.h +@@ -27,4 +27,10 @@ typedef enum + GRD_VNC_AUTH_METHOD_PASSWORD + } GrdVncAuthMethod; + ++typedef enum ++{ ++ GRD_VNC_ENCRYPTION_NONE = 1 << 0, ++ GRD_VNC_ENCRYPTION_TLS_ANON = 1 << 1, ++} GrdVncEncryption; ++ + #endif /* GRD_ENUMS_H */ +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index 5d40971..ce4dd29 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -44,7 +44,9 @@ struct _GrdSessionVnc + { + GrdSession parent; + ++ GrdVncServer *vnc_server; + GSocketConnection *connection; ++ GList *socket_grabs; + GSource *source; + rfbScreenInfoPtr rfb_screen; + rfbClientPtr rfb_client; +@@ -465,12 +467,30 @@ check_rfb_password (rfbClientPtr rfb_client, + } + } + ++int ++grd_session_vnc_get_fd (GrdSessionVnc *session_vnc) ++{ ++ return session_vnc->rfb_screen->inetdSock; ++} ++ + int + grd_session_vnc_get_framebuffer_stride (GrdSessionVnc *session_vnc) + { + return session_vnc->rfb_screen->paddedWidthInBytes; + } + ++rfbClientPtr ++grd_session_vnc_get_rfb_client (GrdSessionVnc *session_vnc) ++{ ++ return session_vnc->rfb_client; ++} ++ ++GrdVncServer * ++grd_session_vnc_get_vnc_server (GrdSessionVnc *session_vnc) ++{ ++ return session_vnc->vnc_server; ++} ++ + static void + init_vnc_session (GrdSessionVnc *session_vnc) + { +@@ -509,33 +529,74 @@ init_vnc_session (GrdSessionVnc *session_vnc) + rfbProcessEvents (rfb_screen, 0); + } + ++void ++grd_session_vnc_grab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func) ++{ ++ session_vnc->socket_grabs = g_list_prepend (session_vnc->socket_grabs, ++ grab_func); ++} ++ ++void ++grd_session_vnc_ungrab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func) ++{ ++ session_vnc->socket_grabs = g_list_remove (session_vnc->socket_grabs, ++ grab_func); ++} ++ ++static gboolean ++vnc_socket_grab_func (GrdSessionVnc *session_vnc, ++ GError **error) ++{ ++ if (rfbIsActive (session_vnc->rfb_screen)) ++ { ++ rfbProcessEvents (session_vnc->rfb_screen, 0); ++ ++ if (session_vnc->pending_framebuffer_resize && ++ session_vnc->rfb_client->preferredEncoding != -1) ++ { ++ resize_vnc_framebuffer (session_vnc, ++ session_vnc->pending_framebuffer_width, ++ session_vnc->pending_framebuffer_height); ++ session_vnc->pending_framebuffer_resize = FALSE; ++ } ++ } ++ ++ return TRUE; ++} ++ + static gboolean + handle_socket_data (GSocket *socket, + GIOCondition condition, + gpointer user_data) + { +- GrdSessionVnc *session_vnc = user_data; ++ GrdSessionVnc *session_vnc = GRD_SESSION_VNC (user_data); ++ GrdSession *session = GRD_SESSION (session_vnc); + +- if (condition & G_IO_IN) ++ if (condition & (G_IO_ERR | G_IO_HUP)) ++ { ++ g_warning ("Client disconnected"); ++ ++ grd_session_stop (session); ++ } ++ else if (condition & G_IO_IN) + { +- if (rfbIsActive (session_vnc->rfb_screen)) ++ GrdVncSocketGrabFunc grab_func; ++ g_autoptr (GError) error = NULL; ++ ++ grab_func = g_list_first (session_vnc->socket_grabs)->data; ++ if (!grab_func (session_vnc, &error)) + { +- rfbProcessEvents (session_vnc->rfb_screen, 0); ++ g_warning ("Error when reading socket: %s", error->message); + +- if (session_vnc->pending_framebuffer_resize && +- session_vnc->rfb_client->preferredEncoding != -1) +- { +- resize_vnc_framebuffer (session_vnc, +- session_vnc->pending_framebuffer_width, +- session_vnc->pending_framebuffer_height); +- session_vnc->pending_framebuffer_resize = FALSE; +- } ++ grd_session_stop (session); + } + } + else + { +- g_debug ("Unhandled socket condition %d\n", condition); +- return G_SOURCE_REMOVE; ++ g_warning ("Unhandled socket condition %d\n", condition); ++ g_assert_not_reached (); + } + + return G_SOURCE_CONTINUE; +@@ -548,7 +609,10 @@ grd_session_vnc_attach_source (GrdSessionVnc *session_vnc) + + socket = g_socket_connection_get_socket (session_vnc->connection); + session_vnc->source = g_socket_create_source (socket, +- G_IO_IN | G_IO_PRI, ++ (G_IO_IN | ++ G_IO_PRI | ++ G_IO_ERR | ++ G_IO_HUP), + NULL); + g_source_set_callback (session_vnc->source, + (GSourceFunc) handle_socket_data, +@@ -574,8 +638,10 @@ grd_session_vnc_new (GrdVncServer *vnc_server, + "context", context, + NULL); + ++ session_vnc->vnc_server = vnc_server; + session_vnc->connection = g_object_ref (connection); + ++ grd_session_vnc_grab_socket (session_vnc, vnc_socket_grab_func); + grd_session_vnc_attach_source (session_vnc); + + init_vnc_session (session_vnc); +@@ -590,6 +656,8 @@ grd_session_vnc_dispose (GObject *object) + + g_assert (!session_vnc->rfb_screen); + ++ g_clear_pointer (&session_vnc->socket_grabs, g_list_free); ++ + g_clear_pointer (&session_vnc->pressed_keys, g_hash_table_unref); + + G_OBJECT_CLASS (grd_session_vnc_parent_class)->dispose (object); +diff --git a/src/grd-session-vnc.h b/src/grd-session-vnc.h +index 6bd067a..33245bc 100644 +--- a/src/grd-session-vnc.h ++++ b/src/grd-session-vnc.h +@@ -25,6 +25,7 @@ + + #include + #include ++#include + + #include "grd-session.h" + #include "grd-types.h" +@@ -35,6 +36,9 @@ G_DECLARE_FINAL_TYPE (GrdSessionVnc, + GRD, SESSION_VNC, + GrdSession); + ++typedef gboolean (* GrdVncSocketGrabFunc) (GrdSessionVnc *session_vnc, ++ GError **error); ++ + GrdSessionVnc *grd_session_vnc_new (GrdVncServer *vnc_server, + GSocketConnection *connection); + +@@ -45,6 +49,18 @@ void grd_session_vnc_queue_resize_framebuffer (GrdSessionVnc *session_vnc, + void grd_session_vnc_draw_buffer (GrdSessionVnc *session_vnc, + void *data); + ++int grd_session_vnc_get_fd (GrdSessionVnc *session_vnc); ++ + int grd_session_vnc_get_framebuffer_stride (GrdSessionVnc *session_vnc); + ++rfbClientPtr grd_session_vnc_get_rfb_client (GrdSessionVnc *session_vnc); ++ ++void grd_session_vnc_grab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func); ++ ++void grd_session_vnc_ungrab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func); ++ ++GrdVncServer * grd_session_vnc_get_vnc_server (GrdSessionVnc *session_vnc); ++ + #endif /* GRD_SESSION_VNC_H */ +diff --git a/src/grd-settings.c b/src/grd-settings.c +index a3a2afa..c886b7e 100644 +--- a/src/grd-settings.c ++++ b/src/grd-settings.c +@@ -46,6 +46,7 @@ struct _GrdSettings + GSettings *settings; + gboolean view_only; + GrdVncAuthMethod auth_method; ++ GrdVncEncryption encryption; + } vnc; + }; + +@@ -87,6 +88,12 @@ grd_settings_get_vnc_auth_method (GrdSettings *settings) + return settings->vnc.auth_method; + } + ++GrdVncEncryption ++grd_settings_get_vnc_encryption (GrdSettings *settings) ++{ ++ return settings->vnc.encryption; ++} ++ + static void + update_vnc_view_only (GrdSettings *settings) + { +@@ -101,6 +108,13 @@ update_vnc_auth_method (GrdSettings *settings) + "auth-method"); + } + ++static void ++update_vnc_encryption (GrdSettings *settings) ++{ ++ settings->vnc.encryption = g_settings_get_flags (settings->vnc.settings, ++ "encryption"); ++} ++ + static void + on_vnc_settings_changed (GSettings *vnc_settings, + const char *key, +@@ -116,6 +130,11 @@ on_vnc_settings_changed (GSettings *vnc_settings, + update_vnc_auth_method (settings); + g_signal_emit (settings, signals[VNC_AUTH_METHOD_CHANGED], 0); + } ++ else if (strcmp (key, "encryption") == 0) ++ { ++ update_vnc_encryption (settings); ++ g_signal_emit (settings, signals[VNC_ENCRYPTION_CHANGED], 0); ++ } + } + + static void +@@ -137,6 +156,7 @@ grd_settings_init (GrdSettings *settings) + + update_vnc_view_only (settings); + update_vnc_auth_method (settings); ++ update_vnc_encryption (settings); + } + + static void +@@ -160,4 +180,11 @@ grd_settings_class_init (GrdSettingsClass *klass) + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); ++ signals[VNC_ENCRYPTION_CHANGED] = ++ g_signal_new ("vnc-encryption-changed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, NULL, ++ G_TYPE_NONE, 0); + } +diff --git a/src/grd-settings.h b/src/grd-settings.h +index 9b23b09..4bca403 100644 +--- a/src/grd-settings.h ++++ b/src/grd-settings.h +@@ -40,4 +40,6 @@ gboolean grd_settings_get_vnc_view_only (GrdSettings *settings); + + GrdVncAuthMethod grd_settings_get_vnc_auth_method (GrdSettings *settings); + ++GrdVncEncryption grd_settings_get_vnc_encryption (GrdSettings *settings); ++ + #endif /* GRD_SETTINGS_H */ +diff --git a/src/grd-vnc-server.c b/src/grd-vnc-server.c +index a8fed02..769b7ec 100644 +--- a/src/grd-vnc-server.c ++++ b/src/grd-vnc-server.c +@@ -24,11 +24,13 @@ + + #include "grd-vnc-server.h" + ++#include + #include + #include + + #include "grd-context.h" + #include "grd-session-vnc.h" ++#include "grd-vnc-tls.h" + + #define GRD_VNC_SERVER_PORT 5900 + +@@ -131,6 +133,43 @@ on_incoming (GSocketService *service, + return TRUE; + } + ++static void ++sync_encryption_settings (GrdVncServer *vnc_server) ++{ ++ GrdSettings *settings = grd_context_get_settings (vnc_server->context); ++ rfbSecurityHandler *tls_security_handler; ++ GrdVncEncryption encryption; ++ ++ tls_security_handler = grd_vnc_tls_get_security_handler (); ++ encryption = grd_settings_get_vnc_encryption (settings); ++ ++ if (encryption == (GRD_VNC_ENCRYPTION_NONE | GRD_VNC_ENCRYPTION_TLS_ANON)) ++ { ++ rfbRegisterSecurityHandler (tls_security_handler); ++ rfbUnregisterChannelSecurityHandler (tls_security_handler); ++ } ++ else if (encryption == GRD_VNC_ENCRYPTION_NONE) ++ { ++ rfbUnregisterSecurityHandler (tls_security_handler); ++ rfbUnregisterChannelSecurityHandler (tls_security_handler); ++ } ++ else ++ { ++ if (encryption != GRD_VNC_ENCRYPTION_TLS_ANON) ++ g_warning ("Invalid VNC encryption setting, falling back to TLS-ANON"); ++ ++ rfbRegisterChannelSecurityHandler (tls_security_handler); ++ rfbUnregisterSecurityHandler (tls_security_handler); ++ } ++} ++ ++static void ++on_vnc_encryption_changed (GrdSettings *settings, ++ GrdVncServer *vnc_server) ++{ ++ sync_encryption_settings (vnc_server); ++} ++ + gboolean + grd_vnc_server_start (GrdVncServer *vnc_server, + GError **error) +@@ -219,12 +258,18 @@ static void + grd_vnc_server_constructed (GObject *object) + { + GrdVncServer *vnc_server = GRD_VNC_SERVER (object); ++ GrdSettings *settings = grd_context_get_settings (vnc_server->context); + + if (grd_context_get_debug_flags (vnc_server->context) & GRD_DEBUG_VNC) + rfbLogEnable (1); + else + rfbLogEnable (0); + ++ g_signal_connect (settings, "vnc-encryption-changed", ++ G_CALLBACK (on_vnc_encryption_changed), ++ vnc_server); ++ sync_encryption_settings (vnc_server); ++ + G_OBJECT_CLASS (grd_vnc_server_parent_class)->constructed (object); + } + +diff --git a/src/grd-vnc-tls.c b/src/grd-vnc-tls.c +new file mode 100644 +index 0000000..8fc0fc2 +--- /dev/null ++++ b/src/grd-vnc-tls.c +@@ -0,0 +1,444 @@ ++/* ++ * Copyright (C) 2018 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. ++ * ++ */ ++ ++#include "grd-vnc-tls.h" ++ ++#include ++#include ++#include ++#include ++ ++#include "grd-session-vnc.h" ++#include "grd-vnc-server.h" ++ ++typedef struct _GrdVncTlsContext ++{ ++ gnutls_anon_server_credentials_t anon_credentials; ++ gnutls_dh_params_t dh_params; ++} GrdVncTlsContext; ++ ++typedef enum _GrdTlsHandshakeState ++{ ++ GRD_TLS_HANDSHAKE_STATE_INIT, ++ GRD_TLS_HANDSHAKE_STATE_DURING, ++ GRD_TLS_HANDSHAKE_STATE_FINISHED ++} GrdTlsHandshakeState; ++ ++typedef struct _GrdVncTlsSession ++{ ++ GrdVncTlsContext *tls_context; ++ ++ int fd; ++ ++ gnutls_session_t tls_session; ++ GrdTlsHandshakeState handshake_state; ++ ++ char *peek_buffer; ++ int peek_buffer_size; ++ int peek_buffer_len; ++} GrdVncTlsSession; ++ ++static gboolean ++tls_handshake_grab_func (GrdSessionVnc *session_vnc, ++ GError **error); ++ ++static GrdVncTlsContext * ++grd_vnc_tls_context_new (void) ++{ ++ GrdVncTlsContext *tls_context; ++ const unsigned int dh_bits = 1024; ++ ++ tls_context = g_new0 (GrdVncTlsContext, 1); ++ ++ gnutls_global_init (); ++ ++ gnutls_anon_allocate_server_credentials (&tls_context->anon_credentials); ++ ++ gnutls_dh_params_init (&tls_context->dh_params); ++ gnutls_dh_params_generate2 (tls_context->dh_params, dh_bits); ++ ++ gnutls_anon_set_server_dh_params (tls_context->anon_credentials, ++ tls_context->dh_params); ++ ++ return tls_context; ++} ++ ++static void ++grd_vnc_tls_context_free (GrdVncTlsContext *tls_context) ++{ ++ gnutls_dh_params_deinit (tls_context->dh_params); ++ gnutls_anon_free_server_credentials (tls_context->anon_credentials); ++ gnutls_global_deinit (); ++} ++ ++GrdVncTlsContext * ++ensure_tls_context (GrdVncServer *vnc_server) ++{ ++ GrdVncTlsContext *tls_context; ++ ++ tls_context = g_object_get_data (G_OBJECT (vnc_server), "vnc-tls-context"); ++ if (!tls_context) ++ { ++ tls_context = grd_vnc_tls_context_new (); ++ g_object_set_data_full (G_OBJECT (vnc_server), "vnc-tls-context", ++ tls_context, ++ (GDestroyNotify) grd_vnc_tls_context_free); ++ } ++ ++ return tls_context; ++} ++ ++static gboolean ++perform_anon_tls_handshake (GrdVncTlsSession *tls_session, ++ GError **error) ++{ ++ GrdVncTlsContext *tls_context = tls_session->tls_context; ++ const char kx_priority[] = "NORMAL:+ANON-DH"; ++ int ret; ++ ++ gnutls_init (&tls_session->tls_session, GNUTLS_SERVER | GNUTLS_NO_SIGNAL); ++ ++ gnutls_set_default_priority (tls_session->tls_session); ++ gnutls_priority_set_direct (tls_session->tls_session, kx_priority, NULL); ++ ++ gnutls_credentials_set (tls_session->tls_session, ++ GNUTLS_CRD_ANON, ++ tls_context->anon_credentials); ++ gnutls_transport_set_ptr (tls_session->tls_session, ++ GINT_TO_POINTER (tls_session->fd)); ++ ++ ret = gnutls_handshake (tls_session->tls_session); ++ if (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal (ret)) ++ { ++ tls_session->handshake_state = GRD_TLS_HANDSHAKE_STATE_DURING; ++ return TRUE; ++ } ++ ++ if (ret != GNUTLS_E_SUCCESS) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "%s", gnutls_strerror (ret)); ++ gnutls_deinit (tls_session->tls_session); ++ tls_session->tls_session = NULL; ++ return FALSE; ++ } ++ ++ tls_session->handshake_state = GRD_TLS_HANDSHAKE_STATE_FINISHED; ++ return TRUE; ++} ++ ++static gboolean ++continue_tls_handshake (GrdVncTlsSession *tls_session, ++ GError **error) ++{ ++ int ret; ++ ++ ret = gnutls_handshake (tls_session->tls_session); ++ if (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal (ret)) ++ return TRUE; ++ ++ if (ret != GNUTLS_E_SUCCESS) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "%s", gnutls_strerror (ret)); ++ gnutls_deinit (tls_session->tls_session); ++ tls_session->tls_session = NULL; ++ return FALSE; ++ } ++ ++ tls_session->handshake_state = GRD_TLS_HANDSHAKE_STATE_FINISHED; ++ return TRUE; ++} ++ ++static void ++grd_vnc_tls_session_free (GrdVncTlsSession *tls_session) ++{ ++ g_clear_pointer (&tls_session->peek_buffer, g_free); ++ g_clear_pointer (&tls_session->tls_session, (GDestroyNotify) gnutls_deinit); ++ g_free (tls_session); ++} ++ ++static GrdVncTlsSession * ++grd_vnc_tls_session_from_vnc_session (GrdSessionVnc *session_vnc) ++{ ++ return g_object_get_data (G_OBJECT (session_vnc), "vnc-tls-session"); ++} ++ ++static int ++do_read (GrdVncTlsSession *tls_session, ++ char *buf, ++ int len) ++{ ++ do ++ { ++ int ret; ++ ++ ret = gnutls_record_recv (tls_session->tls_session, buf, len); ++ if (ret == GNUTLS_E_AGAIN || ++ ret == GNUTLS_E_INTERRUPTED) ++ { ++ continue; ++ } ++ else if (ret < 0) ++ { ++ g_debug ("gnutls_record_recv failed: %s", gnutls_strerror (ret)); ++ errno = EIO; ++ return -1; ++ } ++ else ++ { ++ return ret; ++ } ++ } ++ while (TRUE); ++} ++ ++static int ++grd_vnc_tls_read_from_socket (rfbClientPtr rfb_client, ++ char *buf, ++ int len) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ int to_read = len; ++ int len_read = 0; ++ ++ if (to_read < tls_session->peek_buffer_len) ++ { ++ memcpy (buf, tls_session->peek_buffer, to_read); ++ memmove (buf, ++ tls_session->peek_buffer + to_read, ++ tls_session->peek_buffer_len - to_read); ++ len_read = to_read; ++ to_read = 0; ++ } ++ else ++ { ++ memcpy (buf, ++ tls_session->peek_buffer, ++ tls_session->peek_buffer_len); ++ to_read -= tls_session->peek_buffer_len; ++ len_read = tls_session->peek_buffer_len; ++ ++ g_clear_pointer (&tls_session->peek_buffer, ++ g_free); ++ tls_session->peek_buffer_len = 0; ++ tls_session->peek_buffer_size = 0; ++ } ++ ++ if (to_read > 0) ++ { ++ int ret; ++ ++ ret = do_read (tls_session, buf + len_read, to_read); ++ if (ret == -1) ++ return -1; ++ ++ len_read += ret; ++ } ++ ++ return len_read; ++} ++ ++static int ++grd_vnc_tls_peek_at_socket (rfbClientPtr rfb_client, ++ char *buf, ++ int len) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ int peekable_len; ++ ++ if (tls_session->peek_buffer_len < len) ++ { ++ int ret; ++ ++ if (len > tls_session->peek_buffer_size) ++ { ++ tls_session->peek_buffer = g_renew (char, ++ tls_session->peek_buffer, ++ len); ++ tls_session->peek_buffer_size = len; ++ } ++ ++ ret = do_read (tls_session, ++ tls_session->peek_buffer + tls_session->peek_buffer_len, ++ len - tls_session->peek_buffer_len); ++ if (ret == -1) ++ return -1; ++ ++ tls_session->peek_buffer_len += ret; ++ } ++ ++ peekable_len = MIN (len, tls_session->peek_buffer_len); ++ memcpy (buf, tls_session->peek_buffer, peekable_len); ++ ++ return peekable_len; ++} ++ ++static rfbBool ++grd_vnc_tls_has_pending_on_socket (rfbClientPtr rfb_client) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ ++ if (tls_session->peek_buffer_len > 0) ++ return TRUE; ++ ++ if (gnutls_record_check_pending (tls_session->tls_session) > 0) ++ return TRUE; ++ ++ return FALSE; ++} ++ ++static int ++grd_vnc_tls_write_to_socket (rfbClientPtr rfb_client, ++ const char *buf, ++ int len) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ ++ do ++ { ++ int ret; ++ ++ ret = gnutls_record_send (tls_session->tls_session, buf, len); ++ if (ret == GNUTLS_E_AGAIN || ++ ret == GNUTLS_E_INTERRUPTED) ++ { ++ continue; ++ } ++ else if (ret < 0) ++ { ++ g_debug ("gnutls_record_send failed: %s", gnutls_strerror (ret)); ++ errno = EIO; ++ return -1; ++ } ++ else ++ { ++ return ret; ++ } ++ } ++ while (TRUE); ++} ++ ++static gboolean ++perform_handshake (GrdSessionVnc *session_vnc, ++ GError **error) ++{ ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ ++ switch (tls_session->handshake_state) ++ { ++ case GRD_TLS_HANDSHAKE_STATE_INIT: ++ if (!perform_anon_tls_handshake (tls_session, error)) ++ return FALSE; ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_DURING: ++ if (!continue_tls_handshake (tls_session, error)) ++ return FALSE; ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_FINISHED: ++ break; ++ } ++ ++ switch (tls_session->handshake_state) ++ { ++ case GRD_TLS_HANDSHAKE_STATE_INIT: ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_DURING: ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_FINISHED: ++ grd_session_vnc_ungrab_socket (session_vnc, tls_handshake_grab_func); ++ rfbSendSecurityTypeList (grd_session_vnc_get_rfb_client (session_vnc), ++ RFB_SECURITY_TAG_CHANNEL); ++ break; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++tls_handshake_grab_func (GrdSessionVnc *session_vnc, ++ GError **error) ++{ ++ g_autoptr (GError) handshake_error = NULL; ++ ++ if (!perform_handshake (session_vnc, &handshake_error)) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "TLS handshake failed: %s", handshake_error->message); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static void ++rfb_tls_security_handler (rfbClientPtr rfb_client) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session; ++ g_autoptr(GError) error = NULL; ++ ++ tls_session = grd_vnc_tls_session_from_vnc_session (session_vnc); ++ if (!tls_session) ++ { ++ GrdVncServer *vnc_server = grd_session_vnc_get_vnc_server (session_vnc); ++ ++ tls_session = g_new0 (GrdVncTlsSession, 1); ++ tls_session->fd = grd_session_vnc_get_fd (session_vnc); ++ tls_session->tls_context = ensure_tls_context (vnc_server); ++ g_object_set_data_full (G_OBJECT (session_vnc), "vnc-tls-session", ++ tls_session, ++ (GDestroyNotify) grd_vnc_tls_session_free); ++ ++ rfb_client->readFromSocket = grd_vnc_tls_read_from_socket; ++ rfb_client->peekAtSocket = grd_vnc_tls_peek_at_socket; ++ rfb_client->hasPendingOnSocket = grd_vnc_tls_has_pending_on_socket; ++ rfb_client->writeToSocket = grd_vnc_tls_write_to_socket; ++ ++ grd_session_vnc_grab_socket (session_vnc, tls_handshake_grab_func); ++ } ++ ++ if (!perform_handshake (session_vnc, &error)) ++ { ++ g_warning ("TLS handshake failed: %s", error->message); ++ rfbCloseClient (rfb_client); ++ } ++} ++ ++static rfbSecurityHandler anon_tls_security_handler = { ++ .type = rfbTLS, ++ .handler = rfb_tls_security_handler, ++ .securityTags = RFB_SECURITY_TAG_CHANNEL, ++}; ++ ++rfbSecurityHandler * ++grd_vnc_tls_get_security_handler (void) ++{ ++ return &anon_tls_security_handler; ++} +diff --git a/src/grd-vnc-tls.h b/src/grd-vnc-tls.h +new file mode 100644 +index 0000000..135ef8c +--- /dev/null ++++ b/src/grd-vnc-tls.h +@@ -0,0 +1,28 @@ ++/* ++ * Copyright (C) 2018 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. ++ * ++ */ ++ ++#ifndef GRD_VNC_TLS_H ++#define GRD_VNC_TLS_H ++ ++#include ++ ++rfbSecurityHandler * grd_vnc_tls_get_security_handler (void); ++ ++#endif /* GRD_VNC_TLS_H */ +diff --git a/src/meson.build b/src/meson.build +index 70e2102..b633ad7 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -19,6 +19,8 @@ daemon_sources = files([ + 'grd-vnc-pipewire-stream.h', + 'grd-vnc-server.c', + 'grd-vnc-server.h', ++ 'grd-vnc-tls.c', ++ 'grd-vnc-tls.h', + ]) + + gen_daemon_sources = [] +@@ -49,7 +51,8 @@ executable('gnome-remote-desktop-daemon', + pipewire_dep, + libvncserver_dep, + libsecret_dep, +- libnotify_dep], ++ libnotify_dep, ++ gnutls_dep], + include_directories: [configinc], + install: true, + install_dir: libexecdir) +diff --git a/src/org.gnome.desktop.remote-desktop.gschema.xml b/src/org.gnome.desktop.remote-desktop.gschema.xml +index a5c2022..846e65b 100644 +--- a/src/org.gnome.desktop.remote-desktop.gschema.xml ++++ b/src/org.gnome.desktop.remote-desktop.gschema.xml +@@ -23,5 +23,15 @@ + * password - by requiring the remote client to provide a known password + + ++ ++ ['tls-anon'] ++ Allowed encryption method to use ++ ++ Allowed encryption methods. Includes the following: ++ ++ * none - no encryption ++ * tls-anon - anonymous (unauthenticated) TLS ++ ++ + + +-- +2.17.1 + diff --git a/0002-vnc-pipewire-stream-Close-session-when-disconnected.patch b/0002-vnc-pipewire-stream-Close-session-when-disconnected.patch new file mode 100644 index 0000000..cd1c5e4 --- /dev/null +++ b/0002-vnc-pipewire-stream-Close-session-when-disconnected.patch @@ -0,0 +1,28 @@ +From 59188d81cf8936cd9f5400df040d875427251bf2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 1 Oct 2018 18:05:07 +0200 +Subject: [PATCH 2/2] vnc-pipewire-stream: Close session when disconnected + +When there is an active stream, and we're disconnected from PipeWire +(e.g. because it terminated), close the session. +--- + src/grd-vnc-pipewire-stream.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/grd-vnc-pipewire-stream.c b/src/grd-vnc-pipewire-stream.c +index 66d66a0..d6454b8 100644 +--- a/src/grd-vnc-pipewire-stream.c ++++ b/src/grd-vnc-pipewire-stream.c +@@ -392,6 +392,9 @@ on_state_changed (void *user_data, + } + break; + case PW_REMOTE_STATE_UNCONNECTED: ++ if (stream->pipewire_stream) ++ g_signal_emit (stream, signals[CLOSED], 0); ++ break; + case PW_REMOTE_STATE_CONNECTING: + break; + } +-- +2.17.1 + diff --git a/gnome-remote-desktop-0.1.6.tar.xz b/gnome-remote-desktop-0.1.6.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..128146ced535cf7befa2e9da0dcf575f15c47d45 GIT binary patch literal 24612 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 literal 0 HcmV?d00001 diff --git a/gnome-remote-desktop.spec b/gnome-remote-desktop.spec new file mode 100644 index 0000000..dc26389 --- /dev/null +++ b/gnome-remote-desktop.spec @@ -0,0 +1,53 @@ +Name: gnome-remote-desktop +Version: 0.1.6 +Release: 3 +Summary: Screen share service of GNOME Remote Desktop + +License: GPLv2+ +URL: https://gitlab.gnome.org/jadahl/gnome-remote-desktop +Source0: gnome-remote-desktop-0.1.6.tar.xz +Patch00001: 0001-vnc-Add-anonymous-TLS-encryption-support.patch +Patch00002: 0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch +Patch00003: 0001-session-vnc-Don-t-requeue-close-session-idle.patch +Patch00004: 0002-vnc-pipewire-stream-Close-session-when-disconnected.patch + +BuildRequires: meson >= 0.36.0 pkgconfig pkgconfig(glib-2.0) >= 2.32 pkgconfig(gio-unix-2.0) >= 2.32 +BuildRequires: pkgconfig(libpipewire-0.2) >= 0.2.2 pkgconfig(libvncserver) >= 0.9.11-7 pkgconfig(libsecret-1) +BuildRequires: pkgconfig(libnotify) pkgconfig(gnutls) systemd + +Requires: pipewire >= 0.2.2 + +%description +GNOME Remote Desktop is a remote desktop daemon for GNOME using pipewire. + +%prep +%autosetup -n %{name}-%{version} -p1 + +%build +%meson +%meson_build + + +%install +%meson_install + + +%post +%systemd_user_post gnome-remote-desktop.service + +%preun +%systemd_user_preun gnome-remote-desktop.service + +%postun +%systemd_user_postun_with_restart gnome-remote-desktop.service + +%files +%doc README COPYING +%{_libexecdir}/gnome-remote-desktop-daemon +%{_datadir}/glib-2.0/schemas/org.gnome.desktop.remote-desktop.{gschema.xml,enums.xml} +%{_userunitdir}/gnome-remote-desktop.service + +%changelog +* Wed Dec 11 2019 daiqianwen - 0.1.6-3 +- Package init +