GUAC-1389: Restore guacd within build. Migrate guacd to screen sharing changes.

This commit is contained in:
Michael Jumper 2016-02-29 21:40:39 -08:00
parent 873a7a3211
commit 9e7663463f
18 changed files with 1397 additions and 697 deletions

View File

@ -38,6 +38,7 @@ DIST_SUBDIRS = \
SUBDIRS = \ SUBDIRS = \
src/libguac \ src/libguac \
src/common \ src/common \
src/guacd \
tests tests
if ENABLE_COMMON_SSH if ENABLE_COMMON_SSH

View File

@ -29,21 +29,27 @@ man_MANS = \
man/guacd.conf.5 man/guacd.conf.5
noinst_HEADERS = \ noinst_HEADERS = \
client.h \
client-map.h \
conf-args.h \ conf-args.h \
conf-file.h \ conf-file.h \
conf-parse.h \ conf-parse.h \
log.h connection.h \
log.h \
move-fd.h \
proc.h \
proc-map.h \
user.h
guacd_SOURCES = \ guacd_SOURCES = \
daemon.c \
client.c \
client-map.c \
conf-args.c \ conf-args.c \
conf-file.c \ conf-file.c \
conf-parse.c \ conf-parse.c \
log.c connection.c \
daemon.c \
log.c \
move-fd.c \
proc.c \
proc-map.c \
user.c
guacd_CFLAGS = \ guacd_CFLAGS = \
-Werror -Wall -pedantic \ -Werror -Wall -pedantic \

View File

@ -1,209 +0,0 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "client.h"
#include "log.h"
#include <guacamole/client.h>
#include <guacamole/error.h>
#include <guacamole/instruction.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/timestamp.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
/**
* Sleep for the given number of milliseconds.
*
* @param millis The number of milliseconds to sleep.
*/
void __guacdd_sleep(int millis) {
struct timespec sleep_period;
sleep_period.tv_sec = millis / 1000;
sleep_period.tv_nsec = (millis % 1000) * 1000000L;
nanosleep(&sleep_period, NULL);
}
void* __guacd_client_output_thread(void* data) {
guac_client* client = (guac_client*) data;
guac_socket* socket = client->socket;
guac_client_log(client, GUAC_LOG_DEBUG,
"Starting output thread.");
/* Guacamole client output loop */
while (client->state == GUAC_CLIENT_RUNNING) {
/* Handle server messages */
if (client->handle_messages) {
/* Only handle messages if synced within threshold */
if (client->last_sent_timestamp - client->last_received_timestamp
< GUACD_SYNC_THRESHOLD) {
int retval = client->handle_messages(client);
if (retval) {
guacd_client_log_guac_error(client, GUAC_LOG_DEBUG,
"Error handling server messages");
guac_client_stop(client);
return NULL;
}
/* Send sync instruction */
client->last_sent_timestamp = guac_timestamp_current();
if (guac_protocol_send_sync(socket, client->last_sent_timestamp)) {
guacd_client_log_guac_error(client, GUAC_LOG_DEBUG,
"Error sending \"sync\" instruction");
guac_client_stop(client);
return NULL;
}
/* Flush */
if (guac_socket_flush(socket)) {
guacd_client_log_guac_error(client, GUAC_LOG_DEBUG,
"Error flushing output");
guac_client_stop(client);
return NULL;
}
}
/* Do not spin while waiting for old sync */
else
__guacdd_sleep(GUACD_MESSAGE_HANDLE_FREQUENCY);
}
/* If no message handler, just sleep until next sync ping */
else
__guacdd_sleep(GUACD_SYNC_FREQUENCY);
} /* End of output loop */
guac_client_log(client, GUAC_LOG_DEBUG,
"Output thread terminated.");
guac_client_stop(client);
return NULL;
}
void* __guacd_client_input_thread(void* data) {
guac_client* client = (guac_client*) data;
guac_socket* socket = client->socket;
guac_client_log(client, GUAC_LOG_DEBUG,
"Starting input thread.");
/* Guacamole client input loop */
while (client->state == GUAC_CLIENT_RUNNING) {
/* Read instruction */
guac_instruction* instruction =
guac_instruction_read(socket, GUACD_USEC_TIMEOUT);
/* Stop on error */
if (instruction == NULL) {
if (guac_error == GUAC_STATUS_TIMEOUT)
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_TIMEOUT, "Client is not responding.");
else {
if (guac_error != GUAC_STATUS_CLOSED)
guacd_client_log_guac_error(client, GUAC_LOG_WARNING,
"Guacamole connection failure");
guac_client_stop(client);
}
return NULL;
}
/* Reset guac_error and guac_error_message (client handlers are not
* guaranteed to set these) */
guac_error = GUAC_STATUS_SUCCESS;
guac_error_message = NULL;
/* Call handler, stop on error */
if (guac_client_handle_instruction(client, instruction) < 0) {
/* Log error */
guacd_client_log_guac_error(client, GUAC_LOG_WARNING,
"Connection aborted");
/* Log handler details */
guac_client_log(client, GUAC_LOG_DEBUG,
"Failing instruction handler in client was \"%s\"",
instruction->opcode);
guac_instruction_free(instruction);
guac_client_stop(client);
return NULL;
}
/* Free allocated instruction */
guac_instruction_free(instruction);
}
guac_client_log(client, GUAC_LOG_DEBUG,
"Input thread terminated.");
return NULL;
}
int guacd_client_start(guac_client* client) {
pthread_t input_thread, output_thread;
if (pthread_create(&output_thread, NULL, __guacd_client_output_thread, (void*) client)) {
guac_client_log(client, GUAC_LOG_ERROR, "Unable to start output thread");
return -1;
}
if (pthread_create(&input_thread, NULL, __guacd_client_input_thread, (void*) client)) {
guac_client_log(client, GUAC_LOG_ERROR, "Unable to start input thread");
guac_client_stop(client);
pthread_join(output_thread, NULL);
return -1;
}
/* Wait for I/O threads */
pthread_join(input_thread, NULL);
pthread_join(output_thread, NULL);
/* Done */
return 0;
}

330
src/guacd/connection.c Normal file
View File

