| /* |
| * 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 <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.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); |
| |
| } |
| |
| /** |
| * 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) { |
| |
| /* Init client for selected protocol */ |
| if (guac_client_load_plugin(proc->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"); |
| |
| guac_client_free(proc->client); |
| close(proc->fd_socket); |
| free(proc); |
| exit(1); |
| } |
| |
| /* 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; |
| |
| } |
| |
| /* Stop and free client */ |
| guac_client_stop(proc->client); |
| guac_client_free(proc->client); |
| |
| /* Child is finished */ |
| close(proc->fd_socket); |
| free(proc); |
| exit(0); |
| |
| } |
| |
| 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); |
| |
| } |
| |