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 = \
|
SUBDIRS = \
|
||||||
src/libguac \
|
src/libguac \
|
||||||
src/common \
|
src/common \
|
||||||
|
src/guacd \
|
||||||
tests
|
tests
|
||||||
|
|
||||||
if ENABLE_COMMON_SSH
|
if ENABLE_COMMON_SSH
|
||||||
|
@ -29,21 +29,27 @@ man_MANS = \
|
|||||||
man/guacd.conf.5
|
man/guacd.conf.5
|
||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
client.h \
|
|
||||||
client-map.h \
|
|
||||||
conf-args.h \
|
conf-args.h \
|
||||||
conf-file.h \
|
conf-file.h \
|
||||||
conf-parse.h \
|
conf-parse.h \
|
||||||
log.h
|
connection.h \
|
||||||
|
log.h \
|
||||||
|
move-fd.h \
|
||||||
|
proc.h \
|
||||||
|
proc-map.h \
|
||||||
|
user.h
|
||||||
|
|
||||||
guacd_SOURCES = \
|
guacd_SOURCES = \
|
||||||
daemon.c \
|
|
||||||
client.c \
|
|
||||||
client-map.c \
|
|
||||||
conf-args.c \
|
conf-args.c \
|
||||||
conf-file.c \
|
conf-file.c \
|
||||||
conf-parse.c \
|
conf-parse.c \
|
||||||
log.c
|
connection.c \
|
||||||
|
daemon.c \
|
||||||
|
log.c \
|
||||||
|
move-fd.c \
|
||||||
|
proc.c \
|
||||||
|
proc-map.c \
|
||||||
|
user.c
|
||||||
|
|
||||||
guacd_CFLAGS = \
|
guacd_CFLAGS = \
|
||||||
-Werror -Wall -pedantic \
|
-Werror -Wall -pedantic \
|
||||||
|
@ -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 "config.h"
|
||||||
|
|
||||||
#include "client.h"
|
#include "connection.h"
|
||||||
#include "client-map.h"
|
|
||||||
#include "conf-args.h"
|
#include "conf-args.h"
|
||||||
#include "conf-file.h"
|
#include "conf-file.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "proc-map.h"
|
||||||
#include <guacamole/client.h>
|
#include "user.h"
|
||||||
#include <guacamole/error.h>
|
|
||||||
#include <guacamole/instruction.h>
|
|
||||||
#include <guacamole/plugin.h>
|
|
||||||
#include <guacamole/protocol.h>
|
|
||||||
#include <guacamole/socket.h>
|
|
||||||
|
|
||||||
#ifdef ENABLE_SSL
|
#ifdef ENABLE_SSL
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include "socket-ssl.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -59,347 +52,21 @@
|
|||||||
#define GUACD_ROOT "/"
|
#define GUACD_ROOT "/"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a reasonable explanatory message regarding handshake failure based on
|
* Redirects the given file descriptor to /dev/null. The given flags must match
|
||||||
* the current value of guac_error.
|
* the read/write flags of the file descriptor given (if the given file
|
||||||
*/
|
* descriptor was opened write-only, flags here must be O_WRONLY, etc.).
|
||||||
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().
|
|
||||||
*
|
*
|
||||||
* @param mimetypes
|
* @param fd
|
||||||
* The array of mimetypes to copy.
|
* The file descriptor to redirect to /dev/null.
|
||||||
*
|
*
|
||||||
* @param count
|
* @param flags
|
||||||
* The number of mimetypes in the given array.
|
* 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
|
* @return
|
||||||
* A newly-allocated, NULL-terminated array containing newly-allocated
|
* Zero on success, non-zero if redirecting the file descriptor fails.
|
||||||
* copies of each of the mimetypes provided in the original mimetypes
|
|
||||||
* array.
|
|
||||||
*/
|
*/
|
||||||
static char** guacd_copy_mimetypes(char** mimetypes, int count) {
|
static int redirect_fd(int fd, int flags) {
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* Allocate sufficient space for NULL-terminated array of mimetypes */
|
|
||||||
char** mimetypes_copy = malloc(sizeof(char*) * (count+1));
|
|
||||||
|
|
||||||
/* Copy each provided mimetype */
|
|
||||||
for (i = 0; i < count; i++)
|
|
||||||
mimetypes_copy[i] = strdup(mimetypes[i]);
|
|
||||||
|
|
||||||
/* Terminate with NULL */
|
|
||||||
mimetypes_copy[count] = NULL;
|
|
||||||
|
|
||||||
return mimetypes_copy;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees the given array of mimetypes, including the space allocated to each
|
|
||||||
* mimetype string within the array. The provided array of mimetypes MUST have
|
|
||||||
* been allocated with guacd_copy_mimetypes().
|
|
||||||
*
|
|
||||||
* @param mimetypes
|
|
||||||
* The NULL-terminated array of mimetypes to free. This array MUST have
|
|
||||||
* been previously allocated with guacd_copy_mimetypes().
|
|
||||||
*/
|
|
||||||
static void guacd_free_mimetypes(char** mimetypes) {
|
|
||||||
|
|
||||||
char** current_mimetype = mimetypes;
|
|
||||||
|
|
||||||
/* Free all strings within NULL-terminated mimetype array */
|
|
||||||
while (*current_mimetype != NULL) {
|
|
||||||
free(*current_mimetype);
|
|
||||||
current_mimetype++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free the array itself, now that its contents have been freed */
|
|
||||||
free(mimetypes);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new guac_client for the connection on the given socket, adding
|
|
||||||
* it to the client map based on its ID.
|
|
||||||
*/
|
|
||||||
static void guacd_handle_connection(guacd_client_map* map, guac_socket* socket) {
|
|
||||||
|
|
||||||
guac_client* client;
|
|
||||||
guac_client_plugin* plugin;
|
|
||||||
guac_instruction* select;
|
|
||||||
guac_instruction* size;
|
|
||||||
guac_instruction* audio;
|
|
||||||
guac_instruction* video;
|
|
||||||
guac_instruction* image;
|
|
||||||
guac_instruction* connect;
|
|
||||||
int init_result;
|
|
||||||
|
|
||||||
/* Reset guac_error */
|
|
||||||
guac_error = GUAC_STATUS_SUCCESS;
|
|
||||||
guac_error_message = NULL;
|
|
||||||
|
|
||||||
/* Get protocol from select instruction */
|
|
||||||
select = guac_instruction_expect(socket, GUACD_USEC_TIMEOUT, "select");
|
|
||||||
if (select == NULL) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log_guac_error(GUAC_LOG_DEBUG,
|
|
||||||
"Error reading \"select\"");
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Validate args to select */
|
|
||||||
if (select->argc != 1) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log(GUAC_LOG_ERROR, "Bad number of arguments to \"select\" (%i)",
|
|
||||||
select->argc);
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
guacd_log(GUAC_LOG_INFO, "Protocol \"%s\" selected", select->argv[0]);
|
|
||||||
|
|
||||||
/* Get plugin from protocol in select */
|
|
||||||
plugin = guac_client_plugin_open(select->argv[0]);
|
|
||||||
guac_instruction_free(select);
|
|
||||||
|
|
||||||
if (plugin == NULL) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
if (guac_error == GUAC_STATUS_NOT_FOUND)
|
|
||||||
guacd_log(GUAC_LOG_WARNING,
|
|
||||||
"Support for selected protocol is not installed");
|
|
||||||
else
|
|
||||||
guacd_log_guac_error(GUAC_LOG_ERROR,
|
|
||||||
"Unable to load client plugin");
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Send args response */
|
|
||||||
if (guac_protocol_send_args(socket, plugin->args)
|
|
||||||
|| guac_socket_flush(socket)) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error sending \"args\"");
|
|
||||||
|
|
||||||
if (guac_client_plugin_close(plugin))
|
|
||||||
guacd_log_guac_error(GUAC_LOG_WARNING,
|
|
||||||
"Unable to close client plugin");
|
|
||||||
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get client */
|
|
||||||
client = guac_client_alloc();
|
|
||||||
if (client == NULL) {
|
|
||||||
guacd_log_guac_error(GUAC_LOG_ERROR, "Unable to create client");
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get optimal screen size */
|
|
||||||
size = guac_instruction_expect(
|
|
||||||
socket, GUACD_USEC_TIMEOUT, "size");
|
|
||||||
if (size == NULL) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"size\"");
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
guac_client_free(client);
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse optimal screen dimensions from size instruction */
|
|
||||||
client->info.optimal_width = atoi(size->argv[0]);
|
|
||||||
client->info.optimal_height = atoi(size->argv[1]);
|
|
||||||
|
|
||||||
/* If DPI given, set the client resolution */
|
|
||||||
if (size->argc >= 3)
|
|
||||||
client->info.optimal_resolution = atoi(size->argv[2]);
|
|
||||||
|
|
||||||
/* Otherwise, use a safe default for rough backwards compatibility */
|
|
||||||
else
|
|
||||||
client->info.optimal_resolution = 96;
|
|
||||||
|
|
||||||
guac_instruction_free(size);
|
|
||||||
|
|
||||||
/* Get supported audio formats */
|
|
||||||
audio = guac_instruction_expect(
|
|
||||||
socket, GUACD_USEC_TIMEOUT, "audio");
|
|
||||||
if (audio == NULL) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"audio\"");
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
guac_client_free(client);
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Store audio mimetypes */
|
|
||||||
client->info.audio_mimetypes =
|
|
||||||
guacd_copy_mimetypes(audio->argv, audio->argc);
|
|
||||||
|
|
||||||
guac_instruction_free(audio);
|
|
||||||
|
|
||||||
/* Get supported video formats */
|
|
||||||
video = guac_instruction_expect(
|
|
||||||
socket, GUACD_USEC_TIMEOUT, "video");
|
|
||||||
if (video == NULL) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"video\"");
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
guac_client_free(client);
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Store video mimetypes */
|
|
||||||
client->info.video_mimetypes =
|
|
||||||
guacd_copy_mimetypes(video->argv, video->argc);
|
|
||||||
|
|
||||||
guac_instruction_free(video);
|
|
||||||
|
|
||||||
/* Get supported image formats */
|
|
||||||
image = guac_instruction_expect(
|
|
||||||
socket, GUACD_USEC_TIMEOUT, "image");
|
|
||||||
if (image == NULL) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"image\"");
|
|
||||||
|
|
||||||
/* Free resources */
|
|
||||||
guac_client_free(client);
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Store image mimetypes */
|
|
||||||
client->info.image_mimetypes =
|
|
||||||
guacd_copy_mimetypes(image->argv, image->argc);
|
|
||||||
|
|
||||||
guac_instruction_free(image);
|
|
||||||
|
|
||||||
/* Get args from connect instruction */
|
|
||||||
connect = guac_instruction_expect(
|
|
||||||
socket, GUACD_USEC_TIMEOUT, "connect");
|
|
||||||
if (connect == NULL) {
|
|
||||||
|
|
||||||
/* Log error */
|
|
||||||
guacd_log_handshake_failure();
|
|
||||||
guacd_log_guac_error(GUAC_LOG_DEBUG, "Error reading \"connect\"");
|
|
||||||
|
|
||||||
if (guac_client_plugin_close(plugin))
|
|
||||||
guacd_log_guac_error(GUAC_LOG_WARNING,
|
|
||||||
"Unable to close client plugin");
|
|
||||||
|
|
||||||
guac_client_free(client);
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->socket = socket;
|
|
||||||
client->log_handler = guacd_client_log;
|
|
||||||
|
|
||||||
/* Store client */
|
|
||||||
if (guacd_client_map_add(map, client))
|
|
||||||
guacd_log(GUAC_LOG_ERROR, "Unable to add client. Internal client storage has failed");
|
|
||||||
|
|
||||||
/* Send connection ID */
|
|
||||||
guacd_log(GUAC_LOG_INFO, "Connection ID is \"%s\"", client->connection_id);
|
|
||||||
guac_protocol_send_ready(socket, client->connection_id);
|
|
||||||
|
|
||||||
/* Init client */
|
|
||||||
init_result = guac_client_plugin_init_client(plugin,
|
|
||||||
client, connect->argc, connect->argv);
|
|
||||||
|
|
||||||
guac_instruction_free(connect);
|
|
||||||
|
|
||||||
/* If client could not be started, free everything and fail */
|
|
||||||
if (init_result) {
|
|
||||||
|
|
||||||
guac_client_free(client);
|
|
||||||
|
|
||||||
guacd_log_guac_error(GUAC_LOG_INFO, "Connection did not succeed");
|
|
||||||
|
|
||||||
if (guac_client_plugin_close(plugin))
|
|
||||||
guacd_log_guac_error(GUAC_LOG_WARNING,
|
|
||||||
"Unable to close client plugin");
|
|
||||||
|
|
||||||
guac_socket_free(socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start client threads */
|
|
||||||
guacd_log(GUAC_LOG_INFO, "Starting client");
|
|
||||||
if (guacd_client_start(client))
|
|
||||||
guacd_log(GUAC_LOG_WARNING, "Client finished abnormally");
|
|
||||||
else
|
|
||||||
guacd_log(GUAC_LOG_INFO, "Client disconnected");
|
|
||||||
|
|
||||||
/* Remove client */
|
|
||||||
if (guacd_client_map_remove(map, client->connection_id) == NULL)
|
|
||||||
guacd_log(GUAC_LOG_ERROR, "Unable to remove client. Internal client storage has failed");
|
|
||||||
|
|
||||||
/* Free mimetype lists */
|
|
||||||
guacd_free_mimetypes(client->info.audio_mimetypes);
|
|
||||||
guacd_free_mimetypes(client->info.video_mimetypes);
|
|
||||||
guacd_free_mimetypes(client->info.image_mimetypes);
|
|
||||||
|
|
||||||
/* Clean up */
|
|
||||||
guac_client_free(client);
|
|
||||||
if (guac_client_plugin_close(plugin))
|
|
||||||
guacd_log_guac_error(GUAC_LOG_WARNING,
|
|
||||||
"Unable to close client plugin");
|
|
||||||
|
|
||||||
/* Close socket */
|
|
||||||
guac_socket_free(socket);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int redirect_fd(int fd, int flags) {
|
|
||||||
|
|
||||||
/* Attempt to open bit bucket */
|
/* Attempt to open bit bucket */
|
||||||
int new_fd = open(GUACD_DEV_NULL, flags);
|
int new_fd = open(GUACD_DEV_NULL, flags);
|
||||||
@ -416,7 +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;
|
pid_t pid;
|
||||||
|
|
||||||
@ -499,7 +180,7 @@ int main(int argc, char* argv[]) {
|
|||||||
SSL_CTX* ssl_context = NULL;
|
SSL_CTX* ssl_context = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
guacd_client_map* map = guacd_client_map_alloc();
|
guacd_proc_map* map = guacd_proc_map_alloc();
|
||||||
|
|
||||||
/* General */
|
/* General */
|
||||||
int retval;
|
int retval;
|
||||||
@ -675,7 +356,7 @@ int main(int argc, char* argv[]) {
|
|||||||
/* Daemon loop */
|
/* Daemon loop */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
pid_t child_pid;
|
pthread_t child_thread;
|
||||||
|
|
||||||
/* Accept connection */
|
/* Accept connection */
|
||||||
client_addr_len = sizeof(client_addr);
|
client_addr_len = sizeof(client_addr);
|
||||||
@ -683,59 +364,27 @@ int main(int argc, char* argv[]) {
|
|||||||
(struct sockaddr*) &client_addr, &client_addr_len);
|
(struct sockaddr*) &client_addr, &client_addr_len);
|
||||||
|
|
||||||
if (connected_socket_fd < 0) {
|
if (connected_socket_fd < 0) {
|
||||||
guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s",
|
guacd_log(GUAC_LOG_ERROR, "Could not accept client connection: %s", strerror(errno));
|
||||||
strerror(errno));
|
continue;
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Create parameters for connection thread */
|
||||||
* Once connection is accepted, send child into background.
|
guacd_connection_thread_params* params = malloc(sizeof(guacd_connection_thread_params));
|
||||||
*
|
if (params == NULL) {
|
||||||
* Note that we prefer fork() over threads for connection-handling
|
guacd_log(GUAC_LOG_ERROR, "Could not create connection thread: %s", strerror(errno));
|
||||||
* processes as they give each connection its own memory area, and
|
continue;
|
||||||
* isolate the main daemon and other connections from errors in any
|
}
|
||||||
* particular client plugin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
child_pid = fork();
|
params->map = map;
|
||||||
|
params->connected_socket_fd = connected_socket_fd;
|
||||||
/* If error, log */
|
|
||||||
if (child_pid == -1)
|
|
||||||
guacd_log(GUAC_LOG_ERROR, "Error forking child process: %s", strerror(errno));
|
|
||||||
|
|
||||||
/* If child, start client, and exit when finished */
|
|
||||||
else if (child_pid == 0) {
|
|
||||||
|
|
||||||
guac_socket* socket;
|
|
||||||
|
|
||||||
#ifdef ENABLE_SSL
|
#ifdef ENABLE_SSL
|
||||||
|
params->ssl_context = ssl_context;
|
||||||
/* If SSL chosen, use it */
|
|
||||||
if (ssl_context != NULL) {
|
|
||||||
socket = guac_socket_open_secure(ssl_context, connected_socket_fd);
|
|
||||||
if (socket == NULL) {
|
|
||||||
guacd_log_guac_error(GUAC_LOG_ERROR,
|
|
||||||
"Unable to set up SSL/TLS");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
socket = guac_socket_open(connected_socket_fd);
|
|
||||||
#else
|
|
||||||
/* Open guac_socket */
|
|
||||||
socket = guac_socket_open(connected_socket_fd);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
guacd_handle_connection(map, socket);
|
/* Spawn thread to handle connection */
|
||||||
close(connected_socket_fd);
|
pthread_create(&child_thread, NULL, guacd_connection_thread, params);
|
||||||
return 0;
|
pthread_detach(child_thread);
|
||||||
}
|
|
||||||
|
|
||||||
/* If parent, close reference to child's descriptor */
|
|
||||||
else if (close(connected_socket_fd) < 0) {
|
|
||||||
guacd_log(GUAC_LOG_ERROR, "Error closing daemon reference to "
|
|
||||||
"child descriptor: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
void guacd_client_log_guac_error(guac_client* client,
|
||||||
guac_client_log_level level, const char* message);
|
guac_client_log_level level, const char* message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a reasonable explanatory message regarding handshake failure based on
|
||||||
|
* the current value of guac_error.
|
||||||
|
*/
|
||||||
|
void guacd_log_handshake_failure();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
115
src/guacd/move-fd.c
Normal file
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 "config.h"
|
||||||
#include "client.h"
|
|
||||||
#include "client-map.h"
|
|
||||||
#include "guac_list.h"
|
#include "guac_list.h"
|
||||||
|
#include "proc.h"
|
||||||
|
#include "proc-map.h"
|
||||||
|
#include "user.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
|
|
||||||
@ -31,7 +32,13 @@
|
|||||||
#include <string.h>
|
#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) {
|
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.
|
* Locates the bucket corresponding to the hash code indicated by the given id,
|
||||||
* Each bucket is an instance of guac_common_list.
|
* 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];
|
return map->__buckets[index];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a list of guac_client instances, returns the guac_client having the
|
* Given a bucket of guacd_proc instances, returns the guacd_proc having the
|
||||||
* given ID, or NULL if no such client is stored.
|
* 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;
|
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) {
|
while (current != NULL) {
|
||||||
|
|
||||||
/* Check connection ID */
|
/* Check connection ID */
|
||||||
guac_client* client = (guac_client*) current->data;
|
guacd_proc* proc = (guacd_proc*) current->data;
|
||||||
if (strcmp(client->connection_id, id) == 0)
|
if (strcmp(proc->client->connection_id, id) == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
current = current->next;
|
current = current->next;
|
||||||
@ -80,9 +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;
|
guac_common_list** current;
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
@ -90,7 +123,7 @@ guacd_client_map* guacd_client_map_alloc() {
|
|||||||
/* Init all buckets */
|
/* Init all buckets */
|
||||||
current = map->__buckets;
|
current = map->__buckets;
|
||||||
|
|
||||||
for (i=0; i<GUACD_CLIENT_MAP_BUCKETS; i++) {
|
for (i=0; i<GUACD_PROC_MAP_BUCKETS; i++) {
|
||||||
*current = guac_common_list_alloc();
|
*current = guac_common_list_alloc();
|
||||||
current++;
|
current++;
|
||||||
}
|
}
|
||||||
@ -99,18 +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;
|
guac_common_list_element* found;
|
||||||
|
|
||||||
/* Retrieve corresponding element, if any */
|
/* Retrieve corresponding element, if any */
|
||||||
guac_common_list_lock(bucket);
|
guac_common_list_lock(bucket);
|
||||||
found = __guacd_client_find(bucket, client->connection_id);
|
found = __guacd_proc_find(bucket, identifier);
|
||||||
|
|
||||||
/* If no such element, we can add the new client successfully */
|
/* If no such element, we can add the new client successfully */
|
||||||
if (found == NULL) {
|
if (found == NULL) {
|
||||||
guac_common_list_add(bucket, client);
|
guac_common_list_add(bucket, proc);
|
||||||
guac_common_list_unlock(bucket);
|
guac_common_list_unlock(bucket);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -121,16 +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;
|
guac_common_list_element* found;
|
||||||
|
|
||||||
/* Retrieve corresponding element, if any */
|
/* Retrieve corresponding element, if any */
|
||||||
guac_common_list_lock(bucket);
|
guac_common_list_lock(bucket);
|
||||||
found = __guacd_client_find(bucket, id);
|
found = __guacd_proc_find(bucket, id);
|
||||||
|
|
||||||
/* If no such element, fail */
|
/* If no such element, fail */
|
||||||
if (found == NULL) {
|
if (found == NULL) {
|
||||||
@ -138,23 +172,23 @@ guac_client* guacd_client_map_retrieve(guacd_client_map* map, const char* id) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
client = (guac_client*) found->data;
|
proc = (guacd_proc*) found->data;
|
||||||
|
|
||||||
guac_common_list_unlock(bucket);
|
guac_common_list_unlock(bucket);
|
||||||
return client;
|
return proc;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guac_client* guacd_client_map_remove(guacd_client_map* map, const char* id) {
|
guacd_proc* guacd_proc_map_remove(guacd_proc_map* map, const char* id) {
|
||||||
|
|
||||||
guac_client* client;
|
guacd_proc* proc;
|
||||||
|
|
||||||
guac_common_list* bucket = __guacd_client_find_bucket(map, id);
|
guac_common_list* bucket = __guacd_proc_find_bucket(map, id);
|
||||||
guac_common_list_element* found;
|
guac_common_list_element* found;
|
||||||
|
|
||||||
/* Retrieve corresponding element, if any */
|
/* Retrieve corresponding element, if any */
|
||||||
guac_common_list_lock(bucket);
|
guac_common_list_lock(bucket);
|
||||||
found = __guacd_client_find(bucket, id);
|
found = __guacd_proc_find(bucket, id);
|
||||||
|
|
||||||
/* If no such element, fail */
|
/* If no such element, fail */
|
||||||
if (found == NULL) {
|
if (found == NULL) {
|
||||||
@ -162,11 +196,11 @@ guac_client* guacd_client_map_remove(guacd_client_map* map, const char* id) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
client = (guac_client*) found->data;
|
proc = (guacd_proc*) found->data;
|
||||||
guac_common_list_remove(bucket, found);
|
guac_common_list_remove(bucket, found);
|
||||||
|
|
||||||
guac_common_list_unlock(bucket);
|
guac_common_list_unlock(bucket);
|
||||||
return client;
|
return proc;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
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;
|
guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data;
|
||||||
SSL_shutdown(data->ssl);
|
SSL_shutdown(data->ssl);
|
||||||
|
|
||||||
|
/* Close file descriptor */
|
||||||
|
close(data->fd);
|
||||||
|
|
||||||
free(data);
|
free(data);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,19 @@ typedef struct guac_socket_ssl_data {
|
|||||||
} guac_socket_ssl_data;
|
} guac_socket_ssl_data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new guac_socket which will use SSL for all communication.
|
* Creates a new guac_socket which will use SSL for all communication. Freeing
|
||||||
|
* this guac_socket will automatically close the associated file descriptor.
|
||||||
|
*
|
||||||
|
* @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);
|
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
|
#ifndef _GUACD_USER_H
|
||||||
#define _GUACD_CLIENT_H
|
#define _GUACD_USER_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/parser.h>
|
||||||
|
#include <guacamole/socket.h>
|
||||||
/**
|
#include <guacamole/user.h>
|
||||||
* The time to allow between sync responses in milliseconds. If a sync
|
|
||||||
* instruction is sent to the client and no response is received within this
|
|
||||||
* timeframe, server messages will not be handled until a sync instruction is
|
|
||||||
* received from the client.
|
|
||||||
*/
|
|
||||||
#define GUACD_SYNC_THRESHOLD 500
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The time to allow between server sync messages in milliseconds. A sync
|
|
||||||
* message from the server will be sent every GUACD_SYNC_FREQUENCY milliseconds.
|
|
||||||
* As this will induce a response from a client that is not malfunctioning,
|
|
||||||
* this is used to detect when a client has died. This must be set to a
|
|
||||||
* reasonable value to avoid clients being disconnected unnecessarily due
|
|
||||||
* to timeout.
|
|
||||||
*/
|
|
||||||
#define GUACD_SYNC_FREQUENCY 5000
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The amount of time to wait after handling server messages. If a client
|
|
||||||
* plugin has a message handler, and sends instructions when server messages
|
|
||||||
* are being handled, there will be a pause of this many milliseconds before
|
|
||||||
* the next call to the message handler.
|
|
||||||
*/
|
|
||||||
#define GUACD_MESSAGE_HANDLE_FREQUENCY 50
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of milliseconds to wait for messages in any phase before
|
* The number of milliseconds to wait for messages in any phase before
|
||||||
@ -73,7 +49,53 @@
|
|||||||
*/
|
*/
|
||||||
#define GUACD_CLIENT_MAX_CONNECTIONS 65536
|
#define GUACD_CLIENT_MAX_CONNECTIONS 65536
|
||||||
|
|
||||||
int guacd_client_start(guac_client* client);
|
/**
|
||||||
|
* Parameters required by the user input thread.
|
||||||
|
*/
|
||||||
|
typedef struct guacd_user_input_thread_params {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parser which will be used throughout the user's session.
|
||||||
|
*/
|
||||||
|
guac_parser* parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the connected user.
|
||||||
|
*/
|
||||||
|
guac_user* user;
|
||||||
|
|
||||||
|
} guacd_user_input_thread_params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the input/output threads of a new user. This function will block
|
||||||
|
* until the user disconnects. 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
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user