Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/libguac/guacamole/recording.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ typedef struct guac_recording {
*/
int include_clipboard;

/**
* The client associated with this recording. Used to abort the connection
* if a write to the recording fails and require_recording is set.
*/
guac_client* client;

/**
* Whether the connection should be aborted if writing to the recording
* fails. If non-zero, any write error on the recording socket will
* trigger a connection abort.
*/
int require_recording;

} guac_recording;

/**
Expand Down Expand Up @@ -179,6 +192,11 @@ typedef struct guac_recording {
* caution. Clipboard can easily contain sensitive information, such as
* passwords, credit card numbers, etc.
*
* @param require_recording
* Non-zero if the connection should be aborted when a write to the
* recording file fails, zero otherwise. If zero, write errors will be
* silently ignored and the connection will continue.
*
* @return
* A new guac_recording structure representing the in-progress
* recording if the recording file has been successfully created and a
Expand All @@ -187,7 +205,8 @@ 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_clipboard);
int include_keys, int allow_write_existing, int include_clipboard,
int require_recording);

/**
* Frees the resources associated with the given in-progress recording. Note
Expand Down
134 changes: 133 additions & 1 deletion src/libguac/recording.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,134 @@
#include <string.h>
#include <unistd.h>

/**
* Data specific to the write-required socket wrapper, which aborts the
* associated client connection if any write to the underlying socket fails.
*/
typedef struct guac_socket_require_write_data {

/**
* The wrapped socket to which all write operations are delegated.
*/
guac_socket* wrapped;

/**
* The client to abort if a write fails.
*/
guac_client* client;

} guac_socket_require_write_data;

/**
* Write handler for the require-write socket wrapper. Delegates the write to
* the wrapped socket, aborting the client connection on failure.
*/
static ssize_t __guac_socket_require_write_handler(guac_socket* socket,
const void* buf, size_t count) {

guac_socket_require_write_data* data =
(guac_socket_require_write_data*) socket->data;

if (guac_socket_write(data->wrapped, buf, count)) {
guac_client_abort(data->client,
GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Write to recording failed. Aborting connection.");
return -1;
}

return count;

}

/**
* Flush handler for the require-write socket wrapper. Delegates the flush to
* the wrapped socket, aborting the client connection on failure.
*/
static ssize_t __guac_socket_require_flush_handler(guac_socket* socket) {

guac_socket_require_write_data* data =
(guac_socket_require_write_data*) socket->data;

if (guac_socket_flush(data->wrapped)) {
guac_client_abort(data->client,
GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Flush of recording failed. Aborting connection.");
return -1;
}

return 0;

}

/**
* Lock handler for the require-write socket wrapper.
*/
static void __guac_socket_require_lock_handler(guac_socket* socket) {
guac_socket_require_write_data* data =
(guac_socket_require_write_data*) socket->data;
guac_socket_instruction_begin(data->wrapped);
}

/**
* Unlock handler for the require-write socket wrapper.
*/
static void __guac_socket_require_unlock_handler(guac_socket* socket) {
guac_socket_require_write_data* data =
(guac_socket_require_write_data*) socket->data;
guac_socket_instruction_end(data->wrapped);
}

/**
* Free handler for the require-write socket wrapper. Frees the wrapped socket
* and the wrapper data.
*/
static int __guac_socket_require_free_handler(guac_socket* socket) {
guac_socket_require_write_data* data =
(guac_socket_require_write_data*) socket->data;
guac_socket_free(data->wrapped);
guac_mem_free(data);
return 0;
}

/**
* Creates a socket wrapper that aborts the given client's connection if any
* write to the underlying socket fails.
*
* @param socket
* The socket to wrap.
*
* @param client
* The client to abort on write failure.
*
* @return
* A new guac_socket that delegates to the given socket but aborts the
* client on any write or flush error.
*/
static guac_socket* guac_socket_require_write(guac_socket* wrapped,
guac_client* client) {

guac_socket_require_write_data* data =
guac_mem_alloc(sizeof(guac_socket_require_write_data));
data->wrapped = wrapped;
data->client = client;

guac_socket* socket = guac_socket_alloc();
socket->data = data;
socket->write_handler = __guac_socket_require_write_handler;
socket->flush_handler = __guac_socket_require_flush_handler;
socket->lock_handler = __guac_socket_require_lock_handler;
socket->unlock_handler = __guac_socket_require_unlock_handler;
socket->free_handler = __guac_socket_require_free_handler;

return socket;

}

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_clipboard) {
int include_keys, int allow_write_existing, int include_clipboard,
int require_recording) {

char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH];

Expand Down Expand Up @@ -77,6 +201,14 @@ guac_recording* guac_recording_create(guac_client* client,
recording->include_touch = include_touch;
recording->include_keys = include_keys;
recording->include_clipboard = include_clipboard;
recording->client = client;
recording->require_recording = require_recording;

/* If recording is required, wrap the recording socket so that any write
* failure will automatically abort the client connection */
if (require_recording)
recording->socket = guac_socket_require_write(
recording->socket, client);

if (include_clipboard)
recording->clipboard_stream = guac_client_alloc_stream(client);
Expand Down
11 changes: 10 additions & 1 deletion src/protocols/kubernetes/kubernetes.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,16 @@ void* guac_kubernetes_client_thread(void* data) {
0, /* Touch events not supported */
settings->recording_include_keys,
settings->recording_write_existing,
settings->recording_include_clipboard);
settings->recording_include_clipboard,
settings->require_recording);

/* If recording is required but failed, abort the connection */
if (kubernetes_client->recording == NULL && settings->require_recording) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Session recording is required but could not be created. "
"Aborting connection.");
return NULL;
}
}