@ -0,0 +1,330 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "connection.h"
#include "log.h"
#include "move-fd.h"
#include "proc.h"
#include "proc-map.h"
#include "user.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>
#ifdef ENABLE_SSL
#include <openssl/ssl.h>
#include "socket-ssl.h"
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
/**
* Behaves exactly as write(), but writes as much as possible, returning
* successfully only if the entire buffer was written. If the write fails for
* any reason, a negative value is returned.
*/
static int __write_all(int fd, char* buffer, int length) {
/* Repeatedly write() until all data is written */
while (length > 0) {
int written = write(fd, buffer, length);
if (written < 0)
return -1;
length -= written;
buffer += written;
}
return length;
}
/**
* Continuously reads from a guac_socket, writing all data read to a file
* descriptor.
*/
static void* guacd_connection_write_thread(void* data) {
guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data;
char buffer[8192];
int length;
while ((length = guac_parser_shift(params->parser, buffer, sizeof(buffer))) > 0) {
if (__write_all(params->fd, buffer, length) < 0)
break;
}
/* Parser is no longer needed */
guac_parser_free(params->parser);
/* Transfer data from file descriptor to socket */
while ((length = guac_socket_read(params->socket, buffer, sizeof(buffer))) > 0) {
if (__write_all(params->fd, buffer, length) < 0)
break;
}
return NULL;
}
void* guacd_connection_io_thread(void* data) {
guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data;
char buffer[8192];
int length;
pthread_t write_thread;
pthread_create(&write_thread, NULL, guacd_connection_write_thread, params);
/* Transfer data from file descriptor to socket */
while ((length = read(params->fd, buffer, sizeof(buffer))) > 0) {
if (guac_socket_write(params->socket, buffer, length))
break;
guac_socket_flush(params->socket);
}
/* Wait for write thread to die */
pthread_join(write_thread, NULL);
/* Clean up */
guac_socket_free(params->socket);
close(params->fd);
free(params);
return NULL;
}
/**
* Adds the given socket as a new user to the given process, automatically
* reading/writing from the socket via read/write threads. The given socket and
* any associated resources will be freed unless the user is not added
* successfully.
*
* If adding the user fails for any reason, non-zero is returned. Zero is
* returned upon success.
*/
static int guacd_add_user(guacd_proc* proc, guac_parser* parser, guac_socket* socket) {
int sockets[2];
/* Set up socket pair */
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
guacd_log(GUAC_LOG_ERROR, "Unable to allocate file descriptors for I/O transfer: %s", strerror(errno));
return 1;
}
int user_fd = sockets[0];
int proc_fd = sockets[1];
/* Send user file descriptor to process */
if (!guacd_send_fd(proc->fd_socket, proc_fd)) {
guacd_log(GUAC_LOG_ERROR, "Unable to add user.");
return 1;
}
/* Close our end of the process file descriptor */
close(proc_fd);
guacd_connection_io_thread_params* params = malloc(sizeof(guacd_connection_io_thread_params));
params->parser = parser;
params->socket = socket;
params->fd = user_fd;
/* Start I/O thread */
pthread_t io_thread;
pthread_create(&io_thread, NULL, guacd_connection_io_thread, params);
pthread_detach(io_thread);
return 0;
}
/**
* Routes the connection on the given socket according to the Guacamole
* protocol on the given socket, adding new users and creating new client
* processes as needed.
*
* The socket provided will be automatically freed when the connection
* terminates unless routing fails, in which case non-zero is returned.
*/
static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) {
guac_parser* parser = guac_parser_alloc();
/* Reset guac_error */
guac_error = GUAC_STATUS_SUCCESS;
guac_error_message = NULL;
/* Get protocol from select instruction */
if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "select")) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG,
"Error reading \"select\"");
guac_parser_free(parser);
return 1;
}
/* Validate args to select */
if (parser->argc != 1) {
/* Log error */
guacd_log_handshake_failure();
guacd_log(GUAC_LOG_ERROR, "Bad number of arguments to \"select\" (%i)",
parser->argc);
guac_parser_free(parser);
return 1;
}
guacd_proc* proc;
int new_process;
const char* identifier = parser->argv[0];
/* If connection ID, retrieve existing process */
if (identifier[0] == GUAC_CLIENT_ID_PREFIX) {
proc = guacd_proc_map_retrieve(map, identifier);
if (proc == NULL)
guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist.", identifier);
else
guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"", identifier);
new_process = 0;
}
/* Otherwise, create new client */
else {
guacd_log(GUAC_LOG_INFO, "Creating new client for protocol \"%s\"", identifier);
proc = guacd_create_proc(parser, identifier);
new_process = 1;
}
if (proc == NULL) {
guacd_log_guac_error(GUAC_LOG_INFO, "Connection did not succeed");
guac_parser_free(parser);
return 1;
}
/* Add new user (in the case of a new process, this will be the owner */
if (guacd_add_user(proc, parser, socket) == 0) {
/* If new process was created, manage that process */
if (new_process) {
/* Log connection ID */
guacd_log(GUAC_LOG_INFO, "Connection ID is \"%s\"", proc->client->connection_id);
/* Store process, allowing other users to join */
guacd_proc_map_add(map, proc);
/* Wait for child to finish */
waitpid(proc->pid, NULL, 0);
/* Remove client */
if (guacd_proc_map_remove(map, proc->client->connection_id) == NULL)
guacd_log(GUAC_LOG_ERROR, "Internal failure removing client \"%s\". Client record will never be freed.",
proc->client->connection_id);
else
guacd_log(GUAC_LOG_INFO, "Connection \"%s\" removed.", proc->client->connection_id);
/* Free skeleton client */
guac_client_free(proc->client);
/* Clean up */
close(proc->fd_socket);
free(proc);
}
return 0;
}
/* Add of user failed */
else {
guac_parser_free(parser);
return 1;
}
}
void* guacd_connection_thread(void* data) {
guacd_connection_thread_params* params = (guacd_connection_thread_params*) data;
guacd_proc_map* map = params->map;
int connected_socket_fd = params->connected_socket_fd;
guac_socket* socket;
#ifdef ENABLE_SSL
SSL_CTX* ssl_context = params->ssl_context;
/* If SSL chosen, use it */
if (ssl_context != NULL) {
socket = guac_socket_open_secure(ssl_context, connected_socket_fd);
if (socket == NULL) {
guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to set up SSL/TLS");
free(params);
return NULL;
}
}
else
socket = guac_socket_open(connected_socket_fd);
#else
/* Open guac_socket */
socket = guac_socket_open(connected_socket_fd);
#endif
/* Route connection according to Guacamole, creating a new process if needed */
if (guacd_route_connection(map, socket))
guac_socket_free(socket);
free(params);
return NULL;
}

