/** * Handles a user's entire connection and socket lifecycle. * * @param data * A pointer to a guacd_user_thread_params structure describing the user's * associated file descriptor, whether that user is the connection owner * (the first person to join), as well as the process associated with the * connection being joined. * * @return * Always NULL. */ static void* guacd_user_thread(void* data) { guacd_user_thread_params* params = (guacd_user_thread_params*) data; guacd_proc* proc = params->proc; guac_client* client = proc->client; /* Get guac_socket for user's file descriptor */ guac_socket* socket = guac_socket_open(params->fd); if (socket == NULL) return NULL; /* Create skeleton user */ guac_user* user = guac_user_alloc(); user->socket = socket; user->client = client; user->owner = params->owner; /* Handle user connection from handshake until disconnect/completion */ guac_user_handle_connection(user, GUACD_USEC_TIMEOUT); /* Stop client and prevent future users if all users are disconnected */ if (client->connected_users == 0) { guacd_log(GUAC_LOG_INFO, "Last user of connection \"%s\" disconnected", client->connection_id); guacd_proc_stop(proc); } /* Clean up */ guac_socket_free(socket); guac_user_free(user); free(params); return NULL; }
guac_socket* guac_socket_open_secure(SSL_CTX* context, int fd) { /* Create new SSL structure */ SSL* ssl = SSL_new(context); if (ssl == NULL) return NULL; /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); guac_socket_ssl_data* data = malloc(sizeof(guac_socket_ssl_data)); /* Init SSL */ data->context = context; data->ssl = ssl; SSL_set_fd(data->ssl, fd); /* Accept SSL connection, handle errors */ if (SSL_accept(ssl) <= 0) { guac_error = GUAC_STATUS_INTERNAL_ERROR; guac_error_message = "SSL accept failed"; free(data); guac_socket_free(socket); SSL_free(ssl); return NULL; } /* Store file descriptor as socket data */ data->fd = fd; socket->data = data; /* Set read/write handlers */ socket->read_handler = __guac_socket_ssl_read_handler; socket->write_handler = __guac_socket_ssl_write_handler; socket->select_handler = __guac_socket_ssl_select_handler; socket->free_handler = __guac_socket_ssl_free_handler; return socket; }
void guac_client_free(guac_client* client) { /* Remove all users */ while (client->__users != NULL) guac_client_remove_user(client, client->__users); if (client->free_handler) { /* FIXME: Errors currently ignored... */ client->free_handler(client); } /* Free socket */ guac_socket_free(client->socket); /* Free layer pools */ guac_pool_free(client->__buffer_pool); guac_pool_free(client->__layer_pool); /* Free streams */ free(client->__output_streams); /* Free stream pool */ guac_pool_free(client->__stream_pool); /* Close associated plugin */ if (client->__plugin_handle != NULL) { if (dlclose(client->__plugin_handle)) 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); }
/** * Creates a new guac_client for the connection on the given socket, adding * it to the client map based on its ID. */ static void guacd_handle_connection(guacd_client_map* map, guac_socket* socket) { guac_client* client; guac_client_plugin* plugin; guac_instruction* select; guac_instruction* size; guac_instruction* audio; guac_instruction* video; guac_instruction* connect; int init_result; /* Reset guac_error */ guac_error = GUAC_STATUS_SUCCESS; guac_error_message = NULL; /* Get protocol from select instruction */ select = guac_instruction_expect( socket, GUACD_USEC_TIMEOUT, "select"); if (select == NULL) { /* Log error */ guacd_log_guac_error("Error reading \"select\""); /* Free resources */ guac_socket_free(socket); return; } /* Validate args to select */ if (select->argc != 1) { /* Log error */ guacd_log_error("Bad number of arguments to \"select\" (%i)", select->argc); /* Free resources */ guac_socket_free(socket); return; } guacd_log_info("Protocol \"%s\" selected", select->argv[0]); /* Get plugin from protocol in select */ plugin = guac_client_plugin_open(select->argv[0]); guac_instruction_free(select); if (plugin == NULL) { /* Log error */ guacd_log_guac_error("Error loading client plugin"); /* Free resources */ guac_socket_free(socket); return; } /* Send args response */ if (guac_protocol_send_args(socket, plugin->args) || guac_socket_flush(socket)) { /* Log error */ guacd_log_guac_error("Error sending \"args\""); if (guac_client_plugin_close(plugin)) guacd_log_guac_error("Error closing client plugin"); guac_socket_free(socket); return; } /* Get optimal screen size */ size = guac_instruction_expect( socket, GUACD_USEC_TIMEOUT, "size"); if (size == NULL) { /* Log error */ guacd_log_guac_error("Error reading \"size\""); /* Free resources */ guac_socket_free(socket); return; } /* Get supported audio formats */ audio = guac_instruction_expect( socket, GUACD_USEC_TIMEOUT, "audio"); if (audio == NULL) { /* Log error */ guacd_log_guac_error("Error reading \"audio\""); /* Free resources */ guac_socket_free(socket); return; } /* Get supported video formats */ video = guac_instruction_expect( socket, GUACD_USEC_TIMEOUT, "video"); if (video == NULL) { /* Log error */ guacd_log_guac_error("Error reading \"video\""); /* Free resources */ guac_socket_free(socket); return; } /* Get args from connect instruction */ connect = guac_instruction_expect( socket, GUACD_USEC_TIMEOUT, "connect"); if (connect == NULL) { /* Log error */ guacd_log_guac_error("Error reading \"connect\""); if (guac_client_plugin_close(plugin)) guacd_log_guac_error("Error closing client plugin"); guac_socket_free(socket); return; } /* Get client */ client = guac_client_alloc(); if (client == NULL) { guacd_log_guac_error("Client could not be allocated"); guac_socket_free(socket); return; } client->socket = socket; client->log_info_handler = guacd_client_log_info; client->log_error_handler = guacd_client_log_error; /* Parse optimal screen dimensions from size instruction */ client->info.optimal_width = atoi(size->argv[0]); client->info.optimal_height = atoi(size->argv[1]); /* If DPI given, set the client resolution */ if (size->argc >= 3) client->info.optimal_resolution = atoi(size->argv[2]); /* Otherwise, use a safe default for rough backwards compatibility */ else client->info.optimal_resolution = 96; /* Store audio mimetypes */ client->info.audio_mimetypes = malloc(sizeof(char*) * (audio->argc+1)); memcpy(client->info.audio_mimetypes, audio->argv, sizeof(char*) * audio->argc); client->info.audio_mimetypes[audio->argc] = NULL; /* Store video mimetypes */ client->info.video_mimetypes = malloc(sizeof(char*) * (video->argc+1)); memcpy(client->info.video_mimetypes, video->argv, sizeof(char*) * video->argc); client->info.video_mimetypes[video->argc] = NULL; /* Store client */ if (guacd_client_map_add(map, client)) guacd_log_error("Unable to add client. Internal client storage has failed"); /* Send connection ID */ guacd_log_info("Connection ID is \"%s\"", client->connection_id); guac_protocol_send_ready(socket, client->connection_id); /* Init client */ init_result = guac_client_plugin_init_client(plugin, client, connect->argc, connect->argv); guac_instruction_free(connect); /* If client could not be started, free everything and fail */ if (init_result) { guac_client_free(client); guacd_log_guac_error("Error instantiating client"); if (guac_client_plugin_close(plugin)) guacd_log_guac_error("Error closing client plugin"); guac_socket_free(socket); return; } /* Start client threads */ guacd_log_info("Starting client"); if (guacd_client_start(client)) guacd_log_error("Client finished abnormally"); else guacd_log_info("Client finished normally"); /* Remove client */ if (guacd_client_map_remove(map, client->connection_id) == NULL) guacd_log_error("Unable to remove client. Internal client storage has failed"); /* Free mimetype lists */ free(client->info.audio_mimetypes); free(client->info.video_mimetypes); /* Free remaining instructions */ guac_instruction_free(audio); guac_instruction_free(video); guac_instruction_free(size); /* Clean up */ guac_client_free(client); if (guac_client_plugin_close(plugin)) guacd_log_error("Error closing client plugin"); /* Close socket */ guac_socket_free(socket); }
int guaclog_interpret(const char* path, const char* out_path, bool force) { /* Open input file */ int fd = open(path, O_RDONLY); if (fd < 0) { guaclog_log(GUAC_LOG_ERROR, "%s: %s", path, strerror(errno)); return 1; } /* Lock entire input file for reading by the current process */ struct flock file_lock = { .l_type = F_RDLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, .l_pid = getpid() }; /* Abort if file cannot be locked for reading */ if (!force && fcntl(fd, F_SETLK, &file_lock) == -1) { /* Warn if lock cannot be acquired */ if (errno == EACCES || errno == EAGAIN) guaclog_log(GUAC_LOG_WARNING, "Refusing to interpret log of " "in-progress session \"%s\" (specify the -f option to " "override this behavior).", path); /* Log an error if locking fails in an unexpected way */ else guaclog_log(GUAC_LOG_ERROR, "Cannot lock \"%s\" for reading: %s", path, strerror(errno)); close(fd); return 1; } /* Allocate input state for interpreting process */ guaclog_state* state = guaclog_state_alloc(out_path); if (state == NULL) { close(fd); return 1; } /* Obtain guac_socket wrapping file descriptor */ guac_socket* socket = guac_socket_open(fd); if (socket == NULL) { guaclog_log(GUAC_LOG_ERROR, "%s: %s", path, guac_status_string(guac_error)); close(fd); guaclog_state_free(state); return 1; } guaclog_log(GUAC_LOG_INFO, "Writing input events from \"%s\" " "to \"%s\" ...", path, out_path); /* Attempt to read all instructions in the file */ if (guaclog_read_instructions(state, path, socket)) { guac_socket_free(socket); guaclog_state_free(state); return 1; } /* Close input and finish interpreting process */ guac_socket_free(socket); return guaclog_state_free(state); }
/** * Attempts to open a new recording within the given path and having the given * name. If such a file already exists, sequential numeric suffixes (.1, .2, * .3, etc.) are appended until a filename is found which does not exist (or * until the maximum number of numeric suffixes has been tried). If the file * absolutely cannot be opened due to an error, -1 is returned and errno is set * appropriately. * * @param path * The full path to the directory in which the data file should be created. * * @param name * The name of the data file which should be crated within the given path. * * @param basename * A buffer in which the path, a path separator, the filename, any * necessary suffix, and a NULL terminator will be stored. If insufficient * space is available, -1 will be returned, and errno will be set to * ENAMETOOLONG. * * @param basename_size * The number of bytes available within the provided basename buffer. * * @return * The file descriptor of the open data file if open succeeded, or -1 on * failure. */ static int guac_common_recording_open(const char* path, const char* name, char* basename, int basename_size) { int i; /* Concatenate path and name (separated by a single slash) */ int basename_length = snprintf(basename, basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH, "%s/%s", path, name); /* Abort if maximum length reached */ if (basename_length == basename_size - GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH) { errno = ENAMETOOLONG; return -1; } /* Attempt to open recording */ int fd = open(basename, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); /* Continuously retry with alternate names on failure */ if (fd == -1) { /* Prepare basename for additional suffix */ basename[basename_length] = '.'; char* suffix = &(basename[basename_length + 1]); /* Continue retrying alternative suffixes if file already exists */ for (i = 1; fd == -1 && errno == EEXIST && i <= GUAC_COMMON_RECORDING_MAX_SUFFIX; i++) { /* Append new suffix */ sprintf(suffix, "%i", i); /* Retry with newly-suffixed filename */ fd = open(basename, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); } /* Abort if we've run out of filenames */ if (fd == -1) return -1; } /* end if open succeeded */ /* Explicit file locks are required only on POSIX platforms */ #ifndef __MINGW32__ /* Lock entire output file for writing by the current process */ struct flock file_lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, .l_pid = getpid() }; /* Abort if file cannot be locked for reading */ if (fcntl(fd, F_SETLK, &file_lock) == -1) { close(fd); return -1; } #endif return fd; } guac_common_recording* guac_common_recording_create(guac_client* client, const char* path, const char* name, int create_path, int include_output, int include_mouse, int include_keys) { char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; /* Create path if it does not exist, fail if impossible */ #ifndef __MINGW32__ if (create_path && mkdir(path, S_IRWXU) && errno != EEXIST) { #else if (create_path && _mkdir(path) && errno != EEXIST) { #endif guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording failed: %s", strerror(errno)); return NULL; } /* Attempt to open recording file */ int fd = guac_common_recording_open(path, name, filename, sizeof(filename)); if (fd == -1) { guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording failed: %s", strerror(errno)); return NULL; } /* Create recording structure with reference to underlying socket */ guac_common_recording* recording = malloc(sizeof(guac_common_recording)); recording->socket = guac_socket_open(fd); recording->include_output = include_output; recording->include_mouse = include_mouse; recording->include_keys = include_keys; /* Replace client socket with wrapped recording socket only if including * output within the recording */ if (include_output) client->socket = guac_socket_tee(client->socket, recording->socket); /* Recording creation succeeded */ guac_client_log(client, GUAC_LOG_INFO, "Recording of session will be saved to \"%s\".", filename); return recording; } void guac_common_recording_free(guac_common_recording* recording) { /* If not including broadcast output, the output socket is not associated * with the client, and must be freed manually */ if (!recording->include_output) guac_socket_free(recording->socket); /* Free recording itself */ free(recording); } void guac_common_recording_report_mouse(guac_common_recording* recording, int x, int y, int button_mask) { /* Report mouse location only if recording should contain mouse events */ if (recording->include_mouse) guac_protocol_send_mouse(recording->socket, x, y, button_mask, guac_timestamp_current()); } void guac_common_recording_report_key(guac_common_recording* recording, int keysym, int pressed) { /* Report key state only if recording should contain key events */ if (recording->include_keys) guac_protocol_send_key(recording->socket, keysym, pressed, guac_timestamp_current()); }
void test_instruction_read() { int rfd, wfd; int fd[2], childpid; char test_string[] = "4.test,6.a" UTF8_4 "b," "5.12345,10.a" UTF8_8 "c;" "5.test2,10.hellohello,15.worldworldworld;"; /* Create pipe */ CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); /* File descriptors */ rfd = fd[0]; wfd = fd[1]; /* Fork */ if ((childpid = fork()) == -1) { /* ERROR */ perror("fork"); return; } /* Child (pipe writer) */ if (childpid != 0) { close(rfd); CU_ASSERT_EQUAL( write(wfd, test_string, sizeof(test_string)), sizeof(test_string) ); exit(0); } /* Parent (unit test) */ else { guac_socket* socket; guac_instruction* instruction; close(wfd); /* Open guac socket */ socket = guac_socket_open(rfd); CU_ASSERT_PTR_NOT_NULL_FATAL(socket); /* Read instruction */ instruction = guac_instruction_read(socket, 1000000); CU_ASSERT_PTR_NOT_NULL_FATAL(instruction); /* Validate contents */ CU_ASSERT_STRING_EQUAL(instruction->opcode, "test"); CU_ASSERT_EQUAL_FATAL(instruction->argc, 3); CU_ASSERT_STRING_EQUAL(instruction->argv[0], "a" UTF8_4 "b"); CU_ASSERT_STRING_EQUAL(instruction->argv[1], "12345"); CU_ASSERT_STRING_EQUAL(instruction->argv[2], "a" UTF8_8 "c"); /* Read another instruction */ guac_instruction_free(instruction); instruction = guac_instruction_read(socket, 1000000); /* Validate contents */ CU_ASSERT_STRING_EQUAL(instruction->opcode, "test2"); CU_ASSERT_EQUAL_FATAL(instruction->argc, 2); CU_ASSERT_STRING_EQUAL(instruction->argv[0], "hellohello"); CU_ASSERT_STRING_EQUAL(instruction->argv[1], "worldworldworld"); guac_instruction_free(instruction); guac_socket_free(socket); } }
void test_instruction_write() { int rfd, wfd; int fd[2], childpid; /* Create pipe */ CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); /* File descriptors */ rfd = fd[0]; wfd = fd[1]; /* Fork */ if ((childpid = fork()) == -1) { /* ERROR */ perror("fork"); return; } /* Child (pipe writer) */ if (childpid != 0) { guac_socket* socket; close(rfd); /* Open guac socket */ socket = guac_socket_open(wfd); /* Write instruction */ guac_protocol_send_clipboard(socket, "a" UTF8_4 "b" UTF8_4 "c"); guac_protocol_send_sync(socket, 12345); guac_socket_flush(socket); guac_socket_free(socket); exit(0); } /* Parent (unit test) */ else { char expected[] = "9.clipboard,11.a" UTF8_4 "b" UTF8_4 "c;" "4.sync,5.12345;"; int numread; char buffer[1024]; int offset = 0; close(wfd); /* Read everything available into buffer */ while ((numread = read(rfd, &(buffer[offset]), sizeof(buffer)-offset)) != 0) { offset += numread; } /* Add NULL terminator */ buffer[offset] = '\0'; /* Read value should be equal to expected value */ CU_ASSERT_STRING_EQUAL(buffer, expected); } }