/* Create terminal options with required parameters */
Expand Down
12 changes: 12 additions & 0 deletions src/protocols/kubernetes/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = {
"recording-include-clipboard",
"create-recording-path",
"recording-write-existing",
"require-recording",
"read-only",
"backspace",
"scrollback",
Expand Down Expand Up @@ -238,6 +239,12 @@ enum KUBERNETES_ARGS_IDX {
*/
IDX_RECORDING_WRITE_EXISTING,

/**
* Whether recording is required. If set to "true", the connection will
* be closed if the recording cannot be created for any reason.
*/
IDX_REQUIRE_RECORDING,

/**
* "true" if this connection should be read-only (user input should be
* dropped), "false" or blank otherwise.
Expand Down Expand Up @@ -443,6 +450,11 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_RECORDING_WRITE_EXISTING, false);

/* Parse require recording flag */
settings->require_recording =
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_REQUIRE_RECORDING, false);

/* Parse backspace key code */
settings->backspace =
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
Expand Down
7 changes: 7 additions & 0 deletions src/protocols/kubernetes/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ typedef struct guac_kubernetes_settings {
*/
bool recording_write_existing;

/**
* Whether the connection should be closed when a recording cannot be
* created. Disabled by default, meaning the connection will proceed even
* if recording fails.
*/
bool require_recording;

/**
* 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
Expand Down
11 changes: 10 additions & 1 deletion src/protocols/rdp/rdp.c
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,16 @@ void* guac_rdp_client_thread(void* data) {
!settings->recording_exclude_touch,
settings->recording_include_keys,
settings->recording_write_existing,
settings->recording_include_clipboard);
settings->recording_include_clipboard,
settings->require_recording);

/* If recording is required but failed, abort the connection */
if (rdp_client->recording == NULL && settings->require_recording) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Session recording is required but could not be created. "
"Aborting connection.");
return NULL;
}
}

/* Continue handling connections until error or client disconnect */
Expand Down
12 changes: 12 additions & 0 deletions src/protocols/rdp/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = {
"recording-include-clipboard",
"create-recording-path",
"recording-write-existing",
"require-recording",
"resize-method",
"enable-audio-input",
"enable-touch",
Expand Down Expand Up @@ -604,6 +605,12 @@ enum RDP_ARGS_IDX {
*/
IDX_RECORDING_WRITE_EXISTING,

/**
* Whether recording is required. If set to "true", the connection will
* be closed if the recording cannot be created for any reason.
*/
IDX_REQUIRE_RECORDING,

/**
* The method to use to apply screen size changes requested by the user.
* Valid values are blank, "display-update", and "reconnect".
Expand Down Expand Up @@ -1232,6 +1239,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_RECORDING_WRITE_EXISTING, 0);

/* Parse require recording flag */
settings->require_recording =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_REQUIRE_RECORDING, 0);

/* No resize method */
if (strcmp(argv[IDX_RESIZE_METHOD], "") == 0) {
guac_user_log(user, GUAC_LOG_INFO, "Resize method: none");
Expand Down
7 changes: 7 additions & 0 deletions src/protocols/rdp/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,13 @@ typedef struct guac_rdp_settings {
*/
int recording_write_existing;

/**
* Whether the connection should be closed when a recording cannot be
* created. Disabled by default, meaning the connection will proceed even
* if recording fails.
*/
bool require_recording;

/**
* The method to apply when the user's display changes size.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/protocols/ssh/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = {
"recording-include-clipboard",
"create-recording-path",
"recording-write-existing",
"require-recording",
"read-only",
"server-alive-interval",
"backspace",
Expand Down Expand Up @@ -277,6 +278,12 @@ enum SSH_ARGS_IDX {
*/
IDX_RECORDING_WRITE_EXISTING,

/**
* Whether recording is required. If set to "true", the connection will
* be closed if the recording cannot be created for any reason.
*/
IDX_REQUIRE_RECORDING,

/**
* "true" if this connection should be read-only (user input should be
* dropped), "false" or blank otherwise.
Expand Down Expand Up @@ -562,6 +569,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user,
guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_RECORDING_WRITE_EXISTING, false);

/* Parse require recording flag */
settings->require_recording =
guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv,
IDX_REQUIRE_RECORDING, false);

/* Parse server alive interval */
settings->server_alive_interval =
guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv,
Expand Down
7 changes: 7 additions & 0 deletions src/protocols/ssh/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,13 @@ typedef struct guac_ssh_settings {
*/
bool recording_write_existing;

/**
* Whether the connection should be closed when a recording cannot be
* created. Disabled by default, meaning the connection will proceed even
* if recording fails.
*/
bool require_recording;
Comment on lines +291 to +296
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use "zero" but are using a bool here (instead of int as in the RDP code). It should probably be both consistent and the language in the comments should match the type used.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, was primarily testing rdp protocol when I first made the changes.
Updated this & the comments to reflect it's a bool flag


/**
* The number of seconds between sending server alive messages.
*/
Expand Down
11 changes: 10 additions & 1 deletion src/protocols/ssh/ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,16 @@ void* ssh_client_thread(void* data) {
0, /* Touch events not supported */
settings->recording_include_keys,
settings->recording_write_existing,
settings->recording_include_clipboard);
settings->recording_include_clipboard,
settings->require_recording);

/* If recording is required but failed, abort the connection */
if (ssh_client->recording == NULL && settings->require_recording) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Session recording is required but could not be created. "
"Aborting connection.");
return NULL;
}
}

/* Create terminal options with required parameters */
Expand Down
Loading