99
src/guacd/connection.h Normal file
View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACD_CONNECTION_H
#define GUACD_CONNECTION_H
#include "config.h"
#include "proc-map.h"
#ifdef ENABLE_SSL
#include <openssl/ssl.h>
#endif
/**
* Parameters required by each connection thread.
*/
typedef struct guacd_connection_thread_params {
/**
* The shared map of all connected clients.
*/
guacd_proc_map* map;
#ifdef ENABLE_SSL
/**
* SSL context for encrypted connections to guacd. If SSL is not active,
* this will be NULL.
*/
SSL_CTX* ssl_context;
#endif
/**
* The file descriptor associated with the newly-accepted connection.
*/
int connected_socket_fd;
} guacd_connection_thread_params;
/**
* Handles an inbound connection to guacd, allowing guacd to continue listening
* for other connections. It is expected that this thread will operate
* detached. The creating process need not join on the resulting thread.
*/
void* guacd_connection_thread(void* data);
/**
* Parameters required by the per-connection I/O transfer thread.
*/
typedef struct guacd_connection_io_thread_params {
/**
* The guac_parser which may contain buffered, but unparsed, data from the
* original guac_socket which must be transferred to the
* connection-specific process.
*/
guac_parser* parser;
/**
* The guac_socket which is directly handling I/O from a user's connection
* to guacd.
*/
guac_socket* socket;
/**
* The file descriptor which is being handled by a guac_socket within the
* connection-specific process.
*/
int fd;
} guacd_connection_io_thread_params;
/**
* Transfers data back and forth between the guacd-side guac_socket and the
* file descriptor used by the process-side guac_socket.
*/
void* guacd_connection_io_thread(void* data);
#endif

View File

