| /* |
| * 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 "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> |
| |
| #include <errno.h> |
| #include <pthread.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/wait.h> |
| |
| /** |
| * Parameters for the user thread. |
| */ |
| typedef struct guacd_user_thread_params { |
| |
| /** |
| * The process being joined. |
| */ |
| guacd_proc* proc; |
| |
| /** |
| * The file descriptor of the joining user's socket. |
| */ |
| int fd; |
| |
| /** |
| * Whether the joining user is the connection owner. |
| */ |
| int owner; |
| |
| } guacd_user_thread_params; |
| |
| /** |
| * 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; |
| |
| } |
| |
| /** |
| * Begins a new user connection under a given process, using the given file |
| * descriptor. The connection will be managed by a separate and detached thread |
| * which is started by this function. |
| * |
| * @param proc |
| * The process that the user is being added to. |
| * |
| * @param fd |
| * The file descriptor associated with the user's network connection to |
| * guacd. |
| * |
| * @param owner |
| * Non-zero if the user is the owner of the connection being joined (they |
| * are the first user to join), or zero otherwise. |
| */ |
| static void guacd_proc_add_user(guacd_proc* proc, int fd, int owner) { |
| |
| guacd_user_thread_params* params = malloc(sizeof(guacd_user_thread_params)); |
| params->proc = proc; |
| params->fd = fd; |
| params->owner = owner; |
| |
| /* Start user thread */ |
| pthread_t user_thread; |
| pthread_create(&user_thread, NULL, guacd_user_thread, params); |
| pthread_detach(user_thread); |
| |
| } |
| |
| /** |
| * Forcibly kills all processes within the current process group, including the |
| * current process and all child processes. This function is only safe to call |
| * if the process group ID has been correctly set. Calling this function within |
| * a process which does not have a PGID separate from the main guacd process |
| * can result in guacd itself being terminated. |
| */ |
| static void guacd_kill_current_proc_group() { |
| |
| /* Forcibly kill all children within process group */ |
| if (kill(0, SIGKILL)) |
| guacd_log(GUAC_LOG_WARNING, "Unable to forcibly terminate " |
| "client process: %s ", strerror(errno)); |
| |
| } |
| |
| /** |
| * The current status of a background attempt to free a guac_client instance. |
| */ |
| typedef struct guacd_client_free { |
| |
| /** |
| * The guac_client instance being freed. |
| */ |
| guac_client* client; |
| |
| /** |
| * The condition which is signalled whenever changes are made to the |
| * completed flag. The completed flag only changes from zero (not yet |
| * freed) to non-zero (successfully freed). |
| */ |
| pthread_cond_t completed_cond; |
| |
| /** |
| * Mutex which must be acquired before any changes are made to the |
| * completed flag. |
| */ |
| pthread_mutex_t completed_mutex; |
| |
| /** |
| * Whether the guac_client has been successfully freed. Initially, this |
| * will be zero, indicating that the free operation has not yet been |
| * attempted. If the client is eventually successfully freed, this will be |
| * set to a non-zero value. Changes to this flag are signalled through |
| * the completed_cond condition. |
| */ |
| int completed; |
| |
| } guacd_client_free; |
| |
| /** |
| * Thread which frees a given guac_client instance in the background. If the |
| * free operation succeeds, a flag is set on the provided structure, and the |
| * change in that flag is signalled with a pthread condition. |
| * |
| * At the time this function is provided to a pthread_create() call, the |
| * completed flag of the associated guacd_client_free structure MUST be |
| * initialized to zero, the pthread mutex and condition MUST both be |
| * initialized, and the client pointer must point to the guac_client being |
| * freed. |
| * |
| * @param data |
| * A pointer to a guacd_client_free structure describing the free |
| * operation. |
| * |
| * @return |
| * Always NULL. |
| */ |
| static void* guacd_client_free_thread(void* data) { |
| |
| guacd_client_free* free_operation = (guacd_client_free*) data; |
| |
| /* Attempt to free client (this may never return if the client is |
| * malfunctioning) */ |
| guac_client_free(free_operation->client); |
| |
| /* Signal that the client was successfully freed */ |
| pthread_mutex_lock(&free_operation->completed_mutex); |
| free_operation->completed = 1; |
| pthread_cond_broadcast(&free_operation->completed_cond); |
| pthread_mutex_unlock(&free_operation->completed_mutex); |
| |
| return NULL; |
| |
| } |
| |
| /** |
| * Attempts to free the given guac_client, restricting the time taken by the |
| * free handler of the guac_client to a finite number of seconds. If the free |
| * handler does not complete within the time alotted, this function returns |
| * and the intended free operation is left in an undefined state. |
| * |
| * @param client |
| * The guac_client instance to free. |
| * |
| * @param timeout |
| * The maximum amount of time to wait for the guac_client to be freed, |
| * in seconds. |
| * |
| * @return |
| * Zero if the guac_client was successfully freed within the time alotted, |
| * non-zero otherwise. |
| */ |
| static int guacd_timed_client_free(guac_client* client, int timeout) { |
| |
| pthread_t client_free_thread; |
| |
| guacd_client_free free_operation = { |
| .client = client, |
| .completed_cond = PTHREAD_COND_INITIALIZER, |
| .completed_mutex = PTHREAD_MUTEX_INITIALIZER, |
| .completed = 0 |
| }; |
| |
| /* Get current time */ |
| struct timeval current_time; |
| if (gettimeofday(¤t_time, NULL)) |
| return 1; |
| |
| /* Calculate exact time that the free operation MUST complete by */ |
| struct timespec deadline = { |
| .tv_sec = current_time.tv_sec + timeout, |
| .tv_nsec = current_time.tv_usec * 1000 |
| }; |
| |
| /* The mutex associated with the pthread conditional and flag MUST be |
| * acquired before attempting to wait for the condition */ |
| if (pthread_mutex_lock(&free_operation.completed_mutex)) |
| return 1; |
| |
| /* Free the client in a separate thread, so we can time the free operation */ |
| if (!pthread_create(&client_free_thread, NULL, |
| guacd_client_free_thread, &free_operation)) { |
| |
| /* Wait a finite amount of time for the free operation to finish */ |
| (void) pthread_cond_timedwait(&free_operation.completed_cond, |
| &free_operation.completed_mutex, &deadline); |
| } |
| |
| (void) pthread_mutex_unlock(&free_operation.completed_mutex); |
| |
| /* Return status of free operation */ |
| return !free_operation.completed; |
| } |
| |
| /** |
| * Starts protocol-specific handling on the given process by loading the client |
| * plugin for that protocol. This function does NOT return. It initializes the |
| * process with protocol-specific handlers and then runs until the guacd_proc's |
| * fd_socket is closed, adding any file descriptors received along fd_socket as |
| * new users. |
| * |
| * @param proc |
| * The process that any new users received along fd_socket should be added |
| * to (after the process has been initialized for the given protocol). |
| * |
| * @param protocol |
| * The protocol to initialize the given process for. |
| */ |
| static void guacd_exec_proc(guacd_proc* proc, const char* protocol) { |
| |
| int result = 1; |
| |
| /* Set process group ID to match PID */ |
| if (setpgid(0, 0)) { |
| guacd_log(GUAC_LOG_ERROR, "Cannot set PGID for connection process: %s", |
| strerror(errno)); |
| goto cleanup_process; |
| } |
| |
| /* Init client for selected protocol */ |
| guac_client* client = proc->client; |
| if (guac_client_load_plugin(client, protocol)) { |
| |
| /* Log error */ |
| if (guac_error == GUAC_STATUS_NOT_FOUND) |
| guacd_log(GUAC_LOG_WARNING, |
| "Support for protocol \"%s\" is not installed", protocol); |
| else |
| guacd_log_guac_error(GUAC_LOG_ERROR, |
| "Unable to load client plugin"); |
| |
| goto cleanup_client; |
| } |
| |
| /* The first file descriptor is the owner */ |
| int owner = 1; |
| |
| /* Add each received file descriptor as a new user */ |
| int received_fd; |
| while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) { |
| |
| guacd_proc_add_user(proc, received_fd, owner); |
| |
| /* Future file descriptors are not owners */ |
| owner = 0; |
| |
| } |
| |
| cleanup_client: |
| |
| /* Request client to stop/disconnect */ |
| guac_client_stop(client); |
| |
| /* Attempt to free client cleanly */ |
| guacd_log(GUAC_LOG_DEBUG, "Requesting termination of client..."); |
| result = guacd_timed_client_free(client, GUACD_CLIENT_FREE_TIMEOUT); |
| |
| /* If client was unable to be freed, warn and forcibly kill */ |
| if (result) { |
| guacd_log(GUAC_LOG_WARNING, "Client did not terminate in a timely " |
| "manner. Forcibly terminating client and any child " |
| "processes."); |
| guacd_kill_current_proc_group(); |
| } |
| else |
| guacd_log(GUAC_LOG_DEBUG, "Client terminated successfully."); |
| |
| /* Verify whether children were all properly reaped */ |
| pid_t child_pid; |
| while ((child_pid = waitpid(0, NULL, WNOHANG)) > 0) { |
| guacd_log(GUAC_LOG_DEBUG, "Automatically reaped unreaped " |
| "(zombie) child process with PID %i.", child_pid); |
| } |
| |
| /* If running children remain, warn and forcibly kill */ |
| if (child_pid == 0) { |
| guacd_log(GUAC_LOG_WARNING, "Client reported successful termination, " |
| "but child processes remain. Forcibly terminating client and " |
| "child processes."); |
| guacd_kill_current_proc_group(); |
| } |
| |
| cleanup_process: |
| |
| /* Free up all internal resources outside the client */ |
| close(proc->fd_socket); |
| free(proc); |
| |
| exit(result); |
| |
| } |
| |
| guacd_proc* guacd_create_proc(const char* protocol) { |
| |
| int sockets[2]; |
| |
| /* Open UNIX socket pair */ |
| if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) { |
| guacd_log(GUAC_LOG_ERROR, "Error opening socket pair: %s", strerror(errno)); |
| return NULL; |
| } |
| |
| int parent_socket = sockets[0]; |
| int child_socket = sockets[1]; |
| |
| /* Allocate process */ |
| guacd_proc* proc = calloc(1, sizeof(guacd_proc)); |
| if (proc == NULL) { |
| close(parent_socket); |
| close(child_socket); |
| return NULL; |
| } |
| |
| /* Associate new client */ |
| proc->client = guac_client_alloc(); |
| if (proc->client == NULL) { |
| guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to create client"); |
| close(parent_socket); |
| close(child_socket); |
| free(proc); |
| return NULL; |
| } |
| |
| /* Init logging */ |
| proc->client->log_handler = guacd_client_log; |
| |
| /* Fork */ |
| proc->pid = fork(); |
| if (proc->pid < 0) { |
| guacd_log(GUAC_LOG_ERROR, "Cannot fork child process: %s", strerror(errno)); |
| close(parent_socket); |
| close(child_socket); |
| guac_client_free(proc->client); |
| free(proc); |
| return NULL; |
| } |
| |
| /* Child */ |
| else if (proc->pid == 0) { |
| |
| /* Communicate with parent */ |
| proc->fd_socket = parent_socket; |
| close(child_socket); |
| |
| /* Start protocol-specific handling */ |
| guacd_exec_proc(proc, protocol); |
| |
| } |
| |
| /* Parent */ |
| else { |
| |
| /* Communicate with child */ |
| proc->fd_socket = child_socket; |
| close(parent_socket); |
| |
| } |
| |
| return proc; |
| |
| } |
| |
| void guacd_proc_stop(guacd_proc* proc) { |
| |
| /* Signal client to stop */ |
| guac_client_stop(proc->client); |
| |
| /* Shutdown socket - in-progress recvmsg() will not fail otherwise */ |
| if (shutdown(proc->fd_socket, SHUT_RDWR) == -1) |
| guacd_log(GUAC_LOG_ERROR, "Unable to shutdown internal socket for " |
| "connection %s. Corresponding process may remain running but " |
| "inactive.", proc->client->connection_id); |
| |
| /* Clean up our end of the socket */ |
| close(proc->fd_socket); |
| |
| } |
| |