| /* |
| * 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 "config.h" |
| |
| #include "connection.h" |
| #include "log.h" |
| #include "move-fd.h" |
| #include "proc.h" |
| #include "proc-map.h" |
| |
| #include <guacamole/client.h> |
| #include <guacamole/error.h> |
| #include <guacamole/parser.h> |
| #include <guacamole/plugin.h> |
| #include <guacamole/protocol.h> |
| #include <guacamole/socket.h> |
| #include <guacamole/user.h> |
| |
| #ifdef ENABLE_SSL |
| #include <openssl/ssl.h> |
| #include <guacamole/socket-ssl.h> |
| #endif |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/wait.h> |
| |
| /** |
| * Behaves exactly as write(), but writes as much as possible, returning |
| * successfully only if the entire buffer was written. If the write fails for |
| * any reason, a negative value is returned. |
| * |
| * @param fd |
| * The file descriptor to write to. |
| * |
| * @param buffer |
| * The buffer containing the data to be written. |
| * |
| * @param length |
| * The number of bytes in the buffer to write. |
| * |
| * @return |
| * The number of bytes written, or -1 if an error occurs. As this function |
| * is guaranteed to write ALL bytes, this will always be the number of |
| * bytes specified by length unless an error occurs. |
| */ |
| static int __write_all(int fd, char* buffer, int length) { |
| |
| /* Repeatedly write() until all data is written */ |
| while (length > 0) { |
| |
| int written = write(fd, buffer, length); |
| if (written < 0) |
| return -1; |
| |
| length -= written; |
| buffer += written; |
| |
| } |
| |
| return length; |
| |
| } |
| |
| /** |
| * Continuously reads from a guac_socket, writing all data read to a file |
| * descriptor. Any data already buffered from that guac_socket by a given |
| * guac_parser is read first, prior to reading further data from the |
| * guac_socket. The provided guac_parser will be freed once its buffers have |
| * been emptied, but the guac_socket will not. |
| * |
| * This thread ultimately terminates when no further data can be read from the |
| * guac_socket. |
| * |
| * @param data |
| * A pointer to a guacd_connection_io_thread_params structure containing |
| * the guac_socket to read from, the file descriptor to write the read data |
| * to, and the guac_parser associated with the guac_socket which may have |
| * unhandled data in its parsing buffers. |
| * |
| * @return |
| * Always NULL. |
| */ |
| static void* guacd_connection_write_thread(void* data) { |
| |
| guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data; |
| char buffer[8192]; |
| |
| int length; |
| |
| /* Read all buffered data from parser first */ |
| while ((length = guac_parser_shift(params->parser, buffer, sizeof(buffer))) > 0) { |
| if (__write_all(params->fd, buffer, length) < 0) |
| break; |
| } |
| |
| /* Parser is no longer needed */ |
| guac_parser_free(params->parser); |
| |
| /* Transfer data from file descriptor to socket */ |
| while ((length = guac_socket_read(params->socket, buffer, sizeof(buffer))) > 0) { |
| if (__write_all(params->fd, buffer, length) < 0) |
| break; |
| } |
| |
| return NULL; |
| |
| } |
| |
| void* guacd_connection_io_thread(void* data) { |
| |
| guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data; |
| char buffer[8192]; |
| |
| int length; |
| |
| pthread_t write_thread; |
| pthread_create(&write_thread, NULL, guacd_connection_write_thread, params); |
| |
| /* Transfer data from file descriptor to socket */ |
| while ((length = read(params->fd, buffer, sizeof(buffer))) > 0) { |
| if (guac_socket_write(params->socket, buffer, length)) |
| break; |
| guac_socket_flush(params->socket); |
| } |
| |
| /* Wait for write thread to die */ |
| pthread_join(write_thread, NULL); |
| |
| /* Clean up */ |
| guac_socket_free(params->socket); |
| close(params->fd); |
| free(params); |
| |
| return NULL; |
| |
| } |
| |
| /** |
| * Adds the given socket as a new user to the given process, automatically |
| * reading/writing from the socket via read/write threads. The given socket, |
| * parser, and any associated resources will be freed unless the user is not |
| * added successfully. |
| * |
| * If adding the user fails for any reason, non-zero is returned. Zero is |
| * returned upon success. |
| * |
| * @param proc |
| * The existing process to add the user to. |
| * |
| * @param parser |
| * The parser associated with the given guac_socket (used to handle the |
| * user's connection handshake thus far). |
| * |
| * @param socket |
| * The socket associated with the user to be added to the existing |
| * process. |
| * |
| * @return |
| * Zero if the user was added successfully, non-zero if an error occurred. |
| */ |
| static int guacd_add_user(guacd_proc* proc, guac_parser* parser, guac_socket* socket) { |
| |
| int sockets[2]; |
| |
| /* Set up socket pair */ |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) { |
| guacd_log(GUAC_LOG_ERROR, "Unable to allocate file descriptors for I/O transfer: %s", strerror(errno)); |
| return 1; |
| } |
| |
| int user_fd = sockets[0]; |
| int proc_fd = sockets[1]; |
| |
| /* Send user file descriptor to process */ |
| if (!guacd_send_fd(proc->fd_socket, proc_fd)) { |
| guacd_log(GUAC_LOG_ERROR, "Unable to add user."); |
| return 1; |
| } |
| |
| /* Close our end of the process file descriptor */ |
| close(proc_fd); |
| |
| guacd_connection_io_thread_params* params = malloc(sizeof(guacd_connection_io_thread_params)); |
| params->parser = parser; |
| params->socket = socket; |
| params->fd = user_fd; |
| |
| /* Start I/O thread */ |
| pthread_t io_thread; |
| pthread_create(&io_thread, NULL, guacd_connection_io_thread, params); |
| pthread_detach(io_thread); |
| |
| return 0; |
| |
| } |
| |
| /** |
| * Routes the connection on the given socket according to the Guacamole |
| * protocol, adding new users and creating new client processes as needed. If a |
| * new process is created, this function blocks until that process terminates, |
| * automatically deregistering the process at that point. |
| * |
| * The socket provided will be automatically freed when the connection |
| * terminates unless routing fails, in which case non-zero is returned. |
| * |
| * @param map |
| * The map of existing client processes. |
| * |
| * @param socket |
| * The socket associated with the new connection that must be routed to |
| * a new or existing process within the given map. |
| * |
| * @return |
| * Zero if the connection was successfully routed, non-zero if routing has |
| * failed. |
| */ |
| static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) { |
| |
| guac_parser* parser = guac_parser_alloc(); |
| |
| /* Reset guac_error */ |
| guac_error = GUAC_STATUS_SUCCESS; |
| guac_error_message = NULL; |
| |
| /* Get protocol from select instruction */ |
| if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "select")) { |
| |
| /* Log error */ |
| guacd_log_handshake_failure(); |
| guacd_log_guac_error(GUAC_LOG_DEBUG, |
| "Error reading \"select\""); |
| |
| guac_parser_free(parser); |
| return 1; |
| } |
| |
| /* Validate args to select */ |
| if (parser->argc != 1) { |
| |
| /* Log error */ |
| guacd_log_handshake_failure(); |
| guacd_log(GUAC_LOG_ERROR, "Bad number of arguments to \"select\" (%i)", |
| parser->argc); |
| |
| guac_parser_free(parser); |
| return 1; |
| } |
| |
| guacd_proc* proc; |
| int new_process; |
| |
| const char* identifier = parser->argv[0]; |
| |
| /* If connection ID, retrieve existing process */ |
| if (identifier[0] == GUAC_CLIENT_ID_PREFIX) { |
| |
| proc = guacd_proc_map_retrieve(map, identifier); |
| new_process = 0; |
| |
| /* Warn if requested connection does not exist */ |
| if (proc == NULL) |
| guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist.", |
| identifier); |
| else |
| guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"", |
| identifier); |
| |
| } |
| |
| /* Otherwise, create new client */ |
| else { |
| |
| guacd_log(GUAC_LOG_INFO, "Creating new client for protocol \"%s\"", |
| identifier); |
| |
| /* Create new process */ |
| proc = guacd_create_proc(identifier); |
| new_process = 1; |
| |
| } |
| |
| /* Abort if no process exists for the requested connection */ |
| if (proc == NULL) { |
| guacd_log_guac_error(GUAC_LOG_INFO, "Connection did not succeed"); |
| guac_parser_free(parser); |
| return 1; |
| } |
| |
| /* Add new user (in the case of a new process, this will be the owner */ |
| int add_user_failed = guacd_add_user(proc, parser, socket); |
| |
| /* If new process was created, manage that process */ |
| if (new_process) { |
| |
| /* The new process will only be active if the user was added */ |
| if (!add_user_failed) { |
| |
| /* Log connection ID */ |
| guacd_log(GUAC_LOG_INFO, "Connection ID is \"%s\"", |
| proc->client->connection_id); |
| |
| /* Store process, allowing other users to join */ |
| guacd_proc_map_add(map, proc); |
| |
| /* Wait for child to finish */ |
| waitpid(proc->pid, NULL, 0); |
| |
| /* Remove client */ |
| if (guacd_proc_map_remove(map, proc->client->connection_id) == NULL) |
| guacd_log(GUAC_LOG_ERROR, "Internal failure removing " |
| "client \"%s\". Client record will never be freed.", |
| proc->client->connection_id); |
| else |
| guacd_log(GUAC_LOG_INFO, "Connection \"%s\" removed.", |
| proc->client->connection_id); |
| |
| } |
| |
| /* Parser must be manually freed if the process did not start */ |
| else |
| guac_parser_free(parser); |
| |
| /* Force process to stop and clean up */ |
| guacd_proc_stop(proc); |
| |
| /* Free skeleton client */ |
| guac_client_free(proc->client); |
| |
| /* Clean up */ |
| close(proc->fd_socket); |
| free(proc); |
| |
| } |
| |
| /* Routing succeeded only if the user was added to a process */ |
| return add_user_failed; |
| |
| } |
| |
| void* guacd_connection_thread(void* data) { |
| |
| guacd_connection_thread_params* params = (guacd_connection_thread_params*) data; |
| |
| guacd_proc_map* map = params->map; |
| int connected_socket_fd = params->connected_socket_fd; |
| |
| guac_socket* socket; |
| |
| #ifdef ENABLE_SSL |
| |
| SSL_CTX* ssl_context = params->ssl_context; |
| |
| /* 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(GUAC_LOG_ERROR, "Unable to set up SSL/TLS"); |
| close(connected_socket_fd); |
| free(params); |
| return NULL; |
| } |
| } |
| else |
| socket = guac_socket_open(connected_socket_fd); |
| |
| #else |
| /* Open guac_socket */ |
| socket = guac_socket_open(connected_socket_fd); |
| #endif |
| |
| /* Route connection according to Guacamole, creating a new process if needed */ |
| if (guacd_route_connection(map, socket)) |
| guac_socket_free(socket); |
| |
| free(params); |
| return NULL; |
| |
| } |
| |