@ -22,22 +22,15 @@
#include "config.h" #include "config.h"
#include "client.h" #include "connection.h"
#include "client-map.h"
#include "conf-args.h" #include "conf-args.h"
#include "conf-file.h" #include "conf-file.h"
#include "log.h" #include "log.h"
#include "proc-map.h"
#include <guacamole/client.h> #include "user.h"
#include <guacamole/error.h>
#include <guacamole/instruction.h>
#include <guacamole/plugin.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#ifdef ENABLE_SSL #ifdef ENABLE_SSL
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include "socket-ssl.h"
#endif #endif
#include <errno.h> #include <errno.h>
@ -59,347 +52,10 @@
#define GUACD_ROOT "/" #define GUACD_ROOT "/"
/** /**
* Logs a reasonable explanatory message regarding handshake failure based on * Redirects the given file descriptor to /dev/null. The given flags must match
* the current value of guac_error. * the read/write flags of the file descriptor given.
*/ */
static void guacd_log_handshake_failure() { static int redirect_fd(int fd, int flags) {
if (guac_error == GUAC_STATUS_CLOSED)
guacd_log(GUAC_LOG_INFO,
"Guacamole connection closed during handshake");
else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR)
guacd_log(GUAC_LOG_ERROR,
"Guacamole protocol violation. Perhaps the version of "
"guacamole-client is incompatible with this version of "
"guacd?");
else
guacd_log(GUAC_LOG_WARNING,
"Guacamole handshake failed: %s",
guac_status_string(guac_error));
}
/**
* Copies the given array of mimetypes (strings) into a newly-allocated NULL-
* terminated array of strings. Both the array and the strings within the array
* are newly-allocated and must be later freed via guacd_free_mimetypes().
*
* @param mimetypes
* The array of mimetypes to copy.
*
* @param count
* The number of mimetypes in the given array.
*
* @return
* A newly-allocated, NULL-terminated array containing newly-allocated
* copies of each of the mimetypes provided in the original mimetypes
* array.
*/
static char** guacd_copy_mimetypes(char** mimetypes, int count) {
int i;
/* Allocate sufficient space for NULL-terminated array of mimetypes */
char** mimetypes_copy = malloc(sizeof(char*) * (count+1));
/* Copy each provided mimetype */
for (i = 0; i < count; i++)
mimetypes_copy[i] = strdup(mimetypes[i]);
/* Terminate with NULL */
mimetypes_copy[count] = NULL;
return mimetypes_copy;
}
/**
* Frees the given array of mimetypes, including the space allocated to each
* mimetype string within the array. The provided array of mimetypes MUST have
* been allocated with guacd_copy_mimetypes().
*
* @param mimetypes
* The NULL-terminated array of mimetypes to free. This array MUST have
* been previously allocated with guacd_copy_mimetypes().
*/
static void guacd_free_mimetypes(char** mimetypes) {
char** current_mimetype = mimetypes;
/* Free all strings within NULL-terminated mimetype array */
while (*current_mimetype != NULL) {
free(*current_mimetype);
current_mimetype++;
}
/* Free the array itself, now that its contents have been freed */
free(mimetypes);
}
/**
* Creates a new guac_client for the connection on the given socket, adding
* it to the client map based on its ID.
*/
static void guacd_handle_connection(guacd_client_map* map, guac_socket* socket) {
guac_client* client;
guac_client_plugin* plugin;
guac_instruction* select;
guac_instruction* size;
guac_instruction* audio;
guac_instruction* video;
guac_instruction* image;
guac_instruction* connect;
int init_result;
/* Reset guac_error */
guac_error = GUAC_STATUS_SUCCESS;
guac_error_message = NULL;
/* Get protocol from select instruction */
select = guac_instruction_expect(socket, GUACD_USEC_TIMEOUT, "select");
if (select == NULL) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG,
"Error reading \"select\"");
/* Free resources */
guac_socket_free(socket);
return;
}
/* Validate args to select */
if (select->argc != 1) {
/* Log error */
guacd_log_handshake_failure();
guacd_log(GUAC_LOG_ERROR, "Bad number of arguments to \"select\" (%i)",
select->argc);
/* Free resources */
guac_socket_free(socket);
return;
}
guacd_log(GUAC_LOG_INFO, "Protocol \"%s\" selected", select->argv[0]);
/* Get plugin from protocol in select */
plugin = guac_client_plugin_open(select->argv[0]);
guac_instruction_free(select);
if (plugin == NULL) {
/* Log error */
if (guac_error == GUAC_STATUS_NOT_FOUND)
guacd_log(GUAC_LOG_WARNING,
"Support for selected protocol is not installed");
else
guacd_log_guac_error(GUAC_LOG_ERROR,
"Unable to load client plugin");
/* Free resources */
guac_socket_free(socket);
return;
}
/* Send args response */
if (guac_protocol_send_args(socket, plugin->args)
|| guac_socket_flush(socket)) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error sending \"args\"");
if (guac_client_plugin_close(plugin))
guacd_log_guac_error(GUAC_LOG_WARNING,
"Unable to close client plugin");
guac_socket_free(socket);
return;
}
/* Get client */
client = guac_client_alloc();
if (client == NULL) {
guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to create client");
guac_socket_free(socket);
return;
}
/* Get optimal screen size */
size = guac_instruction_expect(
socket, GUACD_USEC_TIMEOUT, "size");
if (size == NULL) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"size\"");
/* Free resources */
guac_client_free(client);
guac_socket_free(socket);
return;
}
/* Parse optimal screen dimensions from size instruction */
client->info.optimal_width = atoi(size->argv[0]);
client->info.optimal_height = atoi(size->argv[1]);
/* If DPI given, set the client resolution */
if (size->argc >= 3)
client->info.optimal_resolution = atoi(size->argv[2]);
/* Otherwise, use a safe default for rough backwards compatibility */
else
client->info.optimal_resolution = 96;
guac_instruction_free(size);
/* Get supported audio formats */
audio = guac_instruction_expect(
socket, GUACD_USEC_TIMEOUT, "audio");
if (audio == NULL) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"audio\"");
/* Free resources */
guac_client_free(client);
guac_socket_free(socket);
return;
}
/* Store audio mimetypes */
client->info.audio_mimetypes =
guacd_copy_mimetypes(audio->argv, audio->argc);
guac_instruction_free(audio);
/* Get supported video formats */
video = guac_instruction_expect(
socket, GUACD_USEC_TIMEOUT, "video");
if (video == NULL) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"video\"");
/* Free resources */
guac_client_free(client);
guac_socket_free(socket);
return;
}
/* Store video mimetypes */
client->info.video_mimetypes =
guacd_copy_mimetypes(video->argv, video->argc);
guac_instruction_free(video);
/* Get supported image formats */
image = guac_instruction_expect(
socket, GUACD_USEC_TIMEOUT, "image");
if (image == NULL) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"image\"");
/* Free resources */
guac_client_free(client);
guac_socket_free(socket);
return;
}
/* Store image mimetypes */
client->info.image_mimetypes =
guacd_copy_mimetypes(image->argv, image->argc);
guac_instruction_free(image);
/* Get args from connect instruction */
connect = guac_instruction_expect(
socket, GUACD_USEC_TIMEOUT, "connect");
if (connect == NULL) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"connect\"");
if (guac_client_plugin_close(plugin))
guacd_log_guac_error(GUAC_LOG_WARNING,
"Unable to close client plugin");
guac_client_free(client);
guac_socket_free(socket);
return;
}
client->socket = socket;
client->log_handler = guacd_client_log;
/* Store client */
if (guacd_client_map_add(map, client))
guacd_log(GUAC_LOG_ERROR, "Unable to add client. Internal client storage has failed");
/* Send connection ID */
guacd_log(GUAC_LOG_INFO, "Connection ID is \"%s\"", client->connection_id);
guac_protocol_send_ready(socket, client->connection_id);
/* Init client */
init_result = guac_client_plugin_init_client(plugin,
client, connect->argc, connect->argv);
guac_instruction_free(connect);
/* If client could not be started, free everything and fail */
if (init_result) {
guac_client_free(client);
guacd_log_guac_error(GUAC_LOG_INFO, "Connection did not succeed");
if (guac_client_plugin_close(plugin))
guacd_log_guac_error(GUAC_LOG_WARNING,
"Unable to close client plugin");
guac_socket_free(socket);
return;
}
/* Start client threads */
guacd_log(GUAC_LOG_INFO, "Starting client");
if (guacd_client_start(client))
guacd_log(GUAC_LOG_WARNING, "Client finished abnormally");
else
guacd_log(GUAC_LOG_INFO, "Client disconnected");
/* Remove client */
if (guacd_client_map_remove(map, client->connection_id) == NULL)
guacd_log(GUAC_LOG_ERROR, "Unable to remove client. Internal client storage has failed");
/* Free mimetype lists */
guacd_free_mimetypes(client->info.audio_mimetypes);
guacd_free_mimetypes(client->info.video_mimetypes);
guacd_free_mimetypes(client->info.image_mimetypes);
/* Clean up */
guac_client_free(client);
if (guac_client_plugin_close(plugin))
guacd_log_guac_error(GUAC_LOG_WARNING,
"Unable to close client plugin");
/* Close socket */
guac_socket_free(socket);
}
int redirect_fd(int fd, int flags) {
/* Attempt to open bit bucket */ /* Attempt to open bit bucket */
int new_fd = open(GUACD_DEV_NULL, flags); int new_fd = open(GUACD_DEV_NULL, flags);
@ -416,7 +72,10 @@ int redirect_fd(int fd, int flags) {
} }
int daemonize() { /**
* Turns the current process into a daemon through a series of fork() calls.
*/
static int daemonize() {
pid_t pid; pid_t pid;
@ -499,7 +158,7 @@ int main(int argc, char* argv[]) {
SSL_CTX* ssl_context = NULL; SSL_CTX* ssl_context = NULL;
#endif #endif
guacd_client_map* map = guacd_client_map_alloc(); guacd_proc_map* map = guacd_proc_map_alloc();
/* General */ /* General */
int retval; int retval;
@ -675,7 +334,7 @@ int main(int argc, char* argv[]) {
/* Daemon loop */ /* Daemon loop */
for (;;) { for (;;) {
pid_t child_pid; pthread_t child_thread;
/* Accept connection */ /* Accept connection */
client_addr_len = sizeof(client_addr); client_addr_len = sizeof(client_addr);
@ -683,59 +342,27 @@ int main(int argc, char* argv[]) {
(struct sockaddr*) &client_addr, &client_addr_len); (struct sockaddr*) &client_addr, &client_addr_len);
if (connected_socket_fd < 0) { if (connected_socket_fd < 0) {
guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno));
strerror(errno)); continue;
return 3;
} }
/* /* Create parameters for connection thread */
* Once connection is accepted, send child into background. guacd_connection_thread_params* params = malloc(sizeof(guacd_connection_thread_params));
* if (params == NULL) {
* Note that we prefer fork() over threads for connection-handling guacd_log(GUAC_LOG_ERROR, "Could not create connection thread: %s", strerror(errno));
* processes as they give each connection its own memory area, and continue;
* isolate the main daemon and other connections from errors in any }
* particular client plugin.
*/
child_pid = fork(); params->map = map;
params->connected_socket_fd = connected_socket_fd;
/* If error, log */
if (child_pid == -1)
guacd_log(GUAC_LOG_ERROR, "Error forking child process: %s", strerror(errno));
/* If child, start client, and exit when finished */
else if (child_pid == 0) {
guac_socket* socket;
#ifdef ENABLE_SSL #ifdef ENABLE_SSL
params->ssl_context = ssl_context;
/* If SSL chosen, use it */
if (ssl_context != NULL) {
socket = guac_socket_open_secure(ssl_context, connected_socket_fd);
if (socket == NULL) {
guacd_log_guac_error(GUAC_LOG_ERROR,
"Unable to set up SSL/TLS");
return 0;
}
}
else
socket = guac_socket_open(connected_socket_fd);
#else
/* Open guac_socket */
socket = guac_socket_open(connected_socket_fd);
#endif #endif
guacd_handle_connection(map, socket); /* Spawn thread to handle connection */
close(connected_socket_fd); pthread_create(&child_thread, NULL, guacd_connection_thread, params);
return 0; pthread_detach(child_thread);
}
/* If parent, close reference to child's descriptor */
else if (close(connected_socket_fd) < 0) {
guacd_log(GUAC_LOG_ERROR, "Error closing daemon reference to "
"child descriptor: %s", strerror(errno));
}
} }

