From 06bb2f70483fb0a3bf05fc0747d6ef6c45598d0f Mon Sep 17 00:00:00 2001 From: Lukas Raska Date: Fri, 19 Jul 2024 14:11:26 +0200 Subject: [PATCH 1/2] GUACAMOLE-1969: Implement recording-include-clipboard connection parameter --- src/libguac/guacamole/recording.h | 65 ++++++++++++++++++++++++++- src/libguac/recording.c | 20 ++++++++- src/protocols/kubernetes/clipboard.c | 16 +++++++ src/protocols/kubernetes/kubernetes.c | 3 +- src/protocols/kubernetes/settings.c | 16 +++++++ src/protocols/kubernetes/settings.h | 10 +++++ src/protocols/rdp/channels/cliprdr.c | 13 ++++++ src/protocols/rdp/rdp.c | 3 +- src/protocols/rdp/settings.c | 16 +++++++ src/protocols/rdp/settings.h | 10 +++++ src/protocols/ssh/clipboard.c | 19 +++++++- src/protocols/ssh/settings.c | 16 +++++++ src/protocols/ssh/settings.h | 10 +++++ src/protocols/ssh/ssh.c | 3 +- src/protocols/telnet/clipboard.c | 18 +++++++- src/protocols/telnet/settings.c | 16 +++++++ src/protocols/telnet/settings.h | 10 +++++ src/protocols/telnet/telnet.c | 3 +- src/protocols/vnc/clipboard.c | 14 ++++++ src/protocols/vnc/settings.c | 16 +++++++ src/protocols/vnc/settings.h | 10 +++++ src/protocols/vnc/vnc.c | 3 +- 22 files changed, 300 insertions(+), 10 deletions(-) diff --git a/src/libguac/guacamole/recording.h b/src/libguac/guacamole/recording.h index e44bc8468d..beaa4928b4 100644 --- a/src/libguac/guacamole/recording.h +++ b/src/libguac/guacamole/recording.h @@ -95,6 +95,15 @@ typedef struct guac_recording { */ int include_keys; + /** + * Non-zero if clipboard paste data should be included in the session + * recording, zero otherwise. Including clipboard data within the recording may + * be necessary in certain auditing contexts, but should only be done with + * caution. Clipboard can easily contain sensitive information, such as + * passwords, credit card numbers, etc. + */ + int include_clipboard; + } guac_recording; /** @@ -152,6 +161,13 @@ typedef struct guac_recording { * Non-zero if writing to an existing file should be allowed, or zero * otherwise. * + * @param include_clipboard + * Non-zero if clipboard paste data should be included in the session + * recording, zero otherwise. Including clipboard data within the recording may + * be necessary in certain auditing contexts, but should only be done with + * caution. Clipboard can easily contain sensitive information, such as + * passwords, credit card numbers, etc. + * * @return * A new guac_recording structure representing the in-progress * recording if the recording file has been successfully created and a @@ -160,7 +176,7 @@ typedef struct guac_recording { guac_recording* guac_recording_create(guac_client* client, const char* path, const char* name, int create_path, int include_output, int include_mouse, int include_touch, - int include_keys, int allow_write_existing); + int include_keys, int allow_write_existing, int include_clipboard); /** * Frees the resources associated with the given in-progress recording. Note @@ -256,5 +272,52 @@ void guac_recording_report_touch(guac_recording* recording, void guac_recording_report_key(guac_recording* recording, int keysym, int pressed); +/** + * Reports a clipboard paste instruction within the recording. + * The full structure consists of clipboard instruction, one or more + * blob instructions and end instruction. + * + * @param recording + * The guac_recording associated with the clipboard instruction. + * + * @param stream + * The guac_stream allocated for the clipboard paste instruction. + * + * @param mimetype + * The clipboard data mimetype + */ +void guac_recording_report_clipboard(guac_recording* recording, + guac_stream* stream, char* mimetype); + +/** + * Report a clipboard paste blob within the recording. + * + * @param recording + * The guac_recording associated with the clipboard instruction. + * + * @param stream + * The guac_stream associated with the clipboard instruction. + * + * @param data + * The clipboard blob data. + * + * @param length + * Length of the blob data. + */ +void guac_recording_report_clipboard_blob(guac_recording* recording, + guac_stream* stream, void* data, int length); + +/** + * Report a clipboard paste end instruction within the recording. + * + * @param recording + * The guac_recording associated with the clipboard instruction. + * + * @param stream + * The guac_stream associated with the clipboard instruction. + */ +void guac_recording_report_clipboard_end(guac_recording* recording, + guac_stream* stream); + #endif diff --git a/src/libguac/recording.c b/src/libguac/recording.c index 2cfcdf39dd..594a7352d8 100644 --- a/src/libguac/recording.c +++ b/src/libguac/recording.c @@ -42,7 +42,7 @@ guac_recording* guac_recording_create(guac_client* client, const char* path, const char* name, int create_path, int include_output, int include_mouse, int include_touch, - int include_keys, int allow_write_existing) { + int include_keys, int allow_write_existing, int include_clipboard) { char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; @@ -76,6 +76,7 @@ guac_recording* guac_recording_create(guac_client* client, recording->include_mouse = include_mouse; recording->include_touch = include_touch; recording->include_keys = include_keys; + recording->include_clipboard = include_clipboard; /* Replace client socket with wrapped recording socket only if including * output within the recording */ @@ -133,3 +134,20 @@ void guac_recording_report_key(guac_recording* recording, } +void guac_recording_report_clipboard(guac_recording* recording, guac_stream* stream, char* mimetype) { + /* Report clipboard only if recording should contain it */ + if (recording->include_clipboard) + guac_protocol_send_clipboard(recording->socket, stream, mimetype); +} + +void guac_recording_report_clipboard_blob(guac_recording* recording, guac_stream* stream, void* data, int length) { + /* Report clipboard only if recording should contain it */ + if (recording->include_clipboard) + guac_protocol_send_blob(recording->socket, stream, data, length); +} + +void guac_recording_report_clipboard_end(guac_recording* recording, guac_stream* stream) { + /* Report clipboard only if recording should contain it */ + if (recording->include_clipboard) + guac_protocol_send_end(recording->socket, stream); +} diff --git a/src/protocols/kubernetes/clipboard.c b/src/protocols/kubernetes/clipboard.c index f031f11e28..a11109b1ae 100644 --- a/src/protocols/kubernetes/clipboard.c +++ b/src/protocols/kubernetes/clipboard.c @@ -39,6 +39,10 @@ int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream, stream->blob_handler = guac_kubernetes_clipboard_blob_handler; stream->end_handler = guac_kubernetes_clipboard_end_handler; + /* Report clipboard within recording */ + if (kubernetes_client->recording != NULL) + guac_recording_report_clipboard(kubernetes_client->recording, stream, mimetype); + return 0; } @@ -49,6 +53,10 @@ int guac_kubernetes_clipboard_blob_handler(guac_user* user, guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; + /* Report clipboard blob within recording */ + if (kubernetes_client->recording != NULL) + guac_recording_report_clipboard_blob(kubernetes_client->recording, stream, data, length); + /* Append new data */ guac_terminal_clipboard_append(kubernetes_client->term, data, length); @@ -58,6 +66,14 @@ int guac_kubernetes_clipboard_blob_handler(guac_user* user, int guac_kubernetes_clipboard_end_handler(guac_user* user, guac_stream* stream) { + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Report clipboard blob within recording */ + if (kubernetes_client->recording != NULL) + guac_recording_report_clipboard_end(kubernetes_client->recording, stream); + /* Nothing to do - clipboard is implemented within client */ return 0; diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 7eef33b16a..f3d88103ce 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -238,7 +238,8 @@ void* guac_kubernetes_client_thread(void* data) { !settings->recording_exclude_mouse, 0, /* Touch events not supported */ settings->recording_include_keys, - settings->recording_write_existing); + settings->recording_write_existing, + settings->recording_include_clipboard); } /* Create terminal options with required parameters */ diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index a56ef8027f..0533a13e25 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -51,6 +51,7 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { "recording-exclude-output", "recording-exclude-mouse", "recording-include-keys", + "recording-include-clipboard", "create-recording-path", "recording-write-existing", "read-only", @@ -213,6 +214,16 @@ enum KUBERNETES_ARGS_IDX { */ IDX_RECORDING_INCLUDE_KEYS, + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_CLIPBOARD, + /** * Whether the specified screen recording path should automatically be * created if it does not yet exist. @@ -409,6 +420,11 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, IDX_RECORDING_INCLUDE_KEYS, false); + /* Parse clipboard inclusion flag */ + settings->recording_include_clipboard = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_CLIPBOARD, false); + /* Parse path creation flag */ settings->create_recording_path = guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index 2222c0a1d4..65173904cb 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -245,6 +245,16 @@ typedef struct guac_kubernetes_settings { */ bool recording_include_keys; + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_clipboard; + /** * Whether existing files should be appended to when creating a new recording. * Disabled by default. diff --git a/src/protocols/rdp/channels/cliprdr.c b/src/protocols/rdp/channels/cliprdr.c index a93bf8c15c..5593d87d3c 100644 --- a/src/protocols/rdp/channels/cliprdr.c +++ b/src/protocols/rdp/channels/cliprdr.c @@ -695,6 +695,11 @@ int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream, /* Clear any current contents, assigning the mimetype the data which will * be received */ guac_common_clipboard_reset(clipboard->clipboard, mimetype); + + /* Report clipboard within recording */ + if (rdp_client->recording != NULL) + guac_recording_report_clipboard(rdp_client->recording, stream, mimetype); + return 0; } @@ -711,6 +716,10 @@ int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream, if (clipboard == NULL) return 0; + /* Report clipboard blob within recording */ + if (rdp_client->recording != NULL) + guac_recording_report_clipboard_blob(rdp_client->recording, stream, data, length); + /* Append received data to current clipboard contents */ guac_common_clipboard_append(clipboard->clipboard, (char*) data, length); return 0; @@ -728,6 +737,10 @@ int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream) { if (clipboard == NULL) return 0; + /* Report clipboard stream end within recording */ + if (rdp_client->recording != NULL) + guac_recording_report_clipboard_end(rdp_client->recording, stream); + /* Terminate clipboard data with NULL */ guac_common_clipboard_append(clipboard->clipboard, "", 1); diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index aad1763b85..8e9fa305b7 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -930,7 +930,8 @@ void* guac_rdp_client_thread(void* data) { !settings->recording_exclude_mouse, !settings->recording_exclude_touch, settings->recording_include_keys, - settings->recording_write_existing); + settings->recording_write_existing, + settings->recording_include_clipboard); } /* Continue handling connections until error or client disconnect */ diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index 2b2c91cbe1..ea81331f77 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -125,6 +125,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "recording-exclude-mouse", "recording-exclude-touch", "recording-include-keys", + "recording-include-clipboard", "create-recording-path", "recording-write-existing", "resize-method", @@ -581,6 +582,16 @@ enum RDP_ARGS_IDX { */ IDX_RECORDING_INCLUDE_KEYS, + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_CLIPBOARD, + /** * Whether the specified screen recording path should automatically be * created if it does not yet exist. @@ -1205,6 +1216,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_RECORDING_INCLUDE_KEYS, 0); + /* Parse clipboard inclusion flag */ + settings->recording_include_clipboard = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_CLIPBOARD, false); + /* Parse path creation flag */ settings->create_recording_path = guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h index 5f01a1adf3..ce71f84848 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -590,6 +590,16 @@ typedef struct guac_rdp_settings { */ int recording_include_keys; + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_clipboard; + /** * Non-zero if existing files should be appended to when creating a new * recording. Disabled by default. diff --git a/src/protocols/ssh/clipboard.c b/src/protocols/ssh/clipboard.c index 0e2363259f..b9ed966c1b 100644 --- a/src/protocols/ssh/clipboard.c +++ b/src/protocols/ssh/clipboard.c @@ -38,15 +38,23 @@ int guac_ssh_clipboard_handler(guac_user* user, guac_stream* stream, stream->blob_handler = guac_ssh_clipboard_blob_handler; stream->end_handler = guac_ssh_clipboard_end_handler; + /* Report clipboard within recording */ + if (ssh_client->recording != NULL) + guac_recording_report_clipboard(ssh_client->recording, stream, mimetype); + return 0; } int guac_ssh_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { - - /* Append new data */ guac_client* client = user->client; guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + + /* Report clipboard blob within recording */ + if (ssh_client->recording != NULL) + guac_recording_report_clipboard_blob(ssh_client->recording, stream, data, length); + + /* Append new data */ guac_terminal_clipboard_append(ssh_client->term, data, length); return 0; @@ -54,6 +62,13 @@ int guac_ssh_clipboard_blob_handler(guac_user* user, guac_stream* stream, int guac_ssh_clipboard_end_handler(guac_user* user, guac_stream* stream) { + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + + /* Report clipboard stream end within recording */ + if (ssh_client->recording != NULL) + guac_recording_report_clipboard_end(ssh_client->recording, stream); + /* Nothing to do - clipboard is implemented within client */ return 0; diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index a4ea39ba61..69c1b1bb69 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -65,6 +65,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "recording-exclude-output", "recording-exclude-mouse", "recording-include-keys", + "recording-include-clipboard", "create-recording-path", "recording-write-existing", "read-only", @@ -251,6 +252,16 @@ enum SSH_ARGS_IDX { */ IDX_RECORDING_INCLUDE_KEYS, + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_CLIPBOARD, + /** * Whether the specified screen recording path should automatically be * created if it does not yet exist. @@ -527,6 +538,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_RECORDING_INCLUDE_KEYS, false); + /* Parse clipboard inclusion flag */ + settings->recording_include_clipboard = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_CLIPBOARD, false); + /* Parse path creation flag */ settings->create_recording_path = guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index c67c292f56..521ab07282 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -274,6 +274,16 @@ typedef struct guac_ssh_settings { */ bool recording_include_keys; + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_clipboard; + /** * Whether existing files should be appended to when creating a new recording. * Disabled by default. diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 1c5ba76f5e..e978e62242 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -286,7 +286,8 @@ void* ssh_client_thread(void* data) { !settings->recording_exclude_mouse, 0, /* Touch events not supported */ settings->recording_include_keys, - settings->recording_write_existing); + settings->recording_write_existing, + settings->recording_include_clipboard); } /* Create terminal options with required parameters */ diff --git a/src/protocols/telnet/clipboard.c b/src/protocols/telnet/clipboard.c index 08d28b111f..439df79917 100644 --- a/src/protocols/telnet/clipboard.c +++ b/src/protocols/telnet/clipboard.c @@ -38,15 +38,24 @@ int guac_telnet_clipboard_handler(guac_user* user, guac_stream* stream, stream->blob_handler = guac_telnet_clipboard_blob_handler; stream->end_handler = guac_telnet_clipboard_end_handler; + /* Report clipboard within recording */ + if (telnet_client->recording != NULL) + guac_recording_report_clipboard(telnet_client->recording, stream, mimetype); + return 0; } int guac_telnet_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { - /* Append new data */ guac_client* client = user->client; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + + /* Report clipboard blob within recording */ + if (telnet_client->recording != NULL) + guac_recording_report_clipboard_blob(telnet_client->recording, stream, data, length); + + /* Append new data */ guac_terminal_clipboard_append(telnet_client->term, data, length); return 0; @@ -54,6 +63,13 @@ int guac_telnet_clipboard_blob_handler(guac_user* user, guac_stream* stream, int guac_telnet_clipboard_end_handler(guac_user* user, guac_stream* stream) { + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + + /* Report clipboard stream end within recording */ + if (telnet_client->recording != NULL) + guac_recording_report_clipboard_end(telnet_client->recording, stream); + /* Nothing to do - clipboard is implemented within client */ return 0; diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 7a8277927a..0ce89a7dd2 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -56,6 +56,7 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "recording-exclude-output", "recording-exclude-mouse", "recording-include-keys", + "recording-include-clipboard", "create-recording-path", "recording-write-existing", "read-only", @@ -197,6 +198,16 @@ enum TELNET_ARGS_IDX { */ IDX_RECORDING_INCLUDE_KEYS, + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_CLIPBOARD, + /** * Whether the specified screen recording path should automatically be * created if it does not yet exist. @@ -507,6 +518,11 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_RECORDING_INCLUDE_KEYS, false); + /* Parse clipboard inclusion flag */ + settings->recording_include_clipboard = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_CLIPBOARD, false); + /* Parse path creation flag */ settings->create_recording_path = guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 5eb8f2fb80..6fe378c18b 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -250,6 +250,16 @@ typedef struct guac_telnet_settings { */ bool recording_include_keys; + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_clipboard; + /** * Whether existing files should be appended to when creating a new recording. * Disabled by default. diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 37e68e0777..8f6afb1d3a 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -537,7 +537,8 @@ void* guac_telnet_client_thread(void* data) { !settings->recording_exclude_mouse, 0, /* Touch events not supported */ settings->recording_include_keys, - settings->recording_write_existing); + settings->recording_write_existing, + settings->recording_include_clipboard); } /* Create terminal options with required parameters */ diff --git a/src/protocols/vnc/clipboard.c b/src/protocols/vnc/clipboard.c index aea0bcd226..ae3702aeff 100644 --- a/src/protocols/vnc/clipboard.c +++ b/src/protocols/vnc/clipboard.c @@ -92,6 +92,10 @@ int guac_vnc_clipboard_handler(guac_user* user, guac_stream* stream, stream->blob_handler = guac_vnc_clipboard_blob_handler; stream->end_handler = guac_vnc_clipboard_end_handler; + /* Report clipboard within recording */ + if (vnc_client->recording != NULL) + guac_recording_report_clipboard(vnc_client->recording, stream, mimetype); + return 0; } @@ -106,6 +110,12 @@ int guac_vnc_clipboard_blob_handler(guac_user* user, guac_stream* stream, if (clipboard == NULL) return 0; + guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; + + /* Report clipboard blob within recording */ + if (vnc_client->recording != NULL) + guac_recording_report_clipboard_blob(vnc_client->recording, stream, data, length); + /* Append new data */ guac_common_clipboard_append(clipboard, (char*) data, length); @@ -131,6 +141,10 @@ int guac_vnc_clipboard_end_handler(guac_user* user, guac_stream* stream) { char* output = output_data; guac_iconv_write* writer = vnc_client->clipboard_writer; + /* Report clipboard stream end within recording */ + if (vnc_client->recording != NULL) + guac_recording_report_clipboard_end(vnc_client->recording, stream); + /* Convert clipboard contents */ guac_iconv(GUAC_READ_UTF8, &input, clipboard->length, writer, &output, output_buf_size); diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 64d3f9266c..570e222e94 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -87,6 +87,7 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "recording-exclude-output", "recording-exclude-mouse", "recording-include-keys", + "recording-include-clipboard", "create-recording-path", "recording-write-existing", "clipboard-buffer-size", @@ -353,6 +354,16 @@ enum VNC_ARGS_IDX { */ IDX_RECORDING_INCLUDE_KEYS, + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_CLIPBOARD, + /** * Whether the specified screen recording path should automatically be * created if it does not yet exist. @@ -676,6 +687,11 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_RECORDING_INCLUDE_KEYS, false); + /* Parse clipboard inclusion flag */ + settings->recording_include_clipboard = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_CLIPBOARD, false); + /* Parse path creation flag */ settings->create_recording_path = guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index a23585f0db..9d489ad13c 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -312,6 +312,16 @@ typedef struct guac_vnc_settings { */ bool recording_include_keys; + /** + * Whether clipboard paste data should be included in the session recording. + * Clipboard data is NOT included by default within the recording, + * as doing so has privacy and security implications. Including clipboard data + * may be necessary in certain auditing contexts, but should only be done + * with caution. Clipboard data can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_clipboard; + /** * Whether existing files should be appended to when creating a new recording. * Disabled by default. diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 8f5fa9129f..5b6886c631 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -587,7 +587,8 @@ void* guac_vnc_client_thread(void* data) { !settings->recording_exclude_mouse, 0, /* Touch events not supported */ settings->recording_include_keys, - settings->recording_write_existing); + settings->recording_write_existing, + settings->recording_include_clipboard); } /* Create display */ From 98097a38b85f9a9c219bf4498f81f8653d748884 Mon Sep 17 00:00:00 2001 From: Alexander Leitner Date: Mon, 15 Dec 2025 14:19:54 -0500 Subject: [PATCH 2/2] GUACAMOLE-1969: Record remote clipboard changes. --- src/libguac/guacamole/recording.h | 32 +++++++++++++++++++++-- src/libguac/recording.c | 39 +++++++++++++++++++++++++++- src/protocols/kubernetes/clipboard.c | 3 +-- src/protocols/rdp/channels/cliprdr.c | 10 ++++++- src/protocols/ssh/clipboard.c | 4 +-- src/protocols/telnet/clipboard.c | 4 +-- src/protocols/vnc/clipboard.c | 6 ++--- 7 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/libguac/guacamole/recording.h b/src/libguac/guacamole/recording.h index beaa4928b4..eeafbca5df 100644 --- a/src/libguac/guacamole/recording.h +++ b/src/libguac/guacamole/recording.h @@ -48,6 +48,11 @@ */ #define GUAC_COMMON_RECORDING_MAX_NAME_LENGTH 2048 +/** + * The block size to use when sending clipboard data in a recording. + */ +#define GUAC_RECORDING_CLIPBOARD_BLOCK_SIZE 4096 + /** * An in-progress session recording, attached to a guac_client instance such * that output Guacamole instructions may be dynamically intercepted and @@ -61,6 +66,12 @@ typedef struct guac_recording { */ guac_socket* socket; + /** + * The stream used for recording clipboard data. This stream is allocated + * once during recording creation and reused for all clipboard events. + */ + guac_stream* clipboard_stream; + /** * Non-zero if output which is broadcast to each connected client * (graphics, streams, etc.) should be included in the session recording, @@ -286,7 +297,7 @@ void guac_recording_report_key(guac_recording* recording, * @param mimetype * The clipboard data mimetype */ -void guac_recording_report_clipboard(guac_recording* recording, +void guac_recording_report_clipboard_begin(guac_recording* recording, guac_stream* stream, char* mimetype); /** @@ -319,5 +330,22 @@ void guac_recording_report_clipboard_blob(guac_recording* recording, void guac_recording_report_clipboard_end(guac_recording* recording, guac_stream* stream); -#endif +/** + * Reports clipboard data within the recording. + * + * @param recording + * The guac_recording to write clipboard data to. + * + * @param mimetype + * The mimetype of the clipboard data (e.g., "text/plain"). + * + * @param data + * The clipboard data buffer. + * + * @param length + * The length of the clipboard data in bytes. + */ +void guac_recording_report_clipboard(guac_recording* recording, + const char* mimetype, const char* data, int length); +#endif diff --git a/src/libguac/recording.c b/src/libguac/recording.c index 594a7352d8..e5636ec144 100644 --- a/src/libguac/recording.c +++ b/src/libguac/recording.c @@ -78,6 +78,11 @@ guac_recording* guac_recording_create(guac_client* client, recording->include_keys = include_keys; recording->include_clipboard = include_clipboard; + if (include_clipboard) + recording->clipboard_stream = guac_client_alloc_stream(client); + else + recording->clipboard_stream = NULL; + /* Replace client socket with wrapped recording socket only if including * output within the recording */ if (include_output) @@ -134,7 +139,7 @@ void guac_recording_report_key(guac_recording* recording, } -void guac_recording_report_clipboard(guac_recording* recording, guac_stream* stream, char* mimetype) { +void guac_recording_report_clipboard_begin(guac_recording* recording, guac_stream* stream, char* mimetype) { /* Report clipboard only if recording should contain it */ if (recording->include_clipboard) guac_protocol_send_clipboard(recording->socket, stream, mimetype); @@ -151,3 +156,35 @@ void guac_recording_report_clipboard_end(guac_recording* recording, guac_stream* if (recording->include_clipboard) guac_protocol_send_end(recording->socket, stream); } + +void guac_recording_report_clipboard(guac_recording* recording, + const char* mimetype, const char* data, int length) { + + /* Report clipboard only if recording should contain clipboard changes */ + if (!recording->include_clipboard || !recording->clipboard_stream) + return; + + guac_socket* socket = recording->socket; + guac_stream* stream = recording->clipboard_stream; + + const char* current = data; + int remaining = length; + + guac_protocol_send_clipboard(socket, stream, mimetype); + + while (remaining > 0) { + + int block_size = GUAC_RECORDING_CLIPBOARD_BLOCK_SIZE; + if (remaining < block_size) + block_size = remaining; + + guac_protocol_send_blob(socket, stream, current, block_size); + + remaining -= block_size; + current += block_size; + + } + + guac_protocol_send_end(socket, stream); + +} diff --git a/src/protocols/kubernetes/clipboard.c b/src/protocols/kubernetes/clipboard.c index a11109b1ae..0fc3fd463f 100644 --- a/src/protocols/kubernetes/clipboard.c +++ b/src/protocols/kubernetes/clipboard.c @@ -41,7 +41,7 @@ int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream, /* Report clipboard within recording */ if (kubernetes_client->recording != NULL) - guac_recording_report_clipboard(kubernetes_client->recording, stream, mimetype); + guac_recording_report_clipboard_begin(kubernetes_client->recording, stream, mimetype); return 0; } @@ -78,4 +78,3 @@ int guac_kubernetes_clipboard_end_handler(guac_user* user, return 0; } - diff --git a/src/protocols/rdp/channels/cliprdr.c b/src/protocols/rdp/channels/cliprdr.c index 5593d87d3c..bc93666388 100644 --- a/src/protocols/rdp/channels/cliprdr.c +++ b/src/protocols/rdp/channels/cliprdr.c @@ -521,6 +521,13 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr, guac_common_clipboard_reset(clipboard->clipboard, "text/plain"); guac_common_clipboard_append(clipboard->clipboard, received_data, length); guac_common_clipboard_send(clipboard->clipboard, client); + + /* Record clipboard if recording is active */ + if (rdp_client->recording != NULL) + guac_recording_report_clipboard(rdp_client->recording, + clipboard->clipboard->mimetype, + clipboard->clipboard->buffer, + clipboard->clipboard->length); } guac_mem_free(received_data); @@ -698,7 +705,8 @@ int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream, /* Report clipboard within recording */ if (rdp_client->recording != NULL) - guac_recording_report_clipboard(rdp_client->recording, stream, mimetype); + guac_recording_report_clipboard_begin(rdp_client->recording, stream, + mimetype); return 0; diff --git a/src/protocols/ssh/clipboard.c b/src/protocols/ssh/clipboard.c index b9ed966c1b..49c8815c4c 100644 --- a/src/protocols/ssh/clipboard.c +++ b/src/protocols/ssh/clipboard.c @@ -40,7 +40,8 @@ int guac_ssh_clipboard_handler(guac_user* user, guac_stream* stream, /* Report clipboard within recording */ if (ssh_client->recording != NULL) - guac_recording_report_clipboard(ssh_client->recording, stream, mimetype); + guac_recording_report_clipboard_begin(ssh_client->recording, stream, + mimetype); return 0; } @@ -73,4 +74,3 @@ int guac_ssh_clipboard_end_handler(guac_user* user, guac_stream* stream) { return 0; } - diff --git a/src/protocols/telnet/clipboard.c b/src/protocols/telnet/clipboard.c index 439df79917..f6e81a6037 100644 --- a/src/protocols/telnet/clipboard.c +++ b/src/protocols/telnet/clipboard.c @@ -40,7 +40,8 @@ int guac_telnet_clipboard_handler(guac_user* user, guac_stream* stream, /* Report clipboard within recording */ if (telnet_client->recording != NULL) - guac_recording_report_clipboard(telnet_client->recording, stream, mimetype); + guac_recording_report_clipboard_begin(telnet_client->recording, stream, + mimetype); return 0; } @@ -74,4 +75,3 @@ int guac_telnet_clipboard_end_handler(guac_user* user, guac_stream* stream) { return 0; } - diff --git a/src/protocols/vnc/clipboard.c b/src/protocols/vnc/clipboard.c index ae3702aeff..8dbbb590f6 100644 --- a/src/protocols/vnc/clipboard.c +++ b/src/protocols/vnc/clipboard.c @@ -94,7 +94,8 @@ int guac_vnc_clipboard_handler(guac_user* user, guac_stream* stream, /* Report clipboard within recording */ if (vnc_client->recording != NULL) - guac_recording_report_clipboard(vnc_client->recording, stream, mimetype); + guac_recording_report_clipboard_begin(vnc_client->recording, stream, + mimetype); return 0; } @@ -110,8 +111,6 @@ int guac_vnc_clipboard_blob_handler(guac_user* user, guac_stream* stream, if (clipboard == NULL) return 0; - guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data; - /* Report clipboard blob within recording */ if (vnc_client->recording != NULL) guac_recording_report_clipboard_blob(vnc_client->recording, stream, data, length); @@ -185,4 +184,3 @@ void guac_vnc_cut_text(rfbClient* client, const char* text, int textlen) { guac_mem_free(received_data); } -