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