View File

@ -152,3 +152,20 @@ void guacd_client_log_guac_error(guac_client* client,
} }
void guacd_log_handshake_failure() {
if (guac_error == GUAC_STATUS_CLOSED)
guacd_log(GUAC_LOG_INFO,
"Guacamole connection closed during handshake");
else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR)
guacd_log(GUAC_LOG_ERROR,
"Guacamole protocol violation. Perhaps the version of "
"guacamole-client is incompatible with this version of "
"guacd?");
else
guacd_log(GUAC_LOG_WARNING,
"Guacamole handshake failed: %s",
guac_status_string(guac_error));
}

View File

@ -73,5 +73,11 @@ void guacd_log_guac_error(guac_client_log_level level, const char* message);
void guacd_client_log_guac_error(guac_client* client, void guacd_client_log_guac_error(guac_client* client,
guac_client_log_level level, const char* message); guac_client_log_level level, const char* message);
/**
* Logs a reasonable explanatory message regarding handshake failure based on
* the current value of guac_error.
*/
void guacd_log_handshake_failure();
#endif #endif

115
src/guacd/move-fd.c Normal file
View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "move-fd.h"
/* Required for CMSG_* macros on BSD */
#define __BSD_VISIBLE 1
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
int guacd_send_fd(int sock, int fd) {
struct msghdr message = {0};
char message_data[] = {'G'};
/* Assign data buffer */
struct iovec io_vector[1];
io_vector[0].iov_base = message_data;
io_vector[0].iov_len = sizeof(message_data);
message.msg_iov = io_vector;
message.msg_iovlen = 1;
/* Assign ancillary data buffer */
char buffer[CMSG_SPACE(sizeof(fd))];
message.msg_control = buffer;
message.msg_controllen = sizeof(buffer);
/* Set fields of control message header */
struct cmsghdr* control = CMSG_FIRSTHDR(&message);
control->cmsg_level = SOL_SOCKET;
control->cmsg_type = SCM_RIGHTS;
control->cmsg_len = CMSG_LEN(sizeof(fd));
/* Add file descriptor to message data */
memcpy(CMSG_DATA(control), &fd, sizeof(fd));
/* Send file descriptor */
return (sendmsg(sock, &message, 0) == sizeof(message_data));
}
int guacd_recv_fd(int sock) {
int fd;
struct msghdr message = {0};
char message_data[1];
/* Assign data buffer */
struct iovec io_vector[1];
io_vector[0].iov_base = message_data;
io_vector[0].iov_len = sizeof(message_data);
message.msg_iov = io_vector;
message.msg_iovlen = 1;
/* Assign ancillary data buffer */
char buffer[CMSG_SPACE(sizeof(fd))];
message.msg_control = buffer;
message.msg_controllen = sizeof(buffer);
/* Receive file descriptor */
if (recvmsg(sock, &message, 0) == sizeof(message_data)) {
/* Validate payload */
if (message_data[0] != 'G') {
errno = EPROTO;
return -1;
}
/* Iterate control headers, looking for the sent file descriptor */
struct cmsghdr* control;
for (control = CMSG_FIRSTHDR(&message); control != NULL; control = CMSG_NXTHDR(&message, control)) {
/* Pull file descriptor from data */
if (control->cmsg_level == SOL_SOCKET && control->cmsg_type == SCM_RIGHTS) {
memcpy(&fd, CMSG_DATA(control), sizeof(fd));
return fd;
}
}
} /* end if recvmsg() success */
/* Failed to receive file descriptor */
return -1;
}

43
src/guacd/move-fd.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACD_MOVE_FD_H
#define GUACD_MOVE_FD_H
#include "config.h"
/**
* Sends the given file descriptor along the given socket. The file descriptor
* must have been sent via guacd_recv_fd. Returns non-zero on success, zero on
* error. If an error does occur, errno will be set appropriately.
*/
int guacd_send_fd(int sock, int fd);
/**
* Waits for a file descriptor on the given socket, returning the received file
* descriptor. If an error occurs, -1 is returned, and errno will be set
* appropriately.
*/
int guacd_recv_fd(int sock);
#endif

View File

