Merge pull request #113 from glyptodon/fake-merge-screen-sharing-004-guacd
GUAC-1389: Add screen sharing support to guacd.
This commit is contained in:
commit
f7b30414fb
@ -38,6 +38,7 @@ DIST_SUBDIRS = \
|
||||
SUBDIRS = \
|
||||
src/libguac \
|
||||
src/common \
|
||||
src/guacd \
|
||||
tests
|
||||
|
||||
if ENABLE_COMMON_SSH
|
||||
|
@ -29,21 +29,27 @@ man_MANS = \
|
||||
man/guacd.conf.5
|
||||
|
||||
noinst_HEADERS = \
|
||||
client.h \
|
||||
client-map.h \
|
||||
conf-args.h \
|
||||
conf-file.h \
|
||||
conf-parse.h \
|
||||
log.h
|
||||
connection.h \
|
||||
log.h \
|
||||
move-fd.h \
|
||||
proc.h \
|
||||
proc-map.h \
|
||||
user.h
|
||||
|
||||
guacd_SOURCES = \
|
||||
daemon.c \
|
||||
client.c \
|
||||
client-map.c \
|
||||
conf-args.c \
|
||||
conf-file.c \
|
||||
conf-parse.c \
|
||||
log.c
|
||||
guacd_SOURCES = \
|
||||
conf-args.c \
|
||||
conf-file.c \
|
||||
conf-parse.c \
|
||||
connection.c \
|
||||
daemon.c \
|
||||
log.c \
|
||||
move-fd.c \
|
||||
proc.c \
|
||||
proc-map.c \
|
||||
user.c
|
||||
|
||||
guacd_CFLAGS = \
|
||||
-Werror -Wall -pedantic \
|
||||
|
@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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_CLIENT_MAP_H
|
||||
#define _GUACD_CLIENT_MAP_H
|
||||
|
||||
#include "config.h"
|
||||
#include "client.h"
|
||||
#include "guac_list.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
#define GUACD_CLIENT_MAP_BUCKETS GUACD_CLIENT_MAX_CONNECTIONS*2
|
||||
|
||||
/**
|
||||
* Set of all active connections to guacd, indexed by connection ID.
|
||||
*/
|
||||
typedef struct guacd_client_map {
|
||||
|
||||
/**
|
||||
* Internal hash buckets. Each bucket is a linked list containing all
|
||||
* guac_client instances which hash to this bucket location.
|
||||
*/
|
||||
guac_common_list* __buckets[GUACD_CLIENT_MAP_BUCKETS];
|
||||
|
||||
} guacd_client_map;
|
||||
|
||||
/**
|
||||
* Allocates a new client map, which persists for the life of guacd.
|
||||
*/
|
||||
guacd_client_map* guacd_client_map_alloc();
|
||||
|
||||
/**
|
||||
* Adds the given client to the given client map. On success, zero is returned.
|
||||
* If adding the client fails (due to lack of space, or duplicate ID), a
|
||||
* non-zero value is returned instead.
|
||||
*/
|
||||
int guacd_client_map_add(guacd_client_map* map, guac_client* client);
|
||||
|
||||
/**
|
||||
* Retrieves the client having the given ID, or NULL if no such client
|
||||
* is stored.
|
||||
*/
|
||||
guac_client* guacd_client_map_retrieve(guacd_client_map* map, const char* id);
|
||||
|
||||
/**
|
||||
* Removes the client having the given ID, returning the corresponding client.
|
||||
* If no such client exists, NULL is returned.
|
||||
*/
|
||||
guac_client* guacd_client_map_remove(guacd_client_map* map, const char* id);
|
||||
|
||||
#endif
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
386
src/guacd/connection.c
Normal file
386
src/guacd/connection.c
Normal file
@ -0,0 +1,386 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* @param fd
|
||||
* The file descriptor to write to.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer containing the data to be written.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes in the buffer to write.
|
||||
*
|
||||
* @return
|
||||
* The number of bytes written, or -1 if an error occurs. As this function
|
||||
* is guaranteed to write ALL bytes, this will always be the number of
|
||||
* bytes specified by length unless an error occurs.
|
||||
*/
|
||||
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. Any data already buffered from that guac_socket by a given
|
||||
* guac_parser is read first, prior to reading further data from the
|
||||
* guac_socket. The provided guac_parser will be freed once its buffers have
|
||||
* been emptied, but the guac_socket will not.
|
||||
*
|
||||
* This thread ultimately terminates when no further data can be read from the
|
||||
* guac_socket.
|
||||
*
|
||||
* @param data
|
||||
* A pointer to a guacd_connection_io_thread_params structure containing
|
||||
* the guac_socket to read from, the file descriptor to write the read data
|
||||
* to, and the guac_parser associated with the guac_socket which may have
|
||||
* unhandled data in its parsing buffers.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
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;
|
||||
|
||||
/* Read all buffered data from parser first */
|
||||
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.
|
||||
*
|
||||
* @param proc
|
||||
* The existing process to add the user to.
|
||||
*
|
||||
* @param parser
|
||||
* The parser associated with the given guac_socket (used to handle the
|
||||
* user's connection handshake thus far).
|
||||
*
|
||||
* @param socket
|
||||
* The socket associated with the user to be added to the existing
|
||||
* process.
|
||||
*
|
||||
* @return
|
||||
* Zero if the user was added successfully, non-zero if an error occurred.
|
||||
*/
|
||||
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, adding new users and creating new client processes as needed. If a
|
||||
* new process is created, this function blocks until that process terminates,
|
||||
* automatically deregistering the process at that point.
|
||||
*
|
||||
* The socket provided will be automatically freed when the connection
|
||||
* terminates unless routing fails, in which case non-zero is returned.
|
||||
*
|
||||
* @param map
|
||||
* The map of existing client processes.
|
||||
*
|
||||
* @param socket
|
||||
* The socket associated with the new connection that must be routed to
|
||||
* a new or existing process within the given map.
|
||||
*
|
||||
* @return
|
||||
* Zero if the connection was successfully routed, non-zero if routing has
|
||||
* failed.
|
||||
*/
|
||||
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(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;
|
||||
|
||||
}
|
||||
|
125
src/guacd/connection.h
Normal file
125
src/guacd/connection.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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. The file descriptor of the inbound connection will
|
||||
* either be given to a new process for a new remote desktop connection, or
|
||||
* will be passed to an existing process for joining an existing remote desktop
|
||||
* connection. It is expected that this thread will operate detached. The
|
||||
* creating process need not join on the resulting thread.
|
||||
*
|
||||
* @param data
|
||||
* A pointer to a guacd_connection_thread_params structure containing the
|
||||
* shared overall map of currently-connected processes, the file
|
||||
* descriptor associated with the newly-established connection that is to
|
||||
* be either (1) associated with a new process or (2) passed on to an
|
||||
* existing process, and the SSL context for the encryption surrounding
|
||||
* that connection (if any).
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
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. Note that both the
|
||||
* provided guac_parser and the guac_socket will be freed once this thread
|
||||
* terminates, which will occur when no further data can be read from the
|
||||
* guac_socket.
|
||||
*
|
||||
* @param data
|
||||
* A pointer to a guacd_connection_io_thread_params structure containing
|
||||
* the guac_socket and file descriptor to transfer data between
|
||||
* (bidirectionally), as well as the guac_parser associated with the
|
||||
* guac_socket (which may have unhandled data in its parsing buffers).
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
void* guacd_connection_io_thread(void* data);
|
||||
|
||||
#endif
|
||||
|
@ -22,22 +22,15 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "client-map.h"
|
||||
#include "connection.h"
|
||||
#include "conf-args.h"
|
||||
#include "conf-file.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/error.h>
|
||||
#include <guacamole/instruction.h>
|
||||
#include <guacamole/plugin.h>
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include "proc-map.h"
|
||||
#include "user.h"
|
||||
|
||||
#ifdef ENABLE_SSL
|
||||
#include <openssl/ssl.h>
|
||||
#include "socket-ssl.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
@ -59,347 +52,21 @@
|
||||
#define GUACD_ROOT "/"
|
||||
|
||||
/**
|
||||
* Logs a reasonable explanatory message regarding handshake failure based on
|
||||
* the current value of guac_error.
|
||||
*/
|
||||
static 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));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().
|
||||
* Redirects the given file descriptor to /dev/null. The given flags must match
|
||||
* the read/write flags of the file descriptor given (if the given file
|
||||
* descriptor was opened write-only, flags here must be O_WRONLY, etc.).
|
||||
*
|
||||
* @param mimetypes
|
||||
* The array of mimetypes to copy.
|
||||
* @param fd
|
||||
* The file descriptor to redirect to /dev/null.
|
||||
*
|
||||
* @param count
|
||||
* The number of mimetypes in the given array.
|
||||
* @param flags
|
||||
* The flags to use when opening /dev/null as the target for redirection.
|
||||
* These flags must match the flags of the file descriptor given.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated, NULL-terminated array containing newly-allocated
|
||||
* copies of each of the mimetypes provided in the original mimetypes
|
||||
* array.
|
||||
* Zero on success, non-zero if redirecting the file descriptor fails.
|
||||
*/
|
||||
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) {
|
||||
static int redirect_fd(int fd, int flags) {
|
||||
|
||||
/* Attempt to open bit bucket */
|
||||
int new_fd = open(GUACD_DEV_NULL, flags);
|
||||
@ -416,7 +83,21 @@ int redirect_fd(int fd, int flags) {
|
||||
|
||||
}
|
||||
|
||||
int daemonize() {
|
||||
/**
|
||||
* Turns the current process into a daemon through a series of fork() calls.
|
||||
* The standard I/O file desriptors for STDIN, STDOUT, and STDERR will be
|
||||
* redirected to /dev/null, and the working directory is changed to root.
|
||||
* Execution within the caller of this function will terminate before this
|
||||
* function returns, while execution within the daemonized child process will
|
||||
* continue.
|
||||
*
|
||||
* @return
|
||||
* Zero if the daemonization process succeeded and we are now in the
|
||||
* daemonized child process, or non-zero if daemonization failed and we are
|
||||
* still the original caller. This function does not return for the original
|
||||
* caller if daemonization succeeds.
|
||||
*/
|
||||
static int daemonize() {
|
||||
|
||||
pid_t pid;
|
||||
|
||||
@ -499,7 +180,7 @@ int main(int argc, char* argv[]) {
|
||||
SSL_CTX* ssl_context = NULL;
|
||||
#endif
|
||||
|
||||
guacd_client_map* map = guacd_client_map_alloc();
|
||||
guacd_proc_map* map = guacd_proc_map_alloc();
|
||||
|
||||
/* General */
|
||||
int retval;
|
||||
@ -675,7 +356,7 @@ int main(int argc, char* argv[]) {
|
||||
/* Daemon loop */
|
||||
for (;;) {
|
||||
|
||||
pid_t child_pid;
|
||||
pthread_t child_thread;
|
||||
|
||||
/* Accept connection */
|
||||
client_addr_len = sizeof(client_addr);
|
||||
@ -683,59 +364,27 @@ int main(int argc, char* argv[]) {
|
||||
(struct sockaddr*) &client_addr, &client_addr_len);
|
||||
|
||||
if (connected_socket_fd < 0) {
|
||||
guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s",
|
||||
strerror(errno));
|
||||
return 3;
|
||||
guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Once connection is accepted, send child into background.
|
||||
*
|
||||
* Note that we prefer fork() over threads for connection-handling
|
||||
* processes as they give each connection its own memory area, and
|
||||
* isolate the main daemon and other connections from errors in any
|
||||
* particular client plugin.
|
||||
*/
|
||||
/* Create parameters for connection thread */
|
||||
guacd_connection_thread_params* params = malloc(sizeof(guacd_connection_thread_params));
|
||||
if (params == NULL) {
|
||||
guacd_log(GUAC_LOG_ERROR, "Could not create connection thread: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
child_pid = fork();
|
||||
|
||||
/* 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;
|
||||
params->map = map;
|
||||
params->connected_socket_fd = connected_socket_fd;
|
||||
|
||||
#ifdef ENABLE_SSL
|
||||
|
||||
/* 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);
|
||||
params->ssl_context = ssl_context;
|
||||
#endif
|
||||
|
||||
guacd_handle_connection(map, socket);
|
||||
close(connected_socket_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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));
|
||||
}
|
||||
/* Spawn thread to handle connection */
|
||||
pthread_create(&child_thread, NULL, guacd_connection_thread, params);
|
||||
pthread_detach(child_thread);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
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
|
||||
|
||||
|
115
src/guacd/move-fd.c
Normal file
115
src/guacd/move-fd.c
Normal 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;
|
||||
|
||||
}
|
||||
|
62
src/guacd/move-fd.h
Normal file
62
src/guacd/move-fd.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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, allowing the
|
||||
* receiving process to use that file descriptor normally. Returns non-zero on
|
||||
* success, zero on error, just as a normal call to sendmsg() would. If an
|
||||
* error does occur, errno will be set appropriately.
|
||||
*
|
||||
* @param sock
|
||||
* The file descriptor of an open UNIX domain socket along which the file
|
||||
* descriptor specified by fd should be sent.
|
||||
*
|
||||
* @param fd
|
||||
* The file descriptor to send along the given UNIX domain socket.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the send operation succeeded, zero on error.
|
||||
*/
|
||||
int guacd_send_fd(int sock, int fd);
|
||||
|
||||
/**
|
||||
* Waits for a file descriptor on the given socket, returning the received file
|
||||
* descriptor. The file descriptor must have been sent via guacd_send_fd. If an
|
||||
* error occurs, -1 is returned, and errno will be set appropriately.
|
||||
*
|
||||
* @param sock
|
||||
* The file descriptor of an open UNIX domain socket along which the file
|
||||
* descriptor will be sent (by guacd_send_fd()).
|
||||
*
|
||||
* @return
|
||||
* The received file descriptor, or -1 if an error occurs preventing
|
||||
* receipt of the file descriptor.
|
||||
*/
|
||||
int guacd_recv_fd(int sock);
|
||||
|
||||
#endif
|
||||
|
@ -21,9 +21,10 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client.h"
|
||||
#include "client-map.h"
|
||||
#include "guac_list.h"
|
||||
#include "proc.h"
|
||||
#include "proc-map.h"
|
||||
#include "user.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
@ -31,7 +32,13 @@
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Returns a hash code based on the connection ID of the given client.
|
||||
* Returns a hash code based on the given connection ID.
|
||||
*
|
||||
* @param str
|
||||
* The string containing the connection ID.
|
||||
*
|
||||
* @return
|
||||
* A reasonably well-distributed hash code for the given string.
|
||||
*/
|
||||
static unsigned int __guacd_client_hash(const char* str) {
|
||||
|
||||
@ -47,21 +54,47 @@ static unsigned int __guacd_client_hash(const char* str) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the bucket corresponding to the hash code indicated by the give id.
|
||||
* Each bucket is an instance of guac_common_list.
|
||||
* Locates the bucket corresponding to the hash code indicated by the given id,
|
||||
* where the hash code is dictated by __guacd_client_hash(). Each bucket is an
|
||||
* instance of guac_common_list.
|
||||
*
|
||||
* @param map
|
||||
* The map to retrieve the hash bucket from.
|
||||
*
|
||||
* @param id
|
||||
* The ID whose hash code determines the bucket being retrieved.
|
||||
*
|
||||
* @return
|
||||
* The bucket corresponding to the hash code for the given ID, represented
|
||||
* by a 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];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of guac_client instances, returns the guac_client having the
|
||||
* given ID, or NULL if no such client is stored.
|
||||
* Given a bucket of guacd_proc instances, returns the guacd_proc having the
|
||||
* guac_client with the given ID, or NULL if no such client is stored.
|
||||
*
|
||||
* @param bucket
|
||||
* The bucket of guacd_proc instances to search, represented as a
|
||||
* guac_common_list.
|
||||
*
|
||||
* @param id
|
||||
* The ID of the guac_client whose corresponding guacd_proc instance should
|
||||
* be located within the bucket.
|
||||
*
|
||||
* @return
|
||||
* The guac_common_list_element containing the guacd_proc instance
|
||||
* corresponding to the guac_client having the given ID, or NULL of no such
|
||||
* element exists.
|
||||
*/
|
||||
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;
|
||||
|
||||
@ -69,8 +102,8 @@ static guac_common_list_element* __guacd_client_find(guac_common_list* bucket, c
|
||||
while (current != NULL) {
|
||||
|
||||
/* Check connection ID */
|
||||
guac_client* client = (guac_client*) current->data;
|
||||
if (strcmp(client->connection_id, id) == 0)
|
||||
guacd_proc* proc = (guacd_proc*) current->data;
|
||||
if (strcmp(proc->client->connection_id, id) == 0)
|
||||
break;
|
||||
|
||||
current = current->next;
|
||||
@ -80,9 +113,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;
|
||||
|
||||
int i;
|
||||
@ -90,7 +123,7 @@ guacd_client_map* guacd_client_map_alloc() {
|
||||
/* Init all 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++;
|
||||
}
|
||||
@ -99,18 +132,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;
|
||||
|
||||
/* Retrieve corresponding element, if any */
|
||||
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 (found == NULL) {
|
||||
guac_common_list_add(bucket, client);
|
||||
guac_common_list_add(bucket, proc);
|
||||
guac_common_list_unlock(bucket);
|
||||
return 0;
|
||||
}
|
||||
@ -121,16 +155,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;
|
||||
|
||||
/* Retrieve corresponding element, if any */
|
||||
guac_common_list_lock(bucket);
|
||||
found = __guacd_client_find(bucket, id);
|
||||
found = __guacd_proc_find(bucket, id);
|
||||
|
||||
/* If no such element, fail */
|
||||
if (found == NULL) {
|
||||
@ -138,23 +172,23 @@ guac_client* guacd_client_map_retrieve(guacd_client_map* map, const char* id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client = (guac_client*) found->data;
|
||||
proc = (guacd_proc*) found->data;
|
||||
|
||||
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;
|
||||
|
||||
/* Retrieve corresponding element, if any */
|
||||
guac_common_list_lock(bucket);
|
||||
found = __guacd_client_find(bucket, id);
|
||||
found = __guacd_proc_find(bucket, id);
|
||||
|
||||
/* If no such element, fail */
|
||||
if (found == NULL) {
|
||||
@ -162,11 +196,11 @@ guac_client* guacd_client_map_remove(guacd_client_map* map, const char* id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client = (guac_client*) found->data;
|
||||
proc = (guacd_proc*) found->data;
|
||||
guac_common_list_remove(bucket, found);
|
||||
|
||||
guac_common_list_unlock(bucket);
|
||||
return client;
|
||||
return proc;
|
||||
|
||||
}
|
||||
|
114
src/guacd/proc-map.h
Normal file
114
src/guacd/proc-map.h
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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_MAP_H
|
||||
#define _GUACD_PROC_MAP_H
|
||||
|
||||
#include "config.h"
|
||||
#include "guac_list.h"
|
||||
#include "proc.h"
|
||||
#include "user.h"
|
||||
|
||||
#include <guacamole/client.h>
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
typedef struct guacd_proc_map {
|
||||
|
||||
/**
|
||||
* Internal hash buckets. Each bucket is a linked list containing all
|
||||
* guac_client instances which hash to this bucket location.
|
||||
*/
|
||||
guac_common_list* __buckets[GUACD_PROC_MAP_BUCKETS];
|
||||
|
||||
} guacd_proc_map;
|
||||
|
||||
/**
|
||||
* Allocates a new client process map. There is intended to be exactly one
|
||||
* process map instance, which persists for the life of guacd.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated client process map.
|
||||
*/
|
||||
guacd_proc_map* guacd_proc_map_alloc();
|
||||
|
||||
/**
|
||||
* Adds the given process to the client process map. On success, zero is
|
||||
* returned. If adding the client fails (due to lack of space, or duplicate
|
||||
* ID), a non-zero value is returned instead. The client process is stored by
|
||||
* the connection ID of the underlying guac_client.
|
||||
*
|
||||
* @param map
|
||||
* The map in which the given client process should be stored.
|
||||
*
|
||||
* @param proc
|
||||
* The client process to store in the given map.
|
||||
*
|
||||
* @return
|
||||
* Zero if the process was successfully stored in the map, or non-zero if
|
||||
* storing the process fails for any reason.
|
||||
*/
|
||||
int guacd_proc_map_add(guacd_proc_map* map, guacd_proc* proc);
|
||||
|
||||
/**
|
||||
* Retrieves the client process having the client with the given ID, or NULL if
|
||||
* no such process is stored.
|
||||
*
|
||||
* @param map
|
||||
* The map from which to retrieve the process associated with the client
|
||||
* having the given ID.
|
||||
*
|
||||
* @param id
|
||||
* The ID of the client whose process should be retrieved.
|
||||
*
|
||||
* @return
|
||||
* The process associated with the client having the given ID, or NULL if
|
||||
* no such process exists.
|
||||
*/
|
||||
guacd_proc* guacd_proc_map_retrieve(guacd_proc_map* map, const char* id);
|
||||
|
||||
/**
|
||||
* Removes the client process having the client with the given ID, returning
|
||||
* the corresponding process. If no such process exists, NULL is returned.
|
||||
*
|
||||
* @param map
|
||||
* The map from which to remove the process associated with the client
|
||||
* having the given ID.
|
||||
*
|
||||
* @param id
|
||||
* The ID of the client whose process should be removed.
|
||||
*
|
||||
* @return
|
||||
* The process associated with the client having the given ID which has now
|
||||
* been removed from the given map, or NULL if no such process exists.
|
||||
*/
|
||||
guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id);
|
||||
|
||||
#endif
|
||||
|
498
src/guacd/proc.c
Normal file
498
src/guacd/proc.c
Normal file
@ -0,0 +1,498 @@
|
||||
/*
|
||||
* 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. This
|
||||
* function blocks until the user disconnects.
|
||||
*
|
||||
* @param user
|
||||
* The user whose handshake and entire Guacamole protocol exchange should
|
||||
* be handled.
|
||||
*
|
||||
* @return
|
||||
* Zero if the user's Guacamole connection was successfully handled and
|
||||
* the user has disconnected, or non-zero if an error prevented the user's
|
||||
* connection from being handled properly.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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 */
|
||||
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. 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);
|
||||
|
||||
}
|
||||
|
91
src/guacd/proc.h
Normal file
91
src/guacd/proc.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 background process for handling the given protocol, returning
|
||||
* a structure allowing communication with and monitoring of the process
|
||||
* created. Within the child process, this function does not return - the
|
||||
* entire child process simply terminates instead.
|
||||
*
|
||||
* @param protocol
|
||||
* The protocol for which this process is client being created.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated process structure pointing to the file descriptor of
|
||||
* the background process specific to the specified protocol, or NULL of
|
||||
* the process could not be created.
|
||||
*/
|
||||
guacd_proc* guacd_create_proc(const char* protocol);
|
||||
|
||||
/**
|
||||
* Signals the given process to stop accepting new users and clean up. This
|
||||
* will eventually cause the child process to exit.
|
||||
*
|
||||
* @param proc
|
||||
* The process to stop.
|
||||
*/
|
||||
void guacd_proc_stop(guacd_proc* proc);
|
||||
|
||||
#endif
|
||||
|
@ -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;
|
||||
SSL_shutdown(data->ssl);
|
||||
|
||||
/* Close file descriptor */
|
||||
close(data->fd);
|
||||
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
@ -54,7 +54,19 @@ typedef struct 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.
|
||||
*
|
||||
* @param context
|
||||
* The SSL_CTX structure describing the desired SSL configuration.
|
||||
*
|
||||
* @param fd
|
||||
* The file descriptor to use for the SSL connection underlying the
|
||||
* created guac_socket.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated guac_socket which will transparently use SSL for
|
||||
* all communication.
|
||||
*/
|
||||
guac_socket* guac_socket_open_secure(SSL_CTX* context, int fd);
|
||||
|
||||
|
112
src/guacd/user.c
Normal file
112
src/guacd/user.c
Normal 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*) ¶ms)) {
|
||||
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;
|
||||
|
||||
}
|
||||
|
@ -21,38 +21,14 @@
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _GUACD_CLIENT_H
|
||||
#define _GUACD_CLIENT_H
|
||||
#ifndef _GUACD_USER_H
|
||||
#define _GUACD_USER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <guacamole/client.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
|
||||
#include <guacamole/parser.h>
|
||||
#include <guacamole/socket.h>
|
||||
#include <guacamole/user.h>
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait for messages in any phase before
|
||||
@ -73,7 +49,53 @@
|
||||
*/
|
||||
#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. If an error prevents the input/output threads
|
||||
* from starting, guac_user_stop() will be invoked on the given user.
|
||||
*
|
||||
* @param parser
|
||||
* The guac_parser to use to handle all input from the given user.
|
||||
*
|
||||
* @param user
|
||||
* The user whose associated I/O transfer threads should be started.
|
||||
*
|
||||
* @return
|
||||
* Zero if the I/O threads started successfully and user has disconnected,
|
||||
* or non-zero if the I/O threads could not be started.
|
||||
*/
|
||||
int guacd_user_start(guac_parser* parser, guac_user* user);
|
||||
|
||||
/**
|
||||
* The thread which handles all user input, calling event handlers for received
|
||||
* instructions.
|
||||
*
|
||||
* @param data
|
||||
* A pointer to a guacd_user_input_thread_params structure describing the
|
||||
* user whose input is being handled and the guac_parser with which to
|
||||
* handle it.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
void* guacd_user_input_thread(void* data);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user