connection.c (11992B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "connection.h" 23#include "log.h" 24#include "move-fd.h" 25#include "proc.h" 26#include "proc-map.h" 27 28#include <guacamole/client.h> 29#include <guacamole/error.h> 30#include <guacamole/mem.h> 31#include <guacamole/parser.h> 32#include <guacamole/plugin.h> 33#include <guacamole/protocol.h> 34#include <guacamole/socket.h> 35#include <guacamole/user.h> 36 37#ifdef ENABLE_SSL 38#include <openssl/ssl.h> 39#include <guacamole/socket-ssl.h> 40#endif 41 42#include <errno.h> 43#include <stdlib.h> 44#include <string.h> 45#include <sys/types.h> 46#include <sys/socket.h> 47#include <sys/wait.h> 48 49/** 50 * Behaves exactly as write(), but writes as much as possible, returning 51 * successfully only if the entire buffer was written. If the write fails for 52 * any reason, a negative value is returned. 53 * 54 * @param fd 55 * The file descriptor to write to. 56 * 57 * @param buffer 58 * The buffer containing the data to be written. 59 * 60 * @param length 61 * The number of bytes in the buffer to write. 62 * 63 * @return 64 * The number of bytes written, or -1 if an error occurs. As this function 65 * is guaranteed to write ALL bytes, this will always be the number of 66 * bytes specified by length unless an error occurs. 67 */ 68static int __write_all(int fd, char* buffer, int length) { 69 70 /* Repeatedly write() until all data is written */ 71 int remaining_length = length; 72 while (remaining_length > 0) { 73 74 int written = write(fd, buffer, remaining_length); 75 if (written < 0) 76 return -1; 77 78 remaining_length -= written; 79 buffer += written; 80 81 } 82 83 return length; 84 85} 86 87/** 88 * Continuously reads from a guac_socket, writing all data read to a file 89 * descriptor. Any data already buffered from that guac_socket by a given 90 * guac_parser is read first, prior to reading further data from the 91 * guac_socket. The provided guac_parser will be freed once its buffers have 92 * been emptied, but the guac_socket will not. 93 * 94 * This thread ultimately terminates when no further data can be read from the 95 * guac_socket. 96 * 97 * @param data 98 * A pointer to a guacd_connection_io_thread_params structure containing 99 * the guac_socket to read from, the file descriptor to write the read data 100 * to, and the guac_parser associated with the guac_socket which may have 101 * unhandled data in its parsing buffers. 102 * 103 * @return 104 * Always NULL. 105 */ 106static void* guacd_connection_write_thread(void* data) { 107 108 guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data; 109 char buffer[8192]; 110 111 int length; 112 113 /* Read all buffered data from parser first */ 114 while ((length = guac_parser_shift(params->parser, buffer, sizeof(buffer))) > 0) { 115 if (__write_all(params->fd, buffer, length) < 0) 116 break; 117 } 118 119 /* Parser is no longer needed */ 120 guac_parser_free(params->parser); 121 122 /* Transfer data from file descriptor to socket */ 123 while ((length = guac_socket_read(params->socket, buffer, sizeof(buffer))) > 0) { 124 if (__write_all(params->fd, buffer, length) < 0) 125 break; 126 } 127 128 return NULL; 129 130} 131 132void* guacd_connection_io_thread(void* data) { 133 134 guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data; 135 char buffer[8192]; 136 137 int length; 138 139 pthread_t write_thread; 140 pthread_create(&write_thread, NULL, guacd_connection_write_thread, params); 141 142 /* Transfer data from file descriptor to socket */ 143 while ((length = read(params->fd, buffer, sizeof(buffer))) > 0) { 144 if (guac_socket_write(params->socket, buffer, length)) 145 break; 146 guac_socket_flush(params->socket); 147 } 148 149 /* Wait for write thread to die */ 150 pthread_join(write_thread, NULL); 151 152 /* Clean up */ 153 guac_socket_free(params->socket); 154 close(params->fd); 155 guac_mem_free(params); 156 157 return NULL; 158 159} 160 161/** 162 * Adds the given socket as a new user to the given process, automatically 163 * reading/writing from the socket via read/write threads. The given socket, 164 * parser, and any associated resources will be freed unless the user is not 165 * added successfully. 166 * 167 * If adding the user fails for any reason, non-zero is returned. Zero is 168 * returned upon success. 169 * 170 * @param proc 171 * The existing process to add the user to. 172 * 173 * @param parser 174 * The parser associated with the given guac_socket (used to handle the 175 * user's connection handshake thus far). 176 * 177 * @param socket 178 * The socket associated with the user to be added to the existing 179 * process. 180 * 181 * @return 182 * Zero if the user was added successfully, non-zero if an error occurred. 183 */ 184static int guacd_add_user(guacd_proc* proc, guac_parser* parser, guac_socket* socket) { 185 186 int sockets[2]; 187 188 /* Set up socket pair */ 189 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) { 190 guacd_log(GUAC_LOG_ERROR, "Unable to allocate file descriptors for I/O transfer: %s", strerror(errno)); 191 return 1; 192 } 193 194 int user_fd = sockets[0]; 195 int proc_fd = sockets[1]; 196 197 /* Send user file descriptor to process */ 198 if (!guacd_send_fd(proc->fd_socket, proc_fd)) { 199 guacd_log(GUAC_LOG_ERROR, "Unable to add user."); 200 return 1; 201 } 202 203 /* Close our end of the process file descriptor */ 204 close(proc_fd); 205 206 guacd_connection_io_thread_params* params = guac_mem_alloc(sizeof(guacd_connection_io_thread_params)); 207 params->parser = parser; 208 params->socket = socket; 209 params->fd = user_fd; 210 211 /* Start I/O thread */ 212 pthread_t io_thread; 213 pthread_create(&io_thread, NULL, guacd_connection_io_thread, params); 214 pthread_detach(io_thread); 215 216 return 0; 217 218} 219 220/** 221 * Routes the connection on the given socket according to the Guacamole 222 * protocol, adding new users and creating new client processes as needed. If a 223 * new process is created, this function blocks until that process terminates, 224 * automatically deregistering the process at that point. 225 * 226 * The socket provided will be automatically freed when the connection 227 * terminates unless routing fails, in which case non-zero is returned. 228 * 229 * @param map 230 * The map of existing client processes. 231 * 232 * @param socket 233 * The socket associated with the new connection that must be routed to 234 * a new or existing process within the given map. 235 * 236 * @return 237 * Zero if the connection was successfully routed, non-zero if routing has 238 * failed. 239 */ 240static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) { 241 242 guac_parser* parser = guac_parser_alloc(); 243 244 /* Reset guac_error */ 245 guac_error = GUAC_STATUS_SUCCESS; 246 guac_error_message = NULL; 247 248 /* Get protocol from select instruction */ 249 if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "select")) { 250 251 /* Log error */ 252 guacd_log_handshake_failure(); 253 guacd_log_guac_error(GUAC_LOG_DEBUG, 254 "Error reading \"select\""); 255 256 guac_parser_free(parser); 257 return 1; 258 } 259 260 /* Validate args to select */ 261 if (parser->argc != 1) { 262 263 /* Log error */ 264 guacd_log_handshake_failure(); 265 guacd_log(GUAC_LOG_ERROR, "Bad number of arguments to \"select\" (%i)", 266 parser->argc); 267 268 guac_parser_free(parser); 269 return 1; 270 } 271 272 guacd_proc* proc; 273 int new_process; 274 275 const char* identifier = parser->argv[0]; 276 277 /* If connection ID, retrieve existing process */ 278 if (identifier[0] == GUAC_CLIENT_ID_PREFIX) { 279 280 proc = guacd_proc_map_retrieve(map, identifier); 281 new_process = 0; 282 283 /* Warn and ward off client if requested connection does not exist */ 284 if (proc == NULL) { 285 guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist", identifier); 286 guac_protocol_send_error(socket, "No such connection.", 287 GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); 288 } 289 290 else 291 guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"", 292 identifier); 293 294 } 295 296 /* Otherwise, create new client */ 297 else { 298 299 guacd_log(GUAC_LOG_INFO, "Creating new client for protocol \"%s\"", 300 identifier); 301 302 /* Create new process */ 303 proc = guacd_create_proc(identifier); 304 new_process = 1; 305 306 } 307 308 /* Abort if no process exists for the requested connection */ 309 if (proc == NULL) { 310 guacd_log_guac_error(GUAC_LOG_INFO, "Connection did not succeed"); 311 guac_parser_free(parser); 312 return 1; 313 } 314 315 /* Add new user (in the case of a new process, this will be the owner */ 316 int add_user_failed = guacd_add_user(proc, parser, socket); 317 318 /* If new process was created, manage that process */ 319 if (new_process) { 320 321 /* The new process will only be active if the user was added */ 322 if (!add_user_failed) { 323 324 /* Log connection ID */ 325 guacd_log(GUAC_LOG_INFO, "Connection ID is \"%s\"", 326 proc->client->connection_id); 327 328 /* Store process, allowing other users to join */ 329 guacd_proc_map_add(map, proc); 330 331 /* Wait for child to finish */ 332 waitpid(proc->pid, NULL, 0); 333 334 /* Remove client */ 335 if (guacd_proc_map_remove(map, proc->client->connection_id) == NULL) 336 guacd_log(GUAC_LOG_ERROR, "Internal failure removing " 337 "client \"%s\". Client record will never be freed.", 338 proc->client->connection_id); 339 else 340 guacd_log(GUAC_LOG_INFO, "Connection \"%s\" removed.", 341 proc->client->connection_id); 342 343 } 344 345 /* Parser must be manually freed if the process did not start */ 346 else 347 guac_parser_free(parser); 348 349 /* Force process to stop and clean up */ 350 guacd_proc_stop(proc); 351 352 /* Free skeleton client */ 353 guac_client_free(proc->client); 354 355 /* Clean up */ 356 close(proc->fd_socket); 357 guac_mem_free(proc); 358 359 } 360 361 /* Routing succeeded only if the user was added to a process */ 362 return add_user_failed; 363 364} 365 366void* guacd_connection_thread(void* data) { 367 368 guacd_connection_thread_params* params = (guacd_connection_thread_params*) data; 369 370 guacd_proc_map* map = params->map; 371 int connected_socket_fd = params->connected_socket_fd; 372 373 guac_socket* socket; 374 375#ifdef ENABLE_SSL 376 377 SSL_CTX* ssl_context = params->ssl_context; 378 379 /* If SSL chosen, use it */ 380 if (ssl_context != NULL) { 381 socket = guac_socket_open_secure(ssl_context, connected_socket_fd); 382 if (socket == NULL) { 383 guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to set up SSL/TLS"); 384 close(connected_socket_fd); 385 guac_mem_free(params); 386 return NULL; 387 } 388 } 389 else 390 socket = guac_socket_open(connected_socket_fd); 391 392#else 393 /* Open guac_socket */ 394 socket = guac_socket_open(connected_socket_fd); 395#endif 396 397 /* Route connection according to Guacamole, creating a new process if needed */ 398 if (guacd_route_connection(map, socket)) 399 guac_socket_free(socket); 400 401 guac_mem_free(params); 402 return NULL; 403 404} 405