@ -21,9 +21,10 @@
*/ */
#include "config.h" #include "config.h"
#include "client.h"
#include "client-map.h"
#include "guac_list.h" #include "guac_list.h"
#include "proc.h"
#include "proc-map.h"
#include "user.h"
#include <guacamole/client.h> #include <guacamole/client.h>
@ -50,18 +51,19 @@ static unsigned int __guacd_client_hash(const char* str) {
* Locates the bucket corresponding to the hash code indicated by the give id. * Locates the bucket corresponding to the hash code indicated by the give id.
* Each bucket is an instance of guac_common_list. * Each bucket is an instance of guac_common_list.
*/ */
static guac_common_list* __guacd_client_find_bucket(guacd_client_map* map, const char* id) { static guac_common_list* __guacd_proc_find_bucket(guacd_proc_map* map, const char* id) {
const int index = __guacd_client_hash(id) % GUACD_CLIENT_MAP_BUCKETS; const int index = __guacd_client_hash(id) % GUACD_PROC_MAP_BUCKETS;
return map->__buckets[index]; return map->__buckets[index];
} }
/** /**
* Given a list of guac_client instances, returns the guac_client having the * Given a list of guacd_proc instances, returns the
* given ID, or NULL if no such client is stored. * guacd_proc having the guac_client with the given ID, or NULL if no
* such client is stored.
*/ */
static guac_common_list_element* __guacd_client_find(guac_common_list* bucket, const char* id) { static guac_common_list_element* __guacd_proc_find(guac_common_list* bucket, const char* id) {
guac_common_list_element* current = bucket->head; guac_common_list_element* current = bucket->head;
@ -69,8 +71,8 @@ static guac_common_list_element* __guacd_client_find(guac_common_list* bucket, c
while (current != NULL) { while (current != NULL) {
/* Check connection ID */ /* Check connection ID */
guac_client* client = (guac_client*) current->data; guacd_proc* proc = (guacd_proc*) current->data;
if (strcmp(client->connection_id, id) == 0) if (strcmp(proc->client->connection_id, id) == 0)
break; break;
current = current->next; current = current->next;
@ -80,9 +82,9 @@ static guac_common_list_element* __guacd_client_find(guac_common_list* bucket, c
} }
guacd_client_map* guacd_client_map_alloc() { guacd_proc_map* guacd_proc_map_alloc() {
guacd_client_map* map = malloc(sizeof(guacd_client_map)); guacd_proc_map* map = malloc(sizeof(guacd_proc_map));
guac_common_list** current; guac_common_list** current;
int i; int i;
@ -90,7 +92,7 @@ guacd_client_map* guacd_client_map_alloc() {
/* Init all buckets */ /* Init all buckets */
current = map->__buckets; current = map->__buckets;
for (i=0; i<GUACD_CLIENT_MAP_BUCKETS; i++) { for (i=0; i<GUACD_PROC_MAP_BUCKETS; i++) {
*current = guac_common_list_alloc(); *current = guac_common_list_alloc();
current++; current++;
} }
@ -99,18 +101,19 @@ guacd_client_map* guacd_client_map_alloc() {
} }
int guacd_client_map_add(guacd_client_map* map, guac_client* client) { int guacd_proc_map_add(guacd_proc_map* map, guacd_proc* proc) {
guac_common_list* bucket = __guacd_client_find_bucket(map, client->connection_id); const char* identifier = proc->client->connection_id;
guac_common_list* bucket = __guacd_proc_find_bucket(map, identifier);
guac_common_list_element* found; guac_common_list_element* found;
/* Retrieve corresponding element, if any */ /* Retrieve corresponding element, if any */
guac_common_list_lock(bucket); guac_common_list_lock(bucket);
found = __guacd_client_find(bucket, client->connection_id); found = __guacd_proc_find(bucket, identifier);
/* If no such element, we can add the new client successfully */ /* If no such element, we can add the new client successfully */
if (found == NULL) { if (found == NULL) {
guac_common_list_add(bucket, client); guac_common_list_add(bucket, proc);
guac_common_list_unlock(bucket); guac_common_list_unlock(bucket);
return 0; return 0;
} }
@ -121,16 +124,16 @@ int guacd_client_map_add(guacd_client_map* map, guac_client* client) {
} }
guac_client* guacd_client_map_retrieve(guacd_client_map* map, const char* id) { guacd_proc* guacd_proc_map_retrieve(guacd_proc_map* map, const char* id) {
guac_client* client; guacd_proc* proc;
guac_common_list* bucket = __guacd_client_find_bucket(map, id); guac_common_list* bucket = __guacd_proc_find_bucket(map, id);
guac_common_list_element* found; guac_common_list_element* found;
/* Retrieve corresponding element, if any */ /* Retrieve corresponding element, if any */
guac_common_list_lock(bucket); guac_common_list_lock(bucket);
found = __guacd_client_find(bucket, id); found = __guacd_proc_find(bucket, id);
/* If no such element, fail */ /* If no such element, fail */
if (found == NULL) { if (found == NULL) {
@ -138,23 +141,23 @@ guac_client* guacd_client_map_retrieve(guacd_client_map* map, const char* id) {
return NULL; return NULL;
} }
client = (guac_client*) found->data; proc = (guacd_proc*) found->data;
guac_common_list_unlock(bucket); guac_common_list_unlock(bucket);
return client; return proc;
} }
guac_client* guacd_client_map_remove(guacd_client_map* map, const char* id) { guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id) {
guac_client* client; guacd_proc* proc;
guac_common_list* bucket = __guacd_client_find_bucket(map, id); guac_common_list* bucket = __guacd_proc_find_bucket(map, id);
guac_common_list_element* found; guac_common_list_element* found;
/* Retrieve corresponding element, if any */ /* Retrieve corresponding element, if any */
guac_common_list_lock(bucket); guac_common_list_lock(bucket);
found = __guacd_client_find(bucket, id); found = __guacd_proc_find(bucket, id);
/* If no such element, fail */ /* If no such element, fail */
if (found == NULL) { if (found == NULL) {
@ -162,11 +165,11 @@ guac_client* guacd_client_map_remove(guacd_client_map* map, const char* id) {
return NULL; return NULL;
} }
client = (guac_client*) found->data; proc = (guacd_proc*) found->data;
guac_common_list_remove(bucket, found); guac_common_list_remove(bucket, found);
guac_common_list_unlock(bucket); guac_common_list_unlock(bucket);
return client; return proc;
} }

View File

@ -21,53 +21,58 @@
*/ */
#ifndef _GUACD_CLIENT_MAP_H #ifndef _GUACD_PROC_MAP_H
#define _GUACD_CLIENT_MAP_H #define _GUACD_PROC_MAP_H
#include "config.h" #include "config.h"
#include "client.h"
#include "guac_list.h" #include "guac_list.h"
#include "proc.h"
#include "user.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#define GUACD_CLIENT_MAP_BUCKETS GUACD_CLIENT_MAX_CONNECTIONS*2 /**
* The number of hash buckets in each process map.
*/
#define GUACD_PROC_MAP_BUCKETS GUACD_CLIENT_MAX_CONNECTIONS*2
/** /**
* Set of all active connections to guacd, indexed by connection ID. * Set of all active connections to guacd, indexed by connection ID.
*/ */
typedef struct guacd_client_map { typedef struct guacd_proc_map {
/** /**
* Internal hash buckets. Each bucket is a linked list containing all * Internal hash buckets. Each bucket is a linked list containing all
* guac_client instances which hash to this bucket location. * guac_client instances which hash to this bucket location.
*/ */
guac_common_list* __buckets[GUACD_CLIENT_MAP_BUCKETS]; guac_common_list* __buckets[GUACD_PROC_MAP_BUCKETS];
} guacd_client_map; } guacd_proc_map;
/** /**
* Allocates a new client map, which persists for the life of guacd. * Allocates a new client process map, which persists for the life of guacd.
*/ */
guacd_client_map* guacd_client_map_alloc(); guacd_proc_map* guacd_proc_map_alloc();
/** /**
* Adds the given client to the given client map. On success, zero is returned. * Adds the given process to the client process map. On success, zero is returned. If
* If adding the client fails (due to lack of space, or duplicate ID), a * adding the client fails (due to lack of space, or duplicate ID), a non-zero
* non-zero value is returned instead. * value is returned instead. The client process is stored by the connection ID
* of the underlying guac_client.
*/ */
int guacd_client_map_add(guacd_client_map* map, guac_client* client); int guacd_proc_map_add(guacd_proc_map* map, guacd_proc* proc);
/** /**
* Retrieves the client having the given ID, or NULL if no such client * Retrieves the client process having the client with the given ID, or NULL if
* is stored. * no such process is stored.
*/ */
guac_client* guacd_client_map_retrieve(guacd_client_map* map, const char* id); guacd_proc* guacd_proc_map_retrieve(guacd_proc_map* map, const char* id);
/** /**
* Removes the client having the given ID, returning the corresponding client. * Removes the client process having the client with the given ID, returning
* If no such client exists, NULL is returned. * the corresponding process. If no such process exists, NULL is returned.
*/ */
guac_client* guacd_client_map_remove(guacd_client_map* map, const char* id); guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id);
#endif #endif

458
src/guacd/proc.c Normal file
View File

@ -0,0 +1,458 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "log.h"
#include "move-fd.h"
#include "proc.h"
#include "proc-map.h"
#include "user.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>
/**
* Copies the given array of mimetypes (strings) into a newly-allocated NULL-
* terminated array of strings. Both the array and the strings within the array
* are newly-allocated and must be later freed via guacd_free_mimetypes().
*
* @param mimetypes
* The array of mimetypes to copy.
*
* @param count
* The number of mimetypes in the given array.
*
* @return
* A newly-allocated, NULL-terminated array containing newly-allocated
* copies of each of the mimetypes provided in the original mimetypes
* array.
*/
static char** guacd_copy_mimetypes(char** mimetypes, int count) {
int i;
/* Allocate sufficient space for NULL-terminated array of mimetypes */
char** mimetypes_copy = malloc(sizeof(char*) * (count+1));
/* Copy each provided mimetype */
for (i = 0; i < count; i++)
mimetypes_copy[i] = strdup(mimetypes[i]);
/* Terminate with NULL */
mimetypes_copy[count] = NULL;
return mimetypes_copy;
}
/**
* Frees the given array of mimetypes, including the space allocated to each
* mimetype string within the array. The provided array of mimetypes MUST have
* been allocated with guacd_copy_mimetypes().
*
* @param mimetypes
* The NULL-terminated array of mimetypes to free. This array MUST have
* been previously allocated with guacd_copy_mimetypes().
*/
static void guacd_free_mimetypes(char** mimetypes) {
char** current_mimetype = mimetypes;
/* Free all strings within NULL-terminated mimetype array */
while (*current_mimetype != NULL) {
free(*current_mimetype);
current_mimetype++;
}
/* Free the array itself, now that its contents have been freed */
free(mimetypes);
}
/**
* Handles the initial handshake of a user and all subsequent I/O.
*/
static int guacd_handle_user(guac_user* user) {
guac_socket* socket = user->socket;
guac_client* client = user->client;
/* Send args */
if (guac_protocol_send_args(socket, client->args)
|| guac_socket_flush(socket)) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error sending \"args\" to new user");
return 1;
}
guac_parser* parser = guac_parser_alloc();
/* Get optimal screen size */
if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "size")) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"size\"");
guac_parser_free(parser);
return 1;
}
/* Validate content of size instruction */
if (parser->argc < 2) {
guacd_log(GUAC_LOG_ERROR, "Received \"size\" instruction lacked required arguments.");
guac_parser_free(parser);
return 1;
}
/* Parse optimal screen dimensions from size instruction */
user->info.optimal_width = atoi(parser->argv[0]);
user->info.optimal_height = atoi(parser->argv[1]);
/* If DPI given, set the client resolution */
if (parser->argc >= 3)
user->info.optimal_resolution = atoi(parser->argv[2]);
/* Otherwise, use a safe default for rough backwards compatibility */
else
user->info.optimal_resolution = 96;
/* Get supported audio formats */
if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "audio")) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"audio\"");
guac_parser_free(parser);
return 1;
}
/* Store audio mimetypes */
char** audio_mimetypes = guacd_copy_mimetypes(parser->argv, parser->argc);
user->info.audio_mimetypes = (const char**) audio_mimetypes;
/* Get supported video formats */
if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "video")) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"video\"");
guac_parser_free(parser);
return 1;
}
/* Store video mimetypes */
char** video_mimetypes = guacd_copy_mimetypes(parser->argv, parser->argc);
user->info.video_mimetypes = (const char**) video_mimetypes;
/* Get supported image formats */
if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "image")) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"image\"");
guac_parser_free(parser);
return 1;
}
/* Store image mimetypes */
char** image_mimetypes = guacd_copy_mimetypes(parser->argv, parser->argc);
user->info.image_mimetypes = (const char**) image_mimetypes;
/* Get args from connect instruction */
if (guac_parser_expect(parser, socket, GUACD_USEC_TIMEOUT, "connect")) {
/* Log error */
guacd_log_handshake_failure();
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"connect\"");
guac_parser_free(parser);
return 1;
}
/* Acknowledge connection availability */
guac_protocol_send_ready(socket, client->connection_id);
guac_socket_flush(socket);
/* Attempt join */
if (guac_client_add_user(client, user, parser->argc, parser->argv))
guacd_log(GUAC_LOG_ERROR, "User \"%s\" could NOT join connection \"%s\"", user->user_id, client->connection_id);
/* Begin user connection if join successful */
else {
guacd_log(GUAC_LOG_INFO, "User \"%s\" joined connection \"%s\" (%i users now present)",
user->user_id, client->connection_id, client->connected_users);
/* Handle user I/O, wait for connection to terminate */
guacd_user_start(parser, user);
/* Remove/free user */
guac_client_remove_user(client, user);
guacd_log(GUAC_LOG_INFO, "User \"%s\" disconnected (%i users remain)", user->user_id, client->connected_users);
}
/* Free mimetype lists */
guacd_free_mimetypes(audio_mimetypes);
guacd_free_mimetypes(video_mimetypes);
guacd_free_mimetypes(image_mimetypes);
guac_parser_free(parser);
/* Successful disconnect */
return 0;
}
/**
* 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.
*/
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 */
guacd_handle_user(user);
/* 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.
*/
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. This function does
* NOT return. It initializes the process with protocol-specific handlers and
* then runs until the fd_socket is closed.
*/
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(guac_parser* parser, 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);
}

