/** * 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; }
int main(int argc, char* argv[]) { /* Server */ int socket_fd; struct addrinfo* addresses; struct addrinfo* current_address; char bound_address[1024]; char bound_port[64]; int opt_on = 1; struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP }; /* Client */ struct sockaddr_in client_addr; socklen_t client_addr_len; int connected_socket_fd; #ifdef ENABLE_SSL SSL_CTX* ssl_context = NULL; #endif guacd_client_map* map = guacd_client_map_alloc(); /* General */ int retval; /* Load configuration */ guacd_config* config = guacd_conf_load(); if (config == NULL || guacd_conf_parse_args(config, argc, argv)) exit(EXIT_FAILURE); /* Set up logging prefix */ strncpy(log_prefix, basename(argv[0]), sizeof(log_prefix)); /* Open log as early as we can */ openlog(NULL, LOG_PID, LOG_DAEMON); /* Log start */ guacd_log_info("Guacamole proxy daemon (guacd) version " VERSION); /* Get addresses for binding */ if ((retval = getaddrinfo(config->bind_host, config->bind_port, &hints, &addresses))) { guacd_log_error("Error parsing given address or port: %s", gai_strerror(retval)); exit(EXIT_FAILURE); } /* Get socket */ socket_fd = socket(AF_INET, SOCK_STREAM, 0); if (socket_fd < 0) { guacd_log_error("Error opening socket: %s", strerror(errno)); exit(EXIT_FAILURE); } /* Allow socket reuse */ if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &opt_on, sizeof(opt_on))) { guacd_log_info("Unable to set socket options for reuse: %s", strerror(errno)); } /* Attempt binding of each address until success */ current_address = addresses; while (current_address != NULL) { int retval; /* Resolve hostname */ if ((retval = getnameinfo(current_address->ai_addr, current_address->ai_addrlen, bound_address, sizeof(bound_address), bound_port, sizeof(bound_port), NI_NUMERICHOST | NI_NUMERICSERV))) guacd_log_error("Unable to resolve host: %s", gai_strerror(retval)); /* Attempt to bind socket to address */ if (bind(socket_fd, current_address->ai_addr, current_address->ai_addrlen) == 0) { guacd_log_info("Successfully bound socket to " "host %s, port %s", bound_address, bound_port); /* Done if successful bind */ break; } /* Otherwise log information regarding bind failure */ else guacd_log_info("Unable to bind socket to " "host %s, port %s: %s", bound_address, bound_port, strerror(errno)); current_address = current_address->ai_next; } /* If unable to bind to anything, fail */ if (current_address == NULL) { guacd_log_error("Unable to bind socket to any addresses."); exit(EXIT_FAILURE); } #ifdef ENABLE_SSL /* Init SSL if enabled */ if (config->key_file != NULL || config->cert_file != NULL) { /* Init SSL */ guacd_log_info("Communication will require SSL/TLS."); SSL_library_init(); SSL_load_error_strings(); ssl_context = SSL_CTX_new(SSLv23_server_method()); /* Load key */ if (config->key_file != NULL) { guacd_log_info("Using PEM keyfile %s", config->key_file); if (!SSL_CTX_use_PrivateKey_file(ssl_context, config->key_file, SSL_FILETYPE_PEM)) { guacd_log_error("Unable to load keyfile."); exit(EXIT_FAILURE); } } else guacd_log_info("No PEM keyfile given - SSL/TLS may not work."); /* Load cert file if specified */ if (config->cert_file != NULL) { guacd_log_info("Using certificate file %s", config->cert_file); if (!SSL_CTX_use_certificate_chain_file(ssl_context, config->cert_file)) { guacd_log_error("Unable to load certificate."); exit(EXIT_FAILURE); } } else guacd_log_info("No certificate file given - SSL/TLS may not work."); } #endif /* Daemonize if requested */ if (!config->foreground) { /* Attempt to daemonize process */ if (daemonize()) { guacd_log_error("Could not become a daemon."); exit(EXIT_FAILURE); } } /* Write PID file if requested */ if (config->pidfile != NULL) { /* Attempt to open pidfile and write PID */ FILE* pidf = fopen(config->pidfile, "w"); if (pidf) { fprintf(pidf, "%d\n", getpid()); fclose(pidf); } /* Fail if could not write PID file*/ else { guacd_log_error("Could not write PID file: %s", strerror(errno)); exit(EXIT_FAILURE); } } /* Ignore SIGPIPE */ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { guacd_log_info("Could not set handler for SIGPIPE to ignore. " "SIGPIPE may cause termination of the daemon."); } /* Ignore SIGCHLD (force automatic removal of children) */ if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) { guacd_log_info("Could not set handler for SIGCHLD to ignore. " "Child processes may pile up in the process table."); } /* Log listening status */ guacd_log_info("Listening on host %s, port %s", bound_address, bound_port); /* Free addresses */ freeaddrinfo(addresses); /* Daemon loop */ for (;;) { pid_t child_pid; /* Listen for connections */ if (listen(socket_fd, 5) < 0) { guacd_log_error("Could not listen on socket: %s", strerror(errno)); return 3; } /* Accept connection */ client_addr_len = sizeof(client_addr); connected_socket_fd = accept(socket_fd, (struct sockaddr*) &client_addr, &client_addr_len); if (connected_socket_fd < 0) { guacd_log_error("Could not accept client connection: %s", strerror(errno)); return 3; } /* * Once connection is accepted, send child into background. * * Note that we prefer fork() over threads for connection-handling * processes as they give each connection its own memory area, and * isolate the main daemon and other connections from errors in any * particular client plugin. */ child_pid = fork(); /* If error, log */ if (child_pid == -1) guacd_log_error("Error forking child process: %s", strerror(errno)); /* If child, start client, and exit when finished */ else if (child_pid == 0) { guac_socket* socket; #ifdef ENABLE_SSL /* If SSL chosen, use it */ if (ssl_context != NULL) { socket = guac_socket_open_secure(ssl_context, connected_socket_fd); if (socket == NULL) { guacd_log_guac_error("Error opening secure connection"); return 0; } } else socket = guac_socket_open(connected_socket_fd); #else /* Open guac_socket */ socket = guac_socket_open(connected_socket_fd); #endif guacd_handle_connection(map, socket); close(connected_socket_fd); return 0; } /* If parent, close reference to child's descriptor */ else if (close(connected_socket_fd) < 0) { guacd_log_error("Error closing daemon reference to " "child descriptor: %s", strerror(errno)); } } /* Close socket */ if (close(socket_fd) < 0) { guacd_log_error("Could not close socket: %s", strerror(errno)); return 3; } return 0; }
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_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_close(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); } }
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); } }