diff --git a/src/libguac/guacamole/recording.h b/src/libguac/guacamole/recording.h index 667713d16..e44bc8468 100644 --- a/src/libguac/guacamole/recording.h +++ b/src/libguac/guacamole/recording.h @@ -148,6 +148,10 @@ typedef struct guac_recording { * caution. Key events can easily contain sensitive information, such as * passwords, credit card numbers, etc. * + * @param allow_write_existing + * Non-zero if writing to an existing file should be allowed, or zero + * otherwise. + * * @return * A new guac_recording structure representing the in-progress * recording if the recording file has been successfully created and a @@ -156,7 +160,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 include_keys, int allow_write_existing); /** * Frees the resources associated with the given in-progress recording. Note diff --git a/src/libguac/recording.c b/src/libguac/recording.c index 7544cc144..7e36f4613 100644 --- a/src/libguac/recording.c +++ b/src/libguac/recording.c @@ -39,11 +39,13 @@ /** * Attempts to open a new recording within the given path and having the given - * name. If such a file already exists, sequential numeric suffixes (.1, .2, - * .3, etc.) are appended until a filename is found which does not exist (or - * until the maximum number of numeric suffixes has been tried). If the file - * absolutely cannot be opened due to an error, -1 is returned and errno is set - * appropriately. + * name. If opening the file fails for any reason, or if such a file already + * exists and allow_write_existing is not set, sequential numeric suffixes + * (.1, .2, .3, etc.) are appended until a filename is found which does not + * exist (or until the maximum number of numeric suffixes has been tried). + * If the file exists and allow_write_existing is set, the recording will be + * appended to any existing file contents. If the file absolutely cannot be + * opened due to an error, -1 is returned and errno is set appropriately. * * @param path * The full path to the directory in which the data file should be created. @@ -60,12 +62,17 @@ * @param basename_size * The number of bytes available within the provided basename buffer. * + * @param allow_write_existing + * Non-zero if writing to an existing file should be allowed, or zero + * otherwise. + * * @return * The file descriptor of the open data file if open succeeded, or -1 on * failure. */ static int guac_recording_open(const char* path, - const char* name, char* basename, int basename_size) { + const char* name, char* basename, int basename_size, + int allow_write_existing) { int i; @@ -81,10 +88,11 @@ static int guac_recording_open(const char* path, return -1; } + /* Require the file not exist already if allow_write_existing not set */ + int flags = O_CREAT | O_WRONLY | (allow_write_existing ? 0 : O_EXCL); + /* Attempt to open recording */ - int fd = open(basename, - O_CREAT | O_EXCL | O_WRONLY, - S_IRUSR | S_IWUSR | S_IRGRP); + int fd = open(basename, flags, S_IRUSR | S_IWUSR | S_IRGRP); /* Continuously retry with alternate names on failure */ if (fd == -1) { @@ -101,9 +109,7 @@ static int guac_recording_open(const char* path, sprintf(suffix, "%i", i); /* Retry with newly-suffixed filename */ - fd = open(basename, - O_CREAT | O_EXCL | O_WRONLY, - S_IRUSR | S_IWUSR | S_IRGRP); + fd = open(basename, flags, S_IRUSR | S_IWUSR | S_IRGRP); } @@ -138,7 +144,7 @@ static int guac_recording_open(const char* path, 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 include_keys, int allow_write_existing) { char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; @@ -155,7 +161,8 @@ guac_recording* guac_recording_create(guac_client* client, } /* Attempt to open recording file */ - int fd = guac_recording_open(path, name, filename, sizeof(filename)); + int fd = guac_recording_open( + path, name, filename, sizeof(filename), allow_write_existing); if (fd == -1) { guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording failed: %s", strerror(errno)); diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 163c55626..f41a1914d 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -237,7 +237,8 @@ void* guac_kubernetes_client_thread(void* data) { !settings->recording_exclude_output, !settings->recording_exclude_mouse, 0, /* Touch events not supported */ - settings->recording_include_keys); + settings->recording_include_keys, + settings->recording_write_existing); } /* Create terminal options with required parameters */ diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 5da7d8bec..fabac6a7b 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-mouse", "recording-include-keys", "create-recording-path", + "recording-write-existing", "read-only", "backspace", "scrollback", @@ -210,6 +211,12 @@ enum KUBERNETES_ARGS_IDX { */ IDX_CREATE_RECORDING_PATH, + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + IDX_RECORDING_WRITE_EXISTING, + /** * "true" if this connection should be read-only (user input should be * dropped), "false" or blank otherwise. @@ -389,6 +396,11 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, false); + /* Parse allow write existing file flag */ + settings->recording_write_existing = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_WRITE_EXISTING, false); + /* Parse backspace key code */ settings->backspace = guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index f7c9c1c37..461ac6cfe 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -234,6 +234,12 @@ typedef struct guac_kubernetes_settings { */ bool recording_include_keys; + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + bool recording_write_existing; + /** * The ASCII code, as an integer, that the Kubernetes client will use when * the backspace key is pressed. By default, this is 127, ASCII delete, if diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 9ec972601..dff536eff 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -841,7 +841,8 @@ void* guac_rdp_client_thread(void* data) { !settings->recording_exclude_output, !settings->recording_exclude_mouse, !settings->recording_exclude_touch, - settings->recording_include_keys); + settings->recording_include_keys, + settings->recording_write_existing); } /* Continue handling connections until error or client disconnect */ diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index 1ab284ff4..4b02b3eb1 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -122,6 +122,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "recording-exclude-touch", "recording-include-keys", "create-recording-path", + "recording-write-existing", "resize-method", "enable-audio-input", "enable-touch", @@ -564,6 +565,12 @@ enum RDP_ARGS_IDX { */ IDX_CREATE_RECORDING_PATH, + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + IDX_RECORDING_WRITE_EXISTING, + /** * The method to use to apply screen size changes requested by the user. * Valid values are blank, "display-update", and "reconnect". @@ -1161,6 +1168,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, 0); + /* Parse allow write existing file flag */ + settings->recording_write_existing = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_RECORDING_WRITE_EXISTING, 0); + /* No resize method */ if (strcmp(argv[IDX_RESIZE_METHOD], "") == 0) { guac_user_log(user, GUAC_LOG_INFO, "Resize method: none"); diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h index 11dbf59bc..7837e6aeb 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -560,6 +560,12 @@ typedef struct guac_rdp_settings { int recording_include_keys; /** + * Non-zero if existing files should be appended to when creating a new + * recording. Disabled by default. + */ + int recording_write_existing; + + /** * The method to apply when the user's display changes size. */ guac_rdp_resize_method resize_method; diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 228068b2f..a92b22b7a 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -63,6 +63,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "recording-exclude-mouse", "recording-include-keys", "create-recording-path", + "recording-write-existing", "read-only", "server-alive-interval", "backspace", @@ -241,6 +242,12 @@ enum SSH_ARGS_IDX { */ IDX_CREATE_RECORDING_PATH, + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + IDX_RECORDING_WRITE_EXISTING, + /** * "true" if this connection should be read-only (user input should be * dropped), "false" or blank otherwise. @@ -495,6 +502,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, false); + /* Parse allow write existing file flag */ + settings->recording_write_existing = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_RECORDING_WRITE_EXISTING, false); + /* Parse server alive interval */ settings->server_alive_interval = guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index e5d207501..c06a7d981 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -251,6 +251,12 @@ typedef struct guac_ssh_settings { */ bool recording_include_keys; + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + bool recording_write_existing; + /** * The number of seconds between sending server alive messages. */ diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 1e18fed4c..7b5bdb873 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -264,7 +264,8 @@ void* ssh_client_thread(void* data) { !settings->recording_exclude_output, !settings->recording_exclude_mouse, 0, /* Touch events not supported */ - settings->recording_include_keys); + settings->recording_include_keys, + settings->recording_write_existing); } /* Create terminal options with required parameters */ diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 86271c69c..183b3778c 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -55,6 +55,7 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "recording-exclude-mouse", "recording-include-keys", "create-recording-path", + "recording-write-existing", "read-only", "backspace", "terminal-type", @@ -193,6 +194,12 @@ enum TELNET_ARGS_IDX { */ IDX_CREATE_RECORDING_PATH, + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + IDX_RECORDING_WRITE_EXISTING, + /** * "true" if this connection should be read-only (user input should be * dropped), "false" or blank otherwise. @@ -486,6 +493,11 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, false); + /* Parse allow write existing file flag */ + settings->recording_write_existing = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_RECORDING_WRITE_EXISTING, false); + /* Parse backspace key code */ settings->backspace = guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv, diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 02de1cb3c..9faa316a3 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -239,6 +239,12 @@ typedef struct guac_telnet_settings { */ bool recording_include_keys; + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + bool recording_write_existing; + /** * The ASCII code, as an integer, that the telnet client will use when the * backspace key is pressed. By default, this is 127, ASCII delete, if diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 501b1d3a3..b0a1cbdf5 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -606,7 +606,8 @@ void* guac_telnet_client_thread(void* data) { !settings->recording_exclude_output, !settings->recording_exclude_mouse, 0, /* Touch events not supported */ - settings->recording_include_keys); + settings->recording_include_keys, + settings->recording_write_existing); } /* Create terminal options with required parameters */ diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 725e10cb7..083a6c633 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -84,6 +84,7 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "recording-exclude-mouse", "recording-include-keys", "create-recording-path", + "recording-write-existing", "disable-copy", "disable-paste", @@ -331,6 +332,12 @@ enum VNC_ARGS_IDX { */ IDX_CREATE_RECORDING_PATH, + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + IDX_RECORDING_WRITE_EXISTING, + /** * Whether outbound clipboard access should be blocked. If set to "true", * it will not be possible to copy data from the remote desktop to the @@ -594,6 +601,11 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, false); + /* Parse allow write existing file flag */ + settings->recording_write_existing = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_RECORDING_WRITE_EXISTING, false); + /* Parse clipboard copy disable flag */ settings->disable_copy = 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 d1d072635..380343415 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -274,6 +274,12 @@ typedef struct guac_vnc_settings { * as passwords, credit card numbers, etc. */ bool recording_include_keys; + + /** + * Whether existing files should be appended to when creating a new recording. + * Disabled by default. + */ + bool recording_write_existing; /** * Whether or not to send the magic Wake-on-LAN (WoL) packet prior to diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 3b94d5e75..9fd9156a8 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -428,7 +428,8 @@ void* guac_vnc_client_thread(void* data) { !settings->recording_exclude_output, !settings->recording_exclude_mouse, 0, /* Touch events not supported */ - settings->recording_include_keys); + settings->recording_include_keys, + settings->recording_write_existing); } /* Create display */