diff --git a/Dockerfile b/Dockerfile index bb0a34dc3..494ace9d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ ARG ALPINE_BASE_IMAGE=latest FROM alpine:${ALPINE_BASE_IMAGE} AS builder # Install build dependencies +RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing openssl1.1-compat-dev RUN apk add --no-cache \ autoconf \ automake \ @@ -40,7 +41,6 @@ RUN apk add --no-cache \ libtool \ libwebp-dev \ make \ - openssl1.1-compat-dev \ pango-dev \ pulseaudio-dev \ util-linux-dev @@ -167,6 +167,7 @@ ENV GUACD_LOG_LEVEL=info COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR} # Bring runtime environment up to date and install runtime dependencies +RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing libssl1.1 libcrypto1.1 RUN apk add --no-cache \ ca-certificates \ ghostscript \ diff --git a/bin/guacctl b/bin/guacctl index 972ef11ee..ea5a3061c 100755 --- a/bin/guacctl +++ b/bin/guacctl @@ -117,7 +117,7 @@ error() { ## usage() { cat >&2 < #include +#include + #include #include #include @@ -67,7 +69,7 @@ void guac_common_ssh_buffer_write_bignum(char** buffer, const BIGNUM* value) { /* Allocate output buffer, add padding byte */ length = BN_num_bytes(value); - bn_buffer = malloc(length); + bn_buffer = guac_mem_alloc(length); /* Convert BIGNUM */ BN_bn2bin(value, bn_buffer); @@ -84,7 +86,7 @@ void guac_common_ssh_buffer_write_bignum(char** buffer, const BIGNUM* value) { memcpy(*buffer, bn_buffer, length); *buffer += length; - free(bn_buffer); + guac_mem_free(bn_buffer); } diff --git a/src/common-ssh/common-ssh/ssh.h b/src/common-ssh/common-ssh/ssh.h index 2601acff4..bc61ed9f1 100644 --- a/src/common-ssh/common-ssh/ssh.h +++ b/src/common-ssh/common-ssh/ssh.h @@ -38,7 +38,7 @@ * * @return * A newly-allocated string containing the credentials provided by - * the user, which must be freed by a call to free(). + * the user, which must be freed by a call to guac_mem_free(). */ typedef char* guac_ssh_credential_handler(guac_client* client, char* cred_name); diff --git a/src/common-ssh/common-ssh/user.h b/src/common-ssh/common-ssh/user.h index 745728f1b..f3065f93a 100644 --- a/src/common-ssh/common-ssh/user.h +++ b/src/common-ssh/common-ssh/user.h @@ -44,6 +44,12 @@ typedef struct guac_common_ssh_user { */ guac_common_ssh_key* private_key; + /** + * The public key which should be used to authenticate this user, if any, + * or NULL if a password or just a private key will be used instead. + */ + char* public_key; + } guac_common_ssh_user; /** @@ -104,5 +110,23 @@ void guac_common_ssh_user_set_password(guac_common_ssh_user* user, int guac_common_ssh_user_import_key(guac_common_ssh_user* user, char* private_key, char* passphrase); +/** + * Imports the given public key, associating that key with the given user. + * If the public key is imported successfully, it will be used for + * future authentication attempts. + * + * @param user + * The user to associate with the given private key. + * + * @param public_key + * The base64-encoded public key to import. + * + * @return + * Zero if public key is successfully imported, or non-zero if the + * public key could not be imported due to an error. + */ +int guac_common_ssh_user_import_public_key(guac_common_ssh_user* user, + char* public_key); + #endif diff --git a/src/common-ssh/key.c b/src/common-ssh/key.c index cecfb413d..ddc84179f 100644 --- a/src/common-ssh/key.c +++ b/src/common-ssh/key.c @@ -22,6 +22,7 @@ #include "common-ssh/buffer.h" #include "common-ssh/key.h" +#include #include #include @@ -137,13 +138,13 @@ guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0')) return NULL; - guac_common_ssh_key* key = malloc(sizeof(guac_common_ssh_key)); + guac_common_ssh_key* key = guac_mem_alloc(sizeof(guac_common_ssh_key)); /* Copy private key to structure */ key->private_key_length = length; - key->private_key = malloc(length); + key->private_key = guac_mem_alloc(length); memcpy(key->private_key, data, length); - key->passphrase = strdup(passphrase); + key->passphrase = guac_strdup(passphrase); return key; @@ -157,10 +158,9 @@ const char* guac_common_ssh_key_error() { } void guac_common_ssh_key_free(guac_common_ssh_key* key) { - - free(key->private_key); - free(key->passphrase); - free(key); + guac_mem_free(key->private_key); + guac_mem_free(key->passphrase); + guac_mem_free(key); } int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client, diff --git a/src/common-ssh/sftp.c b/src/common-ssh/sftp.c index 0100cc161..4a6f7716a 100644 --- a/src/common-ssh/sftp.c +++ b/src/common-ssh/sftp.c @@ -21,6 +21,7 @@ #include "common-ssh/ssh.h" #include +#include #include #include #include @@ -622,7 +623,7 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user, if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { libssh2_sftp_closedir(list_state->directory); guac_user_free_stream(user, stream); - free(list_state); + guac_mem_free(list_state); return 0; } @@ -674,7 +675,7 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user, /* Clean up resources */ libssh2_sftp_closedir(list_state->directory); - free(list_state); + guac_mem_free(list_state); /* Signal of stream */ guac_protocol_send_end(user->socket, stream); @@ -774,7 +775,7 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user, /* Init directory listing state */ guac_common_ssh_sftp_ls_state* list_state = - malloc(sizeof(guac_common_ssh_sftp_ls_state)); + guac_mem_alloc(sizeof(guac_common_ssh_sftp_ls_state)); list_state->directory = dir; list_state->filesystem = filesystem; @@ -786,7 +787,7 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user, if (length >= sizeof(list_state->directory_name)) { guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory " "\"%s\": Path too long", fullpath); - free(list_state); + guac_mem_free(list_state); return 0; } @@ -969,7 +970,7 @@ guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem( /* Allocate data for SFTP session */ guac_common_ssh_sftp_filesystem* filesystem = - malloc(sizeof(guac_common_ssh_sftp_filesystem)); + guac_mem_alloc(sizeof(guac_common_ssh_sftp_filesystem)); /* Associate SSH session with SFTP data and user */ filesystem->ssh_session = session; @@ -984,15 +985,15 @@ guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem( root_path)) { guac_client_log(session->client, GUAC_LOG_WARNING, "Cannot create " "SFTP filesystem - \"%s\" is not a valid path.", root_path); - free(filesystem); + guac_mem_free(filesystem); return NULL; } /* Generate filesystem name from root path if no name is provided */ if (name != NULL) - filesystem->name = strdup(name); + filesystem->name = guac_strdup(name); else - filesystem->name = strdup(filesystem->root_path); + filesystem->name = guac_strdup(filesystem->root_path); /* Initially upload files to current directory */ strcpy(filesystem->upload_path, "."); @@ -1009,8 +1010,8 @@ void guac_common_ssh_destroy_sftp_filesystem( libssh2_sftp_shutdown(filesystem->sftp_session); /* Free associated memory */ - free(filesystem->name); - free(filesystem); + guac_mem_free(filesystem->name); + guac_mem_free(filesystem); } diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 34a1d1e34..c206865a5 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -23,6 +23,8 @@ #include #include +#include +#include #include #ifdef LIBSSH2_USES_GCRYPT @@ -120,7 +122,7 @@ static void guac_common_ssh_openssl_init_locks(int count) { /* Allocate required number of locks */ guac_common_ssh_openssl_locks = - malloc(sizeof(pthread_mutex_t) * count); + guac_mem_alloc(sizeof(pthread_mutex_t), count); /* Initialize each lock */ for (i=0; i < count; i++) @@ -147,7 +149,7 @@ static void guac_common_ssh_openssl_free_locks(int count) { pthread_mutex_destroy(&(guac_common_ssh_openssl_locks[i])); /* Free lock array */ - free(guac_common_ssh_openssl_locks); + guac_mem_free(guac_common_ssh_openssl_locks); } #endif @@ -251,7 +253,7 @@ static void guac_common_ssh_kbd_callback(const char *name, int name_len, /* Send password if only one prompt */ if (num_prompts == 1) { char* password = common_session->user->password; - responses[0].text = strdup(password); + responses[0].text = guac_strdup(password); responses[0].length = strlen(password); } @@ -284,6 +286,8 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) /* Get user credentials */ guac_common_ssh_key* key = user->private_key; + char* public_key = user->public_key; + /* Validate username provided */ if (user->username == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, @@ -317,9 +321,11 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) return 1; } + int public_key_length = public_key == NULL ? 0 : strlen(public_key); + /* Attempt public key auth */ if (libssh2_userauth_publickey_frommemory(session, user->username, - username_len, NULL, 0, key->private_key, + username_len, public_key, public_key_length, key->private_key, key->private_key_length, key->passphrase)) { /* Abort on failure */ @@ -488,7 +494,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, /* Allocate new session */ guac_common_ssh_session* common_session = - malloc(sizeof(guac_common_ssh_session)); + guac_mem_alloc(sizeof(guac_common_ssh_session)); /* Open SSH session */ LIBSSH2_SESSION* session = libssh2_session_init_ex(NULL, NULL, @@ -496,7 +502,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, if (session == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Session allocation failed."); - free(common_session); + guac_mem_free(common_session); close(fd); return NULL; } @@ -516,7 +522,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, if (libssh2_session_handshake(session, fd)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "SSH handshake failed."); - free(common_session); + guac_mem_free(common_session); close(fd); return NULL; } @@ -529,7 +535,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, if (!remote_hostkey) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Failed to get host key for %s", hostname); - free(common_session); + guac_mem_free(common_session); close(fd); return NULL; } @@ -552,7 +558,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Host key did not match any provided known host keys. %s", err_msg); - free(common_session); + guac_mem_free(common_session); close(fd); return NULL; } @@ -566,7 +572,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, /* Attempt authentication */ if (guac_common_ssh_authenticate(common_session)) { - free(common_session); + guac_mem_free(common_session); close(fd); return NULL; } @@ -597,6 +603,6 @@ void guac_common_ssh_destroy_session(guac_common_ssh_session* session) { libssh2_session_free(session->session); /* Free all other data */ - free(session); + guac_mem_free(session); } \ No newline at end of file diff --git a/src/common-ssh/tests/Makefile.am b/src/common-ssh/tests/Makefile.am index 12396991a..8424268a5 100644 --- a/src/common-ssh/tests/Makefile.am +++ b/src/common-ssh/tests/Makefile.am @@ -45,7 +45,8 @@ test_common_ssh_CFLAGS = \ test_common_ssh_LDADD = \ @CUNIT_LIBS@ \ @COMMON_SSH_LTLIB@ \ - @COMMON_LTLIB@ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ # # Autogenerate test runner diff --git a/src/common-ssh/tests/sftp/normalize_path.c b/src/common-ssh/tests/sftp/normalize_path.c index 151b18357..df586c10f 100644 --- a/src/common-ssh/tests/sftp/normalize_path.c +++ b/src/common-ssh/tests/sftp/normalize_path.c @@ -19,6 +19,8 @@ #include "common-ssh/sftp.h" +#include + #include #include @@ -160,7 +162,7 @@ void test_sftp__normalize_relative_mixed() { * Generates a dynamically-allocated path having the given number of bytes, not * counting the null-terminator. The path will contain only UNIX-style path * separators. The returned path must eventually be freed with a call to - * free(). + * guac_mem_free(). * * @param length * The number of bytes to include in the generated path, not counting the @@ -174,16 +176,16 @@ void test_sftp__normalize_relative_mixed() { * @return * A dynamically-allocated path containing the given number of bytes, not * counting the null-terminator. This path must eventually be freed with a - * call to free(). + * call to guac_mem_free(). */ static char* generate_path(int length, int max_depth) { /* If no length given, calculate space required from max_depth */ if (length == -1) - length = max_depth * 2; + length = guac_mem_ckd_mul_or_die(max_depth, 2); int i; - char* input = malloc(length + 1); + char* input = guac_mem_alloc(guac_mem_ckd_add_or_die(length, 1)); /* Fill path with /x/x/x/x/x/x/x/x/x/x/.../xxxxxxxxx... */ for (i = 0; i < length; i++) { @@ -214,17 +216,17 @@ void test_sftp__normalize_long() { /* Exceeds maximum length by a factor of 2 */ input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH * 2, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); - free(input); + guac_mem_free(input); /* Exceeds maximum length by one byte */ input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); - free(input); + guac_mem_free(input); /* Exactly maximum length */ input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH - 1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); - free(input); + guac_mem_free(input); } @@ -240,24 +242,24 @@ void test_sftp__normalize_deep() { /* Exceeds maximum depth by a factor of 2 */ input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH * 2); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); - free(input); + guac_mem_free(input); /* Exceeds maximum depth by one component */ input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH + 1); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); - free(input); + guac_mem_free(input); /* Exactly maximum depth (should still be rejected as SFTP depth limits are * set such that a path with the maximum depth will exceed the maximum * length) */ input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); - free(input); + guac_mem_free(input); /* Less than maximum depth */ input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH - 1); CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); - free(input); + guac_mem_free(input); } diff --git a/src/common-ssh/user.c b/src/common-ssh/user.c index 92c8d963c..3ebb31d59 100644 --- a/src/common-ssh/user.c +++ b/src/common-ssh/user.c @@ -20,17 +20,21 @@ #include "common-ssh/key.h" #include "common-ssh/user.h" +#include +#include + #include #include guac_common_ssh_user* guac_common_ssh_create_user(const char* username) { - guac_common_ssh_user* user = malloc(sizeof(guac_common_ssh_user)); + guac_common_ssh_user* user = guac_mem_alloc(sizeof(guac_common_ssh_user)); /* Init user */ - user->username = strdup(username); + user->username = guac_strdup(username); user->password = NULL; user->private_key = NULL; + user->public_key = NULL; return user; @@ -43,9 +47,10 @@ void guac_common_ssh_destroy_user(guac_common_ssh_user* user) { guac_common_ssh_key_free(user->private_key); /* Free all other data */ - free(user->password); - free(user->username); - free(user); + guac_mem_free(user->password); + guac_mem_free(user->username); + guac_mem_free(user->public_key); + guac_mem_free(user); } @@ -53,8 +58,8 @@ void guac_common_ssh_user_set_password(guac_common_ssh_user* user, const char* password) { /* Replace current password with given value */ - free(user->password); - user->password = strdup(password); + guac_mem_free(user->password); + user->password = guac_strdup(password); } @@ -80,3 +85,15 @@ int guac_common_ssh_user_import_key(guac_common_ssh_user* user, } +int guac_common_ssh_user_import_public_key(guac_common_ssh_user* user, + char* public_key) { + + /* Free existing public key, if present */ + guac_mem_free(user->public_key); + user->public_key = guac_strdup(public_key); + + /* Fail if key could not be read */ + return user->public_key == NULL; + +} + diff --git a/src/common/clipboard.c b/src/common/clipboard.c index f9bf1acb2..72ec263d0 100644 --- a/src/common/clipboard.c +++ b/src/common/clipboard.c @@ -21,6 +21,7 @@ #include "common/clipboard.h" #include +#include #include #include #include @@ -31,11 +32,11 @@ guac_common_clipboard* guac_common_clipboard_alloc() { - guac_common_clipboard* clipboard = malloc(sizeof(guac_common_clipboard)); + guac_common_clipboard* clipboard = guac_mem_alloc(sizeof(guac_common_clipboard)); /* Init clipboard */ clipboard->mimetype[0] = '\0'; - clipboard->buffer = malloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH); + clipboard->buffer = guac_mem_alloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH); clipboard->available = GUAC_COMMON_CLIPBOARD_MAX_LENGTH; clipboard->length = 0; @@ -51,10 +52,11 @@ void guac_common_clipboard_free(guac_common_clipboard* clipboard) { pthread_mutex_destroy(&(clipboard->lock)); /* Free buffer */ - free(clipboard->buffer); + guac_mem_free(clipboard->buffer); /* Free base structure */ - free(clipboard); + guac_mem_free(clipboard); + } /** diff --git a/src/common/common/cursor.h b/src/common/common/cursor.h index 52d93c2ae..aca24faaf 100644 --- a/src/common/common/cursor.h +++ b/src/common/common/cursor.h @@ -68,7 +68,7 @@ typedef struct guac_common_cursor { /** * The size of the image data buffer, in bytes. */ - int image_buffer_size; + size_t image_buffer_size; /** * The current cursor image, if any. If the mouse cursor has not yet been @@ -123,6 +123,14 @@ typedef struct guac_common_cursor { */ guac_timestamp timestamp; + /** + * Lock which restricts simultaneous access to the cursor, guaranteeing + * ordered modifications to the cursor and that incompatible operations + * do not occur simultaneously. This lock is for internal use within the + * cursor only. + */ + pthread_mutex_t _lock; + } guac_common_cursor; /** @@ -153,14 +161,14 @@ void guac_common_cursor_free(guac_common_cursor* cursor); * @param cursor * The cursor to send. * - * @param user + * @param client * The user receiving the updated cursor. * * @param socket * The socket over which the updated cursor should be sent. */ -void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, - guac_socket* socket); +void guac_common_cursor_dup( + guac_common_cursor* cursor, guac_client* client, guac_socket* socket); /** * Updates the current position and button state of the mouse cursor, marking diff --git a/src/common/common/display.h b/src/common/common/display.h index c54289021..33950a177 100644 --- a/src/common/common/display.h +++ b/src/common/common/display.h @@ -151,13 +151,14 @@ void guac_common_display_free(guac_common_display* display); * @param display * The display whose state should be sent along the given socket. * - * @param user - * The user receiving the display state. + * @param client + * The client associated with the users receiving the display state. * * @param socket * The socket over which the display state should be sent. */ -void guac_common_display_dup(guac_common_display* display, guac_user* user, +void guac_common_display_dup( + guac_common_display* display, guac_client* client, guac_socket* socket); /** diff --git a/src/common/common/list.h b/src/common/common/list.h index 5f6be1b76..f1a16a1b1 100644 --- a/src/common/common/list.h +++ b/src/common/common/list.h @@ -75,12 +75,26 @@ typedef struct guac_common_list { */ guac_common_list* guac_common_list_alloc(); +/** + * A handler that will be invoked with the data pointer of each element of + * the list when guac_common_list_free() is invoked. + * + * @param data + * The arbitrary data pointed to by the list element. + */ +typedef void guac_common_list_element_free_handler(void* data); + /** * Frees the given list. * * @param list The list to free. + * + * @param free_element_handler + * A handler that will be invoked with each arbitrary data pointer in the + * list, if not NULL. */ -void guac_common_list_free(guac_common_list* list); +void guac_common_list_free(guac_common_list* list, + guac_common_list_element_free_handler* free_element_handler); /** * Adds the given data to the list as a new element, returning the created diff --git a/src/common/common/string.h b/src/common/common/string.h index 442d7f495..8498cddcc 100644 --- a/src/common/common/string.h +++ b/src/common/common/string.h @@ -22,24 +22,36 @@ #include "config.h" +#include + /** * Counts the number of occurrences of a given character in a string. * - * @param string The string to count occurrences within. - * @param c The character to count occurrences of. - * @return The number of occurrences. + * @param string + * The string to count occurrences within. + * + * @param c + * The character to count occurrences of. + * + * @return + * The number of occurrences. */ -int guac_count_occurrences(const char* string, char c); +size_t guac_count_occurrences(const char* string, char c); /** * Splits a string into a newly-allocated array of strings. The array itself - * and each string within the array will eventually need to be freed. The array - * is NULL-terminated. + * and each string within the array will eventually need to be freed through + * calls to guac_mem_free(). The array is NULL-terminated. + * + * @param string + * The string to split. + * + * @param delim + * The character which separates individual substrings within the + * given string. * - * @param string The string to split. - * @param delim The character which separates individual substrings within the - * given string. - * @return A newly-allocated, NULL-terminated array of strings. + * @return + * A newly-allocated, NULL-terminated array of strings. */ char** guac_split(const char* string, char delim); diff --git a/src/common/common/surface.h b/src/common/common/surface.h index b43dcaaf5..09e5b0d5b 100644 --- a/src/common/common/surface.h +++ b/src/common/common/surface.h @@ -490,14 +490,14 @@ void guac_common_surface_flush(guac_common_surface* surface); * @param surface * The surface to duplicate. * - * @param user - * The user receiving the surface. + * @param client + * The client whos users are receiving the surface. * * @param socket * The socket over which the surface contents should be sent. */ -void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, - guac_socket* socket); +void guac_common_surface_dup(guac_common_surface* surface, + guac_client* client, guac_socket* socket); /** * Declares that the given surface should receive touch events. By default, diff --git a/src/common/cursor.c b/src/common/cursor.c index b70134d81..2a1650fd2 100644 --- a/src/common/cursor.c +++ b/src/common/cursor.c @@ -26,11 +26,13 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -47,7 +49,7 @@ */ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { - guac_common_cursor* cursor = malloc(sizeof(guac_common_cursor)); + guac_common_cursor* cursor = guac_mem_alloc(sizeof(guac_common_cursor)); if (cursor == NULL) return NULL; @@ -57,7 +59,7 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { /* Allocate initial image buffer */ cursor->image_buffer_size = GUAC_COMMON_CURSOR_DEFAULT_SIZE; - cursor->image_buffer = malloc(cursor->image_buffer_size); + cursor->image_buffer = guac_mem_alloc(cursor->image_buffer_size); /* No cursor image yet */ cursor->width = 0; @@ -74,18 +76,22 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { cursor->x = 0; cursor->y = 0; + pthread_mutex_init(&(cursor->_lock), NULL); + return cursor; } void guac_common_cursor_free(guac_common_cursor* cursor) { + pthread_mutex_destroy(&(cursor->_lock)); + guac_client* client = cursor->client; guac_layer* buffer = cursor->buffer; cairo_surface_t* surface = cursor->surface; /* Free image buffer and surface */ - free(cursor->image_buffer); + guac_mem_free(cursor->image_buffer); if (surface != NULL) cairo_surface_destroy(surface); @@ -95,12 +101,14 @@ void guac_common_cursor_free(guac_common_cursor* cursor) { /* Return buffer to pool */ guac_client_free_buffer(client, buffer); - free(cursor); + guac_mem_free(cursor); } -void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, - guac_socket* socket) { +void guac_common_cursor_dup( + guac_common_cursor* cursor, guac_client* client, guac_socket* socket) { + + pthread_mutex_lock(&(cursor->_lock)); /* Synchronize location */ guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask, @@ -111,7 +119,7 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, guac_protocol_send_size(socket, cursor->buffer, cursor->width, cursor->height); - guac_user_stream_png(user, socket, GUAC_COMP_SRC, + guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor->buffer, 0, 0, cursor->surface); guac_protocol_send_cursor(socket, @@ -119,6 +127,8 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user, cursor->buffer, 0, 0, cursor->width, cursor->height); } + pthread_mutex_unlock(&(cursor->_lock)); + guac_socket_flush(socket); } @@ -154,6 +164,8 @@ static void* guac_common_cursor_broadcast_state(guac_user* user, void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, int x, int y, int button_mask) { + pthread_mutex_lock(&(cursor->_lock)); + /* Update current user of cursor */ cursor->user = user; @@ -169,6 +181,8 @@ void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, guac_client_foreach_user(cursor->client, guac_common_cursor_broadcast_state, cursor); + pthread_mutex_unlock(&(cursor->_lock)); + } /** @@ -193,17 +207,17 @@ void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, static void guac_common_cursor_resize(guac_common_cursor* cursor, int width, int height, int stride) { - int minimum_size = height * stride; + size_t minimum_size = guac_mem_ckd_mul_or_die(height, stride); /* Grow image buffer if necessary */ if (cursor->image_buffer_size < minimum_size) { /* Calculate new size */ - cursor->image_buffer_size = minimum_size*2; + cursor->image_buffer_size = guac_mem_ckd_mul_or_die(minimum_size, 2); /* Destructively reallocate image buffer */ - free(cursor->image_buffer); - cursor->image_buffer = malloc(cursor->image_buffer_size); + guac_mem_free(cursor->image_buffer); + cursor->image_buffer = guac_mem_alloc(cursor->image_buffer_size); } @@ -212,6 +226,8 @@ static void guac_common_cursor_resize(guac_common_cursor* cursor, void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy, unsigned const char* data, int width, int height, int stride) { + pthread_mutex_lock(&(cursor->_lock)); + /* Copy image data */ guac_common_cursor_resize(cursor, width, height, stride); memcpy(cursor->image_buffer, data, height * stride); @@ -242,6 +258,8 @@ void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy, guac_socket_flush(cursor->client->socket); + pthread_mutex_unlock(&(cursor->_lock)); + } void guac_common_cursor_set_surface(guac_common_cursor* cursor, int hx, int hy, @@ -298,9 +316,13 @@ void guac_common_cursor_set_blank(guac_common_cursor* cursor) { void guac_common_cursor_remove_user(guac_common_cursor* cursor, guac_user* user) { + pthread_mutex_lock(&(cursor->_lock)); + /* Disassociate from given user */ if (cursor->user == user) cursor->user = NULL; + pthread_mutex_unlock(&(cursor->_lock)); + } diff --git a/src/common/display.c b/src/common/display.c index e8465e443..03ffb37e6 100644 --- a/src/common/display.c +++ b/src/common/display.c @@ -22,6 +22,7 @@ #include "common/surface.h" #include +#include #include #include @@ -37,20 +38,20 @@ * The head element of the linked list of layers to synchronize, which may * be NULL if the list is currently empty. * - * @param user - * The user receiving the layers. + * @param client + * The client associated with the users receiving the layers. * * @param socket * The socket over which each layer should be sent. */ static void guac_common_display_dup_layers(guac_common_display_layer* layers, - guac_user* user, guac_socket* socket) { + guac_client* client, guac_socket* socket) { guac_common_display_layer* current = layers; /* Synchronize all surfaces in given list */ while (current != NULL) { - guac_common_surface_dup(current->surface, user, socket); + guac_common_surface_dup(current->surface, client, socket); current = current->next; } @@ -92,7 +93,7 @@ static void guac_common_display_free_layers(guac_common_display_layer* layers, guac_client_free_layer(client, layer); /* Free current element and advance to next */ - free(current); + guac_mem_free(current); current = next; } @@ -119,14 +120,14 @@ guac_common_display* guac_common_display_alloc(guac_client* client, int width, int height) { /* Allocate display */ - guac_common_display* display = malloc(sizeof(guac_common_display)); + guac_common_display* display = guac_mem_alloc(sizeof(guac_common_display)); if (display == NULL) return NULL; /* Allocate shared cursor */ display->cursor = guac_common_cursor_alloc(client); if (display->cursor == NULL) { - free(display); + guac_mem_free(display); return NULL; } @@ -159,26 +160,25 @@ void guac_common_display_free(guac_common_display* display) { guac_common_display_free_layers(display->layers, display->client); pthread_mutex_destroy(&display->_lock); - free(display); + guac_mem_free(display); } -void guac_common_display_dup(guac_common_display* display, guac_user* user, +void guac_common_display_dup( + guac_common_display* display, guac_client* client, guac_socket* socket) { - guac_client* client = user->client; - pthread_mutex_lock(&display->_lock); /* Sunchronize shared cursor */ - guac_common_cursor_dup(display->cursor, user, socket); + guac_common_cursor_dup(display->cursor, client, socket); /* Synchronize default surface */ - guac_common_surface_dup(display->default_surface, user, socket); + guac_common_surface_dup(display->default_surface, client, socket); /* Synchronize all layers and buffers */ - guac_common_display_dup_layers(display->layers, user, socket); - guac_common_display_dup_layers(display->buffers, user, socket); + guac_common_display_dup_layers(display->layers, client, socket); + guac_common_display_dup_layers(display->buffers, client, socket); /* Sends a sync instruction to mark the boundary of the first frame */ guac_protocol_send_sync(socket, client->last_sent_timestamp, 1); @@ -256,7 +256,7 @@ static guac_common_display_layer* guac_common_display_add_layer( guac_common_display_layer* old_head = *head; guac_common_display_layer* display_layer = - malloc(sizeof(guac_common_display_layer)); + guac_mem_alloc(sizeof(guac_common_display_layer)); /* Init layer/surface pair */ display_layer->layer = layer; @@ -365,7 +365,7 @@ void guac_common_display_free_layer(guac_common_display* display, guac_client_free_layer(display->client, display_layer->layer); /* Free list element */ - free(display_layer); + guac_mem_free(display_layer); pthread_mutex_unlock(&display->_lock); @@ -384,7 +384,7 @@ void guac_common_display_free_buffer(guac_common_display* display, guac_client_free_buffer(display->client, display_buffer->layer); /* Free list element */ - free(display_buffer); + guac_mem_free(display_buffer); pthread_mutex_unlock(&display->_lock); diff --git a/src/common/list.c b/src/common/list.c index 072ef89fa..f10124377 100644 --- a/src/common/list.c +++ b/src/common/list.c @@ -20,12 +20,14 @@ #include "config.h" #include "common/list.h" +#include + #include #include guac_common_list* guac_common_list_alloc() { - guac_common_list* list = malloc(sizeof(guac_common_list)); + guac_common_list* list = guac_mem_alloc(sizeof(guac_common_list)); pthread_mutex_init(&list->_lock, NULL); list->head = NULL; @@ -34,8 +36,26 @@ guac_common_list* guac_common_list_alloc() { } -void guac_common_list_free(guac_common_list* list) { - free(list); +void guac_common_list_free( + guac_common_list* list, + guac_common_list_element_free_handler* free_element_handler) { + + /* Free every element of the list */ + guac_common_list_element* element = list->head; + while(element != NULL) { + + guac_common_list_element* next = element->next; + + if (free_element_handler != NULL) + free_element_handler(element->data); + + guac_mem_free(element); + element = next; + } + + /* Free the list itself */ + guac_mem_free(list); + } guac_common_list_element* guac_common_list_add(guac_common_list* list, @@ -43,7 +63,7 @@ guac_common_list_element* guac_common_list_add(guac_common_list* list, /* Allocate element, initialize as new head */ guac_common_list_element* element = - malloc(sizeof(guac_common_list_element)); + guac_mem_alloc(sizeof(guac_common_list_element)); element->data = data; element->next = list->head; element->_ptr = &(list->head); @@ -67,7 +87,7 @@ void guac_common_list_remove(guac_common_list* list, if (element->next != NULL) element->next->_ptr = element->_ptr; - free(element); + guac_mem_free(element); } diff --git a/src/common/string.c b/src/common/string.c index b2479998c..87bfff5cf 100644 --- a/src/common/string.c +++ b/src/common/string.c @@ -21,18 +21,20 @@ #include "common/string.h" +#include + #include #include -int guac_count_occurrences(const char* string, char c) { +size_t guac_count_occurrences(const char* string, char c) { - int count = 0; + size_t count = 0; while (*string != 0) { /* Count each occurrence */ if (*string == c) - count++; + count = guac_mem_ckd_add_or_die(count, 1); /* Next character */ string++; @@ -45,17 +47,18 @@ int guac_count_occurrences(const char* string, char c) { char** guac_split(const char* string, char delim) { - int i = 0; + size_t i = 0; - int token_count = guac_count_occurrences(string, delim) + 1; + /* Calculate number of tokens present based on number of delimiters */ + size_t token_count = guac_mem_ckd_add_or_die(guac_count_occurrences(string, delim), 1); const char* token_start = string; - /* Allocate space for tokens */ - char** tokens = malloc(sizeof(char*) * (token_count+1)); + /* Allocate space for tokens, including NULL terminator */ + char** tokens = guac_mem_alloc(sizeof(char*), guac_mem_ckd_add_or_die(token_count, 1)); do { - int length; + size_t length; char* token; /* Find end of token */ @@ -66,7 +69,7 @@ char** guac_split(const char* string, char delim) { length = string - token_start; /* Allocate space for token and NULL terminator */ - tokens[i++] = token = malloc(length + 1); + tokens[i++] = token = guac_mem_alloc(guac_mem_ckd_add_or_die(length, 1)); /* Copy token, store null */ memcpy(token, token_start, length); diff --git a/src/common/surface.c b/src/common/surface.c index 61b77c7d2..9334f0ce5 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -396,7 +397,7 @@ static unsigned int __guac_common_surface_calculate_framerate( int x, y; /* Calculate heat map dimensions */ - int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); /* Calculate minimum X/Y coordinates intersecting given rect */ int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; @@ -615,7 +616,7 @@ static void __guac_common_surface_touch_rect(guac_common_surface* surface, int x, y; /* Calculate heat map dimensions */ - int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); /* Calculate minimum X/Y coordinates intersecting given rect */ int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; @@ -1227,11 +1228,11 @@ guac_common_surface* guac_common_surface_alloc(guac_client* client, guac_socket* socket, const guac_layer* layer, int w, int h) { /* Calculate heat map dimensions */ - int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); - int heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); + size_t heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); /* Init surface */ - guac_common_surface* surface = calloc(1, sizeof(guac_common_surface)); + guac_common_surface* surface = guac_mem_zalloc(sizeof(guac_common_surface)); surface->client = client; surface->socket = socket; surface->layer = layer; @@ -1244,10 +1245,10 @@ guac_common_surface* guac_common_surface_alloc(guac_client* client, /* Create corresponding Cairo surface */ surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); - surface->buffer = calloc(h, surface->stride); + surface->buffer = guac_mem_zalloc(h, surface->stride); /* Create corresponding heat map */ - surface->heat_map = calloc(heat_width * heat_height, + surface->heat_map = guac_mem_zalloc(heat_width, heat_height, sizeof(guac_common_surface_heat_cell)); /* Reset clipping rect */ @@ -1274,9 +1275,9 @@ void guac_common_surface_free(guac_common_surface* surface) { pthread_mutex_destroy(&surface->_lock); - free(surface->heat_map); - free(surface->buffer); - free(surface); + guac_mem_free(surface->heat_map); + guac_mem_free(surface->buffer); + guac_mem_free(surface); } @@ -1299,8 +1300,8 @@ void guac_common_surface_resize(guac_common_surface* surface, int w, int h) { int sy = 0; /* Calculate heat map dimensions */ - int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); - int heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); + size_t heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); + size_t heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); /* Copy old surface data */ old_buffer = surface->buffer; @@ -1311,7 +1312,7 @@ void guac_common_surface_resize(guac_common_surface* surface, int w, int h) { surface->width = w; surface->height = h; surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); - surface->buffer = calloc(h, surface->stride); + surface->buffer = guac_mem_zalloc(h, surface->stride); __guac_common_bound_rect(surface, &surface->clip_rect, NULL, NULL); /* Copy relevant old data */ @@ -1319,11 +1320,11 @@ void guac_common_surface_resize(guac_common_surface* surface, int w, int h) { __guac_common_surface_put(old_buffer, old_stride, &sx, &sy, surface, &old_rect, 1); /* Free old data */ - free(old_buffer); + guac_mem_free(old_buffer); /* Allocate completely new heat map (can safely discard old stats) */ - free(surface->heat_map); - surface->heat_map = calloc(heat_width * heat_height, + guac_mem_free(surface->heat_map); + surface->heat_map = guac_mem_alloc(heat_width, heat_height, sizeof(guac_common_surface_heat_cell)); /* Resize dirty rect to fit new surface dimensions */ @@ -1989,8 +1990,8 @@ void guac_common_surface_flush(guac_common_surface* surface) { } -void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, - guac_socket* socket) { +void guac_common_surface_dup(guac_common_surface* surface, + guac_client* client, guac_socket* socket) { pthread_mutex_lock(&surface->_lock); @@ -2028,7 +2029,7 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, surface->width, surface->height, surface->stride); /* Send PNG for rect */ - guac_user_stream_png(user, socket, GUAC_COMP_OVER, surface->layer, + guac_client_stream_png(client, socket, GUAC_COMP_OVER, surface->layer, 0, 0, rect); cairo_surface_destroy(rect); @@ -2038,4 +2039,3 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, pthread_mutex_unlock(&surface->_lock); } - diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am index 526577b21..27ac75cd1 100644 --- a/src/common/tests/Makefile.am +++ b/src/common/tests/Makefile.am @@ -50,11 +50,13 @@ test_common_SOURCES = \ test_common_CFLAGS = \ -Werror -Wall -pedantic \ - @COMMON_INCLUDE@ + @COMMON_INCLUDE@ \ + @LIBGUAC_INCLUDE@ test_common_LDADD = \ @COMMON_LTLIB@ \ - @CUNIT_LIBS@ + @CUNIT_LIBS@ \ + @LIBGUAC_LTLIB@ # # Autogenerate test runner diff --git a/src/common/tests/string/split.c b/src/common/tests/string/split.c index 36f8f1917..d7740188f 100644 --- a/src/common/tests/string/split.c +++ b/src/common/tests/string/split.c @@ -20,6 +20,7 @@ #include "common/string.h" #include +#include #include @@ -52,12 +53,12 @@ void test_string__split() { CU_ASSERT_PTR_NULL(tokens[5]); /* Clean up */ - free(tokens[0]); - free(tokens[1]); - free(tokens[2]); - free(tokens[3]); - free(tokens[4]); - free(tokens); + guac_mem_free(tokens[0]); + guac_mem_free(tokens[1]); + guac_mem_free(tokens[2]); + guac_mem_free(tokens[3]); + guac_mem_free(tokens[4]); + guac_mem_free(tokens); } diff --git a/src/guacd/conf-args.c b/src/guacd/conf-args.c index 239eb3ae5..c45097373 100644 --- a/src/guacd/conf-args.c +++ b/src/guacd/conf-args.c @@ -23,6 +23,9 @@ #include "conf-args.h" #include "conf-parse.h" +#include +#include + #include #include #include @@ -36,14 +39,14 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) { /* -l: Bind port */ if (opt == 'l') { - free(config->bind_port); - config->bind_port = strdup(optarg); + guac_mem_free(config->bind_port); + config->bind_port = guac_strdup(optarg); } /* -b: Bind host */ else if (opt == 'b') { - free(config->bind_host); - config->bind_host = strdup(optarg); + guac_mem_free(config->bind_host); + config->bind_host = guac_strdup(optarg); } /* -f: Run in foreground */ @@ -58,8 +61,8 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) { /* -p: PID file */ else if (opt == 'p') { - free(config->pidfile); - config->pidfile = strdup(optarg); + guac_mem_free(config->pidfile); + config->pidfile = guac_strdup(optarg); } /* -L: Log level */ @@ -79,14 +82,14 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) { #ifdef ENABLE_SSL /* -C SSL certificate */ else if (opt == 'C') { - free(config->cert_file); - config->cert_file = strdup(optarg); + guac_mem_free(config->cert_file); + config->cert_file = guac_strdup(optarg); } /* -K SSL key */ else if (opt == 'K') { - free(config->key_file); - config->key_file = strdup(optarg); + guac_mem_free(config->key_file); + config->key_file = guac_strdup(optarg); } #else else if (opt == 'C' || opt == 'K') { diff --git a/src/guacd/conf-file.c b/src/guacd/conf-file.c index 1f1e56690..ed74dcbcd 100644 --- a/src/guacd/conf-file.c +++ b/src/guacd/conf-file.c @@ -24,6 +24,8 @@ #include "conf-parse.h" #include +#include +#include #include #include @@ -48,15 +50,15 @@ static int guacd_conf_callback(const char* section, const char* param, const cha /* Bind host */ if (strcmp(param, "bind_host") == 0) { - free(config->bind_host); - config->bind_host = strdup(value); + guac_mem_free(config->bind_host); + config->bind_host = guac_strdup(value); return 0; } /* Bind port */ else if (strcmp(param, "bind_port") == 0) { - free(config->bind_port); - config->bind_port = strdup(value); + guac_mem_free(config->bind_port); + config->bind_port = guac_strdup(value); return 0; } @@ -67,8 +69,8 @@ static int guacd_conf_callback(const char* section, const char* param, const cha /* PID file */ if (strcmp(param, "pid_file") == 0) { - free(config->pidfile); - config->pidfile = strdup(value); + guac_mem_free(config->pidfile); + config->pidfile = guac_strdup(value); return 0; } @@ -96,15 +98,15 @@ static int guacd_conf_callback(const char* section, const char* param, const cha #ifdef ENABLE_SSL /* SSL certificate */ if (strcmp(param, "server_certificate") == 0) { - free(config->cert_file); - config->cert_file = strdup(value); + guac_mem_free(config->cert_file); + config->cert_file = guac_strdup(value); return 0; } /* SSL key */ else if (strcmp(param, "server_key") == 0) { - free(config->key_file); - config->key_file = strdup(value); + guac_mem_free(config->key_file); + config->key_file = guac_strdup(value); return 0; } #else @@ -171,13 +173,13 @@ int guacd_conf_parse_file(guacd_config* conf, int fd) { guacd_config* guacd_conf_load() { - guacd_config* conf = malloc(sizeof(guacd_config)); + guacd_config* conf = guac_mem_alloc(sizeof(guacd_config)); if (conf == NULL) return NULL; /* Load defaults */ - conf->bind_host = strdup(GUACD_DEFAULT_BIND_HOST); - conf->bind_port = strdup(GUACD_DEFAULT_BIND_PORT); + conf->bind_host = guac_strdup(GUACD_DEFAULT_BIND_HOST); + conf->bind_port = guac_strdup(GUACD_DEFAULT_BIND_PORT); conf->pidfile = NULL; conf->foreground = 0; conf->print_version = 0; @@ -197,7 +199,7 @@ guacd_config* guacd_conf_load() { if (retval != 0) { fprintf(stderr, "Unable to parse \"" GUACD_CONF_FILE "\".\n"); - free(conf); + guac_mem_free(conf); return NULL; } @@ -206,7 +208,7 @@ guacd_config* guacd_conf_load() { /* Notify of errors preventing reading */ else if (errno != ENOENT) { fprintf(stderr, "Unable to open \"" GUACD_CONF_FILE "\": %s\n", strerror(errno)); - free(conf); + guac_mem_free(conf); return NULL; } diff --git a/src/guacd/connection.c b/src/guacd/connection.c index a5b98adfa..2c8c00d65 100644 --- a/src/guacd/connection.c +++ b/src/guacd/connection.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -67,13 +68,14 @@ static int __write_all(int fd, char* buffer, int length) { /* Repeatedly write() until all data is written */ - while (length > 0) { + int remaining_length = length; + while (remaining_length > 0) { - int written = write(fd, buffer, length); + int written = write(fd, buffer, remaining_length); if (written < 0) return -1; - length -= written; + remaining_length -= written; buffer += written; } @@ -150,7 +152,7 @@ void* guacd_connection_io_thread(void* data) { /* Clean up */ guac_socket_free(params->socket); close(params->fd); - free(params); + guac_mem_free(params); return NULL; @@ -201,7 +203,7 @@ static int guacd_add_user(guacd_proc* proc, guac_parser* parser, guac_socket* so /* Close our end of the process file descriptor */ close(proc_fd); - guacd_connection_io_thread_params* params = malloc(sizeof(guacd_connection_io_thread_params)); + guacd_connection_io_thread_params* params = guac_mem_alloc(sizeof(guacd_connection_io_thread_params)); params->parser = parser; params->socket = socket; params->fd = user_fd; @@ -352,7 +354,7 @@ static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) { /* Clean up */ close(proc->fd_socket); - free(proc); + guac_mem_free(proc); } @@ -380,7 +382,7 @@ void* guacd_connection_thread(void* data) { if (socket == NULL) { guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to set up SSL/TLS"); close(connected_socket_fd); - free(params); + guac_mem_free(params); return NULL; } } @@ -396,7 +398,7 @@ void* guacd_connection_thread(void* data) { if (guacd_route_connection(map, socket)) guac_socket_free(socket); - free(params); + guac_mem_free(params); return NULL; } diff --git a/src/guacd/daemon.c b/src/guacd/daemon.c index 8bf303518..e73e834ef 100644 --- a/src/guacd/daemon.c +++ b/src/guacd/daemon.c @@ -26,6 +26,8 @@ #include "log.h" #include "proc-map.h" +#include + #ifdef ENABLE_SSL #include #endif @@ -43,6 +45,7 @@ #include #include #include +#include #include #define GUACD_DEV_NULL "/dev/null" @@ -211,8 +214,7 @@ static void guacd_openssl_init_locks(int count) { int i; /* Allocate required number of locks */ - guacd_openssl_locks = - malloc(sizeof(pthread_mutex_t) * count); + guacd_openssl_locks = guac_mem_alloc(sizeof(pthread_mutex_t), count); /* Initialize each lock */ for (i=0; i < count; i++) @@ -239,12 +241,55 @@ static void guacd_openssl_free_locks(int count) { pthread_mutex_destroy(&(guacd_openssl_locks[i])); /* Free lock array */ - free(guacd_openssl_locks); + guac_mem_free(guacd_openssl_locks); } #endif #endif +/** + * A flag that, if non-zero, indicates that the daemon should immediately stop + * accepting new connections. + */ +int stop_everything = 0; + +/** + * A signal handler that will set a flag telling the daemon to immediately stop + * accepting new connections. Note that the signal itself will cause any pending + * accept() calls to be interrupted, causing the daemon to unlock and begin + * cleaning up. + * + * @param signal + * The signal that was received. Unused in this function since only + * signals that should result in stopping the daemon should invoke this. + */ +static void signal_stop_handler(int signal) { + + /* Instruct the daemon to stop accepting new connections */ + stop_everything = 1; + +} + +/** + * A callback for guacd_proc_map_foreach which will stop every process in the + * map. + * + * @param proc + * The guacd process to stop. + * + * @param data + * Unused. + */ +static void stop_process_callback(guacd_proc* proc, void* data) { + + guacd_log(GUAC_LOG_DEBUG, + "Killing connection %s (%i)\n", + proc->client->connection_id, (int) proc->pid); + + guacd_proc_stop(proc); + +} + int main(int argc, char* argv[]) { /* Server */ @@ -457,6 +502,11 @@ int main(int argc, char* argv[]) { "Child processes may pile up in the process table."); } + /* Clean up and exit if SIGINT or SIGTERM signals are caught */ + struct sigaction signal_stop_action = { .sa_handler = signal_stop_handler }; + sigaction(SIGINT, &signal_stop_action, NULL); + sigaction(SIGTERM, &signal_stop_action, NULL); + /* Log listening status */ guacd_log(GUAC_LOG_INFO, "Listening on host %s, port %s", bound_address, bound_port); @@ -470,7 +520,7 @@ int main(int argc, char* argv[]) { } /* Daemon loop */ - for (;;) { + while (!stop_everything) { pthread_t child_thread; @@ -480,12 +530,15 @@ int main(int argc, char* argv[]) { (struct sockaddr*) &client_addr, &client_addr_len); if (connected_socket_fd < 0) { - guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno)); + if (errno == EINTR) + guacd_log(GUAC_LOG_DEBUG, "Accepting of further client connection(s) interrupted by signal."); + else + guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno)); continue; } /* Create parameters for connection thread */ - guacd_connection_thread_params* params = malloc(sizeof(guacd_connection_thread_params)); + guacd_connection_thread_params* params = guac_mem_alloc(sizeof(guacd_connection_thread_params)); if (params == NULL) { guacd_log(GUAC_LOG_ERROR, "Could not create connection thread: %s", strerror(errno)); continue; @@ -504,6 +557,26 @@ int main(int argc, char* argv[]) { } + /* Stop all connections */ + if (map != NULL) { + + guacd_proc_map_foreach(map, stop_process_callback, NULL); + + /* + * FIXME: Clean up the proc map. This is not as straightforward as it + * might seem, since the detached connection threads will attempt to + * remove the connection proccesses from the map when they complete, + * which will also happen upon shutdown. So there's a good chance that + * this map cleanup will happen at the same time as the thread cleanup. + * The map _does_ have locking mechanisms in place for ensuring thread + * safety, but cleaning up the map also requires destroying those locks, + * making them unusable for this case. One potential fix could be to + * join every one of the connection threads instead of detaching them, + * but that does complicate the cleanup of thread resources. + */ + + } + /* Close socket */ if (close(socket_fd) < 0) { guacd_log(GUAC_LOG_ERROR, "Could not close socket: %s", strerror(errno)); diff --git a/src/guacd/proc-map.c b/src/guacd/proc-map.c index ac87196ad..ea5f2e9a7 100644 --- a/src/guacd/proc-map.c +++ b/src/guacd/proc-map.c @@ -23,10 +23,29 @@ #include "proc-map.h" #include +#include #include #include +/** + * A value to be stored in the buckets, containing the guacd proc itself, + * as well as a link to the element in the list of all guacd processes. + */ +typedef struct guacd_proc_map_entry { + + /** + * The guacd process itself. + */ + guacd_proc* proc; + + /** + * A pointer to the corresponding entry in the list of all processes. + */ + guac_common_list_element* element; + +} guacd_proc_map_entry; + /** * Returns a hash code based on the given connection ID. * @@ -98,7 +117,7 @@ static guac_common_list_element* __guacd_proc_find(guac_common_list* bucket, while (current != NULL) { /* Check connection ID */ - guacd_proc* proc = (guacd_proc*) current->data; + guacd_proc* proc = ((guacd_proc_map_entry*) current->data)->proc; if (strcmp(proc->client->connection_id, id) == 0) break; @@ -111,7 +130,8 @@ static guac_common_list_element* __guacd_proc_find(guac_common_list* bucket, guacd_proc_map* guacd_proc_map_alloc() { - guacd_proc_map* map = malloc(sizeof(guacd_proc_map)); + guacd_proc_map* map = guac_mem_alloc(sizeof(guacd_proc_map)); + map->processes = guac_common_list_alloc(); guac_common_list** current; int i; @@ -140,8 +160,18 @@ int guacd_proc_map_add(guacd_proc_map* map, guacd_proc* proc) { /* If no such element, we can add the new client successfully */ if (found == NULL) { - guac_common_list_add(bucket, proc); + + guacd_proc_map_entry* entry = guac_mem_alloc(sizeof(guacd_proc_map_entry)); + + guac_common_list_lock(map->processes); + entry->element = guac_common_list_add(map->processes, proc); + guac_common_list_unlock(map->processes); + + entry->proc = proc; + + guac_common_list_add(bucket, entry); guac_common_list_unlock(bucket); + return 0; } @@ -168,7 +198,7 @@ guacd_proc* guacd_proc_map_retrieve(guacd_proc_map* map, const char* id) { return NULL; } - proc = (guacd_proc*) found->data; + proc = ((guacd_proc_map_entry*) found->data)->proc; guac_common_list_unlock(bucket); return proc; @@ -192,11 +222,50 @@ guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id) { return NULL; } - proc = (guacd_proc*) found->data; + guacd_proc_map_entry* entry = (guacd_proc_map_entry*) found->data; + + /* Find and remove the key from the process list */ + guac_common_list_lock(map->processes); + guac_common_list_remove(map->processes, entry->element); + guac_common_list_unlock(map->processes); + + proc = entry->proc; guac_common_list_remove(bucket, found); + free (entry); + guac_common_list_unlock(bucket); return proc; } +void guacd_proc_map_foreach(guacd_proc_map* map, + guacd_proc_map_foreach_callback* callback, void* data) { + + guac_common_list* list = map->processes; + + guac_common_list_lock(list); + + /* Invoke the callback for every element in the list */ + guac_common_list_element* element; + for (element = list->head; element != NULL; element = element->next) + callback((guacd_proc*) element->data, data); + + guac_common_list_unlock(list); + +} + +void guacd_proc_map_free(guacd_proc_map* map) { + + /* Free the list of all processes */ + guac_common_list_free(map->processes, NULL); + + /* Free each bucket */ + guac_common_list** buckets = map->__buckets; + int i; + for (i = 0; i < GUACD_PROC_MAP_BUCKETS; i++) { + guac_common_list_free(*(buckets + i), free); + } + +} + diff --git a/src/guacd/proc-map.h b/src/guacd/proc-map.h index a24218855..07ebbb97c 100644 --- a/src/guacd/proc-map.h +++ b/src/guacd/proc-map.h @@ -49,6 +49,12 @@ typedef struct guacd_proc_map { */ guac_common_list* __buckets[GUACD_PROC_MAP_BUCKETS]; + /** + * All processes present in the map. For internal use only. To operate on these + * keys, use guacd_proc_map_foreach(). + */ + guac_common_list* processes; + } guacd_proc_map; /** @@ -60,6 +66,16 @@ typedef struct guacd_proc_map { */ guacd_proc_map* guacd_proc_map_alloc(); +/** + * Free all resources allocated for the provided map. Note that this function + * will _not_ clean up the processes contained within the map, only the map + * itself. + * + * @param map + * The guacd proc map to free. + */ +void guacd_proc_map_free(guacd_proc_map* map); + /** * Adds the given process to the client process map. On success, zero is * returned. If adding the client fails (due to lack of space, or duplicate @@ -112,5 +128,36 @@ guacd_proc* guacd_proc_map_retrieve(guacd_proc_map* map, const char* id); */ guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id); +/** + * A callback function that will be invoked with every guacd_proc stored + * in the provided map, when provided to guacd_proc_map_foreach(), along with + * any provided arbitrary data. + * + * @param proc + * The current guacd process. + * + * @param data + * The arbitrary data provided to guacd_proc_map_foreach(). + */ +typedef void guacd_proc_map_foreach_callback(guacd_proc* proc, void* data); + +/** + * Invoke the provided callback with any provided arbitrary data and each guacd + * proc contained in the provided map, once each and in no particular order. + * + * @param map + * The map from which all guacd processes should be extracted and provided + * to the callback. + * + * @param callback + * The callback function to be invoked once with each guacd process + * contained in the provided map. + * + * @param data + * Arbitrary data to be provided to the callback function. + */ +void guacd_proc_map_foreach(guacd_proc_map* map, + guacd_proc_map_foreach_callback* callback, void* data); + #endif diff --git a/src/guacd/proc.c b/src/guacd/proc.c index 9641ac05a..28ccac1c1 100644 --- a/src/guacd/proc.c +++ b/src/guacd/proc.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -106,7 +107,7 @@ static void* guacd_user_thread(void* data) { /* Clean up */ guac_socket_free(socket); guac_user_free(user); - free(params); + guac_mem_free(params); return NULL; @@ -130,7 +131,7 @@ static void* guacd_user_thread(void* data) { */ static void guacd_proc_add_user(guacd_proc* proc, int fd, int owner) { - guacd_user_thread_params* params = malloc(sizeof(guacd_user_thread_params)); + guacd_user_thread_params* params = guac_mem_alloc(sizeof(guacd_user_thread_params)); params->proc = proc; params->fd = fd; params->owner = owner; @@ -287,6 +288,26 @@ static int guacd_timed_client_free(guac_client* client, int timeout) { return !free_operation.completed; } +/** + * A reference to the current guacd process. + */ +guacd_proc* guacd_proc_self = NULL; + +/** + * A signal handler that will be invoked when a signal is caught telling this + * guacd process to immediately exit. + * + * @param signal + * The signal that was received. Unused in this function since only + * signals that should result in stopping the proc should invoke this. + */ +static void signal_stop_handler(int signal) { + + /* Stop the current guacd proc */ + guacd_proc_stop(guacd_proc_self); + +} + /** * Starts protocol-specific handling on the given process by loading the client * plugin for that protocol. This function does NOT return. It initializes the @@ -333,6 +354,13 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) { /* Enable keep alive on the broadcast socket */ guac_socket_require_keep_alive(client->socket); + guacd_proc_self = proc; + + /* Clean up and exit if SIGINT or SIGTERM signals are caught */ + struct sigaction signal_stop_action = { .sa_handler = signal_stop_handler }; + sigaction(SIGINT, &signal_stop_action, NULL); + sigaction(SIGTERM, &signal_stop_action, NULL); + /* Add each received file descriptor as a new user */ int received_fd; while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) { @@ -382,7 +410,7 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) { /* Free up all internal resources outside the client */ close(proc->fd_socket); - free(proc); + guac_mem_free(proc); exit(result); @@ -402,7 +430,7 @@ guacd_proc* guacd_create_proc(const char* protocol) { int child_socket = sockets[1]; /* Allocate process */ - guacd_proc* proc = calloc(1, sizeof(guacd_proc)); + guacd_proc* proc = guac_mem_zalloc(sizeof(guacd_proc)); if (proc == NULL) { close(parent_socket); close(child_socket); @@ -415,7 +443,7 @@ guacd_proc* guacd_create_proc(const char* protocol) { guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to create client"); close(parent_socket); close(child_socket); - free(proc); + guac_mem_free(proc); return NULL; } @@ -429,7 +457,7 @@ guacd_proc* guacd_create_proc(const char* protocol) { close(parent_socket); close(child_socket); guac_client_free(proc->client); - free(proc); + guac_mem_free(proc); return NULL; } @@ -458,8 +486,43 @@ guacd_proc* guacd_create_proc(const char* protocol) { } +/** + * Kill the provided child guacd process. This function must be called by the + * parent process, and will block until all processes associated with the + * child process have terminated. + * + * @param proc + * The child guacd process to kill. + */ +static void guacd_proc_kill(guacd_proc* proc) { + + /* Request orderly termination of process */ + if (kill(proc->pid, SIGTERM)) + guacd_log(GUAC_LOG_DEBUG, "Unable to request termination of " + "client process: %s ", strerror(errno)); + + /* Wait for all processes within process group to terminate */ + pid_t child_pid; + while ((child_pid = waitpid(-proc->pid, NULL, 0)) > 0 || errno == EINTR) { + guacd_log(GUAC_LOG_DEBUG, "Child process %i of connection \"%s\" has terminated", + child_pid, proc->client->connection_id); + } + + guacd_log(GUAC_LOG_DEBUG, "All child processes for connection \"%s\" have been terminated.", + proc->client->connection_id); + +} + void guacd_proc_stop(guacd_proc* proc) { + /* A non-zero PID means that this is the parent process */ + if (proc->pid != 0) { + guacd_proc_kill(proc); + return; + } + + /* Otherwise, this is the child process */ + /* Signal client to stop */ guac_client_stop(proc->client); @@ -473,4 +536,3 @@ void guacd_proc_stop(guacd_proc* proc) { close(proc->fd_socket); } - diff --git a/src/guacenc/buffer.c b/src/guacenc/buffer.c index 67fe17fe4..eace8aaf3 100644 --- a/src/guacenc/buffer.c +++ b/src/guacenc/buffer.c @@ -21,12 +21,13 @@ #include "buffer.h" #include +#include #include #include guacenc_buffer* guacenc_buffer_alloc() { - return calloc(1, sizeof(guacenc_buffer)); + return guac_mem_zalloc(sizeof(guacenc_buffer)); } /** @@ -52,7 +53,7 @@ static void guacenc_buffer_free_image(guacenc_buffer* buffer) { } /* Free image data (previously wrapped by Cairo surface */ - free(buffer->image); + guac_mem_free(buffer->image); buffer->image = NULL; } @@ -65,7 +66,7 @@ void guacenc_buffer_free(guacenc_buffer* buffer) { /* Free buffer and underlying image */ guacenc_buffer_free_image(buffer); - free(buffer); + guac_mem_free(buffer); } @@ -86,7 +87,7 @@ int guacenc_buffer_resize(guacenc_buffer* buffer, int width, int height) { /* Allocate data for new image */ int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); - unsigned char* image = calloc(1, stride*height); + unsigned char* image = guac_mem_zalloc(stride, height); /* Wrap data in surface */ cairo_surface_t* surface = cairo_image_surface_create_for_data(image, diff --git a/src/guacenc/cursor.c b/src/guacenc/cursor.c index d67e94611..e47f44c61 100644 --- a/src/guacenc/cursor.c +++ b/src/guacenc/cursor.c @@ -21,19 +21,21 @@ #include "buffer.h" #include "cursor.h" +#include + #include guacenc_cursor* guacenc_cursor_alloc() { /* Allocate new cursor */ - guacenc_cursor* cursor = (guacenc_cursor*) malloc(sizeof(guacenc_cursor)); + guacenc_cursor* cursor = (guacenc_cursor*) guac_mem_alloc(sizeof(guacenc_cursor)); if (cursor == NULL) return NULL; /* Allocate associated buffer (image) */ cursor->buffer = guacenc_buffer_alloc(); if (cursor->buffer == NULL) { - free(cursor); + guac_mem_free(cursor); return NULL; } @@ -53,7 +55,7 @@ void guacenc_cursor_free(guacenc_cursor* cursor) { /* Free underlying buffer */ guacenc_buffer_free(cursor->buffer); - free(cursor); + guac_mem_free(cursor); } diff --git a/src/guacenc/display.c b/src/guacenc/display.c index 094376cfa..e3be0b752 100644 --- a/src/guacenc/display.c +++ b/src/guacenc/display.c @@ -23,6 +23,7 @@ #include "video.h" #include +#include #include @@ -93,7 +94,7 @@ guacenc_display* guacenc_display_alloc(const char* path, const char* codec, /* Allocate display */ guacenc_display* display = - (guacenc_display*) calloc(1, sizeof(guacenc_display)); + (guacenc_display*) guac_mem_zalloc(sizeof(guacenc_display)); /* Associate display with video output */ display->output = video; @@ -131,7 +132,7 @@ int guacenc_display_free(guacenc_display* display) { /* Free cursor */ guacenc_cursor_free(display->cursor); - free(display); + guac_mem_free(display); return retval; } diff --git a/src/guacenc/ffmpeg-compat.c b/src/guacenc/ffmpeg-compat.c index 2412e4817..c893d9c8e 100644 --- a/src/guacenc/ffmpeg-compat.c +++ b/src/guacenc/ffmpeg-compat.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -106,10 +107,11 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) { AVCodecContext* context = video->context; /* Calculate appropriate buffer size */ - int length = FF_MIN_BUFFER_SIZE + 12 * context->width * context->height; + size_t length = guac_mem_ckd_add_or_die(FF_MIN_BUFFER_SIZE, + guac_mem_ckd_mul_or_die(12, context->width, context->height)); /* Allocate space for output */ - uint8_t* data = malloc(length); + uint8_t* data = guac_mem_alloc(length); if (data == NULL) return -1; @@ -118,19 +120,19 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) { if (used < 0) { guacenc_log(GUAC_LOG_WARNING, "Error encoding frame #%" PRId64, video->next_pts); - free(data); + guac_mem_free(data); return -1; } /* Report if no data needs to be written */ if (used == 0) { - free(data); + guac_mem_free(data); return 0; } /* Write data, logging any errors */ guacenc_write_packet(video, data, used); - free(data); + guac_mem_free(data); return 1; #else diff --git a/src/guacenc/image-stream.c b/src/guacenc/image-stream.c index 8789dc16d..6b617b5fe 100644 --- a/src/guacenc/image-stream.c +++ b/src/guacenc/image-stream.c @@ -29,6 +29,7 @@ #endif #include +#include #include #include @@ -67,7 +68,7 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, const char* mimetype, int x, int y) { /* Allocate stream */ - guacenc_image_stream* stream = malloc(sizeof(guacenc_image_stream)); + guacenc_image_stream* stream = guac_mem_alloc(sizeof(guacenc_image_stream)); if (stream == NULL) return NULL; @@ -83,7 +84,7 @@ guacenc_image_stream* guacenc_image_stream_alloc(int mask, int index, /* Allocate initial buffer */ stream->length = 0; stream->max_length = GUACENC_IMAGE_STREAM_INITIAL_LENGTH; - stream->buffer = (unsigned char*) malloc(stream->max_length); + stream->buffer = (unsigned char*) guac_mem_alloc(stream->max_length); return stream; @@ -96,11 +97,12 @@ int guacenc_image_stream_receive(guacenc_image_stream* stream, if (stream->max_length - stream->length < length) { /* Calculate a reasonable new max length guaranteed to fit buffer */ - int new_max_length = stream->max_length * 2 + length; + size_t new_max_length = guac_mem_ckd_add_or_die( + guac_mem_ckd_mul_or_die(stream->max_length, 2), length); /* Attempt to resize buffer */ unsigned char* new_buffer = - (unsigned char*) realloc(stream->buffer, new_max_length); + (unsigned char*) guac_mem_realloc(stream->buffer, new_max_length); if (new_buffer == NULL) return 1; @@ -158,10 +160,10 @@ int guacenc_image_stream_free(guacenc_image_stream* stream) { return 0; /* Free image buffer */ - free(stream->buffer); + guac_mem_free(stream->buffer); /* Free actual stream */ - free(stream); + guac_mem_free(stream); return 0; } diff --git a/src/guacenc/image-stream.h b/src/guacenc/image-stream.h index c73df8853..3547fc31c 100644 --- a/src/guacenc/image-stream.h +++ b/src/guacenc/image-stream.h @@ -25,6 +25,8 @@ #include +#include + /** * The initial number of bytes to allocate for the image data buffer. If this * buffer is not sufficiently large, it will be dynamically reallocated as it @@ -87,13 +89,13 @@ typedef struct guacenc_image_stream { /** * The number of bytes currently stored in the buffer. */ - int length; + size_t length; /** * The maximum number of bytes that can be stored in the current buffer * before it must be reallocated. */ - int max_length; + size_t max_length; /** * The decoder to use when decoding the raw data received along this diff --git a/src/guacenc/jpeg.c b/src/guacenc/jpeg.c index 4bf655f33..a472ae0b9 100644 --- a/src/guacenc/jpeg.c +++ b/src/guacenc/jpeg.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -109,7 +110,7 @@ cairo_surface_t* guacenc_jpeg_decoder(unsigned char* data, int length) { int height = cinfo.output_height; /* Allocate sufficient buffer space for one JPEG scanline */ - unsigned char* jpeg_scanline = malloc(width * 3); + unsigned char* jpeg_scanline = guac_mem_alloc(width, 3); /* Create blank Cairo surface (no transparency in JPEG) */ cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, @@ -135,7 +136,7 @@ cairo_surface_t* guacenc_jpeg_decoder(unsigned char* data, int length) { } /* Scanline buffer is no longer needed */ - free(jpeg_scanline); + guac_mem_free(jpeg_scanline); /* End decompression */ jpeg_finish_decompress(&cinfo); diff --git a/src/guacenc/layer.c b/src/guacenc/layer.c index 0eda10b56..7a81fc592 100644 --- a/src/guacenc/layer.c +++ b/src/guacenc/layer.c @@ -21,19 +21,21 @@ #include "buffer.h" #include "layer.h" +#include + #include guacenc_layer* guacenc_layer_alloc() { /* Allocate new layer */ - guacenc_layer* layer = (guacenc_layer*) calloc(1, sizeof(guacenc_layer)); + guacenc_layer* layer = (guacenc_layer*) guac_mem_zalloc(sizeof(guacenc_layer)); if (layer == NULL) return NULL; /* Allocate associated buffer (width, height, and image storage) */ layer->buffer = guacenc_buffer_alloc(); if (layer->buffer == NULL) { - free(layer); + guac_mem_free(layer); return NULL; } @@ -41,7 +43,7 @@ guacenc_layer* guacenc_layer_alloc() { layer->frame = guacenc_buffer_alloc(); if (layer->frame== NULL) { guacenc_buffer_free(layer->buffer); - free(layer); + guac_mem_free(layer); return NULL; } @@ -67,7 +69,7 @@ void guacenc_layer_free(guacenc_layer* layer) { /* Free underlying buffer */ guacenc_buffer_free(layer->buffer); - free(layer); + guac_mem_free(layer); } diff --git a/src/guacenc/video.c b/src/guacenc/video.c index c8fb8b993..528b0a532 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -138,7 +139,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, } /* Allocate video structure */ - guacenc_video* video = malloc(sizeof(guacenc_video)); + guacenc_video* video = guac_mem_alloc(sizeof(guacenc_video)); if (video == NULL) goto fail_alloc_video; @@ -503,7 +504,7 @@ int guacenc_video_free(guacenc_video* video) { avcodec_free_context(&(video->context)); } - free(video); + guac_mem_free(video); return 0; } diff --git a/src/guaclog/keydef.c b/src/guaclog/keydef.c index 6068c29c5..b9fc1db2d 100644 --- a/src/guaclog/keydef.c +++ b/src/guaclog/keydef.c @@ -21,6 +21,9 @@ #include "keydef.h" #include "log.h" +#include +#include + #include #include #include @@ -279,16 +282,16 @@ static guaclog_keydef* guaclog_get_unicode_key(int keysym) { */ static guaclog_keydef* guaclog_copy_key(guaclog_keydef* keydef) { - guaclog_keydef* copy = malloc(sizeof(guaclog_keydef)); + guaclog_keydef* copy = guac_mem_alloc(sizeof(guaclog_keydef)); /* Always copy keysym and name */ copy->keysym = keydef->keysym; - copy->name = strdup(keydef->name); + copy->name = guac_strdup(keydef->name); copy->modifier = keydef->modifier; /* Copy value only if defined */ if (keydef->value != NULL) - copy->value = strdup(keydef->value); + copy->value = guac_strdup(keydef->value); else copy->value = NULL; @@ -322,9 +325,9 @@ void guaclog_keydef_free(guaclog_keydef* keydef) { if (keydef == NULL) return; - free(keydef->name); - free(keydef->value); - free(keydef); + guac_mem_free(keydef->name); + guac_mem_free(keydef->value); + guac_mem_free(keydef); } diff --git a/src/guaclog/state.c b/src/guaclog/state.c index b33bdd764..6b353dd15 100644 --- a/src/guaclog/state.c +++ b/src/guaclog/state.c @@ -22,6 +22,9 @@ #include "log.h" #include "state.h" +#include +#include + #include #include #include @@ -50,7 +53,7 @@ guaclog_state* guaclog_state_alloc(const char* path) { } /* Allocate state */ - guaclog_state* state = (guaclog_state*) calloc(1, sizeof(guaclog_state)); + guaclog_state* state = (guaclog_state*) guac_mem_zalloc(sizeof(guaclog_state)); if (state == NULL) { goto fail_state; } @@ -90,7 +93,7 @@ int guaclog_state_free(guaclog_state* state) { /* Close output file */ fclose(state->output); - free(state); + guac_mem_free(state); return 0; } diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 0c8680561..9111d3e53 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -29,6 +29,10 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac.la SUBDIRS = . tests +# +# Public headers +# + libguacincdir = $(includedir)/guacamole libguacinc_HEADERS = \ @@ -48,6 +52,7 @@ libguacinc_HEADERS = \ guacamole/hash.h \ guacamole/layer.h \ guacamole/layer-types.h \ + guacamole/mem.h \ guacamole/object.h \ guacamole/object-types.h \ guacamole/parser-constants.h \ @@ -61,6 +66,7 @@ libguacinc_HEADERS = \ guacamole/protocol-constants.h \ guacamole/protocol-types.h \ guacamole/recording.h \ + guacamole/rwlock.h \ guacamole/socket-constants.h \ guacamole/socket.h \ guacamole/socket-fntypes.h \ @@ -78,13 +84,26 @@ libguacinc_HEADERS = \ guacamole/wol.h \ guacamole/wol-constants.h -noinst_HEADERS = \ - id.h \ - encode-jpeg.h \ - encode-png.h \ - palette.h \ - user-handlers.h \ - raw_encoder.h \ +# +# Private, installed headers +# + +libguacprivincdir = $(includedir)/guacamole/private + +libguacprivinc_HEADERS = \ + guacamole/private/mem.h + +# +# Private, non-installed headers +# + +noinst_HEADERS = \ + id.h \ + encode-jpeg.h \ + encode-png.h \ + palette.h \ + user-handlers.h \ + raw_encoder.h \ wait-fd.h libguac_la_SOURCES = \ @@ -97,6 +116,8 @@ libguac_la_SOURCES = \ fips.c \ hash.c \ id.c \ + mem.c \ + rwlock.c \ palette.c \ parser.c \ pool.c \ @@ -139,7 +160,7 @@ libguac_la_CFLAGS = \ -Werror -Wall -pedantic libguac_la_LDFLAGS = \ - -version-info 22:0:1 \ + -version-info 23:0:0 \ -no-undefined \ @CAIRO_LIBS@ \ @DL_LIBS@ \ diff --git a/src/libguac/argv.c b/src/libguac/argv.c index b6f2dcb65..98eb49c2f 100644 --- a/src/libguac/argv.c +++ b/src/libguac/argv.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/argv.h" #include "guacamole/client.h" #include "guacamole/protocol.h" @@ -273,7 +274,7 @@ static int guac_argv_end_handler(guac_user* user, guac_stream* stream) { pthread_mutex_unlock(&await_state.lock); - free(argv); + guac_mem_free(argv); return 0; } @@ -293,7 +294,7 @@ int guac_argv_received(guac_stream* stream, const char* mimetype, const char* na /* Argument matched */ if (strcmp(state->name, name) == 0) { - guac_argv* argv = malloc(sizeof(guac_argv)); + guac_argv* argv = guac_mem_alloc(sizeof(guac_argv)); guac_strlcpy(argv->mimetype, mimetype, sizeof(argv->mimetype)); argv->state = state; argv->length = 0; diff --git a/src/libguac/audio.c b/src/libguac/audio.c index 0f6a6aadb..a3cef3cc8 100644 --- a/src/libguac/audio.c +++ b/src/libguac/audio.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/audio.h" #include "guacamole/client.h" #include "guacamole/protocol.h" @@ -113,13 +114,13 @@ guac_audio_stream* guac_audio_stream_alloc(guac_client* client, guac_audio_stream* audio; /* Allocate stream */ - audio = (guac_audio_stream*) calloc(1, sizeof(guac_audio_stream)); + audio = (guac_audio_stream*) guac_mem_zalloc(sizeof(guac_audio_stream)); audio->client = client; audio->stream = guac_client_alloc_stream(client); /* Abort allocation if underlying stream cannot be allocated */ if (audio->stream == NULL) { - free(audio); + guac_mem_free(audio); return NULL; } @@ -198,7 +199,7 @@ void guac_audio_stream_free(guac_audio_stream* audio) { guac_client_free_stream(audio->client, audio->stream); /* Free associated data */ - free(audio); + guac_mem_free(audio); } diff --git a/src/libguac/client.c b/src/libguac/client.c index 46291b46d..08ccaeece 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -22,12 +22,14 @@ #include "encode-jpeg.h" #include "encode-png.h" #include "encode-webp.h" +#include "guacamole/mem.h" #include "guacamole/client.h" #include "guacamole/error.h" #include "guacamole/layer.h" #include "guacamole/plugin.h" #include "guacamole/pool.h" #include "guacamole/protocol.h" +#include "guacamole/rwlock.h" #include "guacamole/socket.h" #include "guacamole/stream.h" #include "guacamole/string.h" @@ -36,13 +38,39 @@ #include "id.h" #include +#include #include #include +#include #include #include #include #include +/** + * The number of nanoseconds between times that the pending users list will be + * synchronized and emptied (250 milliseconds aka 1/4 second). + */ +#define GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL 250000000 + +/** + * A value that indicates that the pending users timer has yet to be + * initialized and started. + */ +#define GUAC_CLIENT_PENDING_TIMER_UNREGISTERED 0 + +/** + * A value that indicates that the pending users timer has been initialized + * and started, but that the timer handler is not currently running. + */ +#define GUAC_CLIENT_PENDING_TIMER_REGISTERED 1 + +/** + * A value that indicates that the pending users timer has been initialized + * and started, and that the timer handler is currently running. + */ +#define GUAC_CLIENT_PENDING_TIMER_TRIGGERED 2 + /** * Empty NULL-terminated array of argument names. */ @@ -57,7 +85,7 @@ const guac_layer* GUAC_DEFAULT_LAYER = &__GUAC_DEFAULT_LAYER; guac_layer* guac_client_alloc_layer(guac_client* client) { /* Init new layer */ - guac_layer* allocd_layer = malloc(sizeof(guac_layer)); + guac_layer* allocd_layer = guac_mem_alloc(sizeof(guac_layer)); allocd_layer->index = guac_pool_next_int(client->__layer_pool)+1; return allocd_layer; @@ -67,7 +95,7 @@ guac_layer* guac_client_alloc_layer(guac_client* client) { guac_layer* guac_client_alloc_buffer(guac_client* client) { /* Init new layer */ - guac_layer* allocd_layer = malloc(sizeof(guac_layer)); + guac_layer* allocd_layer = guac_mem_alloc(sizeof(guac_layer)); allocd_layer->index = -guac_pool_next_int(client->__buffer_pool) - 1; return allocd_layer; @@ -80,7 +108,7 @@ void guac_client_free_buffer(guac_client* client, guac_layer* layer) { guac_pool_free_int(client->__buffer_pool, -layer->index - 1); /* Free layer */ - free(layer); + guac_mem_free(layer); } @@ -90,7 +118,7 @@ void guac_client_free_layer(guac_client* client, guac_layer* layer) { guac_pool_free_int(client->__layer_pool, layer->index); /* Free layer */ - free(layer); + guac_mem_free(layer); } @@ -128,13 +156,107 @@ void guac_client_free_stream(guac_client* client, guac_stream* stream) { } +/** + * Promote all pending users to full users, calling the join pending handler + * before, if any. + * + * @param data + * The client for which all pending users should be promoted. + */ +static void guac_client_promote_pending_users(union sigval data) { + + guac_client* client = (guac_client*) data.sival_ptr; + + pthread_mutex_lock(&(client->__pending_users_timer_mutex)); + + /* Check if the previous instance of this handler is still running */ + int already_running = ( + client->__pending_users_timer_state + == GUAC_CLIENT_PENDING_TIMER_TRIGGERED); + + /* Mark the handler as running if it isn't already */ + client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_TRIGGERED; + + pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); + + /* Do not start the handler if the previous instance is still running */ + if (already_running) + return; + + /* Acquire the lock for reading and modifying the list of pending users */ + guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); + + /* Skip user promotion entirely if there's no pending users */ + if (client->__pending_users == NULL) + goto promotion_complete; + + /* Run the pending join handler, if one is defined */ + if (client->join_pending_handler) { + + /* If an error occurs in the pending handler */ + if(client->join_pending_handler(client)) { + + /* Log a warning and abort the promotion of the pending users */ + guac_client_log(client, GUAC_LOG_WARNING, + "join_pending_handler did not successfully complete;" + " any pending users have not been promoted.\n"); + + goto promotion_complete; + } + } + + /* The first pending user in the list, if any */ + guac_user* first_user = client->__pending_users; + + /* The final user in the list, if any */ + guac_user* last_user = first_user; + + /* Iterate through the pending users to find the final user */ + guac_user* user = first_user; + while (user != NULL) { + last_user = user; + user = user->__next; + } + + /* Mark the list as empty */ + client->__pending_users = NULL; + + /* Acquire the lock for reading and modifying the list of full users. */ + guac_rwlock_acquire_write_lock(&(client->__users_lock)); + + /* If any users were removed from the pending list, promote them now */ + if (last_user != NULL) { + + /* Add all formerly-pending users to the start of the user list */ + if (client->__users != NULL) + client->__users->__prev = last_user; + + last_user->__next = client->__users; + client->__users = first_user; + + } + + guac_rwlock_release_lock(&(client->__users_lock)); + +promotion_complete: + + /* Release the lock (this is done AFTER updating the connected user list + * to ensure that all users are always on exactly one of these lists) */ + guac_rwlock_release_lock(&(client->__pending_users_lock)); + + /* Mark the handler as complete so the next instance can run */ + pthread_mutex_lock(&(client->__pending_users_timer_mutex)); + client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_REGISTERED; + pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); + +} + guac_client* guac_client_alloc() { int i; - pthread_rwlockattr_t lock_attributes; /* Allocate new client */ - guac_client* client = malloc(sizeof(guac_client)); + guac_client* client = guac_mem_alloc(sizeof(guac_client)); if (client == NULL) { guac_error = GUAC_STATUS_NO_MEMORY; guac_error_message = "Could not allocate memory for client"; @@ -151,7 +273,7 @@ guac_client* guac_client_alloc() { /* Generate ID */ client->connection_id = guac_generate_id(GUAC_CLIENT_ID_PREFIX); if (client->connection_id == NULL) { - free(client); + guac_mem_free(client); return NULL; } @@ -163,21 +285,29 @@ guac_client* guac_client_alloc() { client->__stream_pool = guac_pool_alloc(0); /* Initialize streams */ - client->__output_streams = malloc(sizeof(guac_stream) * GUAC_CLIENT_MAX_STREAMS); + client->__output_streams = guac_mem_alloc(sizeof(guac_stream), GUAC_CLIENT_MAX_STREAMS); for (i=0; i__output_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX; } - /* Init locks */ - pthread_rwlockattr_init(&lock_attributes); - pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); + guac_rwlock_init(&(client->__users_lock)); + guac_rwlock_init(&(client->__pending_users_lock)); + + /* Initialize the write lock flags to 0, as threads won't have yet */ + pthread_key_create(&(client->__users_lock.key), (void *) 0); + pthread_key_create(&(client->__pending_users_lock.key), (void *) 0); + + /* The timer will be lazily created in the child process */ + client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_UNREGISTERED; - pthread_rwlock_init(&(client->__users_lock), &lock_attributes); + /* Set up the pending user promotion mutex */ + pthread_mutex_init(&(client->__pending_users_timer_mutex), NULL); - /* Set up socket to broadcast to all users */ + /* Set up broadcast sockets */ client->socket = guac_socket_broadcast(client); + client->pending_socket = guac_socket_broadcast_pending(client); return client; @@ -185,10 +315,22 @@ guac_client* guac_client_alloc() { void guac_client_free(guac_client* client) { + /* Acquire write locks before referencing user pointers */ + guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); + guac_rwlock_acquire_write_lock(&(client->__users_lock)); + + /* Remove all pending users */ + while (client->__pending_users != NULL) + guac_client_remove_user(client, client->__pending_users); + /* Remove all users */ while (client->__users != NULL) guac_client_remove_user(client, client->__users); + /* Release the locks */ + guac_rwlock_release_lock(&(client->__users_lock)); + guac_rwlock_release_lock(&(client->__pending_users_lock)); + if (client->free_handler) { /* FIXME: Errors currently ignored... */ @@ -196,15 +338,16 @@ void guac_client_free(guac_client* client) { } - /* Free socket */ + /* Free sockets */ guac_socket_free(client->socket); + guac_socket_free(client->pending_socket); /* Free layer pools */ guac_pool_free(client->__buffer_pool); guac_pool_free(client->__layer_pool); /* Free streams */ - free(client->__output_streams); + guac_mem_free(client->__output_streams); /* Free stream pool */ guac_pool_free(client->__stream_pool); @@ -215,9 +358,25 @@ void guac_client_free(guac_client* client) { guac_client_log(client, GUAC_LOG_ERROR, "Unable to close plugin: %s", dlerror()); } - pthread_rwlock_destroy(&(client->__users_lock)); - free(client->connection_id); - free(client); + /* Find out if the pending user promotion timer was ever started */ + pthread_mutex_lock(&(client->__pending_users_timer_mutex)); + int was_started = ( + client->__pending_users_timer_state + != GUAC_CLIENT_PENDING_TIMER_UNREGISTERED); + pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); + + /* If the timer was registered, stop it before destroying the lock */ + if (was_started) + timer_delete(client->__pending_users_timer); + + pthread_mutex_destroy(&(client->__pending_users_timer_mutex)); + + /* Destroy the reentrant read-write locks */ + guac_rwlock_destroy(&(client->__users_lock)); + guac_rwlock_destroy(&(client->__pending_users_lock)); + + guac_mem_free(client->connection_id); + guac_mem_free(client); } void vguac_client_log(guac_client* client, guac_client_log_level level, @@ -277,27 +436,129 @@ void guac_client_abort(guac_client* client, guac_protocol_status status, } +/** + * Add the provided user to the list of pending users who have yet to have + * their connection state synchronized after joining, for the connection + * associated with the given guac client. + * + * @param client + * The client associated with the connection for which the provided user + * is pending a connection state synchronization after joining. + * + * @param user + * The user to add to the pending list. + */ +static void guac_client_add_pending_user( + guac_client* client, guac_user* user) { + + /* Acquire the lock for modifying the list of pending users */ + guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); + + user->__prev = NULL; + user->__next = client->__pending_users; + + if (client->__pending_users != NULL) + client->__pending_users->__prev = user; + + client->__pending_users = user; + + /* Increment the user count */ + client->connected_users++; + + /* Release the lock */ + guac_rwlock_release_lock(&(client->__pending_users_lock)); + +} + +/** + * Periodically promote pending users to full users. Returns zero if the timer + * is already running, or successfully created, or a non-zero value if the + * timer could not be created and started. + * + * @param client + * The guac client for which the new timer should be started, if not + * already running. + * + * @return + * Zero if the timer was successfully created and started, or a negative + * value otherwise. + */ +static int guac_client_start_pending_users_timer(guac_client* client) { + + pthread_mutex_lock(&(client->__pending_users_timer_mutex)); + + /* Return success if the timer is already created and running */ + if (client->__pending_users_timer_state + != GUAC_CLIENT_PENDING_TIMER_UNREGISTERED) { + pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); + return 0; + } + + /* Configure the timer to synchronize and clear the pending users */ + struct sigevent signal_config = { + .sigev_notify = SIGEV_THREAD, + .sigev_notify_function = guac_client_promote_pending_users, + .sigev_value = { .sival_ptr = client }}; + + /* Create a timer to synchronize any pending users periodically */ + if (timer_create( + CLOCK_MONOTONIC, + &signal_config, + &(client->__pending_users_timer))) { + pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); + return 1; + } + + /* Configure the pending users timer to run on the defined interval */ + struct itimerspec time_config = { + .it_interval = { .tv_nsec = GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL }, + .it_value = { .tv_nsec = GUAC_CLIENT_PENDING_USERS_REFRESH_INTERVAL } + }; + + /* Start the timer */ + if (timer_settime( + client->__pending_users_timer, 0, &time_config, NULL) < 0) { + timer_delete(client->__pending_users_timer); + pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); + return 1; + } + + /* Mark the timer as registered but not yet running */ + client->__pending_users_timer_state = GUAC_CLIENT_PENDING_TIMER_REGISTERED; + + pthread_mutex_unlock(&(client->__pending_users_timer_mutex)); + return 0; + +} + int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** argv) { + /* Create and start the timer if it hasn't already been initialized */ + if (guac_client_start_pending_users_timer(client)) { + + /** + * + * If the timer could not be created, do not add the user - they cannot + * be synchronized without the timer. + */ + guac_client_log(client, GUAC_LOG_ERROR, + "Could not start pending user timer: %s.", strerror(errno)); + return 1; + } + int retval = 0; /* Call handler, if defined */ if (client->join_handler) retval = client->join_handler(user, argc, argv); - pthread_rwlock_wrlock(&(client->__users_lock)); - - /* Add to list if join was successful */ if (retval == 0) { - user->__prev = NULL; - user->__next = client->__users; - - if (client->__users != NULL) - client->__users->__prev = user; - - client->__users = user; - client->connected_users++; + /* + * Add the user to the list of pending users, to have their connection + * state synchronized asynchronously. + */ + guac_client_add_pending_user(client, user); /* Update owner pointer if user is owner */ if (user->owner) @@ -305,8 +566,6 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** } - pthread_rwlock_unlock(&(client->__users_lock)); - /* Notify owner of user joining connection. */ if (retval == 0 && !user->owner) guac_client_owner_notify_join(client, user); @@ -317,13 +576,16 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char** void guac_client_remove_user(guac_client* client, guac_user* user) { - pthread_rwlock_wrlock(&(client->__users_lock)); + guac_rwlock_acquire_write_lock(&(client->__pending_users_lock)); + guac_rwlock_acquire_write_lock(&(client->__users_lock)); /* Update prev / head */ if (user->__prev != NULL) user->__prev->__next = user->__next; - else + else if (client->__users == user) client->__users = user->__next; + else if (client->__pending_users == user) + client->__pending_users = user->__next; /* Update next */ if (user->__next != NULL) @@ -335,7 +597,8 @@ void guac_client_remove_user(guac_client* client, guac_user* user) { if (user->owner) client->__owner = NULL; - pthread_rwlock_unlock(&(client->__users_lock)); + guac_rwlock_release_lock(&(client->__users_lock)); + guac_rwlock_release_lock(&(client->__pending_users_lock)); /* Update owner of user having left the connection. */ if (!user->owner) @@ -353,7 +616,7 @@ void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, guac_user* current; - pthread_rwlock_rdlock(&(client->__users_lock)); + guac_rwlock_acquire_read_lock(&(client->__users_lock)); /* Call function on each user */ current = client->__users; @@ -362,7 +625,25 @@ void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, current = current->__next; } - pthread_rwlock_unlock(&(client->__users_lock)); + guac_rwlock_release_lock(&(client->__users_lock)); + +} + +void guac_client_foreach_pending_user( + guac_client* client, guac_user_callback* callback, void* data) { + + guac_user* current; + + guac_rwlock_acquire_read_lock(&(client->__pending_users_lock)); + + /* Call function on each pending user */ + current = client->__pending_users; + while (current != NULL) { + callback(current, data); + current = current->__next; + } + + guac_rwlock_release_lock(&(client->__pending_users_lock)); } @@ -371,12 +652,12 @@ void* guac_client_for_owner(guac_client* client, guac_user_callback* callback, void* retval; - pthread_rwlock_rdlock(&(client->__users_lock)); + guac_rwlock_acquire_read_lock(&(client->__users_lock)); /* Invoke callback with current owner */ retval = callback(client->__owner, data); - pthread_rwlock_unlock(&(client->__users_lock)); + guac_rwlock_release_lock(&(client->__users_lock)); /* Return value from callback */ return retval; @@ -391,7 +672,7 @@ void* guac_client_for_user(guac_client* client, guac_user* user, int user_valid = 0; void* retval; - pthread_rwlock_rdlock(&(client->__users_lock)); + guac_rwlock_acquire_read_lock(&(client->__users_lock)); /* Loop through all users, searching for a pointer to the given user */ current = client->__users; @@ -413,7 +694,7 @@ void* guac_client_for_user(guac_client* client, guac_user* user, /* Invoke callback with requested user (if they exist) */ retval = callback(user, data); - pthread_rwlock_unlock(&(client->__users_lock)); + guac_rwlock_release_lock(&(client->__users_lock)); /* Return value from callback */ return retval; diff --git a/src/libguac/encode-jpeg.c b/src/libguac/encode-jpeg.c index 8e145d86e..65d8ceaeb 100644 --- a/src/libguac/encode-jpeg.c +++ b/src/libguac/encode-jpeg.c @@ -20,6 +20,7 @@ #include "config.h" #include "encode-jpeg.h" +#include "guacamole/mem.h" #include "guacamole/error.h" #include "guacamole/protocol.h" #include "guacamole/stream.h" @@ -211,9 +212,7 @@ int guac_jpeg_write(guac_socket* socket, guac_stream* stream, /* Create a buffer for the write scan line which is where we will * put the converted pixels (BGRx -> RGB) */ - int write_stride = cinfo.image_width * cinfo.input_components; - unsigned char *scanline_data = malloc(write_stride); - memset(scanline_data, 0, write_stride); + unsigned char *scanline_data = guac_mem_zalloc(cinfo.image_width, cinfo.input_components); #endif /* Initialize the JPEG compressor */ @@ -254,7 +253,7 @@ int guac_jpeg_write(guac_socket* socket, guac_stream* stream, } #ifndef JCS_EXTENSIONS - free(scanline_data); + guac_mem_free(scanline_data); #endif /* Finalize compression */ diff --git a/src/libguac/encode-png.c b/src/libguac/encode-png.c index 20d2f5de2..e930c0584 100644 --- a/src/libguac/encode-png.c +++ b/src/libguac/encode-png.c @@ -20,6 +20,7 @@ #include "config.h" #include "encode-png.h" +#include "guacamole/mem.h" #include "guacamole/error.h" #include "guacamole/protocol.h" #include "guacamole/stream.h" @@ -342,11 +343,11 @@ int guac_png_write(guac_socket* socket, guac_stream* stream, guac_png_flush_handler); /* Copy data from surface into PNG data */ - png_rows = (png_byte**) malloc(sizeof(png_byte*) * height); + png_rows = (png_byte**) guac_mem_alloc(sizeof(png_byte*), height); for (y=0; y @@ -176,24 +177,24 @@ static pthread_once_t __guac_error_key_init = PTHREAD_ONCE_INIT; static pthread_key_t __guac_error_message_key; static pthread_once_t __guac_error_message_key_init = PTHREAD_ONCE_INIT; -static void __guac_free_pointer(void* pointer) { +static void __guac_mem_free_pointer(void* pointer) { /* Free memory allocated to status variable */ - free(pointer); + guac_mem_free(pointer); } static void __guac_alloc_error_key() { /* Create key, destroy any allocated variable on thread exit */ - pthread_key_create(&__guac_error_key, __guac_free_pointer); + pthread_key_create(&__guac_error_key, __guac_mem_free_pointer); } static void __guac_alloc_error_message_key() { /* Create key, destroy any allocated variable on thread exit */ - pthread_key_create(&__guac_error_message_key, __guac_free_pointer); + pthread_key_create(&__guac_error_message_key, __guac_mem_free_pointer); } @@ -210,7 +211,7 @@ guac_status* __guac_error() { /* Allocate thread-local status variable if not already allocated */ if (status == NULL) { - status = malloc(sizeof(guac_status)); + status = guac_mem_alloc(sizeof(guac_status)); pthread_setspecific(__guac_error_key, status); } @@ -234,7 +235,7 @@ const char** __guac_error_message() { /* Allocate thread-local message variable if not already allocated */ if (message == NULL) { - message = malloc(sizeof(const char*)); + message = guac_mem_alloc(sizeof(const char*)); pthread_setspecific(__guac_error_message_key, message); } diff --git a/src/libguac/guacamole/client-fntypes.h b/src/libguac/guacamole/client-fntypes.h index 381ead038..367a6f1e2 100644 --- a/src/libguac/guacamole/client-fntypes.h +++ b/src/libguac/guacamole/client-fntypes.h @@ -30,7 +30,9 @@ #include "client-types.h" #include "object-types.h" #include "protocol-types.h" +#include "socket.h" #include "stream-types.h" +#include "user-fntypes.h" #include "user-types.h" #include @@ -48,6 +50,20 @@ */ typedef int guac_client_free_handler(guac_client* client); +/** + * Handler that will run before immediately before pending users are promoted + * to full users. The pending user socket should be used to communicate to the + * pending users. + * + * @param client + * The client whose handler was invoked. + * + * @return + * Zero if the pending handler ran successfuly, or a non-zero value if an + * error occured. + */ +typedef int guac_client_join_pending_handler(guac_client* client); + /** * Handler for logging messages related to a given guac_client instance. * diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index a64dee6a4..328965667 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -32,6 +32,7 @@ #include "layer-types.h" #include "object-types.h" #include "pool-types.h" +#include "rwlock.h" #include "socket-types.h" #include "stream-types.h" #include "timestamp-types.h" @@ -42,20 +43,29 @@ #include #include +#include struct guac_client { /** - * The guac_socket structure to be used to communicate with all connected - * web-clients (users). Unlike the user-level guac_socket, this guac_socket - * will broadcast instructions to all connected users simultaneously. It - * is expected that the implementor of any Guacamole proxy client will - * provide their own mechanism of I/O for their protocol. The guac_socket - * structure is used only to communicate conveniently with the Guacamole - * web-client. + * The guac_socket structure to be used to communicate with all non-pending + * connected web-clients (users). Unlike the user-level guac_socket, this + * guac_socket will broadcast instructions to all non-pending connected users + * simultaneously. It is expected that the implementor of any Guacamole proxy + * client will provide their own mechanism of I/O for their protocol. The + * guac_socket structure is used only to communicate conveniently with the + * Guacamole web-client. */ guac_socket* socket; + /** + * The guac_socket structure to be used to communicate with all pending + * connected web-clients (users). Aside from operating on a different + * subset of users, this socket has all the same behavior and semantics as + * the non-pending socket. + */ + guac_socket* pending_socket; + /** * The current state of the client. When the client is first allocated, * this will be initialized to GUAC_CLIENT_RUNNING. It will remain at @@ -162,7 +172,7 @@ struct guac_client { * Lock which is acquired when the users list is being manipulated, or when * the users list is being iterated. */ - pthread_rwlock_t __users_lock; + guac_rwlock __users_lock; /** * The first user within the list of all connected users, or NULL if no @@ -170,6 +180,37 @@ struct guac_client { */ guac_user* __users; + /** + * Lock which is acquired when the pending users list is being manipulated, + * or when the pending users list is being iterated. + */ + guac_rwlock __pending_users_lock; + + /** + * A timer that will periodically synchronize the list of pending users, + * emptying the list once synchronization is complete. Only for internal + * use within the client. This will be NULL until the first user joins + * the connection, as it is lazily instantiated at that time. + */ + timer_t __pending_users_timer; + + /** + * A flag storing the current state of the pending users timer. + */ + int __pending_users_timer_state; + + /** + * A mutex that must be acquired before modifying or checking the value of + * the timer state. + */ + pthread_mutex_t __pending_users_timer_mutex; + + /** + * The first user within the list of connected users who have not yet had + * their connection states synchronized after joining. + */ + guac_user* __pending_users; + /** * The user that first created this connection. This user will also have * their "owner" flag set to a non-zero value. If the owner has left the @@ -206,6 +247,22 @@ struct guac_client { */ guac_user_join_handler* join_handler; + /** + * A handler that will be run prior to pending users being promoted to full + * users. Any required pending user operations should be performed using + * the client's pending user socket. + * + * Example: + * @code + * int join_pending_handler(guac_client* client); + * + * int guac_client_init(guac_client* client) { + * client->join_pending_handler = join_pending_handler; + * } + * @endcode + */ + guac_client_join_pending_handler* join_pending_handler; + /** * Handler for leave events, called whenever a new user is leaving an * active connection. @@ -446,6 +503,26 @@ void guac_client_remove_user(guac_client* client, guac_user* user); void guac_client_foreach_user(guac_client* client, guac_user_callback* callback, void* data); +/** + * Calls the given function on all pending users of the given client. The + * function will be given a reference to a guac_user and the specified + * arbitrary data. The value returned by the callback will be ignored. + * + * This function is reentrant, but the pending user list MUST NOT be manipulated + * within the same thread as a callback to this function. + * + * @param client + * The client whose users should be iterated. + * + * @param callback + * The function to call for each pending user. + * + * @param data + * Arbitrary data to pass to the callback each time it is invoked. + */ +void guac_client_foreach_pending_user(guac_client* client, + guac_user_callback* callback, void* data); + /** * Calls the given function with the currently-connected user that is marked as * the owner. The owner of a connection is the user that established the diff --git a/src/libguac/guacamole/mem.h b/src/libguac/guacamole/mem.h new file mode 100644 index 000000000..65468c383 --- /dev/null +++ b/src/libguac/guacamole/mem.h @@ -0,0 +1,413 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_MEM_H +#define GUAC_MEM_H + +/** + * Provides convenience macros/functions for performing arithmetic on size_t + * values and for allocating memory, particularly memory related to images, + * audio, etc. where there are multiple factors affecting the final size. + * + * @file mem.h + */ + +#include "private/mem.h" + +#include + +/** + * Allocates a contiguous block of memory with the specified size, returning a + * pointer to the first byte of that block of memory. If multiple sizes are + * provided, these sizes are multiplied together to produce the final size of + * the new block. If memory of the specified size cannot be allocated, or if + * multiplying the sizes would result in integer overflow, guac_error is set + * appropriately and NULL is returned. + * + * This macro is analogous to the standard malloc(), but accepts a list of size + * factors instead of a single integer size. + * + * The pointer returned by guac_mem_alloc() SHOULD be freed with a subsequent call + * to guac_mem_free(), but MAY instead be freed with a subsequent call to free(). + * + * @param ... + * A series of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the allocated block of memory, or NULL if + * such a block could not be allocated. If a block of memory could not be + * allocated, guac_error is set appropriately. + */ +#define guac_mem_alloc(...) \ + PRIV_guac_mem_alloc( \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Allocates a contiguous block of memory with the specified size and with all + * bytes initialized to zero, returning a pointer to the first byte of that + * block of memory. If multiple sizes are provided, these sizes are multiplied + * together to produce the final size of the new block. If memory of the + * specified size cannot be allocated, or if multiplying the sizes would result + * in integer overflow, guac_error is set appropriately and NULL is returned. + * + * This macro is analogous to the standard calloc(), but accepts a list of size + * factors instead of a requiring exactly two integer sizes. + * + * The pointer returned by guac_mem_zalloc() SHOULD be freed with a subsequent call + * to guac_mem_free(), but MAY instead be freed with a subsequent call to free(). + * + * @param ... + * A series of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the allocated block of memory, or NULL if + * such a block could not be allocated. If a block of memory could not be + * allocated, guac_error is set appropriately. + */ +#define guac_mem_zalloc(...) \ + PRIV_guac_mem_zalloc( \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Multiplies together each of the given values, storing the result in a size_t + * variable via the provided pointer. If the result of the multiplication + * overflows the limits of a size_t, non-zero is returned to signal failure. + * + * If the multiplication operation fails, the nature of any result stored in + * the provided pointer is undefined, as is whether a result is stored at all. + * + * For example, the following: + * @code + * size_t some_result; + * int failed = guac_mem_ckd_mul(&some_result, a, b, c); + * @endcode + * + * is equivalent in principle to: + * @code + * size_t some_result = a * b * c; + * @endcode + * + * except that it is possible for interested callers to handle overflow. + * + * @param result + * A pointer to the size_t variable that should receive the result of + * multiplying the given values. + * + * @param ... + * The size_t values that should be multiplied together. + * + * @returns + * Zero if the multiplication was successful and did not overflow the + * limits of a size_t, non-zero otherwise. + */ +#define guac_mem_ckd_mul(result, ...) \ + PRIV_guac_mem_ckd_mul( \ + result, \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Adds together each of the given values, storing the result in a size_t + * variable via the provided pointer. If the result of the addition overflows + * the limits of a size_t, non-zero is returned to signal failure. + * + * If the addition operation fails, the nature of any result stored in the + * provided pointer is undefined, as is whether a result is stored at all. + * + * For example, the following: + * @code + * size_t some_result; + * int failed = guac_mem_ckd_add(&some_result, a, b, c); + * @endcode + * + * is equivalent in principle to: + * @code + * size_t some_result = a + b + c; + * @endcode + * + * except that it is possible for interested callers to handle overflow. + * + * @param result + * A pointer to the size_t variable that should receive the result of + * adding the given values. + * + * @param ... + * The size_t values that should be added together. + * + * @returns + * Zero if the addition was successful and did not overflow the limits of a + * size_t, non-zero otherwise. + */ +#define guac_mem_ckd_add(result, ...) \ + PRIV_guac_mem_ckd_add( \ + result, \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Subtracts each of the given values from each other, storing the result in a + * size_t variable via the provided pointer. If the result of the subtraction + * overflows the limits of a size_t (goes below zero), non-zero is returned to + * signal failure. + * + * If the subtraction operation fails, the nature of any result stored in the + * provided pointer is undefined, as is whether a result is stored at all. + * + * For example, the following: + * @code + * size_t some_result; + * int failed = guac_mem_ckd_sub(&some_result, a, b, c); + * @endcode + * + * is equivalent in principle to: + * @code + * size_t some_result = a - b - c; + * @endcode + * + * except that it is possible for interested callers to handle overflow. + * + * @param result + * A pointer to the size_t variable that should receive the result of + * subtracting the given values from each other. + * + * @param ... + * The size_t values that should be subtracted from each other. + * + * @returns + * Zero if the subtraction was successful and did not overflow the limits + * of a size_t (did not go below zero), non-zero otherwise. + */ +#define guac_mem_ckd_sub(result, ...) \ + PRIV_guac_mem_ckd_sub( \ + result, \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Multiplies together each of the given values, returning the result directly. + * If the result of the multiplication overflows the limits of a size_t, + * execution of the current process is aborted entirely, and this function does + * not return. + * + * For example, the following: + * @code + * size_t some_result = guac_mem_ckd_mul_or_die(a, b, c); + * @endcode + * + * is equivalent in principle to: + * @code + * size_t some_result = a * b * c; + * @endcode + * + * except that an overflow condition will result in the process immediately + * terminating. + * + * @param ... + * The size_t values that should be multiplied together. + * + * @returns + * The result of the multiplication. If the multiplication operation would + * overflow the limits of a size_t, execution of the current process is + * aborted, and this function does not return. + */ +#define guac_mem_ckd_mul_or_die(...) \ + PRIV_guac_mem_ckd_mul_or_die( \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Adds together each of the given values, returning the result directly. If + * the result of the addition overflows the limits of a size_t, execution of + * the current process is aborted entirely, and this function does not return. + * + * For example, the following: + * @code + * size_t some_result = guac_mem_ckd_add_or_die(a, b, c); + * @endcode + * + * is equivalent in principle to: + * @code + * size_t some_result = a + b + c; + * @endcode + * + * except that an overflow condition will result in the process immediately + * terminating. + * + * @param ... + * The size_t values that should be added together. + * + * @returns + * The result of the addition. If the addition operation would overflow the + * limits of a size_t, execution of the current process is aborted, and + * this function does not return. + */ +#define guac_mem_ckd_add_or_die(...) \ + PRIV_guac_mem_ckd_add_or_die( \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Subtracts each of the given values from each other, returning the result + * directly. If the result of the subtraction overflows the limits of a size_t + * (goes below zero), execution of the current process is aborted entirely, and + * this function does not return. + * + * For example, the following: + * @code + * size_t some_result = guac_mem_ckd_sub_or_die(a, b, c); + * @endcode + * + * is equivalent in principle to: + * @code + * size_t some_result = a - b - c; + * @endcode + * + * except that an overflow condition will result in the process immediately + * terminating. + * + * @param ... + * The size_t values that should be subtracted from each other. + * + * @returns + * The result of the subtraction. If the subtraction operation would + * overflow the limits of a size_t (go below zero), execution of the + * current process is aborted, and this function does not return. + */ +#define guac_mem_ckd_sub_or_die(...) \ + PRIV_guac_mem_ckd_sub_or_die( \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Reallocates a contiguous block of memory that was previously allocated with + * guac_mem_alloc(), guac_mem_zalloc(), guac_mem_realloc(), or one of their + * *_or_die() variants, returning a pointer to the first byte of that + * reallocated block of memory. If multiple sizes are provided, these sizes are + * multiplied together to produce the final size of the new block. If memory of + * the specified size cannot be allocated, or if multiplying the sizes would + * result in integer overflow, guac_error is set appropriately, the original + * block of memory is left untouched, and NULL is returned. + * + * This macro is analogous to the standard realloc(), but accepts a list of + * size factors instead of a requiring exactly one integer size. + * + * The returned pointer may be the same as the original pointer, but this is + * not guaranteed. If the returned pointer is different, the original pointer + * is automatically freed. + * + * The pointer returned by guac_mem_realloc() SHOULD be freed with a subsequent + * call to guac_mem_free(), but MAY instead be freed with a subsequent call to + * free(). + * + * @param ... + * A series of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the reallocated block of memory, or NULL + * if such a block could not be allocated. If a block of memory could not + * be allocated, guac_error is set appropriately and the original block of + * memory is left untouched. + */ +#define guac_mem_realloc(mem, ...) \ + PRIV_guac_mem_realloc( \ + mem, \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Reallocates a contiguous block of memory that was previously allocated with + * guac_mem_alloc(), guac_mem_zalloc(), guac_mem_realloc(), or one of their + * *_or_die() variants, returning a pointer to the first byte of that + * reallocated block of memory. If multiple sizes are provided, these sizes are + * multiplied together to produce the final size of the new block. If memory of + * the specified size cannot be allocated, execution of the current process is + * aborted entirely, and this function does not return. + * + * This macro is analogous to the standard realloc(), but accepts a list of + * size factors instead of a requiring exactly one integer size and does not + * return in the event a block cannot be allocated. + * + * The returned pointer may be the same as the original pointer, but this is + * not guaranteed. If the returned pointer is different, the original pointer + * is automatically freed. + * + * The pointer returned by guac_mem_realloc() SHOULD be freed with a subsequent + * call to guac_mem_free(), but MAY instead be freed with a subsequent call to + * free(). + * + * @param ... + * A series of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the reallocated block of memory. If a + * block of memory could not be allocated, execution of the current process + * is aborted, and this function does not return. + */ +#define guac_mem_realloc_or_die(mem, ...) \ + PRIV_guac_mem_realloc_or_die( \ + mem, \ + sizeof((const size_t[]) { __VA_ARGS__ }) / sizeof(const size_t), \ + (const size_t[]) { __VA_ARGS__ } \ + ) + +/** + * Frees the memory block at the given pointer, which MUST have been allocated + * with guac_mem_alloc(), guac_mem_zalloc(), guac_mem_realloc(), or one of + * their *_or_die() variants. The pointer is automatically assigned a value of + * NULL after memory is freed. If the provided pointer is already NULL, this + * macro has no effect. + * + * @param mem + * A pointer to the memory to be freed. + */ +#define guac_mem_free(mem) (PRIV_guac_mem_free(mem), (mem) = NULL, (void) 0) + +/** + * Frees the memory block at the given const pointer, which MUST have been + * allocated with guac_mem_alloc(), guac_mem_zalloc(), guac_mem_realloc(), or + * one of their *_or_die() variants. As the pointer is presumed constant, it is + * not automatically assigned a value of NULL after memory is freed. If the + * provided pointer is NULL, this macro has no effect. + * + * The guac_mem_free() macro should be used in favor of this macro. This macro + * should only be used in cases where a constant pointer is absolutely + * necessary. + * + * @param mem + * A pointer to the memory to be freed. + */ +#define guac_mem_free_const(mem) PRIV_guac_mem_free((void*) (mem)) + +#endif + diff --git a/src/libguac/guacamole/private/mem.h b/src/libguac/guacamole/private/mem.h new file mode 100644 index 000000000..dd24f8a0b --- /dev/null +++ b/src/libguac/guacamole/private/mem.h @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_PRIVATE_MEM_H +#define GUAC_PRIVATE_MEM_H + +/** + * Provides functions used internally for allocating memory. + * + * WARNING: SYMBOLS DEFINED HERE ARE NOT INTENDED TO BE USED DIRECTLY BY + * ANYTHING OUTSIDE LIBGUAC! This header is used internally to define private + * symbols that are only intended for indirect public use through some other, + * non-private mechanism, such as a macro defined in the public API. + * + * @file mem.h + */ + +#include + +/** + * Allocates a contiguous block of memory with the specified size, returning a + * pointer to the first byte of that block of memory. If multiple sizes are + * provided, these sizes are multiplied together to produce the final size of + * the new block. If memory of the specified size cannot be allocated, or if + * multiplying the sizes would result in integer overflow, guac_error is set + * appropriately and NULL is returned. + * + * This function is analogous to the standard malloc(), but accepts a list of + * size factors instead of a single integer size. + * + * The pointer returned by PRIV_guac_mem_alloc() SHOULD be freed with a + * subsequent call to guac_mem_free() or PRIV_guac_mem_free(), but MAY instead + * be freed with a subsequent call to free(). + * + * @param factor_count + * The number of factors to multiply together to produce the desired block + * size. + * + * @param factors + * An array of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the allocated block of memory, or NULL if + * such a block could not be allocated. If a block of memory could not be + * allocated, guac_error is set appropriately. + */ +void* PRIV_guac_mem_alloc(size_t factor_count, const size_t* factors); + +/** + * Allocates a contiguous block of memory with the specified size and with all + * bytes initialized to zero, returning a pointer to the first byte of that + * block of memory. If multiple sizes are provided, these sizes are multiplied + * together to produce the final size of the new block. If memory of the + * specified size cannot be allocated, or if multiplying the sizes would result + * in integer overflow, guac_error is set appropriately and NULL is returned. + * + * This function is analogous to the standard calloc(), but accepts a list of + * size factors instead of a requiring exactly two integer sizes. + * + * The pointer returned by PRIV_guac_mem_zalloc() SHOULD be freed with a + * subsequent call to guac_mem_free() or PRIV_guac_mem_free(), but MAY instead + * be freed with a subsequent call to free(). + * + * @param factor_count + * The number of factors to multiply together to produce the desired block + * size. + * + * @param factors + * An array of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the allocated block of memory, or NULL if + * such a block could not be allocated. If a block of memory could not be + * allocated, guac_error is set appropriately. + */ +void* PRIV_guac_mem_zalloc(size_t factor_count, const size_t* factors); + +/** + * Multiplies together each of the given values, storing the result in a size_t + * variable via the provided pointer. If the result of the multiplication + * overflows the limits of a size_t, non-zero is returned to signal failure. + * + * If the multiplication operation fails, the nature of any result stored in + * the provided pointer is undefined, as is whether a result is stored at all. + * + * @param result + * A pointer to the size_t variable that should receive the result of + * multiplying the given values. + * + * @param factor_count + * The number of factors to multiply together. + * + * @param factors + * An array of one or more size_t values that should be multiplied + * together. At least one value MUST be provided. + * + * @returns + * Zero if the multiplication was successful and did not overflow the + * limits of a size_t, non-zero otherwise. + */ +int PRIV_guac_mem_ckd_mul(size_t* result, size_t factor_count, const size_t* factors); + +/** + * Adds together each of the given values, storing the result in a size_t + * variable via the provided pointer. If the result of the addition overflows + * the limits of a size_t, non-zero is returned to signal failure. + * + * If the addition operation fails, the nature of any result stored in the + * provided pointer is undefined, as is whether a result is stored at all. + * + * @param result + * A pointer to the size_t variable that should receive the result of + * adding the given values. + * + * @param term_count + * The number of terms to be added together. + * + * @param terms + * An array of one or more size_t values that should be added together. At + * least one value MUST be provided. + * + * @returns + * Zero if the addition was successful and did not overflow the limits of a + * size_t, non-zero otherwise. + */ +int PRIV_guac_mem_ckd_add(size_t* result, size_t term_count, const size_t* terms); + +/** + * Subtracts each of the given values from each other, storing the result in a + * size_t variable via the provided pointer. If the result of the subtraction + * overflows the limits of a size_t (goes below zero), non-zero is returned to + * signal failure. + * + * If the subtraction operation fails, the nature of any result stored in the + * provided pointer is undefined, as is whether a result is stored at all. + * + * @param result + * A pointer to the size_t variable that should receive the result of + * subtracting the given values from each other. + * + * @param term_count + * The number of terms to be subtracted from each other. + * + * @param terms + * An array of one or more size_t values that should be subtracted from + * each other. At least one value MUST be provided. + * + * @returns + * Zero if the subtraction was successful and did not overflow the limits + * of a size_t (did not go below zero), non-zero otherwise. + */ +int PRIV_guac_mem_ckd_sub(size_t* result, size_t term_count, const size_t* terms); + +/** + * Multiplies together each of the given values, returning the result directly. + * If the result of the multiplication overflows the limits of a size_t, + * execution of the current process is aborted entirely, and this function does + * not return. + * + * @param factor_count + * The number of factors to multiply together. + * + * @param factors + * An array of one or more size_t values that should be multiplied + * together. At least one value MUST be provided. + * + * @returns + * The result of the multiplication. If the multiplication operation would + * overflow the limits of a size_t, execution of the current process is + * aborted, and this function does not return. + */ +size_t PRIV_guac_mem_ckd_mul_or_die(size_t factor_count, const size_t* factors); + +/** + * Adds together each of the given values, returning the result directly. If + * the result of the addition overflows the limits of a size_t, execution of + * the current process is aborted entirely, and this function does not return. + * + * @param term_count + * The number of terms to be added together. + * + * @param terms + * An array of one or more size_t values that should be added together. At + * least one value MUST be provided. + * + * @returns + * The result of the addition. If the addition operation would overflow the + * limits of a size_t, execution of the current process is aborted, and + * this function does not return. + */ +size_t PRIV_guac_mem_ckd_add_or_die(size_t term_count, const size_t* terms); + +/** + * Subtracts each of the given values from each other, returning the result + * directly. If the result of the subtraction overflows the limits of a size_t + * (goes below zero), execution of the current process is aborted entirely, and + * this function does not return. + * + * @param term_count + * The number of terms to be subtracted from each other. + * + * @param terms + * An array of one or more size_t values that should be subtracted from + * each other. At least one value MUST be provided. + * + * @returns + * The result of the subtraction. If the subtraction operation would + * overflow the limits of a size_t (go below zero), execution of the + * current process is aborted, and this function does not return. + */ +size_t PRIV_guac_mem_ckd_sub_or_die(size_t term_count, const size_t* terms); + +/** + * Reallocates a contiguous block of memory that was previously allocated with + * guac_mem_alloc(), guac_mem_zalloc(), guac_mem_realloc(), or one of their + * PRIV_guac_*() or *_or_die() variants, returning a pointer to the first byte + * of that reallocated block of memory. If multiple sizes are provided, these + * sizes are multiplied together to produce the final size of the new block. If + * memory of the specified size cannot be allocated, or if multiplying the + * sizes would result in integer overflow, guac_error is set appropriately, the + * original block of memory is left untouched, and NULL is returned. + * + * This function is analogous to the standard realloc(), but accepts a list of + * size factors instead of a requiring exactly one integer size. + * + * The returned pointer may be the same as the original pointer, but this is + * not guaranteed. If the returned pointer is different, the original pointer + * is automatically freed. + * + * The pointer returned by guac_mem_realloc() SHOULD be freed with a subsequent + * call to guac_mem_free() or PRIV_guac_mem_free(), but MAY instead be freed + * with a subsequent call to free(). + * + * @param factor_count + * The number of factors to multiply together to produce the desired block + * size. + * + * @param factors + * An array of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the reallocated block of memory, or NULL + * if such a block could not be allocated. If a block of memory could not + * be allocated, guac_error is set appropriately and the original block of + * memory is left untouched. + */ +void* PRIV_guac_mem_realloc(void* mem, size_t factor_count, const size_t* factors); + +/** + * Reallocates a contiguous block of memory that was previously allocated with + * guac_mem_alloc(), guac_mem_zalloc(), guac_mem_realloc(), or one of their + * PRIV_guac_*() or *_or_die() variants, returning a pointer to the first byte + * of that reallocated block of memory. If multiple sizes are provided, these + * sizes are multiplied together to produce the final size of the new block. If + * memory of the specified size cannot be allocated, or if multiplying the + * sizes would result in integer overflow, execution of the current process is + * aborted entirely, and this function does not return. + * + * This function is analogous to the standard realloc(), but accepts a list of + * size factors instead of a requiring exactly one integer size and does not + * return in the event a block cannot be allocated. + * + * The returned pointer may be the same as the original pointer, but this is + * not guaranteed. If the returned pointer is different, the original pointer + * is automatically freed. + * + * The pointer returned by guac_mem_realloc() SHOULD be freed with a subsequent + * call to guac_mem_free() or PRIV_guac_mem_free(), but MAY instead be freed + * with a subsequent call to free(). + * + * @param factor_count + * The number of factors to multiply together to produce the desired block + * size. + * + * @param factors + * An array of one or more size_t values that should be multiplied together + * to produce the desired block size. At least one value MUST be provided. + * + * @returns + * A pointer to the first byte of the reallocated block of memory. If a + * block of memory could not be allocated, execution of the current process + * is aborted, and this function does not return. + */ +void* PRIV_guac_mem_realloc_or_die(void* mem, size_t factor_count, const size_t* factors); + +/** + * Frees the memory block at the given pointer, which MUST have been allocated + * with guac_mem_alloc(), guac_mem_zalloc(), guac_mem_realloc(), or one of + * their PRIV_guac_*() or *_or_die() variants. If the provided pointer is NULL, + * this function has no effect. + * + * @param mem + * A pointer to the memory to be freed. + */ +void PRIV_guac_mem_free(void* mem); + +#endif + diff --git a/src/libguac/guacamole/rwlock.h b/src/libguac/guacamole/rwlock.h new file mode 100644 index 000000000..719a8e303 --- /dev/null +++ b/src/libguac/guacamole/rwlock.h @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef __GUAC_RWLOCK_H +#define __GUAC_RWLOCK_H + +#include + +/** + * This file implements reentrant read-write locks using thread-local storage + * to keep track of how locks are held and released by the current thread, + * since the pthread locks do not support reentrant behavior. + * + * A thread will attempt to acquire the requested lock on the first acquire + * function call, and will release it once the number of unlock requests + * matches the number of lock requests. Therefore, it is safe to aquire a lock + * and then call a function that also acquires the same lock, provided that + * the caller and the callee request to unlock the lock when done with it. + * + * Any lock that's locked using one of the functions defined in this file + * must _only_ be unlocked using the unlock function defined here to avoid + * unexpected behavior. + */ + +/** + * A structure packaging together a pthread rwlock along with a key to a + * thread-local property to keep track of the current status of the lock, + * allowing the functions defined in this header to provide reentrant behavior. + * Note that both the lock and key must be initialized before being provided + * to any of these functions. + */ +typedef struct guac_rwlock { + + /** + * A non-reentrant pthread rwlock to be wrapped by the local lock, + * functions providing reentrant behavior. + */ + pthread_rwlock_t lock; + + /** + * A key to access a thread-local property tracking any ownership of the + * lock by the current thread. + */ + pthread_key_t key; + +} guac_rwlock; + +/** + * Initialize the provided guac reentrant rwlock. The lock will be configured to be + * visible to child processes. + * + * @param lock + * The guac reentrant rwlock to be initialized. + */ +void guac_rwlock_init(guac_rwlock* lock); + +/** + * Clean up and destroy the provided guac reentrant rwlock. + * + * @param lock + * The guac reentrant rwlock to be destroyed. + */ +void guac_rwlock_destroy(guac_rwlock* lock); + +/** + * Aquire the write lock for the provided guac reentrant rwlock, if the key does not + * indicate that the write lock is already acquired. If the key indicates that + * the read lock is already acquired, the read lock will be dropped before the + * write lock is acquired. The thread local property associated with the key + * will be updated as necessary to track the thread's ownership of the lock. + * + * If an error occurs while attempting to acquire the lock, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param reentrant_rwlock + * The guac reentrant rwlock for which the write lock should be acquired + * reentrantly. + * + * @return + * Zero if the lock is succesfully acquired, or a non-zero value if an + * error occured. + */ +int guac_rwlock_acquire_write_lock(guac_rwlock* reentrant_rwlock); + +/** + * Aquire the read lock for the provided guac reentrant rwlock, if the key does not + * indicate that the read or write lock is already acquired. The thread local + * property associated with the key will be updated as necessary to track the + * thread's ownership of the lock. + * + * If an error occurs while attempting to acquire the lock, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param reentrant_rwlock + * The guac reentrant rwlock for which the read lock should be acquired + * reentrantly. + * + * @return + * Zero if the lock is succesfully acquired, or a non-zero value if an + * error occured. + */ +int guac_rwlock_acquire_read_lock(guac_rwlock* reentrant_rwlock); + +/** + * Release the the rwlock associated with the provided guac reentrant rwlock if this + * is the last level of the lock held by this thread. Otherwise, the thread + * local property associated with the key will be updated as needed to ensure + * that the correct number of release requests will finally release the lock. + * + * If an error occurs while attempting to release the lock, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param reentrant_rwlock + * The guac reentrant rwlock that should be released. + * + * @return + * Zero if the lock is succesfully released, or a non-zero value if an + * error occured. + */ +int guac_rwlock_release_lock(guac_rwlock* reentrant_rwlock); + +#endif + diff --git a/src/libguac/guacamole/socket.h b/src/libguac/guacamole/socket.h index e7afade3b..62aa78154 100644 --- a/src/libguac/guacamole/socket.h +++ b/src/libguac/guacamole/socket.h @@ -235,10 +235,10 @@ guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary); /** * Allocates and initializes a new guac_socket which duplicates all - * instructions written across the sockets of each connected user of the given - * guac_client. The returned socket is a write-only socket. Attempts to read - * from the socket will fail. If a write occurs while no users are connected, - * that write will simply be dropped. + * instructions written across the sockets of each connected user of the + * given guac_client. The returned socket is a write-only socket. Attempts + * to read from the socket will fail. If a write occurs while no users are + * connected, that write will simply be dropped. * * Return values (error codes) from each user's socket will not affect the * in-progress write, but each failing user will be forcibly stopped with @@ -253,12 +253,38 @@ guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary); * * @return * A write-only guac_socket object which broadcasts copies of all - * instructions written across all connected users of the given + * instructions written across all non-pending connected users of the given * guac_client, or NULL if an error occurs while allocating the guac_socket * object. */ guac_socket* guac_socket_broadcast(guac_client* client); +/** + * Allocates and initializes a new guac_socket which duplicates all + * instructions written across the sockets of each pending connected + * user of the given guac_client. The returned socket is a write-only socket. + * Attempts to read from the socket will fail. If a write occurs while no + * users are connected, that write will simply be dropped. + * + * Return values (error codes) from each user's socket will not affect the + * in-progress write, but each failing user will be forcibly stopped with + * guac_user_stop(). + * + * If an error occurs while allocating the guac_socket object, NULL is returned, + * and guac_error is set appropriately. + * + * @param client + * The client associated with the group of pending users across which + * duplicates of all instructions should be written. + * + * @return + * A write-only guac_socket object which broadcasts copies of all + * instructions written across all pending connected users of the given + * guac_client, or NULL if an error occurs while allocating the guac_socket + * object. + */ +guac_socket* guac_socket_broadcast_pending(guac_client* client); + /** * Writes the given unsigned int to the given guac_socket object. The data * written may be buffered until the buffer is flushed automatically or diff --git a/src/libguac/guacamole/string.h b/src/libguac/guacamole/string.h index f720ba193..27bf7b10c 100644 --- a/src/libguac/guacamole/string.h +++ b/src/libguac/guacamole/string.h @@ -132,8 +132,17 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n); char* guac_strnstr(const char *haystack, const char *needle, size_t len); /** - * Simple wrapper for strdup() which behaves identically to standard strdup(), - * except that NULL will be returned if the provided string is NULL. + * Duplicates the given string, returning a newly-allocated string containing + * the same contents. The provided string must be null-terminated. The size of + * the memory block for the newly-allocated string is only guaranteed to + * include enough space for the contents of the provided string, including null + * terminator. + * + * The pointer returned by guac_strdup() SHOULD be freed with a subsequent call + * to guac_mem_free(), but MAY instead be freed with a subsequent call to free(). + * + * This function behaves identically to standard strdup(), except that NULL + * will be returned if the provided string is NULL. * * @param str * The string to duplicate as a newly-allocated string. diff --git a/src/libguac/id.c b/src/libguac/id.c index e627f89d8..75cde9627 100644 --- a/src/libguac/id.c +++ b/src/libguac/id.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/error.h" #include "id.h" @@ -68,7 +69,7 @@ char* guac_generate_id(char prefix) { #endif /* Allocate buffer for future formatted ID */ - buffer = malloc(GUAC_UUID_LEN + 2); + buffer = guac_mem_alloc(GUAC_UUID_LEN + 2); if (buffer == NULL) { #ifndef HAVE_LIBUUID uuid_destroy(uuid); @@ -86,7 +87,7 @@ char* guac_generate_id(char prefix) { #else size_t identifier_length = GUAC_UUID_LEN + 1; if (uuid_export(uuid, UUID_FMT_STR, &identifier, &identifier_length) != UUID_RC_OK) { - free(buffer); + guac_mem_free(buffer); uuid_destroy(uuid); guac_error = GUAC_STATUS_INTERNAL_ERROR; guac_error_message = "Conversion of UUID to unique ID failed"; diff --git a/src/libguac/id.h b/src/libguac/id.h index 9b64d4108..4a54cdbfb 100644 --- a/src/libguac/id.h +++ b/src/libguac/id.h @@ -23,13 +23,16 @@ /** * Generates a guaranteed-unique identifier which is a total of 37 characters * long, having the given single-character prefix. The resulting identifier - * must be freed with a call to free() when no longer needed. If an error + * must be freed with a call to guac_mem_free() when no longer needed. If an error * occurs, NULL is returned, no memory is allocated, and guac_error is set * appropriately. * - * @param prefix The single-character prefix to use. - * @return A newly-allocated unique identifier with the given prefix, or - * NULL if the identifier could not be generated. + * @param prefix + * The single-character prefix to use. + * + * @return + * A newly-allocated unique identifier with the given prefix, or NULL if + * the identifier could not be generated. */ char* guac_generate_id(char prefix); diff --git a/src/libguac/mem.c b/src/libguac/mem.c new file mode 100644 index 000000000..8f9a842a6 --- /dev/null +++ b/src/libguac/mem.c @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "guacamole/error.h" +#include "guacamole/mem.h" +#include "guacamole/private/mem.h" + +#include +#include +#include + +/* + * ============================================================================ + * + * IMPORTANT: For compatibility with past usages of libguac, all allocation + * functions implemented here need to remain compatible with the standard + * free() function, as there are past usages of libguac functions that expect + * allocated memory to have been allocated with malloc() or similar. Some good + * examples of this would be guac_strdup() or guac_user_parse_args_string(). + * + * It is OK if these allocation functions add new functionality beyond what + * malloc() provides, but care must be taken to ensure free() can still be used + * safely and without leaks, even if guac_mem_free() will always be preferred. + * + * It is further OK for guac_mem_free() to be incompatible with free() and only + * usable on memory blocks allocated through guac_mem_alloc() and similar. + * + * ============================================================================ + */ + +int PRIV_guac_mem_ckd_mul(size_t* result, size_t factor_count, const size_t* factors) { + + /* Consider calculation invalid if no factors are provided at all */ + if (factor_count == 0) + return 1; + + /* Multiply all provided factors together */ + size_t size = *(factors++); + while (--factor_count && size) { + + size_t factor = *(factors++); + + /* Fail if including this additional factor would exceed SIZE_MAX */ + size_t max_factor = SIZE_MAX / size; + if (factor > max_factor) + return 1; + + size *= factor; + + } + + *result = size; + return 0; + +} + +int PRIV_guac_mem_ckd_add(size_t* result, size_t term_count, const size_t* terms) { + + /* Consider calculation invalid if no terms are provided at all */ + if (term_count == 0) + return 1; + + /* Multiply all provided terms together */ + size_t size = *(terms++); + while (--term_count) { + + size_t term = *(terms++); + + /* Fail if including this additional term would exceed SIZE_MAX */ + size_t max_term = SIZE_MAX - size; + if (term > max_term) + return 1; + + size += term; + + } + + *result = size; + return 0; + +} + +int PRIV_guac_mem_ckd_sub(size_t* result, size_t term_count, const size_t* terms) { + + /* Consider calculation invalid if no terms are provided at all */ + if (term_count == 0) + return 1; + + /* Multiply all provided terms together */ + size_t size = *(terms++); + while (--term_count) { + + size_t term = *(terms++); + + /* Fail if including this additional term would wrap past zero */ + if (term > size) + return 1; + + size -= term; + + } + + *result = size; + return 0; + +} + +size_t PRIV_guac_mem_ckd_mul_or_die(size_t factor_count, const size_t* factors) { + + /* Perform request multiplication, aborting the entire process if the + * calculation overflows */ + size_t result = 0; + if (PRIV_guac_mem_ckd_mul(&result, factor_count, factors)) + abort(); + + return result; + +} + +size_t PRIV_guac_mem_ckd_add_or_die(size_t term_count, const size_t* terms) { + + /* Perform request addition, aborting the entire process if the calculation + * overflows */ + size_t result = 0; + if (PRIV_guac_mem_ckd_add(&result, term_count, terms)) + abort(); + + return result; + +} + +size_t PRIV_guac_mem_ckd_sub_or_die(size_t term_count, const size_t* terms) { + + /* Perform request subtraction, aborting the entire process if the + * calculation overflows */ + size_t result = 0; + if (PRIV_guac_mem_ckd_sub(&result, term_count, terms)) + abort(); + + return result; + +} + +void* PRIV_guac_mem_alloc(size_t factor_count, const size_t* factors) { + + size_t size = 0; + + if (PRIV_guac_mem_ckd_mul(&size, factor_count, factors)) { + guac_error = GUAC_STATUS_NO_MEMORY; + return NULL; + } + else if (size == 0) + return NULL; + + void* mem = malloc(size); + if (mem == NULL) { + /* C does not require that malloc() set errno (though POSIX does). For + * portability, we set guac_error here regardless of the underlying + * behavior of malloc(). */ + guac_error = GUAC_STATUS_NO_MEMORY; + } + + return mem; + +} + +void* PRIV_guac_mem_zalloc(size_t factor_count, const size_t* factors) { + + size_t size = 0; + + if (PRIV_guac_mem_ckd_mul(&size, factor_count, factors)) { + guac_error = GUAC_STATUS_NO_MEMORY; + return NULL; + } + else if (size == 0) + return NULL; + + void* mem = calloc(1, size); + if (mem == NULL) { + /* C does not require that calloc() set errno (though POSIX does). For + * portability, we set guac_error here regardless of the underlying + * behavior of calloc(). */ + guac_error = GUAC_STATUS_NO_MEMORY; + } + + return mem; + +} + +void* PRIV_guac_mem_realloc(void* mem, size_t factor_count, const size_t* factors) { + + size_t size = 0; + + if (PRIV_guac_mem_ckd_mul(&size, factor_count, factors)) { + guac_error = GUAC_STATUS_NO_MEMORY; + return NULL; + } + + /* Resize to 0 is equivalent to free() */ + if (size == 0) { + guac_mem_free(mem); + return NULL; + } + + void* resized_mem = realloc(mem, size); + if (resized_mem == NULL) { + /* C does not require that realloc() set errno (though POSIX does). For + * portability, we set guac_error here regardless of the underlying + * behavior of realloc(). */ + guac_error = GUAC_STATUS_NO_MEMORY; + } + + return resized_mem; + +} + +void* PRIV_guac_mem_realloc_or_die(void* mem, size_t factor_count, const size_t* factors) { + + /* Reset any past errors for upcoming error check */ + guac_error = GUAC_STATUS_SUCCESS; + + /* Perform requested resize, aborting the entire process if this cannot be + * done */ + void* resized_mem = PRIV_guac_mem_realloc(mem, factor_count, factors); + if (resized_mem == NULL && guac_error != GUAC_STATUS_SUCCESS) + abort(); + + return resized_mem; + +} + +void PRIV_guac_mem_free(void* mem) { + free(mem); +} + diff --git a/src/libguac/palette.c b/src/libguac/palette.c index 16f97010c..1c56d8286 100644 --- a/src/libguac/palette.c +++ b/src/libguac/palette.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "palette.h" #include @@ -38,8 +39,7 @@ guac_palette* guac_palette_alloc(cairo_surface_t* surface) { unsigned char* data = cairo_image_surface_get_data(surface); /* Allocate palette */ - guac_palette* palette = (guac_palette*) malloc(sizeof(guac_palette)); - memset(palette, 0, sizeof(guac_palette)); + guac_palette* palette = (guac_palette*) guac_mem_zalloc(sizeof(guac_palette)); for (y=0; y @@ -26,7 +27,7 @@ guac_pool* guac_pool_alloc(int size) { pthread_mutexattr_t lock_attributes; - guac_pool* pool = malloc(sizeof(guac_pool)); + guac_pool* pool = guac_mem_alloc(sizeof(guac_pool)); /* If unable to allocate, just return NULL. */ if (pool == NULL) @@ -57,14 +58,14 @@ void guac_pool_free(guac_pool* pool) { guac_pool_int* old = current; current = current->__next; - free(old); + guac_mem_free(old); } /* Destroy lock */ pthread_mutex_destroy(&(pool->__lock)); /* Free pool */ - free(pool); + guac_mem_free(pool); } @@ -89,7 +90,7 @@ int guac_pool_next_int(guac_pool* pool) { /* If only one element exists, reset pool to empty. */ if (pool->__tail == pool->__head) { - free(pool->__head); + guac_mem_free(pool->__head); pool->__head = NULL; pool->__tail = NULL; } @@ -98,7 +99,7 @@ int guac_pool_next_int(guac_pool* pool) { else { guac_pool_int* old_head = pool->__head; pool->__head = old_head->__next; - free(old_head); + guac_mem_free(old_head); } /* Return retrieved value. */ @@ -109,7 +110,7 @@ int guac_pool_next_int(guac_pool* pool) { void guac_pool_free_int(guac_pool* pool, int value) { /* Allocate and initialize new returned value */ - guac_pool_int* pool_int = malloc(sizeof(guac_pool_int)); + guac_pool_int* pool_int = guac_mem_alloc(sizeof(guac_pool_int)); pool_int->value = value; pool_int->__next = NULL; diff --git a/src/libguac/raw_encoder.c b/src/libguac/raw_encoder.c index 888bde79d..8fdefc754 100644 --- a/src/libguac/raw_encoder.c +++ b/src/libguac/raw_encoder.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/audio.h" #include "guacamole/client.h" #include "guacamole/protocol.h" @@ -52,13 +53,12 @@ static void raw_encoder_begin_handler(guac_audio_stream* audio) { raw_encoder_send_audio(audio, audio->client->socket); /* Allocate and init encoder state */ - audio->data = state = malloc(sizeof(raw_encoder_state)); + audio->data = state = guac_mem_alloc(sizeof(raw_encoder_state)); state->written = 0; - state->length = GUAC_RAW_ENCODER_BUFFER_SIZE - * audio->rate * audio->channels * audio->bps - / 8 / 1000; + state->length = guac_mem_ckd_mul_or_die(GUAC_RAW_ENCODER_BUFFER_SIZE, + audio->rate, audio->channels, audio->bps) / 8 / 1000; - state->buffer = malloc(state->length); + state->buffer = guac_mem_alloc(state->length); } @@ -78,8 +78,8 @@ static void raw_encoder_end_handler(guac_audio_stream* audio) { guac_protocol_send_end(audio->client->socket, audio->stream); /* Free state information */ - free(state->buffer); - free(state); + guac_mem_free(state->buffer); + guac_mem_free(state); } diff --git a/src/libguac/raw_encoder.h b/src/libguac/raw_encoder.h index c3af151c6..e60d12b4c 100644 --- a/src/libguac/raw_encoder.h +++ b/src/libguac/raw_encoder.h @@ -52,7 +52,7 @@ typedef struct raw_encoder_state { /** * Size of the PCM buffer, in bytes. */ - int length; + size_t length; /** * The current number of bytes stored within the PCM buffer. diff --git a/src/libguac/recording.c b/src/libguac/recording.c index 0fd94284f..7544cc144 100644 --- a/src/libguac/recording.c +++ b/src/libguac/recording.c @@ -17,6 +17,7 @@ * under the License. */ +#include "guacamole/mem.h" #include "guacamole/client.h" #include "guacamole/protocol.h" #include "guacamole/recording.h" @@ -162,7 +163,7 @@ guac_recording* guac_recording_create(guac_client* client, } /* Create recording structure with reference to underlying socket */ - guac_recording* recording = malloc(sizeof(guac_recording)); + guac_recording* recording = guac_mem_alloc(sizeof(guac_recording)); recording->socket = guac_socket_open(fd); recording->include_output = include_output; recording->include_mouse = include_mouse; @@ -191,7 +192,7 @@ void guac_recording_free(guac_recording* recording) { guac_socket_free(recording->socket); /* Free recording itself */ - free(recording); + guac_mem_free(recording); } diff --git a/src/libguac/rwlock.c b/src/libguac/rwlock.c new file mode 100644 index 000000000..e400c8e72 --- /dev/null +++ b/src/libguac/rwlock.c @@ -0,0 +1,276 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include "guacamole/error.h" +#include "guacamole/rwlock.h" + +/** + * The value indicating that the current thread holds neither the read or write + * locks. + */ +#define GUAC_REENTRANT_LOCK_NO_LOCK 0 + +/** + * The value indicating that the current thread holds the read lock. + */ +#define GUAC_REENTRANT_LOCK_READ_LOCK 1 + +/** + * The value indicating that the current thread holds the write lock. + */ +#define GUAC_REENTRANT_LOCK_WRITE_LOCK 2 + +void guac_rwlock_init(guac_rwlock* lock) { + + /* Configure to allow sharing this lock with child processes */ + pthread_rwlockattr_t lock_attributes; + pthread_rwlockattr_init(&lock_attributes); + pthread_rwlockattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); + + /* Initialize the rwlock */ + pthread_rwlock_init(&(lock->lock), &lock_attributes); + + /* Initialize the flags to 0, as threads won't have acquired it yet */ + pthread_key_create(&(lock->key), (void *) 0); + +} + +void guac_rwlock_destroy(guac_rwlock* lock) { + + /* Destroy the rwlock */ + pthread_rwlock_destroy(&(lock->lock)); + + /* Destroy the thread-local key */ + pthread_key_delete(lock->key); + +} + +/** + * Clean up and destroy the provided guac reentrant rwlock. + * + * @param lock + * The guac reentrant rwlock to be destroyed. + */ +void guac_rwlock_destroy(guac_rwlock* lock); + +/** + * Extract and return the flag indicating which lock is held, if any, from the + * provided key value. The flag is always stored in the least-significant + * nibble of the value. + * + * @param value + * The key value containing the flag. + * + * @return + * The flag indicating which lock is held, if any. + */ +static uintptr_t get_lock_flag(uintptr_t value) { + return value & 0xF; +} + +/** + * Extract and return the lock count from the provided key. This returned value + * is the difference between the number of lock and unlock requests made by the + * current thread. This count is always stored in the remaining value after the + * least-significant nibble where the flag is stored. + * + * @param value + * The key value containing the count. + * + * @return + * The difference between the number of lock and unlock requests made by + * the current thread. + */ +static uintptr_t get_lock_count(uintptr_t value) { + return value >> 4; +} + +/** + * Given a flag indicating if and how the current thread controls a lock, and + * a count of the depth of lock requests, return a value containing the flag + * in the least-significant nibble, and the count in the rest. + * + * @param flag + * A flag indiciating which lock, if any, is held by the current thread. + * + * @param count + * The depth of the lock attempt by the current thread, i.e. the number of + * lock requests minus unlock requests. + * + * @return + * A value containing the flag in the least-significant nibble, and the + * count in the rest, cast to a void* for thread-local storage. + */ +static void* get_value_from_flag_and_count( + uintptr_t flag, uintptr_t count) { + return (void*) ((flag & 0xF) | count << 4); +} + +/** + * Return zero if adding one to the current count would overflow the storage + * allocated to the count, or a non-zero value otherwise. + * + * @param current_count + * The current count for a lock that the current thread is trying to + * reentrantly acquire. + * + * @return + * Zero if adding one to the current count would overflow the storage + * allocated to the count, or a non-zero value otherwise. + */ +static int would_overflow_count(uintptr_t current_count) { + + /** + * The count will overflow if it's already equal or greated to the maximum + * possible value that can be stored in a uintptr_t excluding the first nibble. + */ + return current_count >= (UINTPTR_MAX >> 4); + +} + +int guac_rwlock_acquire_write_lock(guac_rwlock* reentrant_rwlock) { + + uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key); + uintptr_t flag = get_lock_flag(key_value); + uintptr_t count = get_lock_count(key_value); + + /* If acquiring this lock again would overflow the counter storage */ + if (would_overflow_count(count)) { + + guac_error = GUAC_STATUS_TOO_MANY; + guac_error_message = "Unable to acquire write lock because there's" + " insufficient space to store another level of lock depth"; + + return 1; + + } + + /* If the current thread already holds the write lock, increment the count */ + if (flag == GUAC_REENTRANT_LOCK_WRITE_LOCK) { + pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( + flag, count + 1)); + + /* This thread already has the lock */ + return 0; + } + + /* + * The read lock must be released before the write lock can be acquired. + * This is a little odd because it may mean that a function further down + * the stack may have requested a read lock, which will get upgraded to a + * write lock by another function without the caller knowing about it. This + * shouldn't cause any issues, however. + */ + if (key_value == GUAC_REENTRANT_LOCK_READ_LOCK) + pthread_rwlock_unlock(&(reentrant_rwlock->lock)); + + /* Acquire the write lock */ + pthread_rwlock_wrlock(&(reentrant_rwlock->lock)); + + /* Mark that the current thread has the lock, and increment the count */ + pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( + GUAC_REENTRANT_LOCK_WRITE_LOCK, count + 1)); + + return 0; + +} + +int guac_rwlock_acquire_read_lock(guac_rwlock* reentrant_rwlock) { + + uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key); + uintptr_t flag = get_lock_flag(key_value); + uintptr_t count = get_lock_count(key_value); + + /* If acquiring this lock again would overflow the counter storage */ + if (would_overflow_count(count)) { + + guac_error = GUAC_STATUS_TOO_MANY; + guac_error_message = "Unable to acquire read lock because there's" + " insufficient space to store another level of lock depth"; + + return 1; + + } + + /* The current thread may read if either the read or write lock is held */ + if ( + flag == GUAC_REENTRANT_LOCK_READ_LOCK || + flag == GUAC_REENTRANT_LOCK_WRITE_LOCK + ) { + + /* Increment the depth counter */ + pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( + flag, count + 1)); + + /* This thread already has the lock */ + return 0; + } + + /* Acquire the lock */ + pthread_rwlock_rdlock(&(reentrant_rwlock->lock)); + + /* Set the flag that the current thread has the read lock */ + pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( + GUAC_REENTRANT_LOCK_READ_LOCK, 1)); + + return 0; + +} + +int guac_rwlock_release_lock(guac_rwlock* reentrant_rwlock) { + + uintptr_t key_value = (uintptr_t) pthread_getspecific(reentrant_rwlock->key); + uintptr_t flag = get_lock_flag(key_value); + uintptr_t count = get_lock_count(key_value); + + /* + * Return an error if an attempt is made to release a lock that the current + * thread does not control. + */ + if (count <= 0) { + + guac_error = GUAC_STATUS_INVALID_ARGUMENT; + guac_error_message = "Unable to free rwlock because it's not held by" + " the current thread"; + + return 1; + + } + + /* Release the lock if this is the last locked level */ + if (count == 1) { + + pthread_rwlock_unlock(&(reentrant_rwlock->lock)); + + /* Set the flag that the current thread holds no locks */ + pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( + GUAC_REENTRANT_LOCK_NO_LOCK, 0)); + + return 0; + } + + /* Do not release the lock since it's still in use - just decrement */ + pthread_setspecific(reentrant_rwlock->key, get_value_from_flag_and_count( + flag, count - 1)); + + return 0; + +} diff --git a/src/libguac/socket-broadcast.c b/src/libguac/socket-broadcast.c index f551e8172..2d605feaf 100644 --- a/src/libguac/socket-broadcast.c +++ b/src/libguac/socket-broadcast.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/client.h" #include "guacamole/error.h" #include "guacamole/socket.h" @@ -28,8 +29,25 @@ #include /** - * Data associated with an open socket which writes to all connected users of - * a particular guac_client. + * A function that will broadcast arbitrary data to a subset of users for + * the provided client, using the provided user callback for any user-specific + * operations. + * + * @param client + * The guac_client associated with the users to broadcast to. + * + * @param callback + * A callback that should be invoked with each broadcasted user. + * + * @param data + * Arbitrary data that may be used to broadcast to the subset of users. + */ +typedef void guac_socket_broadcast_handler( + guac_client* client, guac_user_callback* callback, void* data); + +/** + * Data associated with an open socket which writes to a subset of connected + * users of a particular guac_client. */ typedef struct guac_socket_broadcast_data { @@ -45,6 +63,11 @@ typedef struct guac_socket_broadcast_data { */ pthread_mutex_t socket_lock; + /** + * The function to broadcast + */ + guac_socket_broadcast_handler* broadcast_handler; + } guac_socket_broadcast_data; /** @@ -91,7 +114,7 @@ static ssize_t __guac_socket_broadcast_read_handler(guac_socket* socket, } /** - * Callback invoked by guac_client_foreach_user() which write a given chunk of + * Callback invoked by the broadcast handler which write a given chunk of * data to that user's socket. If the write attempt fails, the user is * signalled to stop with guac_user_stop(). * @@ -146,15 +169,15 @@ static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket, chunk.buffer = buf; chunk.length = count; - /* Broadcast chunk to all users */ - guac_client_foreach_user(data->client, __write_chunk_callback, &chunk); + /* Broadcast chunk to the users */ + data->broadcast_handler(data->client, __write_chunk_callback, &chunk); return count; } /** - * Callback which is invoked by guac_client_foreach_user() to flush all + * Callback which is invoked by the broadcast handler to flush all * pending data on the given user's socket. If an error occurs while flushing * a user's socket, that user is signalled to stop with guac_user_stop(). * @@ -162,7 +185,7 @@ static ssize_t __guac_socket_broadcast_write_handler(guac_socket* socket, * The user whose socket should be flushed. * * @param data - * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * Arbitrary data passed to the broadcast handler. This is not needed * by this callback, and should be left as NULL. * * @return @@ -195,15 +218,15 @@ static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) { guac_socket_broadcast_data* data = (guac_socket_broadcast_data*) socket->data; - /* Flush all users */ - guac_client_foreach_user(data->client, __flush_callback, NULL); + /* Flush the users */ + data->broadcast_handler(data->client, __flush_callback, NULL); return 0; } /** - * Callback which is invoked by guac_client_foreach_user() to lock the given + * Callback which is invoked by the broadcast handler to lock the given * user's socket in preparation for the beginning of a Guacamole protocol * instruction. * @@ -211,7 +234,7 @@ static ssize_t __guac_socket_broadcast_flush_handler(guac_socket* socket) { * The user whose socket should be locked. * * @param data - * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * Arbitrary data passed to the broadcast handler. This is not needed * by this callback, and should be left as NULL. * * @return @@ -243,20 +266,20 @@ static void __guac_socket_broadcast_lock_handler(guac_socket* socket) { /* Acquire exclusive access to socket */ pthread_mutex_lock(&(data->socket_lock)); - /* Lock sockets of all users */ - guac_client_foreach_user(data->client, __lock_callback, NULL); + /* Lock sockets of the users */ + data->broadcast_handler(data->client, __lock_callback, NULL); } /** - * Callback which is invoked by guac_client_foreach_user() to unlock the given + * Callback which is invoked by the broadcast handler to unlock the given * user's socket at the end of a Guacamole protocol instruction. * * @param user * The user whose socket should be unlocked. * * @param data - * Arbitrary data passed to guac_client_foreach_user(). This is not needed + * Arbitrary data passed to the broadcast handler. This is not needed * by this callback, and should be left as NULL. * * @return @@ -285,7 +308,7 @@ static void __guac_socket_broadcast_unlock_handler(guac_socket* socket) { (guac_socket_broadcast_data*) socket->data; /* Unlock sockets of all users */ - guac_client_foreach_user(data->client, __unlock_callback, NULL); + data->broadcast_handler(data->client, __unlock_callback, NULL); /* Relinquish exclusive access to socket */ pthread_mutex_unlock(&(data->socket_lock)); @@ -338,19 +361,37 @@ static int __guac_socket_broadcast_free_handler(guac_socket* socket) { /* Destroy locks */ pthread_mutex_destroy(&(data->socket_lock)); - free(data); + guac_mem_free(data); return 0; } -guac_socket* guac_socket_broadcast(guac_client* client) { +/** + * Construct and return a socket that will broadcast to the users given by + * by the provided broadcast handler. + * + * @param client + * The client who's users are being broadcast to. + * + * @param broadcast_handler + * The handler that will peform the broadcast against a subset of users + * of the provided client. + * + * @return + * The newly constructed broadcast socket + */ +static guac_socket* __guac_socket_init( + guac_client* client, guac_socket_broadcast_handler* broadcast_handler) { pthread_mutexattr_t lock_attributes; /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); guac_socket_broadcast_data* data = - malloc(sizeof(guac_socket_broadcast_data)); + guac_mem_alloc(sizeof(guac_socket_broadcast_data)); + + /* Set the provided broadcast handler */ + data->broadcast_handler = broadcast_handler; /* Store client as socket data */ data->client = client; @@ -361,7 +402,7 @@ guac_socket* guac_socket_broadcast(guac_client* client) { /* Init lock */ pthread_mutex_init(&(data->socket_lock), &lock_attributes); - + /* Set read/write handlers */ socket->read_handler = __guac_socket_broadcast_read_handler; socket->write_handler = __guac_socket_broadcast_write_handler; @@ -375,3 +416,17 @@ guac_socket* guac_socket_broadcast(guac_client* client) { } +guac_socket* guac_socket_broadcast(guac_client* client) { + + /* Broadcast to all connected non-pending users*/ + return __guac_socket_init(client, guac_client_foreach_user); + +} + +guac_socket* guac_socket_broadcast_pending(guac_client* client) { + + /* Broadcast to all connected pending users*/ + return __guac_socket_init(client, guac_client_foreach_pending_user); + +} + diff --git a/src/libguac/socket-fd.c b/src/libguac/socket-fd.c index 742cc35d8..44f45e2ec 100644 --- a/src/libguac/socket-fd.c +++ b/src/libguac/socket-fd.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/error.h" #include "guacamole/socket.h" #include "wait-fd.h" @@ -382,7 +383,7 @@ static int guac_socket_fd_free_handler(guac_socket* socket) { /* Close file descriptor */ close(data->fd); - free(data); + guac_mem_free(data); return 0; } @@ -423,7 +424,7 @@ guac_socket* guac_socket_open(int fd) { /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); - guac_socket_fd_data* data = malloc(sizeof(guac_socket_fd_data)); + guac_socket_fd_data* data = guac_mem_alloc(sizeof(guac_socket_fd_data)); /* Store file descriptor as socket data */ data->fd = fd; diff --git a/src/libguac/socket-nest.c b/src/libguac/socket-nest.c index 8bc9291eb..29319097f 100644 --- a/src/libguac/socket-nest.c +++ b/src/libguac/socket-nest.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/protocol.h" #include "guacamole/socket.h" #include "guacamole/unicode.h" @@ -274,7 +275,7 @@ static int guac_socket_nest_free_handler(guac_socket* socket) { /* Free associated data */ guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; - free(data); + guac_mem_free(data); return 0; @@ -314,7 +315,7 @@ guac_socket* guac_socket_nest(guac_socket* parent, int index) { /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); - guac_socket_nest_data* data = malloc(sizeof(guac_socket_nest_data)); + guac_socket_nest_data* data = guac_mem_alloc(sizeof(guac_socket_nest_data)); /* Store nested socket details as socket data */ data->parent = parent; diff --git a/src/libguac/socket-ssl.c b/src/libguac/socket-ssl.c index 3daa128e6..2825a3577 100644 --- a/src/libguac/socket-ssl.c +++ b/src/libguac/socket-ssl.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/error.h" #include "guacamole/socket-ssl.h" #include "guacamole/socket.h" @@ -96,7 +97,7 @@ static int __guac_socket_ssl_free_handler(guac_socket* socket) { /* Close file descriptor */ close(data->fd); - free(data); + guac_mem_free(data); return 0; } @@ -109,7 +110,7 @@ guac_socket* guac_socket_open_secure(SSL_CTX* context, int fd) { /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); - guac_socket_ssl_data* data = malloc(sizeof(guac_socket_ssl_data)); + guac_socket_ssl_data* data = guac_mem_alloc(sizeof(guac_socket_ssl_data)); /* Init SSL */ data->context = context; @@ -122,7 +123,7 @@ guac_socket* guac_socket_open_secure(SSL_CTX* context, int fd) { guac_error = GUAC_STATUS_INTERNAL_ERROR; guac_error_message = "SSL accept failed"; - free(data); + guac_mem_free(data); guac_socket_free(socket); SSL_free(ssl); return NULL; diff --git a/src/libguac/socket-tee.c b/src/libguac/socket-tee.c index cee9108cf..84710455d 100644 --- a/src/libguac/socket-tee.c +++ b/src/libguac/socket-tee.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/socket.h" #include @@ -202,7 +203,7 @@ static int __guac_socket_tee_free_handler(guac_socket* socket) { guac_socket_free(data->secondary); /* Freeing the tee socket always succeeds */ - free(data); + guac_mem_free(data); return 0; } @@ -210,7 +211,7 @@ static int __guac_socket_tee_free_handler(guac_socket* socket) { guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary) { /* Set up socket to split outout into a file */ - guac_socket_tee_data* data = malloc(sizeof(guac_socket_tee_data)); + guac_socket_tee_data* data = guac_mem_alloc(sizeof(guac_socket_tee_data)); data->primary = primary; data->secondary = secondary; diff --git a/src/libguac/socket-wsa.c b/src/libguac/socket-wsa.c index f5602e394..18e794757 100644 --- a/src/libguac/socket-wsa.c +++ b/src/libguac/socket-wsa.c @@ -17,6 +17,7 @@ * under the License. */ +#include "guacamole/mem.h" #include "guacamole/error.h" #include "guacamole/socket.h" @@ -378,7 +379,7 @@ static int guac_socket_wsa_free_handler(guac_socket* socket) { /* Close socket */ closesocket(data->sock); - free(data); + guac_mem_free(data); return 0; } @@ -419,7 +420,7 @@ guac_socket* guac_socket_open_wsa(SOCKET sock) { /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); - guac_socket_wsa_data* data = malloc(sizeof(guac_socket_wsa_data)); + guac_socket_wsa_data* data = guac_mem_alloc(sizeof(guac_socket_wsa_data)); /* Store socket as socket data */ data->sock = sock; diff --git a/src/libguac/socket.c b/src/libguac/socket.c index 27933c43e..b7a4b77e8 100644 --- a/src/libguac/socket.c +++ b/src/libguac/socket.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/error.h" #include "guacamole/protocol.h" #include "guacamole/socket.h" @@ -141,7 +142,7 @@ int guac_socket_select(guac_socket* socket, int usec_timeout) { guac_socket* guac_socket_alloc() { - guac_socket* socket = malloc(sizeof(guac_socket)); + guac_socket* socket = guac_mem_alloc(sizeof(guac_socket)); /* If no memory available, return with error */ if (socket == NULL) { @@ -213,7 +214,7 @@ void guac_socket_free(guac_socket* socket) { pthread_join(socket->__keep_alive_thread, NULL); } - free(socket); + guac_mem_free(socket); } ssize_t guac_socket_write_int(guac_socket* socket, int64_t i) { diff --git a/src/libguac/string.c b/src/libguac/string.c index 260465dab..2a7ec2cd4 100644 --- a/src/libguac/string.c +++ b/src/libguac/string.c @@ -19,6 +19,8 @@ #include "config.h" +#include "guacamole/mem.h" + #include #include @@ -119,8 +121,18 @@ char* guac_strdup(const char* str) { if (str == NULL) return NULL; - /* Otherwise just invoke strdup() */ - return strdup(str); + /* Do not attempt to duplicate if the length is somehow magically so + * obscenely large that it will not be possible to add a null terminator */ + size_t length; + if (guac_mem_ckd_add(&length, strlen(str), 1)) + return NULL; + + /* Otherwise just copy to a new string in same manner as strdup() */ + void* new_str = guac_mem_alloc(length); + if (new_str != NULL) + memcpy(new_str, str, length); + + return new_str; } diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index b7452cf01..3596156cf 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -33,10 +33,24 @@ ACLOCAL_AMFLAGS = -I m4 check_PROGRAMS = test_libguac TESTS = $(check_PROGRAMS) +noinst_HEADERS = \ + assert-signal.h + test_libguac_SOURCES = \ client/buffer_pool.c \ client/layer_pool.c \ id/generate.c \ + mem/alloc.c \ + mem/ckd_add.c \ + mem/ckd_add_or_die.c \ + mem/ckd_mul.c \ + mem/ckd_mul_or_die.c \ + mem/ckd_sub.c \ + mem/ckd_sub_or_die.c \ + mem/free.c \ + mem/realloc.c \ + mem/realloc_or_die.c \ + mem/zalloc.c \ parser/append.c \ parser/read.c \ pool/next_free.c \ diff --git a/src/libguac/tests/assert-signal.h b/src/libguac/tests/assert-signal.h new file mode 100644 index 000000000..76b52c2a1 --- /dev/null +++ b/src/libguac/tests/assert-signal.h @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_LIBGUAC_TESTS_ASSERT_SIGNAL_H +#define GUAC_LIBGUAC_TESTS_ASSERT_SIGNAL_H + +#include +#include +#include +#include +#include + +/** + * Verifies that the given test terminates the calling process with the given + * signal. + * + * @param sig + * The signal that is expected to terminate the calling process. + * + * @param test + * The test that is expected to terminate the calling process with the + * given signal. + */ +#define ASSERT_SIGNALLED(sig, test) \ + do { \ + \ + /* Fork to ensure test can safely terminate */ \ + pid_t _child = fork(); \ + CU_ASSERT_NOT_EQUAL_FATAL(_child, -1); \ + \ + /* Run test strictly within child process */ \ + if (_child == 0) { \ + do { test; } while (0); \ + exit(0); \ + } \ + \ + /* Wait for child process to terminate */ \ + int _status = 0; \ + CU_ASSERT_EQUAL_FATAL(waitpid(_child, &_status, 0), _child); \ + \ + /* Verify process terminated with expected signal */ \ + if (WIFSIGNALED(_status)) { \ + CU_ASSERT_EQUAL(WTERMSIG(_status), (sig)); \ + } \ + else \ + CU_FAIL("Process did not terminate due to a signal"); \ + \ + } while (0) + +#endif + diff --git a/src/libguac/tests/id/generate.c b/src/libguac/tests/id/generate.c index 3142a6f10..31c336a72 100644 --- a/src/libguac/tests/id/generate.c +++ b/src/libguac/tests/id/generate.c @@ -17,6 +17,7 @@ * under the License. */ +#include "guacamole/mem.h" #include "id.h" #include @@ -40,8 +41,8 @@ void test_id__unique() { /* Both strings should be different */ CU_ASSERT_STRING_NOT_EQUAL(id1, id2); - free(id1); - free(id2); + guac_mem_free(id1); + guac_mem_free(id2); } @@ -62,7 +63,7 @@ void test_id__format() { CU_ASSERT_EQUAL(items_read, 6); CU_ASSERT_EQUAL(strlen(id), 37); - free(id); + guac_mem_free(id); } @@ -77,12 +78,12 @@ void test_id__prefix() { id = guac_generate_id('a'); CU_ASSERT_PTR_NOT_NULL_FATAL(id); CU_ASSERT_EQUAL(id[0], 'a'); - free(id); + guac_mem_free(id); id = guac_generate_id('b'); CU_ASSERT_PTR_NOT_NULL_FATAL(id); CU_ASSERT_EQUAL(id[0], 'b'); - free(id); + guac_mem_free(id); } diff --git a/src/libguac/tests/mem/alloc.c b/src/libguac/tests/mem/alloc.c new file mode 100644 index 000000000..bec3145be --- /dev/null +++ b/src/libguac/tests/mem/alloc.c @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +/** + * Test which verifies that guac_mem_alloc() returns NULL for all inputs involving + * at least one zero value. + */ +void test_mem__alloc_fail_zero() { + + CU_ASSERT_PTR_NULL(guac_mem_alloc(0)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(0, 0)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(0, 0, 0)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(0, 0, 0, 0)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(0, 0, 0, 0, 0)); + + CU_ASSERT_PTR_NULL(guac_mem_alloc(1, 0)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(3, 2, 0)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(5, 0, 8, 9)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(99, 99, 99, 0, 99)); + +} + +/** + * Test which verifies that guac_mem_alloc() successfully allocates blocks of + * memory for inputs that can reasonably be expected to succeed. + */ +void test_mem__alloc_success() { + + void* ptr; + + ptr = guac_mem_alloc(123); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(123, 456); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(123, 456, 789); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + +} + +/** + * Test which verifies that guac_mem_alloc() fails to allocate blocks of memory + * that exceed the capacity of a size_t. + */ +void test_mem__alloc_fail_large() { + CU_ASSERT_PTR_NULL(guac_mem_alloc(123, 456, SIZE_MAX)); + CU_ASSERT_PTR_NULL(guac_mem_alloc(SIZE_MAX / 2, SIZE_MAX / 2)); +} + diff --git a/src/libguac/tests/mem/ckd_add.c b/src/libguac/tests/mem/ckd_add.c new file mode 100644 index 000000000..f24e6247b --- /dev/null +++ b/src/libguac/tests/mem/ckd_add.c @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +/** + * Test which verifies that guac_mem_ckd_add() calculates results correctly for + * all inputs involving at least one zero value. + */ +void test_mem__ckd_add_zero() { + + size_t result = SIZE_MAX; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 0, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 0, 0, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 0, 1)); + CU_ASSERT_EQUAL(result, 1); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 1, 0)); + CU_ASSERT_EQUAL(result, 1); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 3, 2, 0)); + CU_ASSERT_EQUAL(result, 3 + 2); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 5, 0, 8, 9)); + CU_ASSERT_EQUAL(result, 5 + 8 + 9); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 99, 99, 99, 0, 99)); + CU_ASSERT_EQUAL(result, 99 + 99 + 99 + 99); + +} + +/** + * Test which verifies that guac_mem_ckd_add() successfully calculates expected + * values for relatively small integer inputs. + */ +void test_mem__ckd_add_small() { + + size_t result = SIZE_MAX; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 123)); + CU_ASSERT_EQUAL(result, 123); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 123, 456)); + CU_ASSERT_EQUAL(result, 123 + 456); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, 123, 456, 789)); + CU_ASSERT_EQUAL(result, 123 + 456 + 789); + +} + +/** + * Test which verifies that guac_mem_ckd_add() behaves as expected for + * relatively large integer inputs, including inputs that cause overflow beyond + * the capacity of a size_t. + */ +void test_mem__ckd_add_large() { + + size_t result = 0; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, SIZE_MAX)); + CU_ASSERT_EQUAL(result, SIZE_MAX); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_add(&result, SIZE_MAX / 2, SIZE_MAX / 2)); + CU_ASSERT_EQUAL(result, (SIZE_MAX / 2) * 2); + + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_add(&result, SIZE_MAX, 1)); + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_add(&result, 123, 456, SIZE_MAX)); + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_add(&result, SIZE_MAX / 2, SIZE_MAX / 2, 2)); + +} + diff --git a/src/libguac/tests/mem/ckd_add_or_die.c b/src/libguac/tests/mem/ckd_add_or_die.c new file mode 100644 index 000000000..497bfe3dd --- /dev/null +++ b/src/libguac/tests/mem/ckd_add_or_die.c @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "assert-signal.h" + +#include +#include +#include + +/** + * Test which verifies that guac_mem_ckd_add_or_die() calculates results + * correctly for all inputs involving at least one zero value. + */ +void test_mem__ckd_add_or_die_zero() { + + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(0, 0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(0, 0, 0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(0, 1), 1); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(1, 0), 1); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(3, 2, 0), 3 + 2); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(5, 0, 8, 9), 5 + 8 + 9); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(99, 99, 99, 0, 99), 99 + 99 + 99 + 99); + +} + +/** + * Test which verifies that guac_mem_ckd_add_or_die() successfully calculates + * expected values for relatively small integer inputs. + */ +void test_mem__ckd_add_or_die_small() { + + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(123), 123); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(123, 456), 123 + 456); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(123, 456, 789), 123 + 456 + 789); + +} + +/** + * Test which verifies that guac_mem_ckd_add_or_die() behaves as expected for + * relatively large integer inputs, including inputs that cause overflow beyond + * the capacity of a size_t. + */ +void test_mem__ckd_add_or_die_large() { + + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(SIZE_MAX), SIZE_MAX); + CU_ASSERT_EQUAL(guac_mem_ckd_add_or_die(SIZE_MAX / 2, SIZE_MAX / 2), (SIZE_MAX / 2) * 2); + + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_add_or_die(SIZE_MAX, 1)); + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_add_or_die(123, 456, SIZE_MAX)); + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_add_or_die(SIZE_MAX / 2, SIZE_MAX / 2, 2)); + +} + diff --git a/src/libguac/tests/mem/ckd_mul.c b/src/libguac/tests/mem/ckd_mul.c new file mode 100644 index 000000000..847c6f19f --- /dev/null +++ b/src/libguac/tests/mem/ckd_mul.c @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +/** + * Test which verifies that guac_mem_ckd_mul() calculates zero values for all + * inputs involving at least one zero value. + */ +void test_mem__ckd_mul_zero() { + + size_t result = SIZE_MAX; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 0, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 0, 0, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 0, 1)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 1, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 3, 2, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 5, 0, 8, 9)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 99, 99, 99, 0, 99)); + CU_ASSERT_EQUAL(result, 0); + +} + +/** + * Test which verifies that guac_mem_ckd_mul() successfully calculates expected + * values for relatively small integer inputs. + */ +void test_mem__ckd_mul_small() { + + size_t result = SIZE_MAX; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 123)); + CU_ASSERT_EQUAL(result, 123); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 123, 456)); + CU_ASSERT_EQUAL(result, 123 * 456); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, 123, 456, 789)); + CU_ASSERT_EQUAL(result, 123 * 456 * 789); + +} + +/** + * Test which verifies that guac_mem_ckd_mul() behaves as expected for + * relatively large integer inputs, including inputs that cause overflow beyond + * the capacity of a size_t. + */ +void test_mem__ckd_mul_large() { + + size_t result = 0; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_mul(&result, SIZE_MAX)); + CU_ASSERT_EQUAL(result, SIZE_MAX); + + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_mul(&result, 123, 456, SIZE_MAX)); + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_mul(&result, SIZE_MAX / 2, SIZE_MAX / 2)); + +} + diff --git a/src/libguac/tests/mem/ckd_mul_or_die.c b/src/libguac/tests/mem/ckd_mul_or_die.c new file mode 100644 index 000000000..c4ba75965 --- /dev/null +++ b/src/libguac/tests/mem/ckd_mul_or_die.c @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "assert-signal.h" + +#include +#include +#include + +/** + * Test which verifies that guac_mem_ckd_mul_or_die() calculates zero values + * for all inputs involving at least one zero value. + */ +void test_mem__ckd_mul_or_die_zero() { + + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(0, 0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(0, 0, 0, 0, 0), 0); + + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(0, 1), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(1, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(3, 2, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(5, 0, 8, 9), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(99, 99, 99, 0, 99), 0); + +} + +/** + * Test which verifies that guac_mem_ckd_mul_or_die() successfully calculates + * expected values for relatively small integer inputs. + */ +void test_mem__ckd_mul_or_die_small() { + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(123), 123); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(123, 456), 123 * 456); + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(123, 456, 789), 123 * 456 * 789); +} + +/** + * Test which verifies that guac_mem_ckd_mul_or_die() behaves as expected for + * relatively large integer inputs. + */ +void test_mem__ckd_mul_or_die_large() { + + CU_ASSERT_EQUAL(guac_mem_ckd_mul_or_die(SIZE_MAX), SIZE_MAX); + + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_mul_or_die(123, 456, SIZE_MAX)); + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_mul_or_die(SIZE_MAX / 2, SIZE_MAX / 2)); + +} + diff --git a/src/libguac/tests/mem/ckd_sub.c b/src/libguac/tests/mem/ckd_sub.c new file mode 100644 index 000000000..4dfa01b58 --- /dev/null +++ b/src/libguac/tests/mem/ckd_sub.c @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +/** + * Test which verifies that guac_mem_ckd_sub() calculates results correctly for + * all inputs involving at least one zero value. + */ +void test_mem__ckd_sub_zero() { + + size_t result = SIZE_MAX; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 0, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 0, 0, 0, 0, 0)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 1, 0)); + CU_ASSERT_EQUAL(result, 1); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 3, 2, 0)); + CU_ASSERT_EQUAL(result, 3 - 2); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 8, 5, 0, 1)); + CU_ASSERT_EQUAL(result, 8 - 5 - 1); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 99, 99, 0)); + CU_ASSERT_EQUAL(result, 0); + +} + +/** + * Test which verifies that guac_mem_ckd_sub() successfully calculates expected + * values for relatively small integer inputs, including inputs that cause + * overflow beyond zero. + */ +void test_mem__ckd_sub_small() { + + size_t result = SIZE_MAX; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 123)); + CU_ASSERT_EQUAL(result, 123); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 456, 123)); + CU_ASSERT_EQUAL(result, 456 - 123); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 789, 456, 123)); + CU_ASSERT_EQUAL(result, 789 - 456 - 123); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, 123, 123)); + CU_ASSERT_EQUAL(result, 0); + + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_sub(&result, 123, 123, 1)); + +} + +/** + * Test which verifies that guac_mem_ckd_sub() behaves as expected for + * relatively large integer inputs, including inputs that cause overflow beyond + * zero. + */ +void test_mem__ckd_sub_large() { + + size_t result = 0; + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, SIZE_MAX)); + CU_ASSERT_EQUAL(result, SIZE_MAX); + + CU_ASSERT_FALSE_FATAL(guac_mem_ckd_sub(&result, SIZE_MAX, SIZE_MAX / 2)); + CU_ASSERT_EQUAL(result, SIZE_MAX - (SIZE_MAX / 2)); + + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_sub(&result, SIZE_MAX, SIZE_MAX, 1)); + CU_ASSERT_TRUE_FATAL(guac_mem_ckd_sub(&result, 0, SIZE_MAX)); + +} + diff --git a/src/libguac/tests/mem/ckd_sub_or_die.c b/src/libguac/tests/mem/ckd_sub_or_die.c new file mode 100644 index 000000000..e29d6c170 --- /dev/null +++ b/src/libguac/tests/mem/ckd_sub_or_die.c @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "assert-signal.h" + +#include +#include +#include + +/** + * Test which verifies that guac_mem_ckd_sub_or_die() calculates results + * correctly for all inputs involving at least one zero value. + */ +void test_mem__ckd_sub_or_die_zero() { + + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(0, 0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(0, 0, 0, 0, 0), 0); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(1, 0), 1); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(3, 2, 0), 3 - 2); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(8, 5, 0, 1), 8 - 5 - 1); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(99, 99, 0), 0); + +} + +/** + * Test which verifies that guac_mem_ckd_sub_or_die() successfully calculates + * expected values for relatively small integer inputs, including inputs that + * cause overflow beyond zero. + */ +void test_mem__ckd_sub_or_die_small() { + + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(123), 123); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(456, 123), 456 - 123); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(789, 456, 123), 789 - 456 - 123); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(123, 123), 0); + + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_sub_or_die(123, 123, 1)); + +} + +/** + * Test which verifies that guac_mem_ckd_sub_or_die() behaves as expected for + * relatively large integer inputs, including inputs that cause overflow beyond + * zero. + */ +void test_mem__ckd_sub_or_die_large() { + + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(SIZE_MAX), SIZE_MAX); + CU_ASSERT_EQUAL(guac_mem_ckd_sub_or_die(SIZE_MAX, SIZE_MAX / 2), SIZE_MAX - (SIZE_MAX / 2)); + + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_sub_or_die(SIZE_MAX, SIZE_MAX, 1)); + ASSERT_SIGNALLED(SIGABRT, guac_mem_ckd_sub_or_die(0, SIZE_MAX)); + +} + diff --git a/src/libguac/tests/mem/free.c b/src/libguac/tests/mem/free.c new file mode 100644 index 000000000..f439b7958 --- /dev/null +++ b/src/libguac/tests/mem/free.c @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +/** + * Test which verifies that guac_mem_free() sets the provided pointer to NULL after + * freeing. + */ +void test_mem__free_assigns_null() { + void* ptr = guac_mem_alloc(123); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + CU_ASSERT_PTR_NULL(ptr); +} + +/** + * Test which verifies that guac_mem_free_const() can be used to free constant + * pointers, but that those pointers are not set to NULL after freeing. + */ +void test_mem__free_const() { + const void* ptr = guac_mem_alloc(123); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free_const(ptr); + CU_ASSERT_PTR_NOT_NULL(ptr); +} + +/** + * Test which verifies that guac_mem_free() does nothing if provided a NULL + * pointer. + */ +void test_mem__free_null() { + void* ptr = NULL; + guac_mem_free(ptr); +} + +/** + * Test which verifies that guac_mem_free_const() does nothing if provided a NULL + * pointer. + */ +void test_mem__free_null_const() { + const void* ptr = NULL; + guac_mem_free_const(ptr); +} + diff --git a/src/libguac/tests/mem/realloc.c b/src/libguac/tests/mem/realloc.c new file mode 100644 index 000000000..91dbef596 --- /dev/null +++ b/src/libguac/tests/mem/realloc.c @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +/** + * Test which verifies that guac_mem_realloc() returns NULL for all inputs + * involving at least one zero value (reallocation to zero bytes is not an + * error but equivalent freeing the memory). + */ +void test_mem__realloc_success_zero() { + + void* ptr; + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 0, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 0, 0, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 0, 0, 0, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 1, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 3, 2, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 5, 0, 8, 9)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 99, 99, 99, 0, 99)); + +} + +/** + * Test which verifies that guac_mem_realloc() successfully allocates blocks of + * memory for inputs that can reasonably be expected to succeed. + */ +void test_mem__realloc_success() { + + void* ptr; + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ptr = guac_mem_realloc(ptr, 123); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ptr = guac_mem_realloc(ptr, 123, 456); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ptr = guac_mem_realloc(ptr, 123, 456, 789); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + +} + +/** + * Test which verifies that guac_mem_realloc() fails to allocate blocks of + * memory that exceed the capacity of a size_t. + */ +void test_mem__realloc_fail_large() { + + void* ptr; + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, 123, 456, SIZE_MAX)); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc(ptr, SIZE_MAX / 2, SIZE_MAX / 2)); + guac_mem_free(ptr); + +} + diff --git a/src/libguac/tests/mem/realloc_or_die.c b/src/libguac/tests/mem/realloc_or_die.c new file mode 100644 index 000000000..4a244afa6 --- /dev/null +++ b/src/libguac/tests/mem/realloc_or_die.c @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "assert-signal.h" + +#include +#include +#include + +/** + * Test which verifies that guac_mem_realloc_or_die() returns NULL for all + * inputs involving at least one zero value (reallocation to zero bytes is not + * an error but equivalent freeing the memory). + */ +void test_mem__realloc_or_die_success_zero() { + + void* ptr; + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 0, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 0, 0, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 0, 0, 0, 0, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 1, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 3, 2, 0)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 5, 0, 8, 9)); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + CU_ASSERT_PTR_NULL(guac_mem_realloc_or_die(ptr, 99, 99, 99, 0, 99)); + +} + +/** + * Test which verifies that guac_mem_realloc_or_die() successfully allocates + * blocks of memory for inputs that can reasonably be expected to succeed. + */ +void test_mem__realloc_or_die_success() { + + void* ptr; + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ptr = guac_mem_realloc_or_die(ptr, 123); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ptr = guac_mem_realloc_or_die(ptr, 123, 456); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ptr = guac_mem_realloc_or_die(ptr, 123, 456, 789); + CU_ASSERT_PTR_NOT_NULL(ptr); + guac_mem_free(ptr); + +} + +/** + * Test which verifies that guac_mem_realloc_or_die() fails to allocate blocks of + * memory that exceed the capacity of a size_t. + */ +void test_mem__realloc_or_die_fail_large() { + + void* ptr; + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ASSERT_SIGNALLED(SIGABRT, guac_mem_realloc_or_die(ptr, 123, 456, SIZE_MAX)); + guac_mem_free(ptr); + + ptr = guac_mem_alloc(1); + CU_ASSERT_PTR_NOT_NULL_FATAL(ptr); + ASSERT_SIGNALLED(SIGABRT, guac_mem_realloc_or_die(ptr, SIZE_MAX / 2, SIZE_MAX / 2)); + guac_mem_free(ptr); + +} + diff --git a/src/libguac/tests/mem/zalloc.c b/src/libguac/tests/mem/zalloc.c new file mode 100644 index 000000000..933dca095 --- /dev/null +++ b/src/libguac/tests/mem/zalloc.c @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +/** + * Test which verifies that guac_mem_zalloc() returns NULL for all inputs involving + * at least one zero value. + */ +void test_mem__zalloc_fail_zero() { + + CU_ASSERT_PTR_NULL(guac_mem_zalloc(0)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(0, 0)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(0, 0, 0)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(0, 0, 0, 0)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(0, 0, 0, 0, 0)); + + CU_ASSERT_PTR_NULL(guac_mem_zalloc(1, 0)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(3, 2, 0)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(5, 0, 8, 9)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(99, 99, 99, 0, 99)); + +} + +/** + * Returns whether all bytes within the given memory region are zero. + * + * @param ptr + * The first byte of the memory region to test. + * + * @param length + * The number of bytes within the memory region. + * + * @returns + * Non-zero if all bytes within the memory region have the value of zero, + * zero otherwise. + */ +static int is_all_zeroes(void* ptr, size_t length) { + + int result = 0; + + unsigned char* current = (unsigned char*) ptr; + for (size_t i = 0; i < length; i++) + result |= *(current++); + + return !result; + +} + +/** + * Test which verifies that guac_mem_zalloc() successfully allocates blocks of + * memory for inputs that can reasonably be expected to succeed, and that each + * block is zeroed out. + */ +void test_mem__zalloc_success() { + + void* ptr; + + ptr = guac_mem_zalloc(123); + CU_ASSERT_PTR_NOT_NULL(ptr); + CU_ASSERT(is_all_zeroes(ptr, 123)); + guac_mem_free(ptr); + + ptr = guac_mem_zalloc(123, 456); + CU_ASSERT_PTR_NOT_NULL(ptr); + CU_ASSERT(is_all_zeroes(ptr, 123 * 456)); + guac_mem_free(ptr); + + ptr = guac_mem_zalloc(123, 456, 789); + CU_ASSERT_PTR_NOT_NULL(ptr); + CU_ASSERT(is_all_zeroes(ptr, 123 * 456 * 789)); + guac_mem_free(ptr); + +} + +/** + * Test which verifies that guac_mem_zalloc() fails to allocate blocks of memory + * that exceed the capacity of a size_t. + */ +void test_mem__zalloc_fail_large() { + CU_ASSERT_PTR_NULL(guac_mem_zalloc(123, 456, SIZE_MAX)); + CU_ASSERT_PTR_NULL(guac_mem_zalloc(SIZE_MAX / 2, SIZE_MAX / 2)); +} + diff --git a/src/libguac/tests/string/strdup.c b/src/libguac/tests/string/strdup.c index 0b1e76b3d..9641e0937 100644 --- a/src/libguac/tests/string/strdup.c +++ b/src/libguac/tests/string/strdup.c @@ -18,6 +18,7 @@ */ #include +#include #include #include @@ -46,4 +47,6 @@ void test_string__strdup() { CU_ASSERT_STRING_EQUAL(dest_string, "Mashing avocados."); CU_ASSERT_PTR_NULL(null_copy); -} \ No newline at end of file + guac_mem_free(dest_string); + +} diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index be7f1a1af..b9013bc7c 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -19,10 +19,12 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/client.h" #include "guacamole/object.h" #include "guacamole/protocol.h" #include "guacamole/stream.h" +#include "guacamole/string.h" #include "guacamole/timestamp.h" #include "guacamole/user.h" #include "user-handlers.h" @@ -688,11 +690,11 @@ int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { int __guac_handshake_name_handler(guac_user* user, int argc, char** argv) { /* Free any past value for the user's name */ - free((char *) user->info.name); + guac_mem_free_const(user->info.name); /* If a value is provided for the name, copy it into guac_user. */ if (argc > 0 && strcmp(argv[0], "")) - user->info.name = (const char*) strdup(argv[0]); + user->info.name = (const char*) guac_strdup(argv[0]); /* No or empty value was provided, so make sure this is NULLed out. */ else @@ -705,11 +707,11 @@ int __guac_handshake_name_handler(guac_user* user, int argc, char** argv) { int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { /* Free any past value */ - free((char *) user->info.timezone); + guac_mem_free_const(user->info.timezone); /* Store timezone, if present */ if (argc > 0 && strcmp(argv[0], "")) - user->info.timezone = (const char*) strdup(argv[0]); + user->info.timezone = (const char*) guac_strdup(argv[0]); else user->info.timezone = NULL; @@ -723,11 +725,12 @@ char** guac_copy_mimetypes(char** mimetypes, int count) { int i; /* Allocate sufficient space for NULL-terminated array of mimetypes */ - char** mimetypes_copy = malloc(sizeof(char*) * (count+1)); + char** mimetypes_copy = guac_mem_alloc(sizeof(char*), + guac_mem_ckd_add_or_die(count, 1)); /* Copy each provided mimetype */ for (i = 0; i < count; i++) - mimetypes_copy[i] = strdup(mimetypes[i]); + mimetypes_copy[i] = guac_strdup(mimetypes[i]); /* Terminate with NULL */ mimetypes_copy[count] = NULL; @@ -745,12 +748,12 @@ void guac_free_mimetypes(char** mimetypes) { /* Free all strings within NULL-terminated mimetype array */ while (*current_mimetype != NULL) { - free(*current_mimetype); + guac_mem_free(*current_mimetype); current_mimetype++; } /* Free the array itself, now that its contents have been freed */ - free(mimetypes); + guac_mem_free(mimetypes); } diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 0863325f7..422c941b7 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -19,6 +19,7 @@ #include "config.h" +#include "guacamole/mem.h" #include "guacamole/client.h" #include "guacamole/error.h" #include "guacamole/parser.h" @@ -372,8 +373,8 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_free_mimetypes((char **) user->info.video_mimetypes); /* Free name and timezone info. */ - free((char *) user->info.name); - free((char *) user->info.timezone); + guac_mem_free_const(user->info.name); + guac_mem_free_const(user->info.timezone); guac_parser_free(parser); diff --git a/src/libguac/user.c b/src/libguac/user.c index d16f43b93..ba6819678 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -22,12 +22,14 @@ #include "encode-jpeg.h" #include "encode-png.h" #include "encode-webp.h" +#include "guacamole/mem.h" #include "guacamole/client.h" #include "guacamole/object.h" #include "guacamole/pool.h" #include "guacamole/protocol.h" #include "guacamole/socket.h" #include "guacamole/stream.h" +#include "guacamole/string.h" #include "guacamole/timestamp.h" #include "guacamole/user.h" #include "id.h" @@ -40,13 +42,13 @@ guac_user* guac_user_alloc() { - guac_user* user = calloc(1, sizeof(guac_user)); + guac_user* user = guac_mem_zalloc(sizeof(guac_user)); int i; /* Generate ID */ user->user_id = guac_generate_id(GUAC_USER_ID_PREFIX); if (user->user_id == NULL) { - free(user); + guac_mem_free(user); return NULL; } @@ -59,8 +61,8 @@ guac_user* guac_user_alloc() { user->__stream_pool = guac_pool_alloc(0); /* Initialze streams */ - user->__input_streams = malloc(sizeof(guac_stream) * GUAC_USER_MAX_STREAMS); - user->__output_streams = malloc(sizeof(guac_stream) * GUAC_USER_MAX_STREAMS); + user->__input_streams = guac_mem_alloc(sizeof(guac_stream), GUAC_USER_MAX_STREAMS); + user->__output_streams = guac_mem_alloc(sizeof(guac_stream), GUAC_USER_MAX_STREAMS); for (i=0; i__input_streams[i].index = GUAC_USER_CLOSED_STREAM_INDEX; @@ -71,7 +73,7 @@ guac_user* guac_user_alloc() { user->__object_pool = guac_pool_alloc(0); /* Initialize objects */ - user->__objects = malloc(sizeof(guac_object) * GUAC_USER_MAX_OBJECTS); + user->__objects = guac_mem_alloc(sizeof(guac_object), GUAC_USER_MAX_OBJECTS); for (i=0; i__objects[i].index = GUAC_USER_UNDEFINED_OBJECT_INDEX; @@ -82,21 +84,21 @@ guac_user* guac_user_alloc() { void guac_user_free(guac_user* user) { /* Free streams */ - free(user->__input_streams); - free(user->__output_streams); + guac_mem_free(user->__input_streams); + guac_mem_free(user->__output_streams); /* Free stream pool */ guac_pool_free(user->__stream_pool); /* Free objects */ - free(user->__objects); + guac_mem_free(user->__objects); /* Free object pool */ guac_pool_free(user->__object_pool); /* Clean up user */ - free(user->user_id); - free(user); + guac_mem_free(user->user_id); + guac_mem_free(user); } @@ -377,12 +379,12 @@ char* guac_user_parse_args_string(guac_user* user, const char** arg_names, guac_user_log(user, GUAC_LOG_DEBUG, "Parameter \"%s\" omitted. Using " "default value of \"%s\".", arg_names[index], default_value); - return strdup(default_value); + return guac_strdup(default_value); } /* Otherwise use provided value */ - return strdup(value); + return guac_strdup(value); } diff --git a/src/protocols/kubernetes/argv.c b/src/protocols/kubernetes/argv.c index 0cbc71601..9b2bfcfcd 100644 --- a/src/protocols/kubernetes/argv.c +++ b/src/protocols/kubernetes/argv.c @@ -63,23 +63,31 @@ int guac_kubernetes_argv_callback(guac_user* user, const char* mimetype, void* guac_kubernetes_send_current_argv(guac_user* user, void* data) { - guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) data; + /* Defer to the batch handler, using the user socket */ + return guac_kubernetes_send_current_argv_batch(user->client, user->socket); + +} + +void* guac_kubernetes_send_current_argv_batch( + guac_client* client, guac_socket* socket) { + + guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; guac_terminal* terminal = kubernetes_client->term; /* Send current color scheme */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_KUBERNETES_ARGV_COLOR_SCHEME, guac_terminal_get_color_scheme(terminal)); /* Send current font name */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_KUBERNETES_ARGV_FONT_NAME, guac_terminal_get_font_name(terminal)); /* Send current font size */ char font_size[64]; sprintf(font_size, "%i", guac_terminal_get_font_size(terminal)); - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_KUBERNETES_ARGV_FONT_SIZE, font_size); return NULL; diff --git a/src/protocols/kubernetes/argv.h b/src/protocols/kubernetes/argv.h index 307ebc71b..dd73d4239 100644 --- a/src/protocols/kubernetes/argv.h +++ b/src/protocols/kubernetes/argv.h @@ -22,6 +22,7 @@ #define GUAC_KUBERNETES_ARGV_H #include "config.h" +#include "kubernetes.h" #include #include @@ -55,7 +56,7 @@ guac_argv_callback guac_kubernetes_argv_callback; * while the connection is running to the given user. Note that the user * receiving these values will not necessarily be able to set new values * themselves if their connection is read-only. This function can be used as - * the callback for guac_client_foreach_user() and guac_client_for_owner() + * the callback for guac_client_foreach_user() and guac_client_for_owner(). * * @param user * The user that should receive the values of all non-sensitive parameters @@ -70,5 +71,23 @@ guac_argv_callback guac_kubernetes_argv_callback; */ void* guac_kubernetes_send_current_argv(guac_user* user, void* data); +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the all users associated with the + * provided socket. Note that the users receiving these values will not + * necessarily be able to set new values themselves if their connection is + * read-only. + * + * @param client + * The client associated with the users who should receive the values of + * all non-sensitive parameters which may be set while the connection is + * running. + * + * @return + * Always NULL. + */ +void* guac_kubernetes_send_current_argv_batch( + guac_client* client, guac_socket* socket); + #endif diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c index 42a18286e..038da2872 100644 --- a/src/protocols/kubernetes/client.c +++ b/src/protocols/kubernetes/client.c @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include @@ -77,6 +79,34 @@ static void guac_kubernetes_log(int level, const char* line) { } +/** + * A pending join handler implementation that will synchronize the connection + * state for all pending users prior to them being promoted to full user. + * + * @param client + * The client whose pending users are about to be promoted to full users, + * and therefore need their connection state synchronized. + * + * @return + * Always zero. + */ +static int guac_kubernetes_join_pending_handler(guac_client* client) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Synchronize the terminal state to all pending users */ + if (kubernetes_client->term != NULL) { + guac_socket* broadcast_socket = client->pending_socket; + guac_terminal_sync_users(kubernetes_client->term, client, broadcast_socket); + guac_kubernetes_send_current_argv_batch(client, broadcast_socket); + guac_socket_flush(broadcast_socket); + } + + return 0; + +} + int guac_client_init(guac_client* client) { /* Ensure reference to main guac_client remains available in all @@ -91,11 +121,12 @@ int guac_client_init(guac_client* client) { client->args = GUAC_KUBERNETES_CLIENT_ARGS; /* Allocate client instance data */ - guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client)); + guac_kubernetes_client* kubernetes_client = guac_mem_zalloc(sizeof(guac_kubernetes_client)); client->data = kubernetes_client; /* Set handlers */ client->join_handler = guac_kubernetes_user_join_handler; + client->join_pending_handler = guac_kubernetes_join_pending_handler; client->free_handler = guac_kubernetes_client_free_handler; client->leave_handler = guac_kubernetes_user_leave_handler; @@ -129,7 +160,7 @@ int guac_kubernetes_client_free_handler(guac_client* client) { if (kubernetes_client->settings != NULL) guac_kubernetes_settings_free(kubernetes_client->settings); - free(kubernetes_client); + guac_mem_free(kubernetes_client); return 0; } diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 28f71e753..3a75bb2e7 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -28,6 +28,7 @@ #include "url.h" #include +#include #include #include #include @@ -255,7 +256,7 @@ void* guac_kubernetes_client_thread(void* data) { kubernetes_client->term = guac_terminal_create(client, options); /* Free options struct now that it's been used */ - free(options); + guac_mem_free(options); /* Fail if terminal init failed */ if (kubernetes_client->term == NULL) { diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index fd93509cc..5da7d8bec 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -21,6 +21,7 @@ #include "settings.h" #include "terminal/terminal.h" +#include #include #include @@ -255,7 +256,7 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, } guac_kubernetes_settings* settings = - calloc(1, sizeof(guac_kubernetes_settings)); + guac_mem_zalloc(sizeof(guac_kubernetes_settings)); /* Read hostname */ settings->hostname = @@ -411,35 +412,35 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) { /* Free network connection information */ - free(settings->hostname); + guac_mem_free(settings->hostname); /* Free Kubernetes pod/container details */ - free(settings->kubernetes_namespace); - free(settings->kubernetes_pod); - free(settings->kubernetes_container); + guac_mem_free(settings->kubernetes_namespace); + guac_mem_free(settings->kubernetes_pod); + guac_mem_free(settings->kubernetes_container); /* Free Kubernetes exec command */ - free(settings->exec_command); + guac_mem_free(settings->exec_command); /* Free SSL/TLS details */ - free(settings->client_cert); - free(settings->client_key); - free(settings->ca_cert); + guac_mem_free(settings->client_cert); + guac_mem_free(settings->client_key); + guac_mem_free(settings->ca_cert); /* Free display preferences */ - free(settings->font_name); - free(settings->color_scheme); + guac_mem_free(settings->font_name); + guac_mem_free(settings->color_scheme); /* Free typescript settings */ - free(settings->typescript_name); - free(settings->typescript_path); + guac_mem_free(settings->typescript_name); + guac_mem_free(settings->typescript_path); /* Free screen recording settings */ - free(settings->recording_name); - free(settings->recording_path); + guac_mem_free(settings->recording_name); + guac_mem_free(settings->recording_path); /* Free overall structure */ - free(settings); + guac_mem_free(settings); } diff --git a/src/protocols/kubernetes/user.c b/src/protocols/kubernetes/user.c index a1c067df9..369923de2 100644 --- a/src/protocols/kubernetes/user.c +++ b/src/protocols/kubernetes/user.c @@ -71,13 +71,6 @@ int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) { } - /* If not owner, synchronize with current display */ - else { - guac_terminal_dup(kubernetes_client->term, user, user->socket); - guac_kubernetes_send_current_argv(user, kubernetes_client); - guac_socket_flush(user->socket); - } - /* Only handle events if not read-only */ if (!settings->read_only) { diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index fe7a3fb41..1c393c30d 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -229,6 +229,7 @@ BUILT_SOURCES = \ rdp_keymaps = \ $(srcdir)/keymaps/base.keymap \ + $(srcdir)/keymaps/base_altgr.keymap \ $(srcdir)/keymaps/failsafe.keymap \ $(srcdir)/keymaps/cs-cz-qwertz.keymap \ $(srcdir)/keymaps/de_de_qwertz.keymap \ diff --git a/src/protocols/rdp/argv.c b/src/protocols/rdp/argv.c index c6c922b91..16080d914 100644 --- a/src/protocols/rdp/argv.c +++ b/src/protocols/rdp/argv.c @@ -22,8 +22,10 @@ #include "rdp.h" #include "settings.h" +#include #include #include +#include #include #include @@ -39,22 +41,22 @@ int guac_rdp_argv_callback(guac_user* user, const char* mimetype, /* Update username */ if (strcmp(name, GUAC_RDP_ARGV_USERNAME) == 0) { - free(settings->username); - settings->username = strdup(value); + guac_mem_free(settings->username); + settings->username = guac_strdup(value); } /* Update password */ else if (strcmp(name, GUAC_RDP_ARGV_PASSWORD) == 0) { - free(settings->password); - settings->password = strdup(value); + guac_mem_free(settings->password); + settings->password = guac_strdup(value); } /* Update domain */ else if (strcmp(name, GUAC_RDP_ARGV_DOMAIN) == 0) { - free(settings->domain); - settings->domain = strdup(value); + guac_mem_free(settings->domain); + settings->domain = guac_strdup(value); } return 0; -} \ No newline at end of file +} diff --git a/src/protocols/rdp/beep.c b/src/protocols/rdp/beep.c index 8fb5001f4..7f59dbbe7 100644 --- a/src/protocols/rdp/beep.c +++ b/src/protocols/rdp/beep.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -93,14 +94,14 @@ static void guac_rdp_beep_fill_triangle_wave(unsigned char* buffer, static void guac_rdp_beep_write_pcm(guac_audio_stream* audio, int frequency, int duration) { - int buffer_size = audio->rate * duration / 1000; - unsigned char* buffer = malloc(buffer_size); + size_t buffer_size = guac_mem_ckd_mul_or_die(audio->rate, duration) / 1000; + unsigned char* buffer = guac_mem_alloc(buffer_size); /* Beep for given frequency/duration using a simple triangle wave */ guac_rdp_beep_fill_triangle_wave(buffer, frequency, audio->rate, buffer_size); guac_audio_stream_write_pcm(audio, buffer, buffer_size); - free(buffer); + guac_mem_free(buffer); } diff --git a/src/protocols/rdp/channels/audio-input/audio-buffer.c b/src/protocols/rdp/channels/audio-input/audio-buffer.c index c584b19e5..38d5b7f1e 100644 --- a/src/protocols/rdp/channels/audio-input/audio-buffer.c +++ b/src/protocols/rdp/channels/audio-input/audio-buffer.c @@ -21,6 +21,7 @@ #include "rdp.h" #include +#include #include #include #include @@ -116,8 +117,8 @@ static int guac_rdp_audio_buffer_duration(const guac_rdp_audio_format* format, i * The number of bytes required to store audio data in the given format * covering the given length of time. */ -static int guac_rdp_audio_buffer_length(const guac_rdp_audio_format* format, int duration) { - return duration * format->rate * format->bps * format->channels / 1000; +static size_t guac_rdp_audio_buffer_length(const guac_rdp_audio_format* format, int duration) { + return guac_mem_ckd_mul_or_die(duration, format->rate, format->bps, format->channels) / 1000; } /** @@ -261,7 +262,7 @@ static void* guac_rdp_audio_buffer_flush_thread(void* data) { guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client) { - guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer)); + guac_rdp_audio_buffer* buffer = guac_mem_zalloc(sizeof(guac_rdp_audio_buffer)); pthread_mutex_init(&(buffer->lock), NULL); pthread_cond_init(&(buffer->modified), NULL); @@ -395,21 +396,23 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, audio_buffer->data = data; /* Calculate size of each packet in bytes */ - audio_buffer->packet_size = packet_frames - * audio_buffer->out_format.channels - * audio_buffer->out_format.bps; + audio_buffer->packet_size = guac_mem_ckd_mul_or_die(packet_frames, + audio_buffer->out_format.channels, + audio_buffer->out_format.bps); /* Ensure outbound buffer includes enough space for at least 250ms of * audio */ - int ideal_size = guac_rdp_audio_buffer_length(&audio_buffer->out_format, + size_t ideal_size = guac_rdp_audio_buffer_length(&audio_buffer->out_format, GUAC_RDP_AUDIO_BUFFER_MIN_DURATION); /* Round up to nearest whole packet */ - int ideal_packets = (ideal_size + audio_buffer->packet_size - 1) / audio_buffer->packet_size; + size_t ideal_packets = guac_mem_ckd_sub_or_die( + guac_mem_ckd_add_or_die(ideal_size, audio_buffer->packet_size), 1 + ) / audio_buffer->packet_size; /* Allocate new buffer */ - audio_buffer->packet_buffer_size = ideal_packets * audio_buffer->packet_size; - audio_buffer->packet = malloc(audio_buffer->packet_buffer_size); + audio_buffer->packet_buffer_size = guac_mem_ckd_mul_or_die(ideal_packets, audio_buffer->packet_size); + audio_buffer->packet = guac_mem_alloc(audio_buffer->packet_buffer_size); guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Output buffer for " "audio input is %i bytes (up to %i ms).", audio_buffer->packet_buffer_size, @@ -609,8 +612,7 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) { audio_buffer->total_bytes_received = 0; /* Free packet (if any) */ - free(audio_buffer->packet); - audio_buffer->packet = NULL; + guac_mem_free(audio_buffer->packet); pthread_cond_broadcast(&(audio_buffer->modified)); pthread_mutex_unlock(&(audio_buffer->lock)); @@ -632,7 +634,7 @@ void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) { pthread_mutex_destroy(&(audio_buffer->lock)); pthread_cond_destroy(&(audio_buffer->modified)); - free(audio_buffer); + guac_mem_free(audio_buffer); } diff --git a/src/protocols/rdp/channels/audio-input/audio-buffer.h b/src/protocols/rdp/channels/audio-input/audio-buffer.h index 723ca6a6a..127198076 100644 --- a/src/protocols/rdp/channels/audio-input/audio-buffer.h +++ b/src/protocols/rdp/channels/audio-input/audio-buffer.h @@ -132,12 +132,12 @@ struct guac_rdp_audio_buffer { * The size that each audio packet must be, in bytes. The packet buffer * within this structure will be at least this size. */ - int packet_size; + size_t packet_size; /** * The total number of bytes available within the packet buffer. */ - int packet_buffer_size; + size_t packet_buffer_size; /** * The number of bytes currently stored within the packet buffer. diff --git a/src/protocols/rdp/channels/cliprdr.c b/src/protocols/rdp/channels/cliprdr.c index f16b41ca7..98002968b 100644 --- a/src/protocols/rdp/channels/cliprdr.c +++ b/src/protocols/rdp/channels/cliprdr.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -358,7 +359,7 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr, guac_iconv_write* remote_writer; const char* input = clipboard->clipboard->buffer; - char* output = malloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH); + char* output = guac_mem_alloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH); /* Map requested clipboard format to a guac_iconv writer */ switch (format_data_request->requestedFormatId) { @@ -379,7 +380,7 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr, "server has requested a clipboard format which was not " "declared as available. This violates the specification " "for the CLIPRDR channel."); - free(output); + guac_mem_free(output); return CHANNEL_RC_OK; } @@ -403,7 +404,7 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr, UINT result = cliprdr->ClientFormatDataResponse(cliprdr, &data_response); pthread_mutex_unlock(&(rdp_client->message_lock)); - free(start); + guac_mem_free(start); return result; } @@ -593,7 +594,7 @@ static void guac_rdp_cliprdr_channel_disconnected(rdpContext* context, guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client) { /* Allocate clipboard and underlying storage */ - guac_rdp_clipboard* clipboard = calloc(1, sizeof(guac_rdp_clipboard)); + guac_rdp_clipboard* clipboard = guac_mem_zalloc(sizeof(guac_rdp_clipboard)); clipboard->client = client; clipboard->clipboard = guac_common_clipboard_alloc(); clipboard->requested_format = CF_TEXT; @@ -637,7 +638,7 @@ void guac_rdp_clipboard_free(guac_rdp_clipboard* clipboard) { /* Free clipboard and underlying storage */ guac_common_clipboard_free(clipboard->clipboard); - free(clipboard); + guac_mem_free(clipboard); } diff --git a/src/protocols/rdp/channels/common-svc.c b/src/protocols/rdp/channels/common-svc.c index ce3bf8f20..774316f4f 100644 --- a/src/protocols/rdp/channels/common-svc.c +++ b/src/protocols/rdp/channels/common-svc.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -38,7 +39,7 @@ int guac_rdp_common_svc_load_plugin(rdpContext* context, guac_client* client = ((rdp_freerdp_context*) context)->client; - guac_rdp_common_svc* svc = calloc(1, sizeof(guac_rdp_common_svc)); + guac_rdp_common_svc* svc = guac_mem_zalloc(sizeof(guac_rdp_common_svc)); svc->client = client; svc->name = svc->_channel_def.name; svc->_connect_handler = connect_handler; @@ -65,7 +66,7 @@ int guac_rdp_common_svc_load_plugin(rdpContext* context, guac_client_log(client, GUAC_LOG_WARNING, "Cannot create static " "channel \"%s\": failed to load \"guac-common-svc\" plugin " "for FreeRDP.", svc->name); - free(svc); + guac_mem_free(svc); } /* Store and log on success (SVC structure will be freed on channel termination) */ diff --git a/src/protocols/rdp/channels/disp.c b/src/protocols/rdp/channels/disp.c index 918676b9a..8a9302742 100644 --- a/src/protocols/rdp/channels/disp.c +++ b/src/protocols/rdp/channels/disp.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -34,7 +35,7 @@ guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client) { - guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp)); + guac_rdp_disp* disp = guac_mem_alloc(sizeof(guac_rdp_disp)); disp->client = client; /* Not yet connected */ @@ -51,7 +52,7 @@ guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client) { } void guac_rdp_disp_free(guac_rdp_disp* disp) { - free(disp); + guac_mem_free(disp); } /** diff --git a/src/protocols/rdp/channels/pipe-svc.c b/src/protocols/rdp/channels/pipe-svc.c index 2db42d688..9029c207a 100644 --- a/src/protocols/rdp/channels/pipe-svc.c +++ b/src/protocols/rdp/channels/pipe-svc.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -41,9 +42,10 @@ void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* pipe_sv } -void guac_rdp_pipe_svc_send_pipes(guac_user* user) { - guac_client* client = user->client; +void guac_rdp_pipe_svc_send_pipes( + guac_client* client, guac_socket* socket) { + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_common_list_lock(rdp_client->available_svc); @@ -51,12 +53,11 @@ void guac_rdp_pipe_svc_send_pipes(guac_user* user) { /* Send pipe for each allocated SVC's output stream */ guac_common_list_element* current = rdp_client->available_svc->head; while (current != NULL) { - guac_rdp_pipe_svc_send_pipe(user->socket, (guac_rdp_pipe_svc*) current->data); + guac_rdp_pipe_svc_send_pipe(socket, (guac_rdp_pipe_svc*) current->data); current = current->next; } guac_common_list_unlock(rdp_client->available_svc); - } void guac_rdp_pipe_svc_add(guac_client* client, guac_rdp_pipe_svc* pipe_svc) { @@ -172,7 +173,7 @@ int guac_rdp_pipe_svc_blob_handler(guac_user* user, guac_stream* stream, void guac_rdp_pipe_svc_process_connect(guac_rdp_common_svc* svc) { /* Associate SVC with new Guacamole pipe */ - guac_rdp_pipe_svc* pipe_svc = malloc(sizeof(guac_rdp_pipe_svc)); + guac_rdp_pipe_svc* pipe_svc = guac_mem_alloc(sizeof(guac_rdp_pipe_svc)); pipe_svc->svc = svc; pipe_svc->output_pipe = guac_client_alloc_stream(svc->client); svc->data = pipe_svc; @@ -214,7 +215,7 @@ void guac_rdp_pipe_svc_process_terminate(guac_rdp_common_svc* svc) { /* Remove and free SVC */ guac_rdp_pipe_svc_remove(svc->client, svc->name); - free(pipe_svc); + guac_mem_free(pipe_svc); } diff --git a/src/protocols/rdp/channels/pipe-svc.h b/src/protocols/rdp/channels/pipe-svc.h index 242d4e50a..f4575d4ec 100644 --- a/src/protocols/rdp/channels/pipe-svc.h +++ b/src/protocols/rdp/channels/pipe-svc.h @@ -95,14 +95,18 @@ void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* svc); /** * Sends the "pipe" instructions describing all static virtual channels - * available to the given user along that user's socket. Each pipe instruction - * will relate the associated SVC's underlying output stream with the SVC's - * name and the mimetype "application/octet-stream". + * available to the all users associated with the provided socket. Each pipe + * instruction will relate the associated SVC's underlying output stream with + * the SVC's name and the mimetype "application/octet-stream". * - * @param user - * The user to send the "pipe" instructions to. + * @param client + * The client associated with the users being sent the pipe instruction. + * + * @param socket + * The socket to send the pipe instruction accross. */ -void guac_rdp_pipe_svc_send_pipes(guac_user* user); +void guac_rdp_pipe_svc_send_pipes( + guac_client* client, guac_socket* socket); /** * Add the given SVC to the list of all available SVCs. This function must be diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.c b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.c index b2d8c415e..e7caecd5d 100644 --- a/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-fs-messages.c @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -165,7 +166,7 @@ void guac_rdpdr_fs_process_read(guac_rdp_common_svc* svc, length = GUAC_RDP_MAX_READ_BUFFER; /* Allocate buffer */ - buffer = malloc(length); + buffer = guac_mem_alloc(length); /* Attempt read */ bytes_read = guac_rdp_fs_read((guac_rdp_fs*) device->data, @@ -187,7 +188,7 @@ void guac_rdpdr_fs_process_read(guac_rdp_common_svc* svc, } guac_rdp_common_svc_write(svc, output_stream); - free(buffer); + guac_mem_free(buffer); } diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr.c b/src/protocols/rdp/channels/rdpdr/rdpdr.c index 68bb73e45..a64a9084d 100644 --- a/src/protocols/rdp/channels/rdpdr/rdpdr.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -145,7 +146,7 @@ void guac_rdpdr_process_connect(guac_rdp_common_svc* svc) { guac_client* client = svc->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_rdpdr* rdpdr = (guac_rdpdr*) calloc(1, sizeof(guac_rdpdr)); + guac_rdpdr* rdpdr = (guac_rdpdr*) guac_mem_zalloc(sizeof(guac_rdpdr)); svc->data = rdpdr; /* Register printer if enabled */ @@ -173,7 +174,7 @@ void guac_rdpdr_process_terminate(guac_rdp_common_svc* svc) { device->free_handler(svc, device); } - free(rdpdr); + guac_mem_free(svc->data); /* rdpdr */ } diff --git a/src/protocols/rdp/channels/rdpei.c b/src/protocols/rdp/channels/rdpei.c index a7e6f3494..8a89af5ea 100644 --- a/src/protocols/rdp/channels/rdpei.c +++ b/src/protocols/rdp/channels/rdpei.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -34,7 +35,7 @@ guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client) { - guac_rdp_rdpei* rdpei = malloc(sizeof(guac_rdp_rdpei)); + guac_rdp_rdpei* rdpei = guac_mem_alloc(sizeof(guac_rdp_rdpei)); rdpei->client = client; /* Not yet connected */ @@ -49,7 +50,7 @@ guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client) { } void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei) { - free(rdpei); + guac_mem_free(rdpei); } /** diff --git a/src/protocols/rdp/channels/rdpsnd/rdpsnd.c b/src/protocols/rdp/channels/rdpsnd/rdpsnd.c index 40b53150b..bbbe6b5ef 100644 --- a/src/protocols/rdp/channels/rdpsnd/rdpsnd.c +++ b/src/protocols/rdp/channels/rdpsnd/rdpsnd.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -86,14 +87,13 @@ void guac_rdpsnd_process_receive(guac_rdp_common_svc* svc, void guac_rdpsnd_process_connect(guac_rdp_common_svc* svc) { - guac_rdpsnd* rdpsnd = (guac_rdpsnd*) calloc(1, sizeof(guac_rdpsnd)); + guac_rdpsnd* rdpsnd = (guac_rdpsnd*) guac_mem_zalloc(sizeof(guac_rdpsnd)); svc->data = rdpsnd; } void guac_rdpsnd_process_terminate(guac_rdp_common_svc* svc) { - guac_rdpsnd* rdpsnd = (guac_rdpsnd*) svc->data; - free(rdpsnd); + guac_mem_free(svc->data); } void guac_rdpsnd_load_plugin(rdpContext* context) { diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index aed5ea68e..8f8c4f689 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -21,6 +21,7 @@ #include "channels/audio-input/audio-buffer.h" #include "channels/cliprdr.h" #include "channels/disp.h" +#include "channels/pipe-svc.h" #include "config.h" #include "fs.h" #include "log.h" @@ -36,6 +37,7 @@ #include #include +#include #include #include @@ -78,6 +80,61 @@ static int is_writable_directory(const char* path) { } +/** + * Add the provided user to the provided audio stream. + * + * @param user + * The pending user who should be added to the audio stream. + * + * @param data + * The audio stream that the user should be added to. + * + * @return + * Always NULL. + */ +static void* guac_rdp_sync_pending_user_audio(guac_user* user, void* data) { + + /* Add the user to the stream */ + guac_audio_stream* audio = (guac_audio_stream*) data; + guac_audio_stream_add_user(audio, user); + + return NULL; + +} + +/** + * A pending join handler implementation that will synchronize the connection + * state for all pending users prior to them being promoted to full user. + * + * @param client + * The client whose pending users are about to be promoted. + * + * @return + * Always zero. + */ +static int guac_rdp_join_pending_handler(guac_client* client) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_socket* broadcast_socket = client->pending_socket; + + /* Synchronize any audio stream for each pending user */ + if (rdp_client->audio) + guac_client_foreach_pending_user( + client, guac_rdp_sync_pending_user_audio, rdp_client->audio); + + /* Bring user up to date with any registered static channels */ + guac_rdp_pipe_svc_send_pipes(client, broadcast_socket); + + /* Synchronize with current display */ + if (rdp_client->display != NULL) { + guac_common_display_dup(rdp_client->display, client, broadcast_socket); + guac_socket_flush(broadcast_socket); + } + + return 0; + +} + int guac_client_init(guac_client* client, int argc, char** argv) { /* Automatically set HOME environment variable if unset (FreeRDP's @@ -138,7 +195,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) { client->args = GUAC_RDP_CLIENT_ARGS; /* Alloc client data */ - guac_rdp_client* rdp_client = calloc(1, sizeof(guac_rdp_client)); + guac_rdp_client* rdp_client = guac_mem_zalloc(sizeof(guac_rdp_client)); client->data = rdp_client; /* Init clipboard */ @@ -164,6 +221,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) { /* Set handlers */ client->join_handler = guac_rdp_user_join_handler; + client->join_pending_handler = guac_rdp_join_pending_handler; client->free_handler = guac_rdp_client_free_handler; client->leave_handler = guac_rdp_user_leave_handler; @@ -239,7 +297,7 @@ int guac_rdp_client_free_handler(guac_client* client) { pthread_mutex_destroy(&(rdp_client->message_lock)); /* Free client data */ - free(rdp_client); + guac_mem_free(rdp_client); return 0; diff --git a/src/protocols/rdp/download.c b/src/protocols/rdp/download.c index 582a0c27d..1b6eeb76b 100644 --- a/src/protocols/rdp/download.c +++ b/src/protocols/rdp/download.c @@ -24,6 +24,7 @@ #include "rdp.h" #include +#include #include #include #include @@ -72,7 +73,7 @@ int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream, else if (bytes_read == 0) { guac_protocol_send_end(user->socket, stream); guac_user_free_stream(user, stream); - free(download_status); + guac_mem_free(download_status); } /* Otherwise, fail stream */ @@ -81,7 +82,7 @@ int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream, "Error reading file for download"); guac_protocol_send_end(user->socket, stream); guac_user_free_stream(user, stream); - free(download_status); + guac_mem_free(download_status); } guac_socket_flush(user->socket); @@ -128,7 +129,7 @@ int guac_rdp_download_get_handler(guac_user* user, guac_object* object, if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) { /* Create stream data */ - guac_rdp_ls_status* ls_status = malloc(sizeof(guac_rdp_ls_status)); + guac_rdp_ls_status* ls_status = guac_mem_alloc(sizeof(guac_rdp_ls_status)); ls_status->fs = fs; ls_status->file_id = file_id; guac_strlcpy(ls_status->directory_name, name, @@ -153,7 +154,7 @@ int guac_rdp_download_get_handler(guac_user* user, guac_object* object, else if (!fs->disable_download) { /* Create stream data */ - guac_rdp_download_status* download_status = malloc(sizeof(guac_rdp_download_status)); + guac_rdp_download_status* download_status = guac_mem_alloc(sizeof(guac_rdp_download_status)); download_status->file_id = file_id; download_status->offset = 0; @@ -209,7 +210,7 @@ void* guac_rdp_download_to_user(guac_user* user, void* data) { /* Associate stream with transfer status */ guac_stream* stream = guac_user_alloc_stream(user); - guac_rdp_download_status* download_status = malloc(sizeof(guac_rdp_download_status)); + guac_rdp_download_status* download_status = guac_mem_alloc(sizeof(guac_rdp_download_status)); stream->data = download_status; stream->ack_handler = guac_rdp_download_ack_handler; download_status->file_id = file_id; diff --git a/src/protocols/rdp/fs.c b/src/protocols/rdp/fs.c index 36bb28d39..be228728c 100644 --- a/src/protocols/rdp/fs.c +++ b/src/protocols/rdp/fs.c @@ -22,6 +22,7 @@ #include "upload.h" #include +#include #include #include #include @@ -59,10 +60,10 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, } } - guac_rdp_fs* fs = malloc(sizeof(guac_rdp_fs)); + guac_rdp_fs* fs = guac_mem_alloc(sizeof(guac_rdp_fs)); fs->client = client; - fs->drive_path = strdup(drive_path); + fs->drive_path = guac_strdup(drive_path); fs->file_id_pool = guac_pool_alloc(0); fs->open_files = 0; fs->disable_download = disable_download; @@ -74,8 +75,8 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, void guac_rdp_fs_free(guac_rdp_fs* fs) { guac_pool_free(fs->file_id_pool); - free(fs->drive_path); - free(fs); + guac_mem_free(fs->drive_path); + guac_mem_free(fs); } guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user) { @@ -366,8 +367,8 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, file->fd = fd; file->dir = NULL; file->dir_pattern[0] = '\0'; - file->absolute_path = strdup(normalized_path); - file->real_path = strdup(real_path); + file->absolute_path = guac_strdup(normalized_path); + file->real_path = guac_strdup(real_path); file->bytes_written = 0; guac_client_log(fs->client, GUAC_LOG_DEBUG, @@ -574,8 +575,8 @@ void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id) { close(file->fd); /* Free name */ - free(file->absolute_path); - free(file->real_path); + guac_mem_free(file->absolute_path); + guac_mem_free(file->real_path); /* Free ID back to pool */ guac_pool_free_int(fs->file_id_pool, file_id); diff --git a/src/protocols/rdp/glyph.c b/src/protocols/rdp/glyph.c index d84138410..7241a385a 100644 --- a/src/protocols/rdp/glyph.c +++ b/src/protocols/rdp/glyph.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -48,7 +49,7 @@ BOOL guac_rdp_glyph_new(rdpContext* context, const rdpGlyph* glyph) { /* Init Cairo buffer */ stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); - image_buffer = malloc(height*stride); + image_buffer = guac_mem_alloc(height, stride); image_buffer_row = image_buffer; /* Copy image data from image data to buffer */ @@ -118,7 +119,7 @@ void guac_rdp_glyph_free(rdpContext* context, rdpGlyph* glyph) { /* Free surface */ cairo_surface_destroy(((guac_rdp_glyph*) glyph)->surface); - free(image_buffer); + guac_mem_free(image_buffer); /* NOTE: FreeRDP-allocated memory for the rdpGlyph will NOT be * automatically released after this free handler is invoked, thus we must diff --git a/src/protocols/rdp/keyboard.c b/src/protocols/rdp/keyboard.c index c263a5644..bb014b0db 100644 --- a/src/protocols/rdp/keyboard.c +++ b/src/protocols/rdp/keyboard.c @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -440,7 +441,7 @@ static void guac_rdp_keyboard_load_keymap(guac_rdp_keyboard* keyboard, guac_rdp_keyboard* guac_rdp_keyboard_alloc(guac_client* client, const guac_rdp_keymap* keymap) { - guac_rdp_keyboard* keyboard = calloc(1, sizeof(guac_rdp_keyboard)); + guac_rdp_keyboard* keyboard = guac_mem_zalloc(sizeof(guac_rdp_keyboard)); keyboard->client = client; /* Load keymap into keyboard */ @@ -451,7 +452,7 @@ guac_rdp_keyboard* guac_rdp_keyboard_alloc(guac_client* client, } void guac_rdp_keyboard_free(guac_rdp_keyboard* keyboard) { - free(keyboard); + guac_mem_free(keyboard); } int guac_rdp_keyboard_is_defined(guac_rdp_keyboard* keyboard, int keysym) { diff --git a/src/protocols/rdp/keymaps/base.keymap b/src/protocols/rdp/keymaps/base.keymap index 5b93fab6d..4d6cb01d9 100644 --- a/src/protocols/rdp/keymaps/base.keymap +++ b/src/protocols/rdp/keymaps/base.keymap @@ -84,7 +84,6 @@ map 0x1D ~ 0xffe3 # Control_L map +ext 0x1D ~ 0xffe4 # Control_R map 0x38 ~ 0xffe9 # Alt_L map +ext 0x38 ~ 0xffea # Alt_R -map +ext 0x38 ~ 0xfe03 # AltGr map +ext 0x5B ~ 0xffe7 # Meta_L map +ext 0x5C ~ 0xffe8 # Meta_R map +ext 0x5B ~ 0xffeb # Super_L diff --git a/src/protocols/rdp/keymaps/base_altgr.keymap b/src/protocols/rdp/keymaps/base_altgr.keymap new file mode 100644 index 000000000..5c4691d07 --- /dev/null +++ b/src/protocols/rdp/keymaps/base_altgr.keymap @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "base_altgr" + +# Modifiers +map +ext 0x38 ~ 0xfe03 # AltGr + diff --git a/src/protocols/rdp/keymaps/cs-cz-qwertz.keymap b/src/protocols/rdp/keymaps/cs-cz-qwertz.keymap index b9bec804d..e29037bc7 100644 --- a/src/protocols/rdp/keymaps/cs-cz-qwertz.keymap +++ b/src/protocols/rdp/keymaps/cs-cz-qwertz.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "cs-cz-qwertz" freerdp "KBD_CZECH" diff --git a/src/protocols/rdp/keymaps/da_dk_qwerty.keymap b/src/protocols/rdp/keymaps/da_dk_qwerty.keymap index aa6139cf3..0c4bf7eb2 100644 --- a/src/protocols/rdp/keymaps/da_dk_qwerty.keymap +++ b/src/protocols/rdp/keymaps/da_dk_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "da-dk-qwerty" freerdp "KBD_DANISH" diff --git a/src/protocols/rdp/keymaps/de_ch_qwertz.keymap b/src/protocols/rdp/keymaps/de_ch_qwertz.keymap index c4d80c7a3..b86f8c8da 100644 --- a/src/protocols/rdp/keymaps/de_ch_qwertz.keymap +++ b/src/protocols/rdp/keymaps/de_ch_qwertz.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "de-ch-qwertz" freerdp "KBD_SWISS_GERMAN" diff --git a/src/protocols/rdp/keymaps/de_de_qwertz.keymap b/src/protocols/rdp/keymaps/de_de_qwertz.keymap index 787c05595..bc9a373ca 100644 --- a/src/protocols/rdp/keymaps/de_de_qwertz.keymap +++ b/src/protocols/rdp/keymaps/de_de_qwertz.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "de-de-qwertz" freerdp "KBD_GERMAN" diff --git a/src/protocols/rdp/keymaps/en_gb_qwerty.keymap b/src/protocols/rdp/keymaps/en_gb_qwerty.keymap index 9f338983d..acf0edc6e 100644 --- a/src/protocols/rdp/keymaps/en_gb_qwerty.keymap +++ b/src/protocols/rdp/keymaps/en_gb_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "en-gb-qwerty" freerdp "KBD_UNITED_KINGDOM" diff --git a/src/protocols/rdp/keymaps/es_es_qwerty.keymap b/src/protocols/rdp/keymaps/es_es_qwerty.keymap index 93453cff2..66d76e9b1 100644 --- a/src/protocols/rdp/keymaps/es_es_qwerty.keymap +++ b/src/protocols/rdp/keymaps/es_es_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "es-es-qwerty" freerdp "KBD_SPANISH" diff --git a/src/protocols/rdp/keymaps/es_latam_qwerty.keymap b/src/protocols/rdp/keymaps/es_latam_qwerty.keymap index 4c4ee578a..d69acf6bf 100644 --- a/src/protocols/rdp/keymaps/es_latam_qwerty.keymap +++ b/src/protocols/rdp/keymaps/es_latam_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "es-latam-qwerty" freerdp "KBD_LATIN_AMERICAN" diff --git a/src/protocols/rdp/keymaps/fr_be_azerty.keymap b/src/protocols/rdp/keymaps/fr_be_azerty.keymap index 35f637ecd..6899ad87b 100644 --- a/src/protocols/rdp/keymaps/fr_be_azerty.keymap +++ b/src/protocols/rdp/keymaps/fr_be_azerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "fr-be-azerty" freerdp "KBD_BELGIAN_FRENCH" diff --git a/src/protocols/rdp/keymaps/fr_ca_qwerty.keymap b/src/protocols/rdp/keymaps/fr_ca_qwerty.keymap index ee79f822c..da0f4df4a 100644 --- a/src/protocols/rdp/keymaps/fr_ca_qwerty.keymap +++ b/src/protocols/rdp/keymaps/fr_ca_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "fr-ca-qwerty" freerdp "KBD_CANADIAN_FRENCH" diff --git a/src/protocols/rdp/keymaps/fr_ch_qwertz.keymap b/src/protocols/rdp/keymaps/fr_ch_qwertz.keymap index 8864d701f..088355497 100644 --- a/src/protocols/rdp/keymaps/fr_ch_qwertz.keymap +++ b/src/protocols/rdp/keymaps/fr_ch_qwertz.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "fr-ch-qwertz" freerdp "KBD_SWISS_FRENCH" diff --git a/src/protocols/rdp/keymaps/fr_fr_azerty.keymap b/src/protocols/rdp/keymaps/fr_fr_azerty.keymap index 7f4f83271..4d29c4c62 100644 --- a/src/protocols/rdp/keymaps/fr_fr_azerty.keymap +++ b/src/protocols/rdp/keymaps/fr_fr_azerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "fr-fr-azerty" freerdp "KBD_FRENCH" diff --git a/src/protocols/rdp/keymaps/hu_hu_qwertz.keymap b/src/protocols/rdp/keymaps/hu_hu_qwertz.keymap index 211d92c21..858d97af9 100644 --- a/src/protocols/rdp/keymaps/hu_hu_qwertz.keymap +++ b/src/protocols/rdp/keymaps/hu_hu_qwertz.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "hu-hu-qwertz" freerdp "KBD_HUNGARIAN" diff --git a/src/protocols/rdp/keymaps/it_it_qwerty.keymap b/src/protocols/rdp/keymaps/it_it_qwerty.keymap index 54f2172ae..9b58331be 100644 --- a/src/protocols/rdp/keymaps/it_it_qwerty.keymap +++ b/src/protocols/rdp/keymaps/it_it_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "it-it-qwerty" freerdp "KBD_ITALIAN" diff --git a/src/protocols/rdp/keymaps/no_no_qwerty.keymap b/src/protocols/rdp/keymaps/no_no_qwerty.keymap index 5fbc126b0..62fe1a43d 100644 --- a/src/protocols/rdp/keymaps/no_no_qwerty.keymap +++ b/src/protocols/rdp/keymaps/no_no_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "no-no-qwerty" freerdp "KBD_NORWEGIAN" diff --git a/src/protocols/rdp/keymaps/pt_br_qwerty.keymap b/src/protocols/rdp/keymaps/pt_br_qwerty.keymap index e0656e9b3..c513d3802 100644 --- a/src/protocols/rdp/keymaps/pt_br_qwerty.keymap +++ b/src/protocols/rdp/keymaps/pt_br_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "pt-br-qwerty" freerdp "KBD_PORTUGUESE_BRAZILIAN_ABNT2" @@ -64,4 +64,4 @@ map -altgr +shift 0x07 ~ 0xFE57 # Dead diaeresis (umlaut) map -altgr +shift 0x1A ~ 0xFE50 # Dead grave map -altgr -shift 0x1A ~ 0xFE51 # Dead acute map -altgr +shift 0x28 ~ 0xFE52 # Dead circumflex -map -altgr -shift 0x28 ~ 0xFE53 # Dead tilde \ No newline at end of file +map -altgr -shift 0x28 ~ 0xFE53 # Dead tilde diff --git a/src/protocols/rdp/keymaps/pt_pt_qwerty.keymap b/src/protocols/rdp/keymaps/pt_pt_qwerty.keymap index 5bfbee8c0..4703b31c1 100644 --- a/src/protocols/rdp/keymaps/pt_pt_qwerty.keymap +++ b/src/protocols/rdp/keymaps/pt_pt_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "pt-pt-qwerty" freerdp "KBD_PORTUGUESE" diff --git a/src/protocols/rdp/keymaps/ro_ro_qwerty.keymap b/src/protocols/rdp/keymaps/ro_ro_qwerty.keymap index 51996fd00..f66641980 100644 --- a/src/protocols/rdp/keymaps/ro_ro_qwerty.keymap +++ b/src/protocols/rdp/keymaps/ro_ro_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "ro-ro-qwerty" freerdp "KBD_ROMANIAN" diff --git a/src/protocols/rdp/keymaps/sv_se_qwerty.keymap b/src/protocols/rdp/keymaps/sv_se_qwerty.keymap index 6ab8b7a50..1a7ce4a40 100644 --- a/src/protocols/rdp/keymaps/sv_se_qwerty.keymap +++ b/src/protocols/rdp/keymaps/sv_se_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "sv-se-qwerty" freerdp "KBD_SWEDISH" diff --git a/src/protocols/rdp/keymaps/tr_tr_qwerty.keymap b/src/protocols/rdp/keymaps/tr_tr_qwerty.keymap index d46a0fda4..d23d1cdf3 100644 --- a/src/protocols/rdp/keymaps/tr_tr_qwerty.keymap +++ b/src/protocols/rdp/keymaps/tr_tr_qwerty.keymap @@ -17,7 +17,7 @@ # under the License. # -parent "base" +parent "base_altgr" name "tr-tr-qwerty" freerdp "KBD_TURKISH_Q" diff --git a/src/protocols/rdp/ls.c b/src/protocols/rdp/ls.c index f0a3b8f4c..f284df3ff 100644 --- a/src/protocols/rdp/ls.c +++ b/src/protocols/rdp/ls.c @@ -21,6 +21,7 @@ #include "ls.h" #include +#include #include #include #include @@ -44,7 +45,7 @@ int guac_rdp_ls_ack_handler(guac_user* user, guac_stream* stream, if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { guac_rdp_fs_close(ls_status->fs, ls_status->file_id); guac_user_free_stream(user, stream); - free(ls_status); + guac_mem_free(ls_status); return 0; } @@ -108,7 +109,7 @@ int guac_rdp_ls_ack_handler(guac_user* user, guac_stream* stream, /* Clean up resources */ guac_rdp_fs_close(ls_status->fs, ls_status->file_id); - free(ls_status); + guac_mem_free(ls_status); /* Signal of stream */ guac_protocol_send_end(user->socket, stream); diff --git a/src/protocols/rdp/plugins/channels.c b/src/protocols/rdp/plugins/channels.c index 0048b8915..342ca33b3 100644 --- a/src/protocols/rdp/plugins/channels.c +++ b/src/protocols/rdp/plugins/channels.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -126,7 +127,7 @@ void guac_freerdp_dynamic_channel_collection_add(rdpSettings* settings, va_start(args, name); /* Copy argument values into DVC entry */ - freerdp_args->argv = malloc(sizeof(char*) * freerdp_args->argc); + freerdp_args->argv = malloc(guac_mem_ckd_mul_or_die(sizeof(char*), freerdp_args->argc)); freerdp_args->argv[0] = strdup(name); int i; for (i = 1; i < freerdp_args->argc; i++) diff --git a/src/protocols/rdp/plugins/guac-common-svc/guac-common-svc.c b/src/protocols/rdp/plugins/guac-common-svc/guac-common-svc.c index 027e7732b..889e4ed4a 100644 --- a/src/protocols/rdp/plugins/guac-common-svc/guac-common-svc.c +++ b/src/protocols/rdp/plugins/guac-common-svc/guac-common-svc.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -203,7 +204,7 @@ static void guac_rdp_common_svc_process_terminate(guac_rdp_common_svc* svc) { guac_client_log(svc->client, GUAC_LOG_DEBUG, "SVC \"%s\" disconnected.", svc->name); - free(svc); + guac_mem_free(svc); } diff --git a/src/protocols/rdp/plugins/guacai/guacai.c b/src/protocols/rdp/plugins/guacai/guacai.c index 0d453505d..36da54552 100644 --- a/src/protocols/rdp/plugins/guacai/guacai.c +++ b/src/protocols/rdp/plugins/guacai/guacai.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -149,7 +150,7 @@ static UINT guac_rdp_ai_close(IWTSVirtualChannelCallback* channel_callback) { "AUDIO_INPUT channel connection closed"); guac_rdp_audio_buffer_end(audio_buffer); - free(ai_channel_callback); + guac_mem_free(ai_channel_callback); return CHANNEL_RC_OK; } @@ -199,7 +200,7 @@ static UINT guac_rdp_ai_new_connection( /* Allocate new channel callback */ guac_rdp_ai_channel_callback* ai_channel_callback = - calloc(1, sizeof(guac_rdp_ai_channel_callback)); + guac_mem_zalloc(sizeof(guac_rdp_ai_channel_callback)); /* Init listener callback with data from plugin */ ai_channel_callback->client = ai_listener_callback->client; @@ -233,7 +234,7 @@ static UINT guac_rdp_ai_initialize(IWTSPlugin* plugin, /* Allocate new listener callback */ guac_rdp_ai_listener_callback* ai_listener_callback = - calloc(1, sizeof(guac_rdp_ai_listener_callback)); + guac_mem_zalloc(sizeof(guac_rdp_ai_listener_callback)); /* Ensure listener callback is freed when plugin is terminated */ guac_rdp_ai_plugin* ai_plugin = (guac_rdp_ai_plugin*) plugin; @@ -268,8 +269,8 @@ static UINT guac_rdp_ai_terminated(IWTSPlugin* plugin) { guac_client* client = ai_plugin->client; /* Free all non-FreeRDP data */ - free(ai_plugin->listener_callback); - free(ai_plugin); + guac_mem_free(ai_plugin->listener_callback); + guac_mem_free(ai_plugin); guac_client_log(client, GUAC_LOG_DEBUG, "AUDIO_INPUT plugin unloaded."); return CHANNEL_RC_OK; @@ -293,7 +294,7 @@ UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) { if (ai_plugin == NULL) { /* Init plugin callbacks and data */ - ai_plugin = calloc(1, sizeof(guac_rdp_ai_plugin)); + ai_plugin = guac_mem_zalloc(sizeof(guac_rdp_ai_plugin)); ai_plugin->parent.Initialize = guac_rdp_ai_initialize; ai_plugin->parent.Terminated = guac_rdp_ai_terminated; ai_plugin->client = client; diff --git a/src/protocols/rdp/pointer.c b/src/protocols/rdp/pointer.c index ae167cf6c..8c024b71b 100644 --- a/src/protocols/rdp/pointer.c +++ b/src/protocols/rdp/pointer.c @@ -42,7 +42,7 @@ BOOL guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { rdp_client->display, pointer->width, pointer->height); /* Allocate data for image */ - unsigned char* data = _aligned_recalloc(NULL, 1, pointer->width * pointer->height * 4, 16); + unsigned char* data = _aligned_malloc(pointer->width * pointer->height * 4, 16); cairo_surface_t* surface; diff --git a/src/protocols/rdp/print-job.c b/src/protocols/rdp/print-job.c index 49efbfdc4..2b6ca1aa3 100644 --- a/src/protocols/rdp/print-job.c +++ b/src/protocols/rdp/print-job.c @@ -21,6 +21,7 @@ #include "rdp.h" #include +#include #include #include #include @@ -454,7 +455,7 @@ void* guac_rdp_print_job_alloc(guac_user* user, void* data) { return NULL; /* Bail early if allocation fails */ - guac_rdp_print_job* job = malloc(sizeof(guac_rdp_print_job)); + guac_rdp_print_job* job = guac_mem_alloc(sizeof(guac_rdp_print_job)); if (job == NULL) return NULL; @@ -478,7 +479,7 @@ void* guac_rdp_print_job_alloc(guac_user* user, void* data) { /* Abort if print filter process cannot be created */ if (job->filter_pid == -1) { guac_user_free_stream(user, stream); - free(job); + guac_mem_free(job); return NULL; } @@ -659,7 +660,7 @@ void guac_rdp_print_job_free(guac_rdp_print_job* job) { pthread_mutex_destroy(&(job->state_lock)); /* Free base structure */ - free(job); + guac_mem_free(job); } diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index b60db48aa..1a16780b5 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -70,6 +70,7 @@ #include #include #include +#include #include #include #include @@ -297,9 +298,9 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, guac_argv_await((const char**) params); /* Free old values and get new values from settings. */ - free(*username); - free(*password); - free(*domain); + guac_mem_free(*username); + guac_mem_free(*password); + guac_mem_free(*domain); *username = guac_strdup(settings->username); *password = guac_strdup(settings->password); *domain = guac_strdup(settings->domain); @@ -650,7 +651,7 @@ static int guac_rdp_handle_connection(guac_client* client) { rdp_client->rdp_inst = NULL; /* Free SVC list */ - guac_common_list_free(rdp_client->available_svc); + guac_common_list_free(rdp_client->available_svc, NULL); rdp_client->available_svc = NULL; /* Free RDP keyboard state */ diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index f2dbeabd4..fb2217a64 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,8 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "server-layout", "security", "ignore-cert", + "cert-tofu", + "cert-fingerprints", "disable-auth", "remote-app", "remote-app-dir", @@ -289,6 +292,21 @@ enum RDP_ARGS_IDX { */ IDX_IGNORE_CERT, + /** + * "true" if a server certificate should be trusted the first time that + * a connection is established, and then subsequently checked for validity, + * or "false" if that behavior should not be forced. Whether or not the + * connection succeeds will be dependent upon other certificate settings, + * like ignore and/or provided fingerprints. + */ + IDX_CERTIFICATE_TOFU, + + /** + * A comma-separate list of fingerprints of certificates that should be + * trusted when establishing this RDP connection. + */ + IDX_CERTIFICATE_FINGERPRINTS, + /** * "true" if authentication should be disabled, "false" or blank otherwise. * This is different from the authentication that takes place when a user @@ -691,7 +709,7 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, return NULL; } - guac_rdp_settings* settings = calloc(1, sizeof(guac_rdp_settings)); + guac_rdp_settings* settings = guac_mem_zalloc(sizeof(guac_rdp_settings)); /* Use console */ settings->console = @@ -708,6 +726,16 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, IDX_IGNORE_CERT, 0); + /* Add new certificates to trust list */ + settings->certificate_tofu = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_CERTIFICATE_TOFU, 0); + + /* Fingerprints of certificates that should be trusted */ + settings->certificate_fingerprints = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_CERTIFICATE_FINGERPRINTS, NULL); + /* Disable authentication */ settings->disable_authentication = guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, @@ -965,7 +993,7 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, /* Preconnection BLOB */ settings->preconnection_blob = NULL; if (argv[IDX_PRECONNECTION_BLOB][0] != '\0') { - settings->preconnection_blob = strdup(argv[IDX_PRECONNECTION_BLOB]); + settings->preconnection_blob = guac_strdup(argv[IDX_PRECONNECTION_BLOB]); guac_user_log(user, GUAC_LOG_DEBUG, "Preconnection BLOB: \"%s\"", settings->preconnection_blob); } @@ -1291,22 +1319,23 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, void guac_rdp_settings_free(guac_rdp_settings* settings) { /* Free settings strings */ - free(settings->client_name); - free(settings->domain); - free(settings->drive_name); - free(settings->drive_path); - free(settings->hostname); - free(settings->initial_program); - free(settings->password); - free(settings->preconnection_blob); - free(settings->recording_name); - free(settings->recording_path); - free(settings->remote_app); - free(settings->remote_app_args); - free(settings->remote_app_dir); - free(settings->timezone); - free(settings->username); - free(settings->printer_name); + guac_mem_free(settings->client_name); + guac_mem_free(settings->domain); + guac_mem_free(settings->drive_name); + guac_mem_free(settings->drive_path); + guac_mem_free(settings->hostname); + guac_mem_free(settings->certificate_fingerprints); + guac_mem_free(settings->initial_program); + guac_mem_free(settings->password); + guac_mem_free(settings->preconnection_blob); + guac_mem_free(settings->recording_name); + guac_mem_free(settings->recording_path); + guac_mem_free(settings->remote_app); + guac_mem_free(settings->remote_app_args); + guac_mem_free(settings->remote_app_dir); + guac_mem_free(settings->timezone); + guac_mem_free(settings->username); + guac_mem_free(settings->printer_name); /* Free channel name array */ if (settings->svc_names != NULL) { @@ -1314,43 +1343,43 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { /* Free all elements of array */ char** current = &(settings->svc_names[0]); while (*current != NULL) { - free(*current); + guac_mem_free(*current); current++; } /* Free array itself */ - free(settings->svc_names); + guac_mem_free(settings->svc_names); } #ifdef ENABLE_COMMON_SSH /* Free SFTP settings */ - free(settings->sftp_directory); - free(settings->sftp_root_directory); - free(settings->sftp_host_key); - free(settings->sftp_hostname); - free(settings->sftp_passphrase); - free(settings->sftp_password); - free(settings->sftp_port); - free(settings->sftp_private_key); - free(settings->sftp_username); + guac_mem_free(settings->sftp_directory); + guac_mem_free(settings->sftp_root_directory); + guac_mem_free(settings->sftp_host_key); + guac_mem_free(settings->sftp_hostname); + guac_mem_free(settings->sftp_passphrase); + guac_mem_free(settings->sftp_password); + guac_mem_free(settings->sftp_port); + guac_mem_free(settings->sftp_private_key); + guac_mem_free(settings->sftp_username); #endif /* Free RD gateway information */ - free(settings->gateway_hostname); - free(settings->gateway_domain); - free(settings->gateway_username); - free(settings->gateway_password); + guac_mem_free(settings->gateway_hostname); + guac_mem_free(settings->gateway_domain); + guac_mem_free(settings->gateway_username); + guac_mem_free(settings->gateway_password); /* Free load balancer information string */ - free(settings->load_balance_info); + guac_mem_free(settings->load_balance_info); /* Free Wake-on-LAN strings */ - free(settings->wol_mac_addr); - free(settings->wol_broadcast_addr); + guac_mem_free(settings->wol_mac_addr); + guac_mem_free(settings->wol_broadcast_addr); /* Free settings structure */ - free(settings); + guac_mem_free(settings); } @@ -1575,9 +1604,12 @@ void guac_rdp_push_settings(guac_client* client, } - /* Authentication */ + /* Security */ rdp_settings->Authentication = !guac_settings->disable_authentication; rdp_settings->IgnoreCertificate = guac_settings->ignore_certificate; + rdp_settings->AutoAcceptCertificate = guac_settings->certificate_tofu; + if (guac_settings->certificate_fingerprints != NULL) + rdp_settings->CertificateAcceptedFingerprints = guac_strdup(guac_settings->certificate_fingerprints); /* RemoteApp */ if (guac_settings->remote_app != NULL) { diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h index 3fe9d0027..11dbf59bc 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -286,6 +286,18 @@ typedef struct guac_rdp_settings { */ int ignore_certificate; + /** + * Whether or not a certificate should be added to the local trust + * store on first use. + */ + int certificate_tofu; + + /** + * The fingerprints of host certificates that should be trusted for + * this connection. + */ + char* certificate_fingerprints; + /** * Whether authentication should be disabled. This is different from the * authentication that takes place when a user provides their username diff --git a/src/protocols/rdp/tests/Makefile.am b/src/protocols/rdp/tests/Makefile.am index 33136696d..e1e22e369 100644 --- a/src/protocols/rdp/tests/Makefile.am +++ b/src/protocols/rdp/tests/Makefile.am @@ -44,7 +44,8 @@ test_rdp_CFLAGS = \ test_rdp_LDADD = \ @CUNIT_LIBS@ \ - @LIBGUAC_CLIENT_RDP_LTLIB@ + @LIBGUAC_CLIENT_RDP_LTLIB@ \ + @LIBGUAC_LTLIB@ # # Autogenerate test runner diff --git a/src/protocols/rdp/tests/fs/normalize_path.c b/src/protocols/rdp/tests/fs/normalize_path.c index 22a2d8094..8640dab8e 100644 --- a/src/protocols/rdp/tests/fs/normalize_path.c +++ b/src/protocols/rdp/tests/fs/normalize_path.c @@ -19,6 +19,8 @@ #include "fs.h" +#include + #include #include @@ -160,7 +162,7 @@ void test_fs__normalize_relative_mixed() { * Generates a dynamically-allocated path having the given number of bytes, not * counting the null-terminator. The path will contain only Windows-style path * separators. The returned path must eventually be freed with a call to - * free(). + * guac_mem_free(). * * @param length * The number of bytes to include in the generated path, not counting the @@ -174,7 +176,7 @@ void test_fs__normalize_relative_mixed() { * @return * A dynamically-allocated path containing the given number of bytes, not * counting the null-terminator. This path must eventually be freed with a - * call to free(). + * call to guac_mem_free(). */ static char* generate_path(int length, int max_depth) { @@ -183,7 +185,7 @@ static char* generate_path(int length, int max_depth) { length = max_depth * 2; int i; - char* input = malloc(length + 1); + char* input = guac_mem_alloc(guac_mem_ckd_add_or_die(length, 1)); /* Fill path with \x\x\x\x\x\x\x\x\x\x\...\xxxxxxxxx... */ for (i = 0; i < length; i++) { @@ -214,17 +216,17 @@ void test_fs__normalize_long() { /* Exceeds maximum length by a factor of 2 */ input = generate_path(GUAC_RDP_FS_MAX_PATH * 2, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); - free(input); + guac_mem_free(input); /* Exceeds maximum length by one byte */ input = generate_path(GUAC_RDP_FS_MAX_PATH, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); - free(input); + guac_mem_free(input); /* Exactly maximum length */ input = generate_path(GUAC_RDP_FS_MAX_PATH - 1, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); - free(input); + guac_mem_free(input); } @@ -240,17 +242,17 @@ void test_fs__normalize_deep() { /* Exceeds maximum depth by a factor of 2 */ input = generate_path(-1, GUAC_RDP_MAX_PATH_DEPTH * 2); CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); - free(input); + guac_mem_free(input); /* Exceeds maximum depth by one component */ input = generate_path(-1, GUAC_RDP_MAX_PATH_DEPTH + 1); CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); - free(input); + guac_mem_free(input); /* Exactly maximum depth */ input = generate_path(-1, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); - free(input); + guac_mem_free(input); } diff --git a/src/protocols/rdp/upload.c b/src/protocols/rdp/upload.c index 2b08b2f7d..ecc497180 100644 --- a/src/protocols/rdp/upload.c +++ b/src/protocols/rdp/upload.c @@ -22,6 +22,7 @@ #include "upload.h" #include +#include #include #include #include @@ -113,7 +114,7 @@ int guac_rdp_upload_file_handler(guac_user* user, guac_stream* stream, } /* Init upload status */ - guac_rdp_upload_status* upload_status = malloc(sizeof(guac_rdp_upload_status)); + guac_rdp_upload_status* upload_status = guac_mem_alloc(sizeof(guac_rdp_upload_status)); upload_status->offset = 0; upload_status->file_id = file_id; stream->data = upload_status; @@ -197,7 +198,7 @@ int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) { GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); - free(upload_status); + guac_mem_free(upload_status); return 0; } @@ -242,7 +243,7 @@ int guac_rdp_upload_put_handler(guac_user* user, guac_object* object, } /* Init upload stream data */ - guac_rdp_upload_status* upload_status = malloc(sizeof(guac_rdp_upload_status)); + guac_rdp_upload_status* upload_status = guac_mem_alloc(sizeof(guac_rdp_upload_status)); upload_status->offset = 0; upload_status->file_id = file_id; diff --git a/src/protocols/rdp/user.c b/src/protocols/rdp/user.c index 60b6f07a7..5381a9822 100644 --- a/src/protocols/rdp/user.c +++ b/src/protocols/rdp/user.c @@ -82,22 +82,6 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { } - /* If not owner, synchronize with current state */ - else { - - /* Synchronize any audio stream */ - if (rdp_client->audio) - guac_audio_stream_add_user(rdp_client->audio, user); - - /* Bring user up to date with any registered static channels */ - guac_rdp_pipe_svc_send_pipes(user); - - /* Synchronize with current display */ - guac_common_display_dup(rdp_client->display, user, user->socket); - guac_socket_flush(user->socket); - - } - /* Only handle events if not read-only */ if (!settings->read_only) { diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c index a8f31f2ec..6082b02ff 100644 --- a/src/protocols/ssh/argv.c +++ b/src/protocols/ssh/argv.c @@ -70,26 +70,33 @@ int guac_ssh_argv_callback(guac_user* user, const char* mimetype, void* guac_ssh_send_current_argv(guac_user* user, void* data) { - guac_ssh_client* ssh_client = (guac_ssh_client*) data; + /* Defer to the batch handler, using the user's socket to send the data */ + guac_ssh_send_current_argv_batch(user->client, user->socket); + + return NULL; + +} + +void guac_ssh_send_current_argv_batch(guac_client* client, guac_socket* socket) { + + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; guac_terminal* terminal = ssh_client->term; /* Send current color scheme */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_SSH_ARGV_COLOR_SCHEME, guac_terminal_get_color_scheme(terminal)); /* Send current font name */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_SSH_ARGV_FONT_NAME, guac_terminal_get_font_name(terminal)); /* Send current font size */ char font_size[64]; sprintf(font_size, "%i", guac_terminal_get_font_size(terminal)); - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_SSH_ARGV_FONT_SIZE, font_size); - return NULL; - } diff --git a/src/protocols/ssh/argv.h b/src/protocols/ssh/argv.h index 4cbdb4c84..0b32d0908 100644 --- a/src/protocols/ssh/argv.h +++ b/src/protocols/ssh/argv.h @@ -69,5 +69,23 @@ guac_argv_callback guac_ssh_argv_callback; */ void* guac_ssh_send_current_argv(guac_user* user, void* data); +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the users associated with the provided + * socket. Note that the users receiving these values will not necessarily be + * able to set new values themselves if their connection is read-only. + * + * @param client + * The client associated with the users that should receive the values of + * all non-sensitive parameters which may be set while the connection is running. + * + * @param socket + * The socket to the arguments to the batch of users along. + * + * @return + * Always NULL. + */ +void guac_ssh_send_current_argv_batch(guac_client* client, guac_socket* socket); + #endif diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index 4a314f061..b116e6d11 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -33,7 +33,36 @@ #include #include +#include #include +#include + + +/** + * A pending join handler implementation that will synchronize the connection + * state for all pending users prior to them being promoted to full user. + * + * @param client + * The client whose pending users are about to be promoted. + * + * @return + * Always zero. + */ +static int guac_ssh_join_pending_handler(guac_client* client) { + + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + + /* Synchronize the terminal state to all pending users */ + if (ssh_client->term != NULL) { + guac_socket* broadcast_socket = client->pending_socket; + guac_terminal_sync_users(ssh_client->term, client, broadcast_socket); + guac_ssh_send_current_argv_batch(client, broadcast_socket); + guac_socket_flush(broadcast_socket); + } + + return 0; + +} int guac_client_init(guac_client* client) { @@ -41,11 +70,12 @@ int guac_client_init(guac_client* client) { client->args = GUAC_SSH_CLIENT_ARGS; /* Allocate client instance data */ - guac_ssh_client* ssh_client = calloc(1, sizeof(guac_ssh_client)); + guac_ssh_client* ssh_client = guac_mem_zalloc(sizeof(guac_ssh_client)); client->data = ssh_client; /* Set handlers */ client->join_handler = guac_ssh_user_join_handler; + client->join_pending_handler = guac_ssh_join_pending_handler; client->free_handler = guac_ssh_client_free_handler; client->leave_handler = guac_ssh_user_leave_handler; @@ -114,7 +144,7 @@ int guac_ssh_client_free_handler(guac_client* client) { guac_ssh_settings_free(ssh_client->settings); /* Free client structure */ - free(ssh_client); + guac_mem_free(ssh_client); guac_common_ssh_uninit(); return 0; diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 686ef6379..228068b2f 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -25,6 +25,7 @@ #include "settings.h" #include "terminal/terminal.h" +#include #include #include @@ -47,6 +48,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "sftp-disable-upload", "private-key", "passphrase", + "public-key", #ifdef ENABLE_SSH_AGENT "enable-agent", #endif @@ -148,6 +150,11 @@ enum SSH_ARGS_IDX { */ IDX_PASSPHRASE, + /** + * The public key to use for authentication, if any. + */ + IDX_PUBLIC_KEY, + #ifdef ENABLE_SSH_AGENT /** * Whether SSH agent forwarding support should be enabled. @@ -345,7 +352,7 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, return NULL; } - guac_ssh_settings* settings = calloc(1, sizeof(guac_ssh_settings)); + guac_ssh_settings* settings = guac_mem_zalloc(sizeof(guac_ssh_settings)); /* Read parameters */ settings->hostname = @@ -373,6 +380,10 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_PASSPHRASE, NULL); + settings->public_key_base64 = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_PUBLIC_KEY, NULL); + /* Read maximum scrollback size */ settings->max_scrollback = guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, @@ -558,48 +569,49 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, void guac_ssh_settings_free(guac_ssh_settings* settings) { /* Free network connection information */ - free(settings->hostname); - free(settings->host_key); - free(settings->port); + guac_mem_free(settings->hostname); + guac_mem_free(settings->host_key); + guac_mem_free(settings->port); /* Free credentials */ - free(settings->username); - free(settings->password); - free(settings->key_base64); - free(settings->key_passphrase); + guac_mem_free(settings->username); + guac_mem_free(settings->password); + guac_mem_free(settings->key_base64); + guac_mem_free(settings->key_passphrase); + guac_mem_free(settings->public_key_base64); /* Free display preferences */ - free(settings->font_name); - free(settings->color_scheme); + guac_mem_free(settings->font_name); + guac_mem_free(settings->color_scheme); /* Free requested command */ - free(settings->command); + guac_mem_free(settings->command); /* Free SFTP settings */ - free(settings->sftp_root_directory); + guac_mem_free(settings->sftp_root_directory); /* Free typescript settings */ - free(settings->typescript_name); - free(settings->typescript_path); + guac_mem_free(settings->typescript_name); + guac_mem_free(settings->typescript_path); /* Free screen recording settings */ - free(settings->recording_name); - free(settings->recording_path); + guac_mem_free(settings->recording_name); + guac_mem_free(settings->recording_path); /* Free terminal emulator type. */ - free(settings->terminal_type); + guac_mem_free(settings->terminal_type); /* Free locale */ - free(settings->locale); + guac_mem_free(settings->locale); /* Free the client timezone. */ - free(settings->timezone); + guac_mem_free(settings->timezone); /* Free Wake-on-LAN settings. */ - free(settings->wol_mac_addr); - free(settings->wol_broadcast_addr); + guac_mem_free(settings->wol_mac_addr); + guac_mem_free(settings->wol_broadcast_addr); /* Free overall structure */ - free(settings); + guac_mem_free(settings); } diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index 103ff0293..e5d207501 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -93,6 +93,12 @@ typedef struct guac_ssh_settings { */ char* key_passphrase; + /** + * The public key, encoded as base64, if any. If no public key is specified, + * this will be NULL. + */ + char* public_key_base64; + /** * Whether this connection is read-only, and user input should be dropped. */ diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 5504393c7..d42e7f62f 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -134,6 +135,32 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { } /* end if key given */ + if (settings->public_key_base64 != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Attempting public key import"); + + /* Attempt to read public key */ + if (guac_common_ssh_user_import_public_key(user, + settings->public_key_base64)) { + + /* If failing*/ + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Auth public key import failed: %s", + guac_common_ssh_key_error()); + + guac_common_ssh_destroy_user(user); + return NULL; + + } + + /* Success */ + guac_client_log(client, GUAC_LOG_INFO, + "Auth public key successfully imported."); + + } + /* If available, get password from settings */ else if (settings->password != NULL) { guac_common_ssh_user_set_password(user, settings->password); @@ -256,7 +283,7 @@ void* ssh_client_thread(void* data) { ssh_client->term = guac_terminal_create(client, options); /* Free options struct now that it's been used */ - free(options); + guac_mem_free(options); /* Fail if terminal init failed */ if (ssh_client->term == NULL) { diff --git a/src/protocols/ssh/ssh_agent.c b/src/protocols/ssh/ssh_agent.c index 199c3b58e..0afbae096 100644 --- a/src/protocols/ssh/ssh_agent.c +++ b/src/protocols/ssh/ssh_agent.c @@ -24,6 +24,7 @@ #include "ssh_buffer.h" #include +#include #include #include #include @@ -189,7 +190,7 @@ void ssh_auth_agent_callback(LIBSSH2_SESSION *session, ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; /* Init auth agent */ - ssh_auth_agent* auth_agent = malloc(sizeof(ssh_auth_agent)); + ssh_auth_agent* auth_agent = guac_mem_alloc(sizeof(ssh_auth_agent)); auth_agent->channel = channel; auth_agent->identity = client_data->key; auth_agent->buffer_length = 0; diff --git a/src/protocols/ssh/user.c b/src/protocols/ssh/user.c index 124884028..eff0d8b5d 100644 --- a/src/protocols/ssh/user.c +++ b/src/protocols/ssh/user.c @@ -73,13 +73,6 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) { } - /* If not owner, synchronize with current display */ - else { - guac_terminal_dup(ssh_client->term, user, user->socket); - guac_ssh_send_current_argv(user, ssh_client); - guac_socket_flush(user->socket); - } - /* Only handle events if not read-only */ if (!settings->read_only) { diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c index 3ea3095ef..0424e323d 100644 --- a/src/protocols/telnet/argv.c +++ b/src/protocols/telnet/argv.c @@ -65,26 +65,34 @@ int guac_telnet_argv_callback(guac_user* user, const char* mimetype, void* guac_telnet_send_current_argv(guac_user* user, void* data) { - guac_telnet_client* telnet_client = (guac_telnet_client*) data; + /* Defer to the batch handler, using the user's socket to send the data */ + guac_telnet_send_current_argv_batch(user->client, user->socket); + + return NULL; + +} + +void guac_telnet_send_current_argv_batch( + guac_client* client, guac_socket* socket) { + + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; guac_terminal* terminal = telnet_client->term; /* Send current color scheme */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_TELNET_ARGV_COLOR_SCHEME, guac_terminal_get_color_scheme(terminal)); /* Send current font name */ - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_TELNET_ARGV_FONT_NAME, guac_terminal_get_font_name(terminal)); /* Send current font size */ char font_size[64]; sprintf(font_size, "%i", guac_terminal_get_font_size(terminal)); - guac_user_stream_argv(user, user->socket, "text/plain", + guac_client_stream_argv(client, socket, "text/plain", GUAC_TELNET_ARGV_FONT_SIZE, font_size); - return NULL; - } diff --git a/src/protocols/telnet/argv.h b/src/protocols/telnet/argv.h index 60c1534f7..2c48c141b 100644 --- a/src/protocols/telnet/argv.h +++ b/src/protocols/telnet/argv.h @@ -69,5 +69,24 @@ guac_argv_callback guac_telnet_argv_callback; */ void* guac_telnet_send_current_argv(guac_user* user, void* data); +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the users associated with the provided + * socket. Note that the users receiving these values will not necessarily be + * able to set new values themselves if their connection is read-only. + * + * @param client + * The client associated with the users that should receive the values of + * all non-sensitive parameters which may be set while the connection is running. + * + * @param socket + * The socket to the arguments to the batch of users along. + * + * @return + * Always NULL. + */ +void guac_telnet_send_current_argv_batch( + guac_client* client, guac_socket* socket); + #endif diff --git a/src/protocols/telnet/client.c b/src/protocols/telnet/client.c index 27edfcdac..ba1f20f48 100644 --- a/src/protocols/telnet/client.c +++ b/src/protocols/telnet/client.c @@ -33,7 +33,35 @@ #include #include +#include #include +#include + +/** + * A pending join handler implementation that will synchronize the connection + * state for all pending users prior to them being promoted to full user. + * + * @param client + * The client whose pending users are about to be promoted. + * + * @return + * Always zero. + */ +static int guac_telnet_join_pending_handler(guac_client* client) { + + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + + /* Synchronize the terminal state to all pending users */ + if (telnet_client->term != NULL) { + guac_socket* broadcast_socket = client->pending_socket; + guac_terminal_sync_users(telnet_client->term, client, broadcast_socket); + guac_telnet_send_current_argv_batch(client, broadcast_socket); + guac_socket_flush(broadcast_socket); + } + + return 0; + +} int guac_client_init(guac_client* client) { @@ -41,7 +69,7 @@ int guac_client_init(guac_client* client) { client->args = GUAC_TELNET_CLIENT_ARGS; /* Allocate client instance data */ - guac_telnet_client* telnet_client = calloc(1, sizeof(guac_telnet_client)); + guac_telnet_client* telnet_client = guac_mem_zalloc(sizeof(guac_telnet_client)); client->data = telnet_client; /* Init telnet client */ @@ -51,6 +79,7 @@ int guac_client_init(guac_client* client) { /* Set handlers */ client->join_handler = guac_telnet_user_join_handler; + client->join_pending_handler = guac_telnet_join_pending_handler; client->free_handler = guac_telnet_client_free_handler; client->leave_handler = guac_telnet_user_leave_handler; @@ -97,7 +126,7 @@ int guac_telnet_client_free_handler(guac_client* client) { if (telnet_client->settings != NULL) guac_telnet_settings_free(telnet_client->settings); - free(telnet_client); + guac_mem_free(telnet_client); return 0; } diff --git a/src/protocols/telnet/input.c b/src/protocols/telnet/input.c index 02ddc1deb..849d1897b 100644 --- a/src/protocols/telnet/input.c +++ b/src/protocols/telnet/input.c @@ -23,6 +23,7 @@ #include "telnet.h" #include +#include #include #include #include @@ -79,7 +80,7 @@ int guac_telnet_user_key_handler(guac_user* user, int keysym, int pressed) { "Stopping password prompt search due to user input."); regfree(settings->password_regex); - free(settings->password_regex); + guac_mem_free(settings->password_regex); settings->password_regex = NULL; } @@ -91,7 +92,7 @@ int guac_telnet_user_key_handler(guac_user* user, int keysym, int pressed) { "Stopping username prompt search due to user input."); regfree(settings->username_regex); - free(settings->username_regex); + guac_mem_free(settings->username_regex); settings->username_regex = NULL; } diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index ddffc8e62..86271c69c 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -24,6 +24,7 @@ #include "settings.h" #include "terminal/terminal.h" +#include #include #include @@ -37,6 +38,7 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "hostname", "port", + "timeout", "username", "username-regex", "password", @@ -81,6 +83,11 @@ enum TELNET_ARGS_IDX { */ IDX_PORT, + /** + * The number of seconds to wait for the remote server to respond. Optional. + */ + IDX_TIMEOUT, + /** * The name of the user to login as. Optional. */ @@ -278,7 +285,7 @@ enum TELNET_ARGS_IDX { /** * Compiles the given regular expression, returning NULL if compilation fails * or of the given regular expression is NULL. The returned regex_t must be - * freed with regfree() AND free(), or with guac_telnet_regex_free(). + * freed with regfree() AND guac_mem_free(), or with guac_telnet_regex_free(). * * @param user * The user who provided the setting associated with the given regex @@ -298,7 +305,7 @@ static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) { return NULL; int compile_result; - regex_t* regex = malloc(sizeof(regex_t)); + regex_t* regex = guac_mem_alloc(sizeof(regex_t)); /* Compile regular expression */ compile_result = regcomp(regex, pattern, @@ -308,7 +315,7 @@ static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) { if (compile_result != 0) { guac_user_log(user, GUAC_LOG_ERROR, "Regular expression '%s' " "could not be compiled.", pattern); - free(regex); + guac_mem_free(regex); return NULL; } @@ -318,7 +325,7 @@ static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) { void guac_telnet_regex_free(regex_t** regex) { if (*regex != NULL) { regfree(*regex); - free(*regex); + guac_mem_free(*regex); *regex = NULL; } } @@ -334,7 +341,7 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, return NULL; } - guac_telnet_settings* settings = calloc(1, sizeof(guac_telnet_settings)); + guac_telnet_settings* settings = guac_mem_zalloc(sizeof(guac_telnet_settings)); /* Read parameters */ settings->hostname = @@ -429,6 +436,11 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_PORT, GUAC_TELNET_DEFAULT_PORT); + /* Read connection timeout */ + settings->timeout = + guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_TIMEOUT, GUAC_TELNET_DEFAULT_TIMEOUT); + /* Read typescript path */ settings->typescript_path = guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, @@ -538,12 +550,12 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, void guac_telnet_settings_free(guac_telnet_settings* settings) { /* Free network connection information */ - free(settings->hostname); - free(settings->port); + guac_mem_free(settings->hostname); + guac_mem_free(settings->port); /* Free credentials */ - free(settings->username); - free(settings->password); + guac_mem_free(settings->username); + guac_mem_free(settings->password); /* Free various regexes */ guac_telnet_regex_free(&settings->username_regex); @@ -552,26 +564,26 @@ void guac_telnet_settings_free(guac_telnet_settings* settings) { guac_telnet_regex_free(&settings->login_failure_regex); /* Free display preferences */ - free(settings->font_name); - free(settings->color_scheme); + guac_mem_free(settings->font_name); + guac_mem_free(settings->color_scheme); /* Free typescript settings */ - free(settings->typescript_name); - free(settings->typescript_path); + guac_mem_free(settings->typescript_name); + guac_mem_free(settings->typescript_path); /* Free screen recording settings */ - free(settings->recording_name); - free(settings->recording_path); + guac_mem_free(settings->recording_name); + guac_mem_free(settings->recording_path); /* Free terminal emulator type. */ - free(settings->terminal_type); + guac_mem_free(settings->terminal_type); /* Free WoL settings. */ - free(settings->wol_mac_addr); - free(settings->wol_broadcast_addr); + guac_mem_free(settings->wol_mac_addr); + guac_mem_free(settings->wol_broadcast_addr); /* Free overall structure */ - free(settings); + guac_mem_free(settings); } diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 3c5ba8b94..02de1cb3c 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -33,6 +33,12 @@ */ #define GUAC_TELNET_DEFAULT_PORT "23" +/** + * The default number of seconds to wait for a successful connection before + * timing out. + */ +#define GUAC_TELNET_DEFAULT_TIMEOUT 10 + /** * The filename to use for the typescript, if not specified. */ @@ -72,6 +78,11 @@ typedef struct guac_telnet_settings { */ char* port; + /** + * The number of seconds to wait for a connection before timing out. + */ + int timeout; + /** * The name of the user to login as, if any. If no username is specified, * this will be NULL. diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 3a4c50496..263d1d193 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -24,6 +24,7 @@ #include "terminal/terminal.h" #include +#include #include #include #include @@ -424,9 +425,29 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) { NI_NUMERICHOST | NI_NUMERICSERV))) guac_client_log(client, GUAC_LOG_DEBUG, "Unable to resolve host: %s", gai_strerror(retval)); - /* Connect */ - if (connect(fd, current_address->ai_addr, - current_address->ai_addrlen) == 0) { + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + + struct timeval timeout_tv; + timeout_tv.tv_sec = settings->timeout; + timeout_tv.tv_usec = 0; + + if (connect(fd, current_address->ai_addr, current_address->ai_addrlen) < 0) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Failed to connect: %s", strerror(errno)); + return NULL; + } + + retval = select(fd + 1, NULL, &fdset, NULL, &timeout_tv); + + if (retval == 0) { + guac_client_log(client, GUAC_LOG_ERROR, "Timeout connecting to " + "host %s, port %s", connected_address, connected_port); + continue; + } + + else if (retval > 0) { guac_client_log(client, GUAC_LOG_DEBUG, "Successfully connected to " "host %s, port %s", connected_address, connected_port); @@ -604,7 +625,7 @@ void* guac_telnet_client_thread(void* data) { telnet_client->term = guac_terminal_create(client, options); /* Free options struct now that it's been used */ - free(options); + guac_mem_free(options); /* Fail if terminal init failed */ if (telnet_client->term == NULL) { diff --git a/src/protocols/telnet/user.c b/src/protocols/telnet/user.c index 2c0e1c613..e9d9df463 100644 --- a/src/protocols/telnet/user.c +++ b/src/protocols/telnet/user.c @@ -70,13 +70,6 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) { } - /* If not owner, synchronize with current display */ - else { - guac_terminal_dup(telnet_client->term, user, user->socket); - guac_telnet_send_current_argv(user, telnet_client); - guac_socket_flush(user->socket); - } - /* Only handle events if not read-only */ if (!settings->read_only) { diff --git a/src/protocols/vnc/argv.c b/src/protocols/vnc/argv.c index e59b1de7a..44fe5ee01 100644 --- a/src/protocols/vnc/argv.c +++ b/src/protocols/vnc/argv.c @@ -21,8 +21,10 @@ #include "argv.h" #include "vnc.h" +#include #include #include +#include #include #include @@ -38,16 +40,16 @@ int guac_vnc_argv_callback(guac_user* user, const char* mimetype, /* Update username */ if (strcmp(name, GUAC_VNC_ARGV_USERNAME) == 0) { - free(settings->username); - settings->username = strdup(value); + guac_mem_free(settings->username); + settings->username = guac_strdup(value); } /* Update password */ else if (strcmp(name, GUAC_VNC_ARGV_PASSWORD) == 0) { - free(settings->password); - settings->password = strdup(value); + guac_mem_free(settings->password); + settings->password = guac_strdup(value); } return 0; -} \ No newline at end of file +} diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 638c14de8..43ead098f 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -34,19 +34,76 @@ #endif #include +#include #include #include #include #include +#ifdef ENABLE_PULSE +/** + * Add the provided user to the provided audio stream. + * + * @param user + * The pending user who should be added to the audio stream. + * + * @param data + * The audio stream that the user should be added to. + * + * @return + * Always NULL. + */ +static void* guac_vnc_sync_pending_user_audio(guac_user* user, void* data) { + + /* Add the user to the stream */ + guac_pa_stream* audio = (guac_pa_stream*) data; + guac_pa_stream_add_user(audio, user); + + return NULL; + +} +#endif + +/** + * A pending join handler implementation that will synchronize the connection + * state for all pending users prior to them being promoted to full user. + * + * @param client + * The client whose pending users are about to be promoted. + * + * @return + * Always zero. + */ +static int guac_vnc_join_pending_handler(guac_client* client) { + + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_socket* broadcast_socket = client->pending_socket; + +#ifdef ENABLE_PULSE + /* Synchronize any audio stream for each pending user */ + if (vnc_client->audio) + guac_client_foreach_pending_user( + client, guac_vnc_sync_pending_user_audio, vnc_client->audio); +#endif + + /* Synchronize with current display */ + if (vnc_client->display != NULL) { + guac_common_display_dup(vnc_client->display, client, broadcast_socket); + guac_socket_flush(broadcast_socket); + } + + return 0; + +} + int guac_client_init(guac_client* client) { /* Set client args */ client->args = GUAC_VNC_CLIENT_ARGS; /* Alloc client data */ - guac_vnc_client* vnc_client = calloc(1, sizeof(guac_vnc_client)); + guac_vnc_client* vnc_client = guac_mem_zalloc(sizeof(guac_vnc_client)); client->data = vnc_client; #ifdef ENABLE_VNC_TLS_LOCKING @@ -59,6 +116,7 @@ int guac_client_init(guac_client* client) { /* Set handlers */ client->join_handler = guac_vnc_user_join_handler; + client->join_pending_handler = guac_vnc_join_pending_handler; client->leave_handler = guac_vnc_user_leave_handler; client->free_handler = guac_vnc_client_free_handler; @@ -152,7 +210,7 @@ int guac_vnc_client_free_handler(guac_client* client) { #endif /* Free generic data struct */ - free(client->data); + guac_mem_free(client->data); return 0; } diff --git a/src/protocols/vnc/cursor.c b/src/protocols/vnc/cursor.c index 22d45ebe4..bd013f559 100644 --- a/src/protocols/vnc/cursor.c +++ b/src/protocols/vnc/cursor.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp) { /* Cairo image buffer */ int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); - unsigned char* buffer = malloc(h*stride); + unsigned char* buffer = guac_mem_alloc(h, stride); unsigned char* buffer_row_current = buffer; /* VNC image buffer */ @@ -120,7 +121,7 @@ void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp) { buffer, w, h, stride); /* Free surface */ - free(buffer); + guac_mem_free(buffer); /* libvncclient does not free rcMask as it does rcSource */ free(client->rcMask); diff --git a/src/protocols/vnc/display.c b/src/protocols/vnc/display.c index 7273e4b31..8848e8933 100644 --- a/src/protocols/vnc/display.c +++ b/src/protocols/vnc/display.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -69,7 +70,7 @@ void guac_vnc_update(rfbClient* client, int x, int y, int w, int h) { /* Init Cairo buffer */ stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); - buffer = malloc(h*stride); + buffer = guac_mem_alloc(h, stride); buffer_row_current = buffer; bpp = client->format.bitsPerPixel/8; @@ -134,7 +135,7 @@ void guac_vnc_update(rfbClient* client, int x, int y, int w, int h) { /* Free surface */ cairo_surface_destroy(surface); - free(buffer); + guac_mem_free(buffer); } diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index a476b2c16..a7171128e 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -24,6 +24,7 @@ #include "common/defaults.h" #include "settings.h" +#include #include #include @@ -395,7 +396,7 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, return NULL; } - guac_vnc_settings* settings = calloc(1, sizeof(guac_vnc_settings)); + guac_vnc_settings* settings = guac_mem_zalloc(sizeof(guac_vnc_settings)); settings->hostname = guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, @@ -646,43 +647,43 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, void guac_vnc_settings_free(guac_vnc_settings* settings) { /* Free settings strings */ - free(settings->clipboard_encoding); - free(settings->encodings); - free(settings->hostname); - free(settings->password); - free(settings->recording_name); - free(settings->recording_path); - free(settings->username); + guac_mem_free(settings->clipboard_encoding); + guac_mem_free(settings->encodings); + guac_mem_free(settings->hostname); + guac_mem_free(settings->password); + guac_mem_free(settings->recording_name); + guac_mem_free(settings->recording_path); + guac_mem_free(settings->username); #ifdef ENABLE_VNC_REPEATER /* Free VNC repeater settings */ - free(settings->dest_host); + guac_mem_free(settings->dest_host); #endif #ifdef ENABLE_COMMON_SSH /* Free SFTP settings */ - free(settings->sftp_directory); - free(settings->sftp_root_directory); - free(settings->sftp_host_key); - free(settings->sftp_hostname); - free(settings->sftp_passphrase); - free(settings->sftp_password); - free(settings->sftp_port); - free(settings->sftp_private_key); - free(settings->sftp_username); + guac_mem_free(settings->sftp_directory); + guac_mem_free(settings->sftp_root_directory); + guac_mem_free(settings->sftp_host_key); + guac_mem_free(settings->sftp_hostname); + guac_mem_free(settings->sftp_passphrase); + guac_mem_free(settings->sftp_password); + guac_mem_free(settings->sftp_port); + guac_mem_free(settings->sftp_private_key); + guac_mem_free(settings->sftp_username); #endif #ifdef ENABLE_PULSE /* Free PulseAudio settings */ - free(settings->pa_servername); + guac_mem_free(settings->pa_servername); #endif /* Free Wake-on-LAN strings */ - free(settings->wol_mac_addr); - free(settings->wol_broadcast_addr); + guac_mem_free(settings->wol_mac_addr); + guac_mem_free(settings->wol_broadcast_addr); /* Free settings structure */ - free(settings); + guac_mem_free(settings); } diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index 4e6f473c8..74204f624 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -74,21 +74,6 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { } - /* If not owner, synchronize with current state */ - else { - -#ifdef ENABLE_PULSE - /* Synchronize an audio stream */ - if (vnc_client->audio) - guac_pa_stream_add_user(vnc_client->audio, user); -#endif - - /* Synchronize with current display */ - guac_common_display_dup(vnc_client->display, user, user->socket); - guac_socket_flush(user->socket); - - } - /* Only handle events if not read-only */ if (!settings->read_only) { diff --git a/src/pulse/pulse.c b/src/pulse/pulse.c index 2f30b1af1..48d792698 100644 --- a/src/pulse/pulse.c +++ b/src/pulse/pulse.c @@ -22,6 +22,7 @@ #include "pulse/pulse.h" #include +#include #include #include #include @@ -312,7 +313,7 @@ guac_pa_stream* guac_pa_stream_alloc(guac_client* client, return NULL; /* Init main loop */ - guac_pa_stream* stream = malloc(sizeof(guac_pa_stream)); + guac_pa_stream* stream = guac_mem_alloc(sizeof(guac_pa_stream)); stream->client = client; stream->audio = audio; stream->pa_mainloop = pa_threaded_mainloop_new(); @@ -347,7 +348,7 @@ void guac_pa_stream_free(guac_pa_stream* stream) { /* Stream now ended */ guac_client_log(stream->client, GUAC_LOG_INFO, "Audio stream finished"); - free(stream); + guac_mem_free(stream); } diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 38baedbad..f503b4c23 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -77,7 +77,7 @@ libguac_terminal_la_LIBADD = \ @LIBGUAC_LTLIB@ libguac_terminal_la_LDFLAGS = \ - -version-info 0:0:0 \ + -version-info 1:0:1 \ -no-undefined \ @CAIRO_LIBS@ \ @MATH_LIBS@ \ diff --git a/src/terminal/buffer.c b/src/terminal/buffer.c index e8ad4b3c2..a8cc87ce7 100644 --- a/src/terminal/buffer.c +++ b/src/terminal/buffer.c @@ -21,6 +21,8 @@ #include "terminal/buffer.h" #include "terminal/common.h" +#include + #include #include @@ -28,7 +30,7 @@ guac_terminal_buffer* guac_terminal_buffer_alloc(int rows, guac_terminal_char* d /* Allocate scrollback */ guac_terminal_buffer* buffer = - malloc(sizeof(guac_terminal_buffer)); + guac_mem_alloc(sizeof(guac_terminal_buffer)); int i; guac_terminal_buffer_row* row; @@ -38,8 +40,7 @@ guac_terminal_buffer* guac_terminal_buffer_alloc(int rows, guac_terminal_char* d buffer->available = rows; buffer->top = 0; buffer->length = 0; - buffer->rows = malloc(sizeof(guac_terminal_buffer_row) * - buffer->available); + buffer->rows = guac_mem_alloc(sizeof(guac_terminal_buffer_row), buffer->available); /* Init scrollback rows */ row = buffer->rows; @@ -48,7 +49,7 @@ guac_terminal_buffer* guac_terminal_buffer_alloc(int rows, guac_terminal_char* d /* Allocate row */ row->available = 256; row->length = 0; - row->characters = malloc(sizeof(guac_terminal_char) * row->available); + row->characters = guac_mem_alloc(sizeof(guac_terminal_char), row->available); /* Next row */ row++; @@ -66,13 +67,13 @@ void guac_terminal_buffer_free(guac_terminal_buffer* buffer) { /* Free all rows */ for (i=0; iavailable; i++) { - free(row->characters); + guac_mem_free(row->characters); row++; } /* Free actual buffer */ - free(buffer->rows); - free(buffer); + guac_mem_free(buffer->rows); + guac_mem_free(buffer); } @@ -95,8 +96,9 @@ guac_terminal_buffer_row* guac_terminal_buffer_get_row(guac_terminal_buffer* buf /* Expand if necessary */ if (width > buffer_row->available) { - buffer_row->available = width*2; - buffer_row->characters = realloc(buffer_row->characters, sizeof(guac_terminal_char) * buffer_row->available); + buffer_row->available = guac_mem_ckd_mul_or_die(width, 2); + buffer_row->characters = guac_mem_realloc_or_die(buffer_row->characters, + sizeof(guac_terminal_char), buffer_row->available); } /* Initialize new part of row */ diff --git a/src/terminal/display.c b/src/terminal/display.c index d4493252e..4a4ade897 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -221,7 +222,7 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, guac_terminal_color (*palette)[256]) { /* Allocate display */ - guac_terminal_display* display = malloc(sizeof(guac_terminal_display)); + guac_terminal_display* display = guac_mem_alloc(sizeof(guac_terminal_display)); display->client = client; /* Initially no font loaded */ @@ -265,7 +266,7 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, if (guac_terminal_display_set_font(display, font_name, font_size, dpi)) { guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to set initial font \"%s\"", font_name); - free(display); + guac_mem_free(display); return NULL; } @@ -279,13 +280,13 @@ void guac_terminal_display_free(guac_terminal_display* display) { pango_font_description_free(display->font_desc); /* Free default palette. */ - free(display->default_palette); + guac_mem_free(display->default_palette); /* Free operations buffers */ - free(display->operations); + guac_mem_free(display->operations); /* Free display */ - free(display); + guac_mem_free(display); } @@ -486,10 +487,10 @@ void guac_terminal_display_resize(guac_terminal_display* display, int width, int /* Free old operations buffer */ if (display->operations != NULL) - free(display->operations); + guac_mem_free(display->operations); /* Alloc operations */ - display->operations = malloc(width * height * + display->operations = guac_mem_alloc(width, height, sizeof(guac_terminal_operation)); /* Init each operation buffer row */ @@ -842,11 +843,11 @@ void guac_terminal_display_flush(guac_terminal_display* display) { } -void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user, - guac_socket* socket) { +void guac_terminal_display_dup( + guac_terminal_display* display, guac_client* client, guac_socket* socket) { /* Create default surface */ - guac_common_surface_dup(display->display_surface, user, socket); + guac_common_surface_dup(display->display_surface, client, socket); /* Select layer is a child of the display layer */ guac_protocol_send_move(socket, display->select_layer, @@ -1051,4 +1052,4 @@ int guac_terminal_display_set_font(guac_terminal_display* display, return 0; -} \ No newline at end of file +} diff --git a/src/terminal/scrollbar.c b/src/terminal/scrollbar.c index a0eec8ec0..9a89474b2 100644 --- a/src/terminal/scrollbar.c +++ b/src/terminal/scrollbar.c @@ -21,8 +21,10 @@ #include #include +#include #include #include +#include #include @@ -31,7 +33,7 @@ guac_terminal_scrollbar* guac_terminal_scrollbar_alloc(guac_client* client, /* Allocate scrollbar */ guac_terminal_scrollbar* scrollbar = - malloc(sizeof(guac_terminal_scrollbar)); + guac_mem_alloc(sizeof(guac_terminal_scrollbar)); /* Associate client */ scrollbar->client = client; @@ -81,7 +83,7 @@ void guac_terminal_scrollbar_free(guac_terminal_scrollbar* scrollbar) { guac_client_free_layer(scrollbar->client, scrollbar->container); /* Free scrollbar */ - free(scrollbar); + guac_mem_free(scrollbar); } @@ -332,7 +334,7 @@ static void calculate_state(guac_terminal_scrollbar* scrollbar, } void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar, - guac_user* user, guac_socket* socket) { + guac_client* client, guac_socket* socket) { /* Get old state */ guac_terminal_scrollbar_render_state* state = &scrollbar->render_state; diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 9d9c418a9..539d1e944 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -46,8 +46,10 @@ #include #include +#include #include #include +#include #include /** @@ -318,7 +320,7 @@ void* guac_terminal_thread(void* data) { guac_terminal_options* guac_terminal_options_create( int width, int height, int dpi) { - guac_terminal_options* options = malloc(sizeof(guac_terminal_options)); + guac_terminal_options* options = guac_mem_alloc(sizeof(guac_terminal_options)); /* Set all required parameters */ options->width = width; @@ -448,22 +450,22 @@ guac_terminal* guac_terminal_create(guac_client* client, /* Initialized by guac_terminal_parse_color_scheme. */ guac_terminal_color (*default_palette)[256] = (guac_terminal_color(*)[256]) - malloc(sizeof(guac_terminal_color[256])); + guac_mem_alloc(sizeof(guac_terminal_color[256])); guac_terminal_parse_color_scheme(client, options->color_scheme, &default_char.attributes.foreground, &default_char.attributes.background, default_palette); - guac_terminal* term = malloc(sizeof(guac_terminal)); + guac_terminal* term = guac_mem_alloc(sizeof(guac_terminal)); term->started = false; term->client = client; term->upload_path_handler = NULL; term->file_download_handler = NULL; /* Copy initially-provided color scheme and font details */ - term->color_scheme = strdup(options->color_scheme); - term->font_name = strdup(options->font_name); + term->color_scheme = guac_strdup(options->color_scheme); + term->font_name = guac_strdup(options->font_name); term->font_size = options->font_size; /* Init modified flag and conditional */ @@ -495,7 +497,7 @@ guac_terminal* guac_terminal_create(guac_client* client, /* Fail if display init failed */ if (term->display == NULL) { guac_client_log(client, GUAC_LOG_DEBUG, "Display initialization failed"); - free(term); + guac_mem_free(term); return NULL; } @@ -534,7 +536,7 @@ guac_terminal* guac_terminal_create(guac_client* client, if (pipe(term->stdin_pipe_fd)) { guac_error = GUAC_STATUS_SEE_ERRNO; guac_error_message = "Unable to open pipe for STDIN"; - free(term); + guac_mem_free(term); return NULL; } @@ -572,6 +574,7 @@ guac_terminal* guac_terminal_create(guac_client* client, /* All keyboard modifiers are released */ term->mod_alt = term->mod_ctrl = + term->mod_meta = term->mod_shift = 0; /* Initialize mouse cursor */ @@ -634,14 +637,14 @@ void guac_terminal_free(guac_terminal* term) { guac_terminal_scrollbar_free(term->scrollbar); /* Free copies of font and color scheme information */ - free((char*) term->color_scheme); - free((char*) term->font_name); + guac_mem_free_const(term->color_scheme); + guac_mem_free_const(term->font_name); /* Free clipboard */ guac_common_clipboard_free(term->clipboard); /* Free the terminal itself */ - free(term); + guac_mem_free(term); } @@ -864,7 +867,7 @@ char* guac_terminal_prompt(guac_terminal* terminal, const char* title, buffer[pos] = 0; /* Return newly-allocated string containing result */ - return strdup(buffer); + return guac_strdup(buffer); } @@ -1574,20 +1577,31 @@ static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed } /* Track modifiers */ - if (keysym == 0xFFE3) + if (keysym == 0xFFE3 || keysym == 0xFFE4) term->mod_ctrl = pressed; - else if (keysym == 0xFFE9) + else if (keysym == 0xFFE7 || keysym == 0xFFE8) + term->mod_meta = pressed; + else if (keysym == 0xFFE9 || keysym == 0xFFEA) term->mod_alt = pressed; - else if (keysym == 0xFFE1) + else if (keysym == 0xFFE1 || keysym == 0xFFE2) term->mod_shift = pressed; /* If key pressed */ else if (pressed) { - /* Ctrl+Shift+V shortcut for paste */ - if (keysym == 'V' && term->mod_ctrl) + /* Ctrl+Shift+V or Cmd+v (mac style) shortcuts for paste */ + if ((keysym == 'V' && term->mod_ctrl) || (keysym == 'v' && term->mod_meta)) return guac_terminal_send_data(term, term->clipboard->buffer, term->clipboard->length); + /* + * Ctrl+Shift+C and Cmd+c shortcuts for copying are not handled, as + * selecting text in the terminal automatically copies it. To avoid + * attempts to use these shortcuts causing unexpected results in the + * terminal, these are just ignored. + */ + if ((keysym == 'C' && term->mod_ctrl) || (keysym == 'c' && term->mod_meta)) + return 0; + /* Shift+PgUp / Shift+PgDown shortcuts for scrolling */ if (term->mod_shift) { @@ -2041,18 +2055,47 @@ int guac_terminal_create_typescript(guac_terminal* term, const char* path, } -void guac_terminal_dup(guac_terminal* term, guac_user* user, - guac_socket* socket) { +/** + * Synchronize the state of the provided terminal to a subset of users of + * the provided guac_client using the provided socket. + * + * @param client + * The client whose users should be synchronized. + * + * @param term + * The terminal state that should be synchronized to the users. + * + * @param socket + * The socket that should be used to communicate with the users. + */ +static void __guac_terminal_sync_socket( + guac_client* client, guac_terminal* term, guac_socket* socket) { /* Synchronize display state with new user */ guac_terminal_repaint_default_layer(term, socket); - guac_terminal_display_dup(term->display, user, socket); + guac_terminal_display_dup(term->display, client, socket); /* Synchronize mouse cursor */ - guac_common_cursor_dup(term->cursor, user, socket); + guac_common_cursor_dup(term->cursor, client, socket); + + /* Paint scrollbar for joining users */ + guac_terminal_scrollbar_dup(term->scrollbar, client, socket); + +} + +void guac_terminal_dup(guac_terminal* term, guac_user* user, + guac_socket* socket) { + + /* Ignore the user and just use the provided socket directly */ + __guac_terminal_sync_socket(user->client, term, socket); + +} + +void guac_terminal_sync_users( + guac_terminal* term, guac_client* client, guac_socket* socket) { - /* Paint scrollbar for joining user */ - guac_terminal_scrollbar_dup(term->scrollbar, user, socket); + /* Use the provided socket to synchronize state to the users */ + __guac_terminal_sync_socket(client, term, socket); } @@ -2084,8 +2127,8 @@ void guac_terminal_apply_color_scheme(guac_terminal* terminal, guac_terminal_lock(terminal); /* Update stored copy of color scheme */ - free((char*) terminal->color_scheme); - terminal->color_scheme = strdup(color_scheme); + guac_mem_free_const(terminal->color_scheme); + terminal->color_scheme = guac_strdup(color_scheme); /* Release terminal */ guac_terminal_unlock(terminal); @@ -2123,7 +2166,7 @@ void guac_terminal_apply_font(guac_terminal* terminal, const char* font_name, /* Update stored copy of font name, if changed */ if (font_name != NULL) - terminal->font_name = strdup(font_name); + terminal->font_name = guac_strdup(font_name); /* Update stored copy of font size, if changed */ if (font_size != -1) diff --git a/src/terminal/terminal/display.h b/src/terminal/terminal/display.h index 1d746211f..afa92ad4d 100644 --- a/src/terminal/terminal/display.h +++ b/src/terminal/terminal/display.h @@ -324,21 +324,23 @@ void guac_terminal_display_resize(guac_terminal_display* display, int width, int void guac_terminal_display_flush(guac_terminal_display* display); /** - * Initializes and syncs the current terminal display state for the given user - * that has just joined the connection, sending the necessary instructions to - * completely recreate and redraw the terminal rendering over the given socket. + * Initializes and syncs the current terminal display state for all joining + * users associated with the provided socket, sending the necessary instructions + * to completely recreate and redraw the terminal rendering over the given + * socket. * * @param display - * The terminal display to sync to the given user. + * The terminal display to sync to the users associated with the provided + * socket. * - * @param user - * The user that has just joined the connection. + * @param client + * The client whose users are joining. * * @param socket * The socket over which any necessary instructions should be sent. */ -void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user, - guac_socket* socket); +void guac_terminal_display_dup( + guac_terminal_display* display, guac_client* client, guac_socket* socket); /** * Draws the text selection rectangle from the given coordinates to the given end coordinates. diff --git a/src/terminal/terminal/scrollbar.h b/src/terminal/terminal/scrollbar.h index c30833779..b33f57d43 100644 --- a/src/terminal/terminal/scrollbar.h +++ b/src/terminal/terminal/scrollbar.h @@ -255,22 +255,22 @@ void guac_terminal_scrollbar_free(guac_terminal_scrollbar* scrollbar); void guac_terminal_scrollbar_flush(guac_terminal_scrollbar* scrollbar); /** - * Forces a complete redraw / resync of scrollbar state for the given user that - * has just joined the connection, sending the necessary instructions to + * Forces a complete redraw / resync of scrollbar state for all joinging users + * associated with the provided socket, sending the necessary instructions to * completely recreate and redraw the scrollbar rendering over the given * socket. * * @param scrollbar - * The scrollbar to sync to the given user. + * The scrollbar to sync to the given users. * - * @param user - * The user that has just joined the connection. + * @param client + * The client associated with the joining users. * * @param socket * The socket over which any necessary instructions should be sent. */ void guac_terminal_scrollbar_dup(guac_terminal_scrollbar* scrollbar, - guac_user* user, guac_socket* socket); + guac_client* client, guac_socket* socket); /** * Sets the minimum and maximum allowed scroll values of the given scrollbar diff --git a/src/terminal/terminal/terminal-priv.h b/src/terminal/terminal/terminal-priv.h index 24cbfec95..2bd092bd0 100644 --- a/src/terminal/terminal/terminal-priv.h +++ b/src/terminal/terminal/terminal-priv.h @@ -406,6 +406,11 @@ struct guac_terminal { */ int mod_ctrl; + /** + * Whether the meta (command on Mac) key is currently being held down. + */ + int mod_meta; + /** * Whether the shift key is currently being held down. */ diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 082a3d23b..cb3ed2488 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -413,7 +413,7 @@ void guac_terminal_notify(guac_terminal* terminal); * @return * A newly-allocated string containing a single line of input read from the * provided terminal's STDIN. This string must eventually be manually - * freed with a call to free(). + * freed with a call to guac_mem_free(). */ char* guac_terminal_prompt(guac_terminal* terminal, const char* title, bool echo); @@ -619,6 +619,9 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...); * connection. All instructions necessary to replicate state are sent over the * given socket. * + * @deprecated The guac_terminal_sync_users method should be used when + * duplicating display state to a set of users. + * * @param term * The terminal emulator associated with the connection being joined. * @@ -632,6 +635,24 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...); void guac_terminal_dup(guac_terminal* term, guac_user* user, guac_socket* socket); +/** + * Replicates the current display state to one or more users that are joining + * the connection. All instructions necessary to replicate state are sent over + * the given socket. The set of users receiving these instructions is + * determined solely by the socket chosen. + * + * @param term + * The terminal whose state should be synchronized to the users. + * + * @param client + * The client associated with the users to be synchronized. + * + * @param socket + * The socket to which the terminal state will be broadcast. + */ +void guac_terminal_sync_users( + guac_terminal* term, guac_client* client, guac_socket* socket); + /** * Resize the client display and terminal to the given pixel dimensions. * diff --git a/src/terminal/typescript.c b/src/terminal/typescript.c index 180547c60..abc8486f0 100644 --- a/src/terminal/typescript.c +++ b/src/terminal/typescript.c @@ -20,6 +20,7 @@ #include "common/io.h" #include "terminal/typescript.h" +#include #include #include @@ -117,7 +118,7 @@ guac_terminal_typescript* guac_terminal_typescript_alloc(const char* path, /* Allocate space for new typescript */ guac_terminal_typescript* typescript = - malloc(sizeof(guac_terminal_typescript)); + guac_mem_alloc(sizeof(guac_terminal_typescript)); /* Attempt to open typescript data file */ typescript->data_fd = guac_terminal_typescript_open_data_file( @@ -125,7 +126,7 @@ guac_terminal_typescript* guac_terminal_typescript_alloc(const char* path, sizeof(typescript->data_filename) - sizeof(GUAC_TERMINAL_TYPESCRIPT_TIMING_SUFFIX)); if (typescript->data_fd == -1) { - free(typescript); + guac_mem_free(typescript); return NULL; } @@ -134,7 +135,7 @@ guac_terminal_typescript* guac_terminal_typescript_alloc(const char* path, "%s.%s", typescript->data_filename, GUAC_TERMINAL_TYPESCRIPT_TIMING_SUFFIX) >= sizeof(typescript->timing_filename)) { close(typescript->data_fd); - free(typescript); + guac_mem_free(typescript); return NULL; } @@ -144,7 +145,7 @@ guac_terminal_typescript* guac_terminal_typescript_alloc(const char* path, S_IRUSR | S_IWUSR | S_IRGRP); if (typescript->timing_fd == -1) { close(typescript->data_fd); - free(typescript); + guac_mem_free(typescript); return NULL; } @@ -228,7 +229,7 @@ void guac_terminal_typescript_free(guac_terminal_typescript* typescript) { close(typescript->timing_fd); /* Free allocated typescript data */ - free(typescript); + guac_mem_free(typescript); }