proc.c (16332B)
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 "log.h" 23#include "move-fd.h" 24#include "proc.h" 25#include "proc-map.h" 26 27#include <guacamole/client.h> 28#include <guacamole/error.h> 29#include <guacamole/mem.h> 30#include <guacamole/parser.h> 31#include <guacamole/plugin.h> 32#include <guacamole/protocol.h> 33#include <guacamole/socket.h> 34#include <guacamole/user.h> 35 36#include <errno.h> 37#include <pthread.h> 38#include <signal.h> 39#include <stdlib.h> 40#include <string.h> 41#include <unistd.h> 42#include <sys/time.h> 43#include <sys/types.h> 44#include <sys/socket.h> 45#include <sys/wait.h> 46 47/** 48 * Parameters for the user thread. 49 */ 50typedef struct guacd_user_thread_params { 51 52 /** 53 * The process being joined. 54 */ 55 guacd_proc* proc; 56 57 /** 58 * The file descriptor of the joining user's socket. 59 */ 60 int fd; 61 62 /** 63 * Whether the joining user is the connection owner. 64 */ 65 int owner; 66 67} guacd_user_thread_params; 68 69/** 70 * Handles a user's entire connection and socket lifecycle. 71 * 72 * @param data 73 * A pointer to a guacd_user_thread_params structure describing the user's 74 * associated file descriptor, whether that user is the connection owner 75 * (the first person to join), as well as the process associated with the 76 * connection being joined. 77 * 78 * @return 79 * Always NULL. 80 */ 81static void* guacd_user_thread(void* data) { 82 83 guacd_user_thread_params* params = (guacd_user_thread_params*) data; 84 guacd_proc* proc = params->proc; 85 guac_client* client = proc->client; 86 87 /* Get guac_socket for user's file descriptor */ 88 guac_socket* socket = guac_socket_open(params->fd); 89 if (socket == NULL) 90 return NULL; 91 92 /* Create skeleton user */ 93 guac_user* user = guac_user_alloc(); 94 user->socket = socket; 95 user->client = client; 96 user->owner = params->owner; 97 98 /* Handle user connection from handshake until disconnect/completion */ 99 guac_user_handle_connection(user, GUACD_USEC_TIMEOUT); 100 101 /* Stop client and prevent future users if all users are disconnected */ 102 if (client->connected_users == 0) { 103 guacd_log(GUAC_LOG_INFO, "Last user of connection \"%s\" disconnected", client->connection_id); 104 guacd_proc_stop(proc); 105 } 106 107 /* Clean up */ 108 guac_socket_free(socket); 109 guac_user_free(user); 110 guac_mem_free(params); 111 112 return NULL; 113 114} 115 116/** 117 * Begins a new user connection under a given process, using the given file 118 * descriptor. The connection will be managed by a separate and detached thread 119 * which is started by this function. 120 * 121 * @param proc 122 * The process that the user is being added to. 123 * 124 * @param fd 125 * The file descriptor associated with the user's network connection to 126 * guacd. 127 * 128 * @param owner 129 * Non-zero if the user is the owner of the connection being joined (they 130 * are the first user to join), or zero otherwise. 131 */ 132static void guacd_proc_add_user(guacd_proc* proc, int fd, int owner) { 133 134 guacd_user_thread_params* params = guac_mem_alloc(sizeof(guacd_user_thread_params)); 135 params->proc = proc; 136 params->fd = fd; 137 params->owner = owner; 138 139 /* Start user thread */ 140 pthread_t user_thread; 141 pthread_create(&user_thread, NULL, guacd_user_thread, params); 142 pthread_detach(user_thread); 143 144} 145 146/** 147 * Forcibly kills all processes within the current process group, including the 148 * current process and all child processes. This function is only safe to call 149 * if the process group ID has been correctly set. Calling this function within 150 * a process which does not have a PGID separate from the main guacd process 151 * can result in guacd itself being terminated. 152 */ 153static void guacd_kill_current_proc_group() { 154 155 /* Forcibly kill all children within process group */ 156 if (kill(0, SIGKILL)) 157 guacd_log(GUAC_LOG_WARNING, "Unable to forcibly terminate " 158 "client process: %s ", strerror(errno)); 159 160} 161 162/** 163 * The current status of a background attempt to free a guac_client instance. 164 */ 165typedef struct guacd_client_free { 166 167 /** 168 * The guac_client instance being freed. 169 */ 170 guac_client* client; 171 172 /** 173 * The condition which is signalled whenever changes are made to the 174 * completed flag. The completed flag only changes from zero (not yet 175 * freed) to non-zero (successfully freed). 176 */ 177 pthread_cond_t completed_cond; 178 179 /** 180 * Mutex which must be acquired before any changes are made to the 181 * completed flag. 182 */ 183 pthread_mutex_t completed_mutex; 184 185 /** 186 * Whether the guac_client has been successfully freed. Initially, this 187 * will be zero, indicating that the free operation has not yet been 188 * attempted. If the client is eventually successfully freed, this will be 189 * set to a non-zero value. Changes to this flag are signalled through 190 * the completed_cond condition. 191 */ 192 int completed; 193 194} guacd_client_free; 195 196/** 197 * Thread which frees a given guac_client instance in the background. If the 198 * free operation succeeds, a flag is set on the provided structure, and the 199 * change in that flag is signalled with a pthread condition. 200 * 201 * At the time this function is provided to a pthread_create() call, the 202 * completed flag of the associated guacd_client_free structure MUST be 203 * initialized to zero, the pthread mutex and condition MUST both be 204 * initialized, and the client pointer must point to the guac_client being 205 * freed. 206 * 207 * @param data 208 * A pointer to a guacd_client_free structure describing the free 209 * operation. 210 * 211 * @return 212 * Always NULL. 213 */ 214static void* guacd_client_free_thread(void* data) { 215 216 guacd_client_free* free_operation = (guacd_client_free*) data; 217 218 /* Attempt to free client (this may never return if the client is 219 * malfunctioning) */ 220 guac_client_free(free_operation->client); 221 222 /* Signal that the client was successfully freed */ 223 pthread_mutex_lock(&free_operation->completed_mutex); 224 free_operation->completed = 1; 225 pthread_cond_broadcast(&free_operation->completed_cond); 226 pthread_mutex_unlock(&free_operation->completed_mutex); 227 228 return NULL; 229 230} 231 232/** 233 * Attempts to free the given guac_client, restricting the time taken by the 234 * free handler of the guac_client to a finite number of seconds. If the free 235 * handler does not complete within the time alotted, this function returns 236 * and the intended free operation is left in an undefined state. 237 * 238 * @param client 239 * The guac_client instance to free. 240 * 241 * @param timeout 242 * The maximum amount of time to wait for the guac_client to be freed, 243 * in seconds. 244 * 245 * @return 246 * Zero if the guac_client was successfully freed within the time alotted, 247 * non-zero otherwise. 248 */ 249static int guacd_timed_client_free(guac_client* client, int timeout) { 250 251 pthread_t client_free_thread; 252 253 guacd_client_free free_operation = { 254 .client = client, 255 .completed_cond = PTHREAD_COND_INITIALIZER, 256 .completed_mutex = PTHREAD_MUTEX_INITIALIZER, 257 .completed = 0 258 }; 259 260 /* Get current time */ 261 struct timeval current_time; 262 if (gettimeofday(¤t_time, NULL)) 263 return 1; 264 265 /* Calculate exact time that the free operation MUST complete by */ 266 struct timespec deadline = { 267 .tv_sec = current_time.tv_sec + timeout, 268 .tv_nsec = current_time.tv_usec * 1000 269 }; 270 271 /* The mutex associated with the pthread conditional and flag MUST be 272 * acquired before attempting to wait for the condition */ 273 if (pthread_mutex_lock(&free_operation.completed_mutex)) 274 return 1; 275 276 /* Free the client in a separate thread, so we can time the free operation */ 277 if (!pthread_create(&client_free_thread, NULL, 278 guacd_client_free_thread, &free_operation)) { 279 280 /* Wait a finite amount of time for the free operation to finish */ 281 (void) pthread_cond_timedwait(&free_operation.completed_cond, 282 &free_operation.completed_mutex, &deadline); 283 } 284 285 (void) pthread_mutex_unlock(&free_operation.completed_mutex); 286 287 /* Return status of free operation */ 288 return !free_operation.completed; 289} 290 291/** 292 * A reference to the current guacd process. 293 */ 294guacd_proc* guacd_proc_self = NULL; 295 296/** 297 * A signal handler that will be invoked when a signal is caught telling this 298 * guacd process to immediately exit. 299 * 300 * @param signal 301 * The signal that was received. Unused in this function since only 302 * signals that should result in stopping the proc should invoke this. 303 */ 304static void signal_stop_handler(int signal) { 305 306 /* Stop the current guacd proc */ 307 guacd_proc_stop(guacd_proc_self); 308 309} 310 311/** 312 * Starts protocol-specific handling on the given process by loading the client 313 * plugin for that protocol. This function does NOT return. It initializes the 314 * process with protocol-specific handlers and then runs until the guacd_proc's 315 * fd_socket is closed, adding any file descriptors received along fd_socket as 316 * new users. 317 * 318 * @param proc 319 * The process that any new users received along fd_socket should be added 320 * to (after the process has been initialized for the given protocol). 321 * 322 * @param protocol 323 * The protocol to initialize the given process for. 324 */ 325static void guacd_exec_proc(guacd_proc* proc, const char* protocol) { 326 327 int result = 1; 328 329 /* Set process group ID to match PID */ 330 if (setpgid(0, 0)) { 331 guacd_log(GUAC_LOG_ERROR, "Cannot set PGID for connection process: %s", 332 strerror(errno)); 333 goto cleanup_process; 334 } 335 336 /* Init client for selected protocol */ 337 guac_client* client = proc->client; 338 if (guac_client_load_plugin(client, protocol)) { 339 340 /* Log error */ 341 if (guac_error == GUAC_STATUS_NOT_FOUND) 342 guacd_log(GUAC_LOG_WARNING, 343 "Support for protocol \"%s\" is not installed", protocol); 344 else 345 guacd_log_guac_error(GUAC_LOG_ERROR, 346 "Unable to load client plugin"); 347 348 goto cleanup_client; 349 } 350 351 /* The first file descriptor is the owner */ 352 int owner = 1; 353 354 /* Enable keep alive on the broadcast socket */ 355 guac_socket_require_keep_alive(client->socket); 356 357 guacd_proc_self = proc; 358 359 /* Clean up and exit if SIGINT or SIGTERM signals are caught */ 360 struct sigaction signal_stop_action = { .sa_handler = signal_stop_handler }; 361 sigaction(SIGINT, &signal_stop_action, NULL); 362 sigaction(SIGTERM, &signal_stop_action, NULL); 363 364 /* Add each received file descriptor as a new user */ 365 int received_fd; 366 while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) { 367 368 guacd_proc_add_user(proc, received_fd, owner); 369 370 /* Future file descriptors are not owners */ 371 owner = 0; 372 373 } 374 375cleanup_client: 376 377 /* Request client to stop/disconnect */ 378 guac_client_stop(client); 379 380 /* Attempt to free client cleanly */ 381 guacd_log(GUAC_LOG_DEBUG, "Requesting termination of client..."); 382 result = guacd_timed_client_free(client, GUACD_CLIENT_FREE_TIMEOUT); 383 384 /* If client was unable to be freed, warn and forcibly kill */ 385 if (result) { 386 guacd_log(GUAC_LOG_WARNING, "Client did not terminate in a timely " 387 "manner. Forcibly terminating client and any child " 388 "processes."); 389 guacd_kill_current_proc_group(); 390 } 391 else 392 guacd_log(GUAC_LOG_DEBUG, "Client terminated successfully."); 393 394 /* Verify whether children were all properly reaped */ 395 pid_t child_pid; 396 while ((child_pid = waitpid(0, NULL, WNOHANG)) > 0) { 397 guacd_log(GUAC_LOG_DEBUG, "Automatically reaped unreaped " 398 "(zombie) child process with PID %i.", child_pid); 399 } 400 401 /* If running children remain, warn and forcibly kill */ 402 if (child_pid == 0) { 403 guacd_log(GUAC_LOG_WARNING, "Client reported successful termination, " 404 "but child processes remain. Forcibly terminating client and " 405 "child processes."); 406 guacd_kill_current_proc_group(); 407 } 408 409cleanup_process: 410 411 /* Free up all internal resources outside the client */ 412 close(proc->fd_socket); 413 guac_mem_free(proc); 414 415 exit(result); 416 417} 418 419guacd_proc* guacd_create_proc(const char* protocol) { 420 421 int sockets[2]; 422 423 /* Open UNIX socket pair */ 424 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) { 425 guacd_log(GUAC_LOG_ERROR, "Error opening socket pair: %s", strerror(errno)); 426 return NULL; 427 } 428 429 int parent_socket = sockets[0]; 430 int child_socket = sockets[1]; 431 432 /* Allocate process */ 433 guacd_proc* proc = guac_mem_zalloc(sizeof(guacd_proc)); 434 if (proc == NULL) { 435 close(parent_socket); 436 close(child_socket); 437 return NULL; 438 } 439 440 /* Associate new client */ 441 proc->client = guac_client_alloc(); 442 if (proc->client == NULL) { 443 guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to create client"); 444 close(parent_socket); 445 close(child_socket); 446 guac_mem_free(proc); 447 return NULL; 448 } 449 450 /* Init logging */ 451 proc->client->log_handler = guacd_client_log; 452 453 /* Fork */ 454 proc->pid = fork(); 455 if (proc->pid < 0) { 456 guacd_log(GUAC_LOG_ERROR, "Cannot fork child process: %s", strerror(errno)); 457 close(parent_socket); 458 close(child_socket); 459 guac_client_free(proc->client); 460 guac_mem_free(proc); 461 return NULL; 462 } 463 464 /* Child */ 465 else if (proc->pid == 0) { 466 467 /* Communicate with parent */ 468 proc->fd_socket = parent_socket; 469 close(child_socket); 470 471 /* Start protocol-specific handling */ 472 guacd_exec_proc(proc, protocol); 473 474 } 475 476 /* Parent */ 477 else { 478 479 /* Communicate with child */ 480 proc->fd_socket = child_socket; 481 close(parent_socket); 482 483 } 484 485 return proc; 486 487} 488 489/** 490 * Kill the provided child guacd process. This function must be called by the 491 * parent process, and will block until all processes associated with the 492 * child process have terminated. 493 * 494 * @param proc 495 * The child guacd process to kill. 496 */ 497static void guacd_proc_kill(guacd_proc* proc) { 498 499 /* Request orderly termination of process */ 500 if (kill(proc->pid, SIGTERM)) 501 guacd_log(GUAC_LOG_DEBUG, "Unable to request termination of " 502 "client process: %s ", strerror(errno)); 503 504 /* Wait for all processes within process group to terminate */ 505 pid_t child_pid; 506 while ((child_pid = waitpid(-proc->pid, NULL, 0)) > 0 || errno == EINTR) { 507 guacd_log(GUAC_LOG_DEBUG, "Child process %i of connection \"%s\" has terminated", 508 child_pid, proc->client->connection_id); 509 } 510 511 guacd_log(GUAC_LOG_DEBUG, "All child processes for connection \"%s\" have been terminated.", 512 proc->client->connection_id); 513 514} 515 516void guacd_proc_stop(guacd_proc* proc) { 517 518 /* A non-zero PID means that this is the parent process */ 519 if (proc->pid != 0) { 520 guacd_proc_kill(proc); 521 return; 522 } 523 524 /* Otherwise, this is the child process */ 525 526 /* Signal client to stop */ 527 guac_client_stop(proc->client); 528 529 /* Shutdown socket - in-progress recvmsg() will not fail otherwise */ 530 if (shutdown(proc->fd_socket, SHUT_RDWR) == -1) 531 guacd_log(GUAC_LOG_ERROR, "Unable to shutdown internal socket for " 532 "connection %s. Corresponding process may remain running but " 533 "inactive.", proc->client->connection_id); 534 535 /* Clean up our end of the socket */ 536 close(proc->fd_socket); 537 538}