80
src/guacd/proc.h Normal file
View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef GUACD_PROC_H
#define GUACD_PROC_H
#include "config.h"
#include <guacamole/client.h>
#include <guacamole/parser.h>
#include <unistd.h>
/**
* Process information of the internal remote desktop client.
*/
typedef struct guacd_proc {
/**
* The process ID of the client. This will only be available to the
* parent process. The child process will see this as 0.
*/
pid_t pid;
/**
* The file descriptor of the UNIX domain socket to use for sending and
* receiving file descriptors of new users. This parent will see this
* as the file descriptor for communicating with the child and vice
* versa.
*/
int fd_socket;
/**
* The actual client instance. This will be visible to both child and
* parent process, but only the child will have a full guac_client
* instance, containing handlers from the plugin, etc.
*
* The parent process will receive a skeleton guac_client, containing only
* a proper connection_id and logging handlers. The actual
* protocol-specific handling will be absent.
*/
guac_client* client;
} guacd_proc;
/**
* Creates a new process for handling the given protocol, returning the process
* created. The created process runs in the background relative to the calling
* process. Within the child process, this function does not return - the
* entire child process simply terminates instead.
*/
guacd_proc* guacd_create_proc(guac_parser* parser, const char* protocol);
/**
* Signals the given process to stop accepting new users and clean up. This
* will eventually cause the child process to exit.
*/
void guacd_proc_stop(guacd_proc* proc);
#endif

