/* * Copyright (C) 2020-2021 Pascal Nowack * * 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 "config.h" #include "grd-clipboard-rdp.h" #include #include #include "grd-rdp-fuse-clipboard.h" #include "grd-session-rdp.h" #define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 4 + 4 + 520) #define MAX_WAIT_TIME_MS 4000 /* Format strings of formats with dynamically assigned ids */ #define GRD_CFSTR_MIME_HTML "HTML Format" #define GRD_CFSTR_MIME_TEXT_HTML "text/html" #define GRD_CFSTR_MIME_PNG "image/png" #define GRD_CFSTR_MIME_JPEG "image/jpeg" #define GRD_CFSTR_MIME_GIF "image/gif" #define GRD_CF_HTML 0xD010 #define GRD_CF_PNG 0xD011 #define GRD_CF_JPEG 0xD012 #define GRD_CF_GIF 0xD013 #define GRD_CF_TEXT_URILIST 0xD014 typedef struct _ServerFormatListUpdateContext { GrdClipboardRdp *clipboard_rdp; GList *mime_type_tables; } ServerFormatListUpdateContext; typedef struct _ServerFormatDataRequestContext { GrdClipboardRdp *clipboard_rdp; GrdMimeType mime_type; uint32_t src_format_id; uint32_t dst_format_id; gboolean needs_null_terminator; gboolean needs_conversion; } ServerFormatDataRequestContext; typedef struct _ClientFormatDataRequestContext { GrdMimeTypeTable mime_type_table; gboolean has_clip_data_id; uint32_t clip_data_id; } ClientFormatDataRequestContext; typedef struct _ClipDataEntry { uint32_t id; wClipboard *system; wClipboardDelegate *delegate; uint64_t serial; gboolean is_independent; gboolean has_file_list; gboolean requests_allowed; } ClipDataEntry; typedef struct _FormatData { uint8_t *data; uint32_t size; } FormatData; struct _GrdClipboardRdp { GrdClipboard parent; CliprdrServerContext *cliprdr_context; gboolean protocol_stopped; gboolean relieve_filename_restriction; wClipboard *system; wClipboardDelegate *delegate; gboolean has_file_list; uint64_t serial; GHashTable *allowed_server_formats; GList *pending_server_formats; GList *queued_server_formats; gboolean server_file_contents_requests_allowed; uint16_t format_list_response_msg_flags; GHashTable *serial_entry_table; GHashTable *clip_data_table; struct { ClipDataEntry *entry; ClipDataEntry *entry_to_replace; gboolean serial_already_in_use; } clipboard_retrieval_context; struct { ClipDataEntry *entry; } clipboard_destruction_context; char *fuse_mount_path; GrdRdpFuseClipboard *rdp_fuse_clipboard; GHashTable *format_data_cache; GrdMimeType which_unicode_format; ServerFormatDataRequestContext *format_data_request_context; GHashTable *pending_client_requests; GQueue *ordered_client_requests; GMutex client_request_mutex; ClientFormatDataRequestContext *current_client_request; CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response; unsigned int client_format_data_response_id; unsigned int pending_server_formats_drop_id; unsigned int client_request_abort_id; GCond completion_cond; GMutex completion_mutex; GMutex clip_data_entry_mutex; unsigned int clipboard_retrieval_id; unsigned int clipboard_destruction_id; gboolean completed_clip_data_entry; GMutex server_format_list_update_mutex; unsigned int server_format_list_update_id; gboolean completed_format_list; GMutex client_format_list_response_mutex; unsigned int client_format_list_response_id; GMutex server_format_data_request_mutex; unsigned int server_format_data_request_id; gboolean completed_format_data_request; }; G_DEFINE_TYPE (GrdClipboardRdp, grd_clipboard_rdp, GRD_TYPE_CLIPBOARD) static gboolean send_mime_type_content_request (GrdClipboardRdp *clipboard_rdp, GrdMimeTypeTable *mime_type_table); static void create_new_winpr_clipboard (GrdClipboardRdp *clipboard_rdp); static void update_allowed_server_formats (GrdClipboardRdp *clipboard_rdp, gboolean received_response) { GList *l; if (received_response) g_debug ("[RDP.CLIPRDR] Handling format list response from client"); if (received_response && clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_OK) { for (l = clipboard_rdp->pending_server_formats; l; l = l->next) { if (GPOINTER_TO_UINT (l->data) == GRD_MIME_TYPE_TEXT_URILIST) { ClipDataEntry *entry; if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (clipboard_rdp->serial), NULL, (gpointer *) &entry)) entry->requests_allowed = TRUE; clipboard_rdp->server_file_contents_requests_allowed = TRUE; } g_hash_table_add (clipboard_rdp->allowed_server_formats, l->data); } g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free); return; } if (received_response && !(clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_FAIL)) { g_warning ("[RDP.CLIPRDR] Protocol violation: Client did not set response " "flag. Assuming CB_RESPONSE_FAIL"); } clipboard_rdp->server_file_contents_requests_allowed = FALSE; g_hash_table_remove_all (clipboard_rdp->allowed_server_formats); g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free); } static void remove_clipboard_format_data_for_mime_type (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type) { FormatData *format_data; format_data = g_hash_table_lookup (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type)); if (!format_data) return; g_free (format_data->data); g_free (format_data); g_hash_table_remove (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type)); } static void remove_duplicated_clipboard_mime_types (GrdClipboardRdp *clipboard_rdp, GList **mime_type_list) { GrdMimeType mime_type; /** * Remove the "x-special/gnome-copied-files" mimetype, since we use the * "text/uri-list" mimetype instead. */ mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES; if (g_list_find (*mime_type_list, GUINT_TO_POINTER (mime_type))) { *mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type)); remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type); } /** * We can only advertise CF_UNICODETEXT as remote format once, so ignore the * other local format if it exists. */ if (g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_PLAIN_UTF8)) && g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_UTF8_STRING))) { mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8; *mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type)); remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type); } } static void update_clipboard_serial (GrdClipboardRdp *clipboard_rdp) { ++clipboard_rdp->serial; while (g_hash_table_contains (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (clipboard_rdp->serial))) ++clipboard_rdp->serial; g_debug ("[RDP.CLIPRDR] Updated clipboard serial to %lu", clipboard_rdp->serial); } static void send_mime_type_list (GrdClipboardRdp *clipboard_rdp, GList *mime_type_list) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FORMAT_LIST format_list = {0}; CLIPRDR_FORMAT *cliprdr_formats; ClipDataEntry *entry; GrdMimeType mime_type; uint32_t n_formats; uint32_t i; GList *l; if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (clipboard_rdp->serial), NULL, (gpointer *) &entry)) { g_debug ("[RDP.CLIPRDR] ClipDataEntry with id %u and serial %lu is now " "independent", entry->id, entry->serial); entry->is_independent = TRUE; create_new_winpr_clipboard (clipboard_rdp); } update_clipboard_serial (clipboard_rdp); n_formats = g_list_length (mime_type_list); cliprdr_formats = g_malloc0 (n_formats * sizeof (CLIPRDR_FORMAT)); for (i = 0, l = mime_type_list; i < n_formats; ++i, l = l->next) { mime_type = GPOINTER_TO_UINT (l->data); switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN: cliprdr_formats[i].formatId = CF_TEXT; break; case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: case GRD_MIME_TYPE_TEXT_UTF8_STRING: cliprdr_formats[i].formatId = CF_UNICODETEXT; clipboard_rdp->which_unicode_format = mime_type; break; case GRD_MIME_TYPE_TEXT_HTML: cliprdr_formats[i].formatId = GRD_CF_HTML; cliprdr_formats[i].formatName = GRD_CFSTR_MIME_HTML; break; case GRD_MIME_TYPE_IMAGE_BMP: cliprdr_formats[i].formatId = CF_DIB; break; case GRD_MIME_TYPE_IMAGE_TIFF: cliprdr_formats[i].formatId = CF_TIFF; break; case GRD_MIME_TYPE_IMAGE_GIF: cliprdr_formats[i].formatId = GRD_CF_GIF; cliprdr_formats[i].formatName = GRD_CFSTR_MIME_GIF; break; case GRD_MIME_TYPE_IMAGE_JPEG: cliprdr_formats[i].formatId = GRD_CF_JPEG; cliprdr_formats[i].formatName = GRD_CFSTR_MIME_JPEG; break; case GRD_MIME_TYPE_IMAGE_PNG: cliprdr_formats[i].formatId = GRD_CF_PNG; cliprdr_formats[i].formatName = GRD_CFSTR_MIME_PNG; break; case GRD_MIME_TYPE_TEXT_URILIST: /** * Most formats don't have a consistent format id, but a dynamically * assigned one. These formats are identified by their name. * * When the client requests the content of file lists, it MUST use the * id that we told the client before when the clipboard format with * the name "FileGroupDescriptorW" was advertised. * * See also 1.3.1.2 Clipboard Format */ cliprdr_formats[i].formatId = GRD_CF_TEXT_URILIST; cliprdr_formats[i].formatName = "FileGroupDescriptorW"; clipboard_rdp->server_file_contents_requests_allowed = FALSE; grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard); grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (rdp_fuse_clipboard); break; default: g_assert_not_reached (); } remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type); g_hash_table_remove (clipboard_rdp->allowed_server_formats, l->data); clipboard_rdp->pending_server_formats = g_list_append (clipboard_rdp->pending_server_formats, l->data); } format_list.common.msgType = CB_FORMAT_LIST; format_list.formats = cliprdr_formats; format_list.numFormats = n_formats; g_debug ("[RDP.CLIPRDR] Sending FormatList"); cliprdr_context->ServerFormatList (cliprdr_context, &format_list); g_free (cliprdr_formats); g_list_free (mime_type_list); } static gboolean drop_pending_server_formats (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; GList *queued_server_formats; g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not send " "format list response (Timeout reached)"); update_allowed_server_formats (clipboard_rdp, FALSE); queued_server_formats = g_steal_pointer (&clipboard_rdp->queued_server_formats); if (queued_server_formats) send_mime_type_list (clipboard_rdp, queued_server_formats); clipboard_rdp->pending_server_formats_drop_id = 0; return G_SOURCE_REMOVE; } static void grd_clipboard_rdp_update_client_mime_type_list (GrdClipboard *clipboard, GList *mime_type_list) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard); remove_duplicated_clipboard_mime_types (clipboard_rdp, &mime_type_list); if (!mime_type_list) return; if (clipboard_rdp->pending_server_formats) { if (clipboard_rdp->queued_server_formats) g_debug ("[RDP.CLIPRDR] Replacing queued server FormatList"); g_clear_pointer (&clipboard_rdp->queued_server_formats, g_list_free); g_debug ("[RDP.CLIPRDR] Queueing new FormatList"); clipboard_rdp->queued_server_formats = mime_type_list; if (!clipboard_rdp->pending_server_formats_drop_id) { clipboard_rdp->pending_server_formats_drop_id = g_timeout_add (MAX_WAIT_TIME_MS, drop_pending_server_formats, clipboard_rdp); } return; } send_mime_type_list (clipboard_rdp, mime_type_list); } void grd_clipboard_rdp_lock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = {0}; g_debug ("[RDP.CLIPRDR] Locking clients clipboard data with clipDataId %u", clip_data_id); lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA; lock_clipboard_data.clipDataId = clip_data_id; cliprdr_context->ServerLockClipboardData (cliprdr_context, &lock_clipboard_data); } void grd_clipboard_rdp_unlock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = {0}; g_debug ("[RDP.CLIPRDR] Unlocking clients clipboard data associated to " "clipDataId %u", clip_data_id); unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA; unlock_clipboard_data.clipDataId = clip_data_id; cliprdr_context->ServerUnlockClipboardData (cliprdr_context, &unlock_clipboard_data); } void grd_clipboard_rdp_request_remote_file_size_async (GrdClipboardRdp *clipboard_rdp, uint32_t stream_id, uint32_t list_index, gboolean has_clip_data_id, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0}; file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST; file_contents_request.streamId = stream_id; file_contents_request.listIndex = list_index; file_contents_request.dwFlags = FILECONTENTS_SIZE; file_contents_request.cbRequested = 0x8; file_contents_request.haveClipDataId = has_clip_data_id; file_contents_request.clipDataId = clip_data_id; cliprdr_context->ServerFileContentsRequest (cliprdr_context, &file_contents_request); } void grd_clipboard_rdp_request_remote_file_range_async (GrdClipboardRdp *clipboard_rdp, uint32_t stream_id, uint32_t list_index, uint64_t offset, uint32_t requested_size, gboolean has_clip_data_id, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0}; file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST; file_contents_request.streamId = stream_id; file_contents_request.listIndex = list_index; file_contents_request.dwFlags = FILECONTENTS_RANGE; file_contents_request.nPositionLow = offset & 0xFFFFFFFF; file_contents_request.nPositionHigh = offset >> 32 & 0xFFFFFFFF; file_contents_request.cbRequested = requested_size; file_contents_request.haveClipDataId = has_clip_data_id; file_contents_request.clipDataId = clip_data_id; cliprdr_context->ServerFileContentsRequest (cliprdr_context, &file_contents_request); } static void track_serial_for_mime_type (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type, unsigned int serial) { GList *serials; serials = g_hash_table_lookup (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type)); serials = g_list_append (serials, GUINT_TO_POINTER (serial)); g_hash_table_insert (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type), serials); } static void enqueue_mime_type_content_request (GrdClipboardRdp *clipboard_rdp, GrdMimeTypeTable *mime_type_table, unsigned int serial) { GrdMimeType mime_type = mime_type_table->mime_type; GrdMimeTypeTable *mime_type_table_copy; g_debug ("[RDP.CLIPRDR] Queueing mime type content request for mime type %s " "with serial %u", grd_mime_type_to_string (mime_type), serial); track_serial_for_mime_type (clipboard_rdp, mime_type, serial); mime_type_table_copy = g_malloc0 (sizeof (GrdMimeTypeTable)); *mime_type_table_copy = *mime_type_table; g_queue_push_tail (clipboard_rdp->ordered_client_requests, GUINT_TO_POINTER (mime_type_table_copy)); } static void abort_client_requests_for_serials (GrdClipboardRdp *clipboard_rdp, GList *serials) { GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); unsigned int serial; GList *l; for (l = serials; l; l = l->next) { serial = GPOINTER_TO_UINT (l->data); grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); } g_list_free (serials); } static void abort_client_requests_for_mime_type (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type) { GList *serials; if (!g_hash_table_steal_extended (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &serials)) return; abort_client_requests_for_serials (clipboard_rdp, serials); } static void abort_client_requests_for_context (GrdClipboardRdp *clipboard_rdp, ClientFormatDataRequestContext *request_context) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; GrdMimeTypeTable *mime_type_table = &request_context->mime_type_table; GrdMimeType mime_type = mime_type_table->mime_type; uint32_t clip_data_id = request_context->clip_data_id; g_debug ("[RDP.CLIPRDR] Aborting FormatDataRequest for mime type %s", grd_mime_type_to_string (mime_type)); if (request_context->has_clip_data_id) grd_rdp_fuse_clipboard_clip_data_id_free (rdp_fuse_clipboard, clip_data_id); abort_client_requests_for_mime_type (clipboard_rdp, mime_type); } static void maybe_send_next_mime_type_content_request (GrdClipboardRdp *clipboard_rdp) { GrdMimeTypeTable *mime_type_table; GrdMimeType mime_type; GList *serials = NULL; mime_type_table = g_queue_pop_head (clipboard_rdp->ordered_client_requests); if (!mime_type_table) return; mime_type = mime_type_table->mime_type; serials = g_hash_table_lookup (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type)); if (!serials) { g_free (mime_type_table); maybe_send_next_mime_type_content_request (clipboard_rdp); return; } if (!send_mime_type_content_request (clipboard_rdp, mime_type_table)) { abort_client_requests_for_mime_type (clipboard_rdp, mime_type); g_free (mime_type_table); maybe_send_next_mime_type_content_request (clipboard_rdp); return; } g_free (mime_type_table); } static gboolean abort_current_client_request (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClientFormatDataRequestContext *request_context; g_mutex_lock (&clipboard_rdp->client_request_mutex); request_context = g_steal_pointer (&clipboard_rdp->current_client_request); g_clear_handle_id (&clipboard_rdp->client_format_data_response_id, g_source_remove); g_mutex_unlock (&clipboard_rdp->client_request_mutex); g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not send " "format data response (Timeout reached)"); abort_client_requests_for_context (clipboard_rdp, request_context); g_free (request_context); clipboard_rdp->client_request_abort_id = 0; maybe_send_next_mime_type_content_request (clipboard_rdp); return G_SOURCE_REMOVE; } static gboolean send_mime_type_content_request (GrdClipboardRdp *clipboard_rdp, GrdMimeTypeTable *mime_type_table) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FORMAT_DATA_REQUEST format_data_request = {0}; GrdMimeType mime_type = mime_type_table->mime_type; ClientFormatDataRequestContext *request_context; uint32_t clip_data_id = 0; g_assert (mime_type != GRD_MIME_TYPE_NONE); g_assert (!clipboard_rdp->current_client_request); g_assert (!clipboard_rdp->client_format_data_response_id); g_assert (!clipboard_rdp->client_request_abort_id); request_context = g_malloc0 (sizeof (ClientFormatDataRequestContext)); request_context->mime_type_table = *mime_type_table; if (clipboard_rdp->cliprdr_context->canLockClipData && (mime_type == GRD_MIME_TYPE_TEXT_URILIST || mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)) { clip_data_id = grd_rdp_fuse_clipboard_clip_data_id_new (rdp_fuse_clipboard); request_context->clip_data_id = clip_data_id; request_context->has_clip_data_id = TRUE; } format_data_request.common.msgType = CB_FORMAT_DATA_REQUEST; format_data_request.common.dataLen = 4; format_data_request.requestedFormatId = mime_type_table->rdp.format_id; g_mutex_lock (&clipboard_rdp->client_request_mutex); if (cliprdr_context->ServerFormatDataRequest (cliprdr_context, &format_data_request)) { g_mutex_unlock (&clipboard_rdp->client_request_mutex); if (request_context->has_clip_data_id) grd_rdp_fuse_clipboard_clip_data_id_free (rdp_fuse_clipboard, clip_data_id); g_free (request_context); return FALSE; } g_debug ("[RDP.CLIPRDR] Sent FormatDataRequest for mime type %s", grd_mime_type_to_string (mime_type)); clipboard_rdp->current_client_request = request_context; g_mutex_unlock (&clipboard_rdp->client_request_mutex); clipboard_rdp->client_request_abort_id = g_timeout_add (MAX_WAIT_TIME_MS, abort_current_client_request, clipboard_rdp); return TRUE; } static void grd_clipboard_rdp_request_client_content_for_mime_type (GrdClipboard *clipboard, GrdMimeTypeTable *mime_type_table, unsigned int serial) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard); GrdMimeType mime_type = mime_type_table->mime_type; gboolean pending_format_list_update; FormatData *format_data; if (mime_type == GRD_MIME_TYPE_NONE) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); return; } if (g_hash_table_lookup_extended (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &format_data)) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, format_data->data, format_data->size); return; } g_mutex_lock (&clipboard_rdp->server_format_list_update_mutex); pending_format_list_update = clipboard_rdp->server_format_list_update_id != 0; g_mutex_unlock (&clipboard_rdp->server_format_list_update_mutex); if (pending_format_list_update) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); return; } if (clipboard_rdp->current_client_request) { enqueue_mime_type_content_request (clipboard_rdp, mime_type_table, serial); return; } g_assert (g_queue_is_empty (clipboard_rdp->ordered_client_requests)); if (!send_mime_type_content_request (clipboard_rdp, mime_type_table)) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); return; } track_serial_for_mime_type (clipboard_rdp, mime_type, serial); } static void serialize_file_list (FILEDESCRIPTORW *files, uint32_t n_files, uint8_t **dst_data, uint32_t *dst_size) { FILEDESCRIPTORW *file; wStream* s = NULL; uint64_t last_write_time; uint32_t i, j; if (!files || !dst_data || !dst_size) return; if (!(s = Stream_New (NULL, 4 + n_files * CLIPRDR_FILEDESCRIPTOR_SIZE))) return; Stream_Write_UINT32 (s, n_files); /* cItems */ for (i = 0; i < n_files; ++i) { file = &files[i]; Stream_Write_UINT32 (s, file->dwFlags); /* flags */ Stream_Zero (s, 32); /* reserved1 */ Stream_Write_UINT32 (s, file->dwFileAttributes); /* fileAttributes */ Stream_Zero (s, 16); /* reserved2 */ last_write_time = file->ftLastWriteTime.dwHighDateTime; last_write_time <<= 32; last_write_time += file->ftLastWriteTime.dwLowDateTime; Stream_Write_UINT64 (s, last_write_time); /* lastWriteTime */ Stream_Write_UINT32 (s, file->nFileSizeHigh); /* fileSizeHigh */ Stream_Write_UINT32 (s, file->nFileSizeLow); /* fileSizeLow */ for (j = 0; j < 260; j++) /* cFileName */ Stream_Write_UINT16 (s, file->cFileName[j]); } Stream_SealLength (s); Stream_GetLength (s, *dst_size); Stream_GetBuffer (s, *dst_data); Stream_Free (s, FALSE); } static void grd_clipboard_rdp_submit_requested_server_content (GrdClipboard *clipboard, uint8_t *src_data, uint32_t src_size) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard); CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; ServerFormatDataRequestContext *request_context; CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0}; GrdMimeType mime_type; uint32_t src_format_id; uint32_t dst_format_id; uint8_t *dst_data = NULL; uint32_t dst_size = 0; BOOL success; request_context = g_steal_pointer (&clipboard_rdp->format_data_request_context); mime_type = request_context->mime_type; src_format_id = request_context->src_format_id; dst_format_id = request_context->dst_format_id; if (src_data) { if (request_context->needs_conversion) { if (request_context->needs_null_terminator) { char *pnull_terminator; src_data = g_realloc (src_data, src_size + 1); pnull_terminator = (char *) src_data + src_size; *pnull_terminator = '\0'; ++src_size; } success = ClipboardSetData (clipboard_rdp->system, src_format_id, src_data, src_size); if (!success) { g_warning ("[RDP.CLIPRDR] Converting clipboard content failed: " "Failed to set data to convert"); } else { dst_data = ClipboardGetData (clipboard_rdp->system, dst_format_id, &dst_size); if (dst_data && mime_type == GRD_MIME_TYPE_TEXT_URILIST) { uint64_t serial = clipboard_rdp->serial; ClipDataEntry *entry; FILEDESCRIPTORW *files; uint32_t n_files; files = (FILEDESCRIPTORW *) dst_data; n_files = dst_size / sizeof (FILEDESCRIPTORW); dst_data = NULL; dst_size = 0; serialize_file_list (files, n_files, &dst_data, &dst_size); g_free (files); clipboard_rdp->has_file_list = TRUE; if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (serial), NULL, (gpointer *) &entry)) entry->has_file_list = TRUE; } } if (!dst_data) { g_warning ("[RDP.CLIPRDR] Converting clipboard content for " "client failed: Failed to get converted data"); } } else { dst_data = g_steal_pointer (&src_data); dst_size = src_size; } } format_data_response.common.msgType = CB_FORMAT_DATA_RESPONSE; format_data_response.common.msgFlags = dst_data ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; format_data_response.common.dataLen = dst_size; format_data_response.requestedFormatData = dst_data; cliprdr_context->ServerFormatDataResponse (cliprdr_context, &format_data_response); g_free (src_data); g_free (dst_data); g_free (request_context); g_mutex_lock (&clipboard_rdp->completion_mutex); clipboard_rdp->completed_format_data_request = TRUE; g_cond_signal (&clipboard_rdp->completion_cond); g_mutex_unlock (&clipboard_rdp->completion_mutex); } /** * FreeRDP already updated our capabilites after the client told us * about its capabilities, there is nothing to do here */ static uint32_t cliprdr_client_capabilities (CliprdrServerContext *cliprdr_context, const CLIPRDR_CAPABILITIES *capabilities) { g_autoptr (GStrvBuilder) client_capabilities = NULL; char **client_caps_strings; g_autofree char *caps_string = NULL; client_capabilities = g_strv_builder_new (); if (cliprdr_context->useLongFormatNames) g_strv_builder_add (client_capabilities, "long format names"); if (cliprdr_context->streamFileClipEnabled) g_strv_builder_add (client_capabilities, "stream file clip"); if (cliprdr_context->fileClipNoFilePaths) g_strv_builder_add (client_capabilities, "file clip no file paths"); if (cliprdr_context->canLockClipData) g_strv_builder_add (client_capabilities, "can lock clip data"); if (cliprdr_context->hasHugeFileSupport) g_strv_builder_add (client_capabilities, "huge file support"); client_caps_strings = g_strv_builder_end (client_capabilities); caps_string = g_strjoinv (", ", client_caps_strings); g_message ("[RDP.CLIPRDR] Client capabilities: %s", caps_string); g_strfreev (client_caps_strings); return CHANNEL_RC_OK; } /** * Client sent us a Temporary Directory PDU. * We don't handle the CF_HDROP format however. It's a relict of the past. */ static uint32_t cliprdr_temp_directory (CliprdrServerContext *cliprdr_context, const CLIPRDR_TEMP_DIRECTORY *temp_directory) { g_debug ("[RDP.CLIPRDR] Client sent a Temporary Directory PDU with path \"%s\"", temp_directory->szTempDir); return CHANNEL_RC_OK; } static void abort_all_client_requests (GrdClipboardRdp *clipboard_rdp) { ClientFormatDataRequestContext *request_context; GHashTableIter iter; GList *serials; g_debug ("[RDP.CLIPRDR] Aborting all pending client requests"); g_mutex_lock (&clipboard_rdp->client_request_mutex); request_context = g_steal_pointer (&clipboard_rdp->current_client_request); g_clear_handle_id (&clipboard_rdp->client_format_data_response_id, g_source_remove); g_mutex_unlock (&clipboard_rdp->client_request_mutex); if (request_context) abort_client_requests_for_context (clipboard_rdp, request_context); g_free (request_context); g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove); g_hash_table_iter_init (&iter, clipboard_rdp->pending_client_requests); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &serials)) { abort_client_requests_for_serials (clipboard_rdp, serials); g_hash_table_iter_remove (&iter); } g_queue_clear_full (clipboard_rdp->ordered_client_requests, g_free); } static gboolean update_server_format_list (gpointer user_data) { ServerFormatListUpdateContext *update_context = user_data; GrdClipboardRdp *clipboard_rdp = update_context->clipboard_rdp; GrdClipboard *clipboard = GRD_CLIPBOARD (update_context->clipboard_rdp); GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {0}; GList *mime_type_tables; GrdMimeTypeTable *mime_type_table; GList *l; mime_type_tables = g_steal_pointer (&update_context->mime_type_tables); for (l = mime_type_tables; l; l = l->next) { mime_type_table = l->data; /* Also indirectly handles the GRD_MIME_TYPE_XS_GNOME_COPIED_FILES case */ if (mime_type_table->mime_type == GRD_MIME_TYPE_TEXT_URILIST) { clipboard_rdp->server_file_contents_requests_allowed = FALSE; grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard); grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (rdp_fuse_clipboard); } remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type_table->mime_type); g_hash_table_remove (clipboard_rdp->allowed_server_formats, GUINT_TO_POINTER (mime_type_table->mime_type)); } abort_all_client_requests (clipboard_rdp); grd_clipboard_update_server_mime_type_list (clipboard, mime_type_tables); format_list_response.common.msgType = CB_FORMAT_LIST_RESPONSE; format_list_response.common.msgFlags = CB_RESPONSE_OK; cliprdr_context->ServerFormatListResponse (cliprdr_context, &format_list_response); /** * Any FileContentsRequest that is still waiting for a FileContentsResponse * won't get a response any more, when the client does not support clipboard * data locking. So, drop all requests without clipDataId here. */ if (!cliprdr_context->canLockClipData) grd_rdp_fuse_clipboard_dismiss_all_no_cdi_requests (rdp_fuse_clipboard); g_mutex_lock (&clipboard_rdp->server_format_list_update_mutex); clipboard_rdp->server_format_list_update_id = 0; g_mutex_unlock (&clipboard_rdp->server_format_list_update_mutex); g_mutex_lock (&clipboard_rdp->completion_mutex); clipboard_rdp->completed_format_list = TRUE; g_cond_signal (&clipboard_rdp->completion_cond); g_mutex_unlock (&clipboard_rdp->completion_mutex); return G_SOURCE_REMOVE; } static void update_context_free (gpointer data) { ServerFormatListUpdateContext *update_context = data; g_clear_list (&update_context->mime_type_tables, g_free); g_free (update_context); } /** * Client notifies us that its clipboard is updated with new clipboard data */ static uint32_t cliprdr_client_format_list (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_LIST *format_list) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; ServerFormatListUpdateContext *update_context; GHashTable *found_mime_types; GrdMimeTypeTable *mime_type_table = NULL; GList *mime_type_tables = NULL; GrdMimeType mime_type; gboolean already_has_text_format = FALSE; uint32_t i; found_mime_types = g_hash_table_new (NULL, NULL); /** * The text format CF_TEXT can depend on the CF_LOCALE content. In such * situations, we might not get the correct content from WinPR. * CF_UNICODETEXT is not affected by the CF_LOCALE content, so try to find * the CF_UNICODETEXT first to ensure that we use CF_UNICODETEXT, when both * formats are available and CF_TEXT is listed first. */ for (i = 0; i < format_list->numFormats; ++i) { if (format_list->formats[i].formatId == CF_UNICODETEXT) { mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8; mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING; mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); already_has_text_format = TRUE; g_debug ("[RDP.CLIPRDR] Force using CF_UNICODETEXT over CF_TEXT as " "external format for text/plain;charset=utf-8 and UTF8_STRING"); break; } } for (i = 0; i < format_list->numFormats; ++i) { mime_type = GRD_MIME_TYPE_NONE; /** * First check formats with dynamically assigned format ids. These formats * don't have consistent ids. Their name, however, is always the same. * * If the client uses short format names, the formatName is truncated to * either 32 ASCII 8 characters or 16 UTF-16 characters. */ if (format_list->formats[i].formatName && (strcmp (format_list->formats[i].formatName, "FileGroupDescriptorW") == 0 || strcmp (format_list->formats[i].formatName, "FileGroupDescri") == 0)) { if (!g_hash_table_contains (found_mime_types, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_URILIST))) { /** * Advertise the "x-special/gnome-copied-files" format in addition to * the "text/uri-list" format */ mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES; mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); } mime_type = GRD_MIME_TYPE_TEXT_URILIST; } else if (format_list->formats[i].formatName && strcmp (format_list->formats[i].formatName, GRD_CFSTR_MIME_HTML) == 0) { mime_type = GRD_MIME_TYPE_TEXT_HTML; } else if (format_list->formats[i].formatName && strcmp (format_list->formats[i].formatName, GRD_CFSTR_MIME_GIF) == 0) { mime_type = GRD_MIME_TYPE_IMAGE_GIF; } else if (format_list->formats[i].formatName && strcmp (format_list->formats[i].formatName, GRD_CFSTR_MIME_PNG) == 0) { mime_type = GRD_MIME_TYPE_IMAGE_PNG; } else if (format_list->formats[i].formatName && strcmp (format_list->formats[i].formatName, GRD_CFSTR_MIME_JPEG) == 0) { mime_type = GRD_MIME_TYPE_IMAGE_JPEG; } else { switch (format_list->formats[i].formatId) { case CF_TEXT: case CF_UNICODETEXT: case CF_OEMTEXT: if (!already_has_text_format) { mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8; g_assert (!g_hash_table_contains (found_mime_types, GUINT_TO_POINTER (mime_type))); mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING; already_has_text_format = TRUE; g_debug ("[RDP.CLIPRDR] Client advertised data for " "text/plain;charset=utf-8 and UTF8_STRING " "(external format: id: %u, name: %s)", format_list->formats[i].formatId, format_list->formats[i].formatName); } break; case CF_DIB: mime_type = GRD_MIME_TYPE_IMAGE_BMP; break; case CF_TIFF: mime_type = GRD_MIME_TYPE_IMAGE_TIFF; break; default: g_debug ("[RDP.CLIPRDR] Client advertised unknown format: id: %u, " "name: %s", format_list->formats[i].formatId, format_list->formats[i].formatName); } } if (mime_type != GRD_MIME_TYPE_NONE && !g_hash_table_contains (found_mime_types, GUINT_TO_POINTER (mime_type))) { mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); } else if (mime_type != GRD_MIME_TYPE_NONE) { g_debug ("[RDP.CLIPRDR] Ignoring duplicated format: id: %u, name: %s", format_list->formats[i].formatId, format_list->formats[i].formatName); } } g_hash_table_destroy (found_mime_types); if (clipboard_rdp->server_format_list_update_id) { g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format list " "without being able to response to last update first"); } g_mutex_lock (&clipboard_rdp->completion_mutex); while (!clipboard_rdp->protocol_stopped && !clipboard_rdp->completed_format_list) { g_cond_wait (&clipboard_rdp->completion_cond, &clipboard_rdp->completion_mutex); } g_mutex_unlock (&clipboard_rdp->completion_mutex); if (clipboard_rdp->protocol_stopped) { g_list_free_full (mime_type_tables, g_free); return CHANNEL_RC_OK; } g_assert (clipboard_rdp->completed_format_list); clipboard_rdp->completed_format_list = FALSE; update_context = g_malloc0 (sizeof (ServerFormatListUpdateContext)); update_context->clipboard_rdp = clipboard_rdp; update_context->mime_type_tables = mime_type_tables; g_mutex_lock (&clipboard_rdp->server_format_list_update_mutex); clipboard_rdp->server_format_list_update_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, update_server_format_list, update_context, update_context_free); g_mutex_unlock (&clipboard_rdp->server_format_list_update_mutex); return CHANNEL_RC_OK; } static gboolean handle_format_list_response (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; GList *queued_server_formats; if (clipboard_rdp->pending_server_formats) update_allowed_server_formats (clipboard_rdp, TRUE); g_clear_handle_id (&clipboard_rdp->pending_server_formats_drop_id, g_source_remove); g_mutex_lock (&clipboard_rdp->client_format_list_response_mutex); clipboard_rdp->client_format_list_response_id = 0; g_mutex_unlock (&clipboard_rdp->client_format_list_response_mutex); queued_server_formats = g_steal_pointer (&clipboard_rdp->queued_server_formats); if (queued_server_formats) send_mime_type_list (clipboard_rdp, queued_server_formats); return G_SOURCE_REMOVE; } /** * Client sent us a response to our format list pdu */ static uint32_t cliprdr_client_format_list_response (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_LIST_RESPONSE *format_list_response) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; g_autoptr (GMutexLocker) locker = NULL; locker = g_mutex_locker_new (&clipboard_rdp->client_format_list_response_mutex); if (clipboard_rdp->client_format_list_response_id != 0) { g_warning ("[RDP.CLIPRDR] Wrong message sequence: Received an unexpected " "format list response. Ignoring..."); return CHANNEL_RC_OK; } clipboard_rdp->format_list_response_msg_flags = format_list_response->common.msgFlags; clipboard_rdp->client_format_list_response_id = g_idle_add (handle_format_list_response, clipboard_rdp); return CHANNEL_RC_OK; } static gboolean retrieve_current_clipboard (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClipDataEntry *entry = clipboard_rdp->clipboard_retrieval_context.entry; ClipDataEntry *entry_to_replace; uint64_t serial = clipboard_rdp->serial; gboolean serial_already_in_use; g_debug ("[RDP.CLIPRDR] Tracking serial %lu for ClipDataEntry", clipboard_rdp->serial); entry_to_replace = clipboard_rdp->clipboard_retrieval_context.entry_to_replace; if (entry_to_replace) { g_hash_table_remove (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (entry_to_replace->serial)); } serial_already_in_use = g_hash_table_contains (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (serial)); if (!serial_already_in_use) { g_hash_table_insert (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (serial), entry); } clipboard_rdp->clipboard_retrieval_context.serial_already_in_use = serial_already_in_use; entry->serial = serial; entry->has_file_list = clipboard_rdp->has_file_list; entry->requests_allowed = clipboard_rdp->server_file_contents_requests_allowed; entry->system = clipboard_rdp->system; entry->delegate = clipboard_rdp->delegate; g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex); clipboard_rdp->clipboard_retrieval_id = 0; g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex); g_mutex_lock (&clipboard_rdp->completion_mutex); clipboard_rdp->completed_clip_data_entry = TRUE; g_cond_signal (&clipboard_rdp->completion_cond); g_mutex_unlock (&clipboard_rdp->completion_mutex); return G_SOURCE_REMOVE; } static gboolean is_clip_data_entry_user_of_serial (gpointer key, gpointer value, gpointer user_data) { ClipDataEntry *entry = value; uint64_t serial = GPOINTER_TO_UINT (user_data); return entry->serial == serial; } /** * Client requests us to retain all file stream data on the clipboard */ static uint32_t cliprdr_client_lock_clipboard_data (CliprdrServerContext *cliprdr_context, const CLIPRDR_LOCK_CLIPBOARD_DATA *lock_clipboard_data) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; uint32_t clip_data_id = lock_clipboard_data->clipDataId; g_autofree ClipDataEntry *entry = NULL; if (clipboard_rdp->protocol_stopped) return CHANNEL_RC_OK; g_debug ("[RDP.CLIPRDR] Client requested a lock with clipDataId: %u", clip_data_id); clipboard_rdp->clipboard_retrieval_context.entry_to_replace = NULL; if (g_hash_table_lookup_extended (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_warning ("[RDP.CLIPRDR] Protocol violation: Client requested a lock with" " an existing clipDataId %u. Replacing existing ClipDataEntry", clip_data_id); clipboard_rdp->clipboard_retrieval_context.entry_to_replace = entry; } g_assert (clipboard_rdp->completed_clip_data_entry); clipboard_rdp->completed_clip_data_entry = FALSE; entry = g_malloc0 (sizeof (ClipDataEntry)); clipboard_rdp->clipboard_retrieval_context.entry = entry; g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex); clipboard_rdp->clipboard_retrieval_id = g_idle_add (retrieve_current_clipboard, clipboard_rdp); g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex); g_mutex_lock (&clipboard_rdp->completion_mutex); while (!clipboard_rdp->protocol_stopped && !clipboard_rdp->completed_clip_data_entry) { g_cond_wait (&clipboard_rdp->completion_cond, &clipboard_rdp->completion_mutex); } g_mutex_unlock (&clipboard_rdp->completion_mutex); if (clipboard_rdp->protocol_stopped) return CHANNEL_RC_OK; g_assert (clipboard_rdp->completed_clip_data_entry); if (clipboard_rdp->clipboard_retrieval_context.serial_already_in_use) { uint64_t serial = entry->serial; g_warning ("[RDP.CLIPRDR] Protocol violation: Double lock detected (" "Clipboard serial already in use). Replacing the clipDataId."); g_free (entry); entry = g_hash_table_find (clipboard_rdp->clip_data_table, is_clip_data_entry_user_of_serial, GUINT_TO_POINTER (serial)); g_hash_table_steal (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (entry->id)); } entry->id = clip_data_id; g_debug ("[RDP.CLIPRDR] Tracking lock with clipDataId %u", clip_data_id); g_hash_table_insert (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), g_steal_pointer (&entry)); return CHANNEL_RC_OK; } static gboolean handle_clip_data_entry_destruction (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClipDataEntry *entry; entry = g_steal_pointer (&clipboard_rdp->clipboard_destruction_context.entry); g_debug ("[RDP.CLIPRDR] Deleting ClipDataEntry with serial %lu", entry->serial); g_hash_table_remove (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (entry->serial)); g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex); clipboard_rdp->clipboard_destruction_id = 0; g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex); g_mutex_lock (&clipboard_rdp->completion_mutex); clipboard_rdp->completed_clip_data_entry = TRUE; g_cond_signal (&clipboard_rdp->completion_cond); g_mutex_unlock (&clipboard_rdp->completion_mutex); return G_SOURCE_REMOVE; } /** * Client notifies us that the file stream data for a specific clip data id MUST * now be released */ static uint32_t cliprdr_client_unlock_clipboard_data (CliprdrServerContext *cliprdr_context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlock_clipboard_data) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; uint32_t clip_data_id = unlock_clipboard_data->clipDataId; ClipDataEntry *entry; if (clipboard_rdp->protocol_stopped) return CHANNEL_RC_OK; g_debug ("[RDP.CLIPRDR] Client requested an unlock with clipDataId: %u", clip_data_id); if (!g_hash_table_lookup_extended (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_warning ("[RDP.CLIPRDR] Protocol violation: ClipDataEntry with id %u " "does not exist", clip_data_id); return CHANNEL_RC_OK; } g_debug ("[RDP.CLIPRDR] Removing lock with clipDataId: %u", clip_data_id); g_assert (clipboard_rdp->completed_clip_data_entry); clipboard_rdp->completed_clip_data_entry = FALSE; clipboard_rdp->clipboard_destruction_context.entry = entry; g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex); clipboard_rdp->clipboard_destruction_id = g_idle_add (handle_clip_data_entry_destruction, clipboard_rdp); g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex); g_mutex_lock (&clipboard_rdp->completion_mutex); while (!clipboard_rdp->protocol_stopped && !clipboard_rdp->completed_clip_data_entry) { g_cond_wait (&clipboard_rdp->completion_cond, &clipboard_rdp->completion_mutex); } g_mutex_unlock (&clipboard_rdp->completion_mutex); g_debug ("[RDP.CLIPRDR] Untracking lock with clipDataId %u", clip_data_id); g_hash_table_remove (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id)); return CHANNEL_RC_OK; } static gboolean request_server_format_data (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; ServerFormatDataRequestContext *request_context; GrdMimeType mime_type; request_context = clipboard_rdp->format_data_request_context; mime_type = request_context->mime_type; if (!g_hash_table_contains (clipboard_rdp->allowed_server_formats, GUINT_TO_POINTER (mime_type))) { CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0}; format_data_response.common.msgType = CB_FORMAT_DATA_RESPONSE; format_data_response.common.msgFlags = CB_RESPONSE_FAIL; cliprdr_context->ServerFormatDataResponse (cliprdr_context, &format_data_response); g_clear_pointer (&clipboard_rdp->format_data_request_context, g_free); g_mutex_lock (&clipboard_rdp->server_format_data_request_mutex); clipboard_rdp->server_format_data_request_id = 0; g_mutex_unlock (&clipboard_rdp->server_format_data_request_mutex); g_mutex_lock (&clipboard_rdp->completion_mutex); clipboard_rdp->completed_format_data_request = TRUE; g_cond_signal (&clipboard_rdp->completion_cond); g_mutex_unlock (&clipboard_rdp->completion_mutex); return G_SOURCE_REMOVE; } grd_clipboard_request_server_content_for_mime_type_async (clipboard, mime_type); g_mutex_lock (&clipboard_rdp->server_format_data_request_mutex); clipboard_rdp->server_format_data_request_id = 0; g_mutex_unlock (&clipboard_rdp->server_format_data_request_mutex); return G_SOURCE_REMOVE; } /** * Client knows our format list, it requests now the data from our clipboard */ static uint32_t cliprdr_client_format_data_request (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_DATA_REQUEST *format_data_request) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; ServerFormatDataRequestContext *request_context; CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0}; GrdMimeType mime_type = GRD_MIME_TYPE_NONE; uint32_t src_format_id = 0; uint32_t dst_format_id; gboolean needs_null_terminator = FALSE; gboolean needs_conversion = FALSE; dst_format_id = format_data_request->requestedFormatId; switch (dst_format_id) { case CF_TEXT: mime_type = GRD_MIME_TYPE_TEXT_PLAIN; needs_null_terminator = TRUE; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/plain"); break; case CF_UNICODETEXT: mime_type = clipboard_rdp->which_unicode_format; needs_null_terminator = TRUE; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/plain"); break; case GRD_CF_HTML: mime_type = GRD_MIME_TYPE_TEXT_HTML; needs_null_terminator = TRUE; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, GRD_CFSTR_MIME_TEXT_HTML); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, GRD_CFSTR_MIME_HTML); break; case CF_DIB: mime_type = GRD_MIME_TYPE_IMAGE_BMP; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "image/bmp"); break; case CF_TIFF: mime_type = GRD_MIME_TYPE_IMAGE_TIFF; break; case GRD_CF_GIF: mime_type = GRD_MIME_TYPE_IMAGE_GIF; break; case GRD_CF_JPEG: mime_type = GRD_MIME_TYPE_IMAGE_JPEG; break; case GRD_CF_PNG: mime_type = GRD_MIME_TYPE_IMAGE_PNG; break; case GRD_CF_TEXT_URILIST: mime_type = GRD_MIME_TYPE_TEXT_URILIST; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/uri-list"); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "FileGroupDescriptorW"); break; } if (clipboard_rdp->server_format_data_request_id) { g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format data " "request without being able to push response for last request " "yet."); } g_mutex_lock (&clipboard_rdp->completion_mutex); while (!clipboard_rdp->protocol_stopped && !clipboard_rdp->completed_format_data_request) { g_cond_wait (&clipboard_rdp->completion_cond, &clipboard_rdp->completion_mutex); } g_mutex_unlock (&clipboard_rdp->completion_mutex); if (clipboard_rdp->protocol_stopped) return CHANNEL_RC_OK; if (mime_type != GRD_MIME_TYPE_NONE) { g_assert (clipboard_rdp->completed_format_data_request); clipboard_rdp->completed_format_data_request = FALSE; request_context = g_malloc0 (sizeof (ServerFormatDataRequestContext)); request_context->clipboard_rdp = clipboard_rdp; request_context->mime_type = mime_type; request_context->src_format_id = src_format_id; request_context->dst_format_id = dst_format_id; request_context->needs_null_terminator = needs_null_terminator; request_context->needs_conversion = needs_conversion; g_assert (!clipboard_rdp->format_data_request_context); clipboard_rdp->format_data_request_context = request_context; g_mutex_lock (&clipboard_rdp->server_format_data_request_mutex); clipboard_rdp->server_format_data_request_id = g_idle_add (request_server_format_data, clipboard_rdp); g_mutex_unlock (&clipboard_rdp->server_format_data_request_mutex); return CHANNEL_RC_OK; } format_data_response.common.msgType = CB_FORMAT_DATA_RESPONSE; format_data_response.common.msgFlags = CB_RESPONSE_FAIL; format_data_response.common.dataLen = 0; format_data_response.requestedFormatData = NULL; return cliprdr_context->ServerFormatDataResponse (cliprdr_context, &format_data_response); } static gboolean extract_format_data_response (GrdClipboardRdp *clipboard_rdp, CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response, uint8_t **data, uint32_t *size) { gboolean response_ok; *data = (uint8_t *) format_data_response->requestedFormatData; *size = format_data_response->common.dataLen; response_ok = format_data_response->common.msgFlags & CB_RESPONSE_OK; *size = response_ok && *data ? *size : 0; if (!response_ok) g_clear_pointer (data, g_free); return response_ok && *data; } static gboolean filedescriptorw_filename_is_valid (const WCHAR *filename) { uint16_t i; if (filename[0] == L'\0') return FALSE; for (i = 1; i < 260; ++i) { if (filename[i] == L'\0') return TRUE; } return FALSE; } static uint8_t * get_uri_list_from_packet_file_list (GrdClipboardRdp *clipboard_rdp, uint8_t *src_data, uint32_t src_size, uint32_t *dst_size, gboolean has_clip_data_id, uint32_t clip_data_id) { g_autofree FILEDESCRIPTORW *files = NULL; FILEDESCRIPTORW *file; uint32_t n_files = 0; g_autofree char *clip_data_dir_name = NULL; char *filename = NULL; char *escaped_name; char *file_uri; g_autoptr (GArray) dst_data = NULL; uint32_t i; clip_data_dir_name = has_clip_data_id ? g_strdup_printf ("%u", clip_data_id) : g_strdup_printf ("%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID); *dst_size = 0; dst_data = g_array_new (TRUE, TRUE, sizeof (char)); if (cliprdr_parse_file_list (src_data, src_size, &files, &n_files)) return NULL; for (i = 0; i < n_files; ++i) { file = &files[i]; if (!filedescriptorw_filename_is_valid (file->cFileName)) return NULL; filename = ConvertWCharToUtf8Alloc (file->cFileName, NULL); if (!filename) return NULL; if (strchr (filename, '\\') || strchr (filename, '/')) { g_free (filename); continue; } escaped_name = g_uri_escape_string (filename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); file_uri = g_strdup_printf ("file://%s/%s/%s\r\n", clipboard_rdp->fuse_mount_path, clip_data_dir_name, escaped_name); g_array_append_vals (dst_data, file_uri, strlen (file_uri)); g_free (file_uri); g_free (escaped_name); g_free (filename); } *dst_size = dst_data->len; return (uint8_t *) g_array_free (g_steal_pointer (&dst_data), FALSE); } static uint8_t * convert_client_content_for_server (GrdClipboardRdp *clipboard_rdp, uint8_t *src_data, uint32_t src_size, GrdMimeType mime_type, uint32_t src_format_id, uint32_t *dst_size, gboolean has_clip_data_id, uint32_t clip_data_id) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; uint32_t dst_format_id; uint8_t *dst_data; gboolean is_null_terminated = FALSE; BOOL success; *dst_size = 0; switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: case GRD_MIME_TYPE_TEXT_UTF8_STRING: is_null_terminated = TRUE; dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/plain"); break; case GRD_MIME_TYPE_TEXT_HTML: is_null_terminated = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, GRD_CFSTR_MIME_HTML); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, GRD_CFSTR_MIME_TEXT_HTML); break; case GRD_MIME_TYPE_IMAGE_BMP: dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "image/bmp"); break; case GRD_MIME_TYPE_TEXT_URILIST: case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES: src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "FileGroupDescriptorW"); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/uri-list"); break; default: g_assert_not_reached (); } success = ClipboardSetData (clipboard_rdp->system, src_format_id, src_data, src_size); if (!success) { g_warning ("[RDP.CLIPRDR] Converting clipboard content failed: " "Failed to set data to convert"); return NULL; } if (mime_type == GRD_MIME_TYPE_TEXT_URILIST || mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) { dst_data = get_uri_list_from_packet_file_list (clipboard_rdp, src_data, src_size, dst_size, has_clip_data_id, clip_data_id); } else { dst_data = ClipboardGetData (clipboard_rdp->system, dst_format_id, dst_size); } if (!dst_data) { g_warning ("[RDP.CLIPRDR] Converting clipboard content failed: " "Failed to get converted data"); return NULL; } if (is_null_terminated) { uint8_t *null_terminator_pos = memchr (dst_data, '\0', *dst_size); if (null_terminator_pos) *dst_size = null_terminator_pos - dst_data; } if (mime_type == GRD_MIME_TYPE_TEXT_URILIST || mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) { FILEDESCRIPTORW *files = NULL; FILEDESCRIPTORW *file; uint32_t n_files = 0; gboolean result; g_autofree char *clip_data_dir_name = NULL; char *filename = NULL; char *escaped_name; char *full_filepath; GrdMimeType second_mime_type; FormatData *format_data; GArray *data_nautilus; const char *nautilus_header; uint8_t *dst_data_nautilus; uint32_t dst_size_nautilus; uint32_t i; if (cliprdr_parse_file_list (src_data, src_size, &files, &n_files)) { g_free (dst_data); return NULL; } if (has_clip_data_id) { result = grd_rdp_fuse_clipboard_set_cdi_selection (rdp_fuse_clipboard, files, n_files, clip_data_id); } else { result = grd_rdp_fuse_clipboard_set_no_cdi_selection (rdp_fuse_clipboard, files, n_files); } if (!result) { g_free (files); g_free (dst_data); return NULL; } clip_data_dir_name = has_clip_data_id ? g_strdup_printf ("%u", clip_data_id) : g_strdup_printf ("%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID); data_nautilus = g_array_new (TRUE, TRUE, sizeof (char)); nautilus_header = "copy"; g_array_append_vals (data_nautilus, nautilus_header, strlen (nautilus_header)); for (i = 0; i < n_files; ++i) { file = &files[i]; filename = ConvertWCharToUtf8Alloc (file->cFileName, NULL); if (!filename) { g_array_free (data_nautilus, TRUE); grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard); g_free (files); g_free (dst_data); return NULL; } if (strchr (filename, '\\') || strchr (filename, '/')) { g_free (filename); continue; } escaped_name = g_uri_escape_string (filename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); full_filepath = g_strdup_printf ("\nfile://%s/%s/%s", clipboard_rdp->fuse_mount_path, clip_data_dir_name, escaped_name); g_array_append_vals (data_nautilus, full_filepath, strlen (full_filepath)); g_free (full_filepath); g_free (escaped_name); g_free (filename); } dst_size_nautilus = data_nautilus->len; dst_data_nautilus = (uint8_t *) g_array_free (data_nautilus, FALSE); format_data = g_malloc0 (sizeof (FormatData)); if (mime_type == GRD_MIME_TYPE_TEXT_URILIST) { /** * Also create the format data for the * "x-special/gnome-copied-files" mimetype */ second_mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES; format_data->size = dst_size_nautilus; format_data->data = dst_data_nautilus; } else if (mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) { /** * Also create the format data for the "text/uri-list" mimetype */ second_mime_type = GRD_MIME_TYPE_TEXT_URILIST; format_data->size = *dst_size; format_data->data = dst_data; *dst_size = dst_size_nautilus; dst_data = dst_data_nautilus; } else { g_assert_not_reached (); } g_hash_table_insert (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (second_mime_type), format_data); g_free (files); } if (!dst_data) g_warning ("[RDP.CLIPRDR] Converting clipboard content failed"); return dst_data; } static gboolean prepare_client_content_for_server (GrdClipboardRdp *clipboard_rdp, uint8_t **src_data, uint32_t src_size, GrdMimeTypeTable *mime_type_table, gboolean has_clip_data_id, uint32_t clip_data_id) { GrdMimeType mime_type = mime_type_table->mime_type; uint32_t src_format_id = mime_type_table->rdp.format_id; FormatData *format_data; uint8_t *dst_data = NULL; uint32_t dst_size = 0; switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: case GRD_MIME_TYPE_TEXT_UTF8_STRING: case GRD_MIME_TYPE_TEXT_HTML: case GRD_MIME_TYPE_IMAGE_BMP: case GRD_MIME_TYPE_TEXT_URILIST: case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES: dst_data = convert_client_content_for_server (clipboard_rdp, *src_data, src_size, mime_type, src_format_id, &dst_size, has_clip_data_id, clip_data_id); break; default: dst_data = g_steal_pointer (src_data); dst_size = src_size; } if (!dst_data) return FALSE; format_data = g_malloc0 (sizeof (FormatData)); format_data->size = dst_size; format_data->data = dst_data; g_hash_table_insert (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type), format_data); return TRUE; } static void serve_mime_type_content (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type) { GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); FormatData *format_data; GList *serials; unsigned int serial; GList *l; if (!g_hash_table_steal_extended (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &serials)) return; if (!g_hash_table_lookup_extended (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &format_data)) g_assert_not_reached (); for (l = serials; l; l = l->next) { serial = GPOINTER_TO_UINT (l->data); grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, format_data->data, format_data->size); } g_list_free (serials); } static gboolean handle_format_data_response (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClientFormatDataRequestContext *request_context; CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response; GrdMimeTypeTable *mime_type_table; GrdMimeType mime_type; uint8_t *src_data; uint32_t src_size; g_mutex_lock (&clipboard_rdp->client_request_mutex); request_context = g_steal_pointer (&clipboard_rdp->current_client_request); format_data_response = g_steal_pointer (&clipboard_rdp->format_data_response); clipboard_rdp->client_format_data_response_id = 0; g_mutex_unlock (&clipboard_rdp->client_request_mutex); mime_type_table = &request_context->mime_type_table; mime_type = mime_type_table->mime_type; if (extract_format_data_response (clipboard_rdp, format_data_response, &src_data, &src_size) && prepare_client_content_for_server (clipboard_rdp, &src_data, src_size, mime_type_table, request_context->has_clip_data_id, request_context->clip_data_id)) { serve_mime_type_content (clipboard_rdp, mime_type); if (mime_type == GRD_MIME_TYPE_TEXT_URILIST) serve_mime_type_content (clipboard_rdp, GRD_MIME_TYPE_XS_GNOME_COPIED_FILES); else if (mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) serve_mime_type_content (clipboard_rdp, GRD_MIME_TYPE_TEXT_URILIST); } else { abort_client_requests_for_context (clipboard_rdp, request_context); } g_free (src_data); g_free (format_data_response); g_free (request_context); g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove); maybe_send_next_mime_type_content_request (clipboard_rdp); return G_SOURCE_REMOVE; } /** * Clients response to our data request that we sent */ static uint32_t cliprdr_client_format_data_response (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; g_mutex_lock (&clipboard_rdp->client_request_mutex); if (!clipboard_rdp->current_client_request) { g_mutex_unlock (&clipboard_rdp->client_request_mutex); return CHANNEL_RC_OK; } if (clipboard_rdp->format_data_response) { g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData); g_clear_pointer (&clipboard_rdp->format_data_response, g_free); } clipboard_rdp->format_data_response = g_memdup2 (format_data_response, sizeof (CLIPRDR_FORMAT_DATA_RESPONSE)); clipboard_rdp->format_data_response->requestedFormatData = g_memdup2 (format_data_response->requestedFormatData, format_data_response->common.dataLen); if (!clipboard_rdp->client_format_data_response_id) { clipboard_rdp->client_format_data_response_id = g_idle_add (handle_format_data_response, clipboard_rdp); } g_mutex_unlock (&clipboard_rdp->client_request_mutex); return CHANNEL_RC_OK; } static uint32_t delegate_request_file_contents_size (wClipboardDelegate *delegate, const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request) { wClipboardFileSizeRequest file_size_request = {0}; file_size_request.streamId = file_contents_request->streamId; file_size_request.listIndex = file_contents_request->listIndex; return delegate->ClientRequestFileSize (delegate, &file_size_request); } static uint32_t delegate_request_file_contents_range (wClipboardDelegate *delegate, const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request) { wClipboardFileRangeRequest file_range_request = {0}; file_range_request.streamId = file_contents_request->streamId; file_range_request.listIndex = file_contents_request->listIndex; file_range_request.nPositionLow = file_contents_request->nPositionLow; file_range_request.nPositionHigh = file_contents_request->nPositionHigh; file_range_request.cbRequested = file_contents_request->cbRequested; return delegate->ClientRequestFileRange (delegate, &file_range_request); } static uint32_t send_file_contents_response_failure (CliprdrServerContext *cliprdr_context, uint32_t stream_id) { CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0}; file_contents_response.common.msgType = CB_FILECONTENTS_RESPONSE; file_contents_response.common.msgFlags = CB_RESPONSE_FAIL; file_contents_response.streamId = stream_id; return cliprdr_context->ServerFileContentsResponse (cliprdr_context, &file_contents_response); } /** * Client requests us to send either the size of the remote file or a portion * of the data in the file */ static uint32_t cliprdr_client_file_contents_request (CliprdrServerContext *cliprdr_context, const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; wClipboardDelegate *delegate = clipboard_rdp->delegate; gboolean has_file_list = clipboard_rdp->has_file_list; gboolean requests_allowed = clipboard_rdp->server_file_contents_requests_allowed; uint32_t clip_data_id = file_contents_request->clipDataId; uint32_t stream_id = file_contents_request->streamId; ClipDataEntry *entry = NULL; uint32_t error = NO_ERROR; if (file_contents_request->haveClipDataId) g_debug ("[RDP.CLIPRDR] FileContentsRequest has clipDataId %u", clip_data_id); else g_debug ("[RDP.CLIPRDR] FileContentsRequest does not have a clipDataId"); if (file_contents_request->haveClipDataId) { if (!g_hash_table_lookup_extended (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) return send_file_contents_response_failure (cliprdr_context, stream_id); delegate = entry->delegate; has_file_list = entry->has_file_list; requests_allowed = entry->requests_allowed; if (!requests_allowed) { g_debug ("[RDP.CLIPRDR] ClipDataEntry with id %u is not eligible of " "requesting file contents.", clip_data_id); } } if (!requests_allowed || !has_file_list) return send_file_contents_response_failure (cliprdr_context, stream_id); if (file_contents_request->dwFlags & FILECONTENTS_SIZE && file_contents_request->dwFlags & FILECONTENTS_RANGE) { /** * FILECONTENTS_SIZE and FILECONTENTS_RANGE are not allowed * to be set at the same time */ return send_file_contents_response_failure (cliprdr_context, stream_id); } if (file_contents_request->dwFlags & FILECONTENTS_SIZE) { error = delegate_request_file_contents_size (delegate, file_contents_request); } else if (file_contents_request->dwFlags & FILECONTENTS_RANGE) { error = delegate_request_file_contents_range (delegate, file_contents_request); } else { error = ERROR_INVALID_DATA; } if (error) return send_file_contents_response_failure (cliprdr_context, stream_id); return CHANNEL_RC_OK; } /** * Clients response to our file contents request that we sent */ static uint32_t cliprdr_client_file_contents_response (CliprdrServerContext *cliprdr_context, const CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; grd_rdp_fuse_clipboard_submit_file_contents_response ( rdp_fuse_clipboard, file_contents_response->streamId, file_contents_response->common.msgFlags & CB_RESPONSE_OK, file_contents_response->requestedData, file_contents_response->cbRequested); return CHANNEL_RC_OK; } static uint32_t cliprdr_file_size_success (wClipboardDelegate *delegate, const wClipboardFileSizeRequest *file_size_request, uint64_t file_size) { GrdClipboardRdp *clipboard_rdp = delegate->custom; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0}; file_contents_response.common.msgType = CB_FILECONTENTS_RESPONSE; file_contents_response.common.msgFlags = CB_RESPONSE_OK; file_contents_response.streamId = file_size_request->streamId; file_contents_response.cbRequested = sizeof (uint64_t); file_contents_response.requestedData = (uint8_t *) &file_size; return cliprdr_context->ServerFileContentsResponse (cliprdr_context, &file_contents_response); } static uint32_t cliprdr_file_size_failure (wClipboardDelegate *delegate, const wClipboardFileSizeRequest *file_size_request, uint32_t error) { GrdClipboardRdp *clipboard_rdp = delegate->custom; return send_file_contents_response_failure (clipboard_rdp->cliprdr_context, file_size_request->streamId); } static uint32_t cliprdr_file_range_success (wClipboardDelegate *delegate, const wClipboardFileRangeRequest *file_range_request, const uint8_t *data, uint32_t size) { GrdClipboardRdp *clipboard_rdp = delegate->custom; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0}; file_contents_response.common.msgType = CB_FILECONTENTS_RESPONSE; file_contents_response.common.msgFlags = CB_RESPONSE_OK; file_contents_response.streamId = file_range_request->streamId; file_contents_response.cbRequested = size; file_contents_response.requestedData = data; return cliprdr_context->ServerFileContentsResponse (cliprdr_context, &file_contents_response); } static uint32_t cliprdr_file_range_failure (wClipboardDelegate *delegate, const wClipboardFileRangeRequest *file_range_request, uint32_t error) { GrdClipboardRdp *clipboard_rdp = delegate->custom; return send_file_contents_response_failure (clipboard_rdp->cliprdr_context, file_range_request->streamId); } static BOOL is_valid_unix_filename (LPCWSTR filename) { LPCWSTR c; if (!filename) return FALSE; if (filename[0] == L'\0') return FALSE; /* Reserved characters */ for (c = filename; *c; ++c) { if (*c == L'/') return FALSE; } return TRUE; } static void create_new_winpr_clipboard (GrdClipboardRdp *clipboard_rdp) { g_debug ("[RDP.CLIPRDR] Creating new WinPR clipboard"); clipboard_rdp->system = ClipboardCreate (); clipboard_rdp->delegate = ClipboardGetDelegate (clipboard_rdp->system); clipboard_rdp->delegate->ClipboardFileSizeSuccess = cliprdr_file_size_success; clipboard_rdp->delegate->ClipboardFileSizeFailure = cliprdr_file_size_failure; clipboard_rdp->delegate->ClipboardFileRangeSuccess = cliprdr_file_range_success; clipboard_rdp->delegate->ClipboardFileRangeFailure = cliprdr_file_range_failure; clipboard_rdp->delegate->basePath = NULL; clipboard_rdp->delegate->custom = clipboard_rdp; if (clipboard_rdp->relieve_filename_restriction) clipboard_rdp->delegate->IsFileNameComponentValid = is_valid_unix_filename; clipboard_rdp->has_file_list = FALSE; } GrdClipboardRdp * grd_clipboard_rdp_new (GrdSessionRdp *session_rdp, HANDLE vcm, gboolean relieve_filename_restriction) { g_autoptr (GrdClipboardRdp) clipboard_rdp = NULL; GrdClipboard *clipboard; CliprdrServerContext *cliprdr_context; clipboard_rdp = g_object_new (GRD_TYPE_CLIPBOARD_RDP, NULL); cliprdr_context = cliprdr_server_context_new (vcm); if (!cliprdr_context) { g_warning ("[RDP.CLIPRDR] Failed to create server context"); return NULL; } clipboard_rdp->cliprdr_context = cliprdr_context; clipboard_rdp->relieve_filename_restriction = relieve_filename_restriction; clipboard = GRD_CLIPBOARD (clipboard_rdp); grd_clipboard_initialize (clipboard, GRD_SESSION (session_rdp)); cliprdr_context->useLongFormatNames = TRUE; cliprdr_context->streamFileClipEnabled = TRUE; cliprdr_context->fileClipNoFilePaths = TRUE; cliprdr_context->canLockClipData = TRUE; cliprdr_context->hasHugeFileSupport = TRUE; cliprdr_context->ClientCapabilities = cliprdr_client_capabilities; cliprdr_context->TempDirectory = cliprdr_temp_directory; cliprdr_context->ClientFormatList = cliprdr_client_format_list; cliprdr_context->ClientFormatListResponse = cliprdr_client_format_list_response; cliprdr_context->ClientLockClipboardData = cliprdr_client_lock_clipboard_data; cliprdr_context->ClientUnlockClipboardData = cliprdr_client_unlock_clipboard_data; cliprdr_context->ClientFormatDataRequest = cliprdr_client_format_data_request; cliprdr_context->ClientFormatDataResponse = cliprdr_client_format_data_response; cliprdr_context->ClientFileContentsRequest = cliprdr_client_file_contents_request; cliprdr_context->ClientFileContentsResponse = cliprdr_client_file_contents_response; cliprdr_context->custom = clipboard_rdp; if (relieve_filename_restriction) { g_message ("[RDP.CLIPRDR] Relieving CLIPRDR filename restriction"); clipboard_rdp->delegate->IsFileNameComponentValid = is_valid_unix_filename; } if (cliprdr_context->Start (cliprdr_context)) { g_warning ("[RDP.CLIPRDR] Failed to open CLIPRDR channel"); g_clear_pointer (&clipboard_rdp->cliprdr_context, cliprdr_server_context_free); return NULL; } return g_steal_pointer (&clipboard_rdp); } static gboolean clear_format_data (gpointer key, gpointer value, gpointer user_data) { FormatData *format_data = value; g_free (format_data->data); g_free (format_data); return TRUE; } static gboolean clear_client_requests (gpointer key, gpointer value, gpointer user_data) { GList *serials = value; g_list_free (serials); return TRUE; } static void grd_clipboard_rdp_dispose (GObject *object) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (object); GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); g_mutex_lock (&clipboard_rdp->completion_mutex); clipboard_rdp->protocol_stopped = TRUE; g_cond_signal (&clipboard_rdp->completion_cond); g_mutex_unlock (&clipboard_rdp->completion_mutex); if (clipboard_rdp->cliprdr_context) { clipboard_rdp->cliprdr_context->Stop (clipboard_rdp->cliprdr_context); grd_clipboard_disable_clipboard (clipboard); } g_clear_pointer (&clipboard_rdp->current_client_request, g_free); if (clipboard_rdp->format_data_response) { g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData); g_clear_pointer (&clipboard_rdp->format_data_response, g_free); } if (clipboard_rdp->clipboard_retrieval_id) g_clear_pointer (&clipboard_rdp->clipboard_retrieval_context.entry, g_free); g_clear_pointer (&clipboard_rdp->format_data_request_context, g_free); g_clear_pointer (&clipboard_rdp->queued_server_formats, g_list_free); g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free); if (clipboard_rdp->ordered_client_requests) { g_queue_free_full (clipboard_rdp->ordered_client_requests, g_free); clipboard_rdp->ordered_client_requests = NULL; } g_hash_table_foreach_remove (clipboard_rdp->pending_client_requests, clear_client_requests, NULL); g_hash_table_foreach_remove (clipboard_rdp->format_data_cache, clear_format_data, NULL); g_clear_object (&clipboard_rdp->rdp_fuse_clipboard); rmdir (clipboard_rdp->fuse_mount_path); g_clear_pointer (&clipboard_rdp->fuse_mount_path, g_free); g_assert (g_hash_table_size (clipboard_rdp->pending_client_requests) == 0); g_assert (g_hash_table_size (clipboard_rdp->format_data_cache) == 0); g_clear_pointer (&clipboard_rdp->pending_client_requests, g_hash_table_unref); g_clear_pointer (&clipboard_rdp->format_data_cache, g_hash_table_unref); g_clear_pointer (&clipboard_rdp->clip_data_table, g_hash_table_destroy); g_clear_pointer (&clipboard_rdp->serial_entry_table, g_hash_table_destroy); g_clear_pointer (&clipboard_rdp->allowed_server_formats, g_hash_table_destroy); g_clear_pointer (&clipboard_rdp->system, ClipboardDestroy); g_clear_pointer (&clipboard_rdp->cliprdr_context, cliprdr_server_context_free); g_clear_handle_id (&clipboard_rdp->pending_server_formats_drop_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->clipboard_retrieval_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->clipboard_destruction_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->server_format_list_update_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->server_format_data_request_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->client_format_list_response_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->client_format_data_response_id, g_source_remove); G_OBJECT_CLASS (grd_clipboard_rdp_parent_class)->dispose (object); } static void grd_clipboard_rdp_finalize (GObject *object) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (object); g_cond_clear (&clipboard_rdp->completion_cond); g_mutex_clear (&clipboard_rdp->completion_mutex); g_mutex_clear (&clipboard_rdp->server_format_data_request_mutex); g_mutex_clear (&clipboard_rdp->client_format_list_response_mutex); g_mutex_clear (&clipboard_rdp->server_format_list_update_mutex); g_mutex_clear (&clipboard_rdp->clip_data_entry_mutex); g_mutex_clear (&clipboard_rdp->client_request_mutex); G_OBJECT_CLASS (grd_clipboard_rdp_parent_class)->finalize (object); } static void clip_data_entry_free (gpointer data) { ClipDataEntry *entry = data; g_debug ("[RDP.CLIPRDR] Freeing ClipDataEntry with id %u and serial %lu. " "ClipDataEntry is independent: %s", entry->id, entry->serial, entry->is_independent ? "true" : "false"); if (entry->is_independent) g_clear_pointer (&entry->system, ClipboardDestroy); g_free (entry); } static void grd_clipboard_rdp_init (GrdClipboardRdp *clipboard_rdp) { const char *grd_path = "/gnome-remote-desktop"; const char *cliprdr_template = "/cliprdr-XXXXXX"; g_autofree char *base_path = NULL; g_autofree char *template_path = NULL; base_path = g_strdup_printf ("%s%s", g_get_user_runtime_dir (), grd_path); template_path = g_strdup_printf ("%s%s", base_path, cliprdr_template); if (g_access (base_path, F_OK)) { if (mkdir (base_path, 0700)) { g_error ("Failed to create base runtime directory for " "gnome-remote-desktop: %s", g_strerror (errno)); } } if (!mkdtemp (template_path)) { g_error ("Failed to create clipboard file directory %s: %s", template_path, g_strerror (errno)); } clipboard_rdp->system = ClipboardCreate (); clipboard_rdp->delegate = ClipboardGetDelegate (clipboard_rdp->system); clipboard_rdp->delegate->ClipboardFileSizeSuccess = cliprdr_file_size_success; clipboard_rdp->delegate->ClipboardFileSizeFailure = cliprdr_file_size_failure; clipboard_rdp->delegate->ClipboardFileRangeSuccess = cliprdr_file_range_success; clipboard_rdp->delegate->ClipboardFileRangeFailure = cliprdr_file_range_failure; clipboard_rdp->delegate->basePath = NULL; clipboard_rdp->delegate->custom = clipboard_rdp; clipboard_rdp->completed_clip_data_entry = TRUE; clipboard_rdp->completed_format_list = TRUE; clipboard_rdp->completed_format_data_request = TRUE; clipboard_rdp->allowed_server_formats = g_hash_table_new (NULL, NULL); clipboard_rdp->serial_entry_table = g_hash_table_new_full (NULL, NULL, NULL, clip_data_entry_free); clipboard_rdp->clip_data_table = g_hash_table_new (NULL, NULL); clipboard_rdp->format_data_cache = g_hash_table_new (NULL, NULL); clipboard_rdp->pending_client_requests = g_hash_table_new (NULL, NULL); clipboard_rdp->ordered_client_requests = g_queue_new (); g_mutex_init (&clipboard_rdp->client_request_mutex); g_mutex_init (&clipboard_rdp->clip_data_entry_mutex); g_mutex_init (&clipboard_rdp->server_format_list_update_mutex); g_mutex_init (&clipboard_rdp->client_format_list_response_mutex); g_mutex_init (&clipboard_rdp->server_format_data_request_mutex); g_mutex_init (&clipboard_rdp->completion_mutex); g_cond_init (&clipboard_rdp->completion_cond); clipboard_rdp->fuse_mount_path = g_steal_pointer (&template_path); clipboard_rdp->rdp_fuse_clipboard = grd_rdp_fuse_clipboard_new (clipboard_rdp, clipboard_rdp->fuse_mount_path); } static void grd_clipboard_rdp_class_init (GrdClipboardRdpClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GrdClipboardClass *clipboard_class = GRD_CLIPBOARD_CLASS (klass); object_class->dispose = grd_clipboard_rdp_dispose; object_class->finalize = grd_clipboard_rdp_finalize; clipboard_class->update_client_mime_type_list = grd_clipboard_rdp_update_client_mime_type_list; clipboard_class->request_client_content_for_mime_type = grd_clipboard_rdp_request_client_content_for_mime_type; clipboard_class->submit_requested_server_content = grd_clipboard_rdp_submit_requested_server_content; }