View File

@ -113,6 +113,9 @@ static int __guac_socket_ssl_free_handler(guac_socket* socket) {
guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data; guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data;
SSL_shutdown(data->ssl); SSL_shutdown(data->ssl);
/* Close file descriptor */
close(data->fd);
free(data); free(data);
return 0; return 0;
} }

View File

@ -54,7 +54,8 @@ typedef struct guac_socket_ssl_data {
} guac_socket_ssl_data; } guac_socket_ssl_data;
/** /**
* Creates a new guac_socket which will use SSL for all communication. * Creates a new guac_socket which will use SSL for all communication. Freeing
* this guac_socket will automatically close the associated file descriptor.
*/ */
guac_socket* guac_socket_open_secure(SSL_CTX* context, int fd); guac_socket* guac_socket_open_secure(SSL_CTX* context, int fd);

112
src/guacd/user.c Normal file
View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h"
#include "log.h"
#include "user.h"
#include <guacamole/client.h>
#include <guacamole/error.h>
#include <guacamole/parser.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <stdlib.h>
void* guacd_user_input_thread(void* data) {
guacd_user_input_thread_params* params = (guacd_user_input_thread_params*) data;
guac_user* user = params->user;
guac_parser* parser = params->parser;
guac_client* client = user->client;
guac_socket* socket = user->socket;
/* Guacamole user input loop */
while (client->state == GUAC_CLIENT_RUNNING && user->active) {
/* Read instruction, stop on error */
if (guac_parser_read(parser, socket, GUACD_USEC_TIMEOUT)) {
if (guac_error == GUAC_STATUS_TIMEOUT)
guac_user_abort(user, GUAC_PROTOCOL_STATUS_CLIENT_TIMEOUT, "User is not responding.");
else {
if (guac_error != GUAC_STATUS_CLOSED)
guacd_client_log_guac_error(client, GUAC_LOG_WARNING,
"Guacamole connection failure");
guac_user_stop(user);
}
return NULL;
}
/* Reset guac_error and guac_error_message (user/client handlers are not
* guaranteed to set these) */
guac_error = GUAC_STATUS_SUCCESS;
guac_error_message = NULL;
/* Call handler, stop on error */
if (guac_user_handle_instruction(user, parser->opcode, parser->argc, parser->argv) < 0) {
/* Log error */
guacd_client_log_guac_error(client, GUAC_LOG_WARNING,
"User connection aborted");
/* Log handler details */
guac_user_log(user, GUAC_LOG_DEBUG, "Failing instruction handler in user was \"%s\"", parser->opcode);
guac_user_stop(user);
return NULL;
}
}
return NULL;
}
int guacd_user_start(guac_parser* parser, guac_user* user) {
guacd_user_input_thread_params params = {
.parser = parser,
.user = user
};
pthread_t input_thread;
if (pthread_create(&input_thread, NULL, guacd_user_input_thread, (void*) &params)) {
guac_user_log(user, GUAC_LOG_ERROR, "Unable to start input thread");
guac_user_stop(user);
return -1;
}
/* Wait for I/O threads */
pthread_join(input_thread, NULL);
/* Done */
return 0;
}

View File

@ -21,38 +21,14 @@
*/ */
#ifndef _GUACD_CLIENT_H #ifndef _GUACD_USER_H
#define _GUACD_CLIENT_H #define _GUACD_USER_H
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/parser.h>
#include <guacamole/socket.h>
/** #include <guacamole/user.h>
* The time to allow between sync responses in milliseconds. If a sync
* instruction is sent to the client and no response is received within this
* timeframe, server messages will not be handled until a sync instruction is
* received from the client.
*/
#define GUACD_SYNC_THRESHOLD 500
/**
* The time to allow between server sync messages in milliseconds. A sync
* message from the server will be sent every GUACD_SYNC_FREQUENCY milliseconds.
* As this will induce a response from a client that is not malfunctioning,
* this is used to detect when a client has died. This must be set to a
* reasonable value to avoid clients being disconnected unnecessarily due
* to timeout.
*/
#define GUACD_SYNC_FREQUENCY 5000
/**
* The amount of time to wait after handling server messages. If a client
* plugin has a message handler, and sends instructions when server messages
* are being handled, there will be a pause of this many milliseconds before
* the next call to the message handler.
*/
#define GUACD_MESSAGE_HANDLE_FREQUENCY 50
/** /**
* The number of milliseconds to wait for messages in any phase before * The number of milliseconds to wait for messages in any phase before
@ -73,7 +49,34 @@
*/ */
#define GUACD_CLIENT_MAX_CONNECTIONS 65536 #define GUACD_CLIENT_MAX_CONNECTIONS 65536
int guacd_client_start(guac_client* client); /**
* Parameters required by the user input thread.
*/
typedef struct guacd_user_input_thread_params {
/**
* The parser which will be used throughout the user's session.
*/
guac_parser* parser;
/**
* A reference to the connected user.
*/
guac_user* user;
} guacd_user_input_thread_params;
/**
* Starts the input/output threads of a new user. This function will block
* until the user disconnects.
*/
int guacd_user_start(guac_parser* parser, guac_user* user);
/**
* The thread which handles all user input, calling event handlers for received
* instructions.
*/
void* guacd_user_input_thread(void* data);
#endif #endif