383 lines
15 KiB
C
383 lines
15 KiB
C
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include "auth.h"
|
||
|
#include "client.h"
|
||
|
#include "channels/audio.h"
|
||
|
#include "channels/clipboard.h"
|
||
|
#include "channels/cursor.h"
|
||
|
#include "channels/display.h"
|
||
|
#include "channels/file.h"
|
||
|
#include "input.h"
|
||
|
#include "keyboard.h"
|
||
|
#include "user.h"
|
||
|
#include "spice.h"
|
||
|
#include "spice-constants.h"
|
||
|
|
||
|
#ifdef ENABLE_COMMON_SSH
|
||
|
#include "common-ssh/sftp.h"
|
||
|
#include "common-ssh/ssh.h"
|
||
|
#include "common-ssh/user.h"
|
||
|
#endif
|
||
|
|
||
|
#include <guacamole/client.h>
|
||
|
#include <guacamole/recording.h>
|
||
|
|
||
|
#include <glib/gmain.h>
|
||
|
#include <pthread.h>
|
||
|
#include <spice-client-glib-2.0/spice-client.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
/**
|
||
|
* Handle events for the main Spice channel, taking the appropriate action
|
||
|
* for known events, and logging warnings for unknowns and non-fatal events.
|
||
|
*
|
||
|
* @param channel
|
||
|
* The channel for which to handle events.
|
||
|
*
|
||
|
* @param event
|
||
|
* The event that is being handled.
|
||
|
*
|
||
|
* @param client
|
||
|
* The guacamole_client associated with this event.
|
||
|
*/
|
||
|
static void guac_spice_client_main_channel_handler(SpiceChannel *channel,
|
||
|
SpiceChannelEvent event, guac_client* client) {
|
||
|
|
||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Received new main channel event: %u", event);
|
||
|
|
||
|
/* Handle the various possible SPICE events. */
|
||
|
switch (event) {
|
||
|
|
||
|
/* Channel has been closed, so we abort the connection. */
|
||
|
case SPICE_CHANNEL_CLOSED:
|
||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||
|
"Disconnected from Spice server.");
|
||
|
break;
|
||
|
|
||
|
/* Channel has been opened - log it and do nothing else. */
|
||
|
case SPICE_CHANNEL_OPENED:
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Channel opened.");
|
||
|
break;
|
||
|
|
||
|
/* Error authenticating, log a warning and prompt user. */
|
||
|
case SPICE_CHANNEL_ERROR_AUTH:
|
||
|
guac_client_log(client, GUAC_LOG_WARNING,
|
||
|
"Channel authentication failed.");
|
||
|
|
||
|
/* Trigger a credential prompt. */
|
||
|
if (guac_spice_get_credentials(client)
|
||
|
&& spice_session_connect(spice_client->spice_session))
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Session connection started.");
|
||
|
|
||
|
else
|
||
|
guac_client_abort(client,
|
||
|
GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
||
|
"Failed to get credentials to connect to server.");
|
||
|
|
||
|
break;
|
||
|
|
||
|
/* TLS error, abort the connection with a warning. */
|
||
|
case SPICE_CHANNEL_ERROR_TLS:
|
||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||
|
"TLS failure connecting to Spice server.");
|
||
|
break;
|
||
|
|
||
|
/* I/O error, abort the connection with a warning. */
|
||
|
case SPICE_CHANNEL_ERROR_IO:
|
||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||
|
"IO error communicating with Spice server.");
|
||
|
break;
|
||
|
|
||
|
/* SPICE link error, abort the connection with a warning. */
|
||
|
case SPICE_CHANNEL_ERROR_LINK:
|
||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||
|
"Link error communicating with Spice server.");
|
||
|
break;
|
||
|
|
||
|
/* Connect error, abort the connection with a warning. */
|
||
|
case SPICE_CHANNEL_ERROR_CONNECT:
|
||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||
|
"Connection error communicating with Spice server.");
|
||
|
break;
|
||
|
|
||
|
/* Some other unknown event - log it and move on. */
|
||
|
default:
|
||
|
guac_client_log(client, GUAC_LOG_WARNING,
|
||
|
"Unknown event received on channel.");
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
int guac_client_init(guac_client* client) {
|
||
|
|
||
|
/* Set client args */
|
||
|
client->args = GUAC_SPICE_CLIENT_ARGS;
|
||
|
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Initializing Spice client.");
|
||
|
|
||
|
/* Alloc client data */
|
||
|
guac_spice_client* spice_client = calloc(1, sizeof(guac_spice_client));
|
||
|
client->data = spice_client;
|
||
|
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Initializing clipboard.");
|
||
|
|
||
|
/* Init clipboard */
|
||
|
spice_client->clipboard =
|
||
|
guac_common_clipboard_alloc(GUAC_SPICE_CLIPBOARD_MAX_LENGTH);
|
||
|
|
||
|
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up user handlers.");
|
||
|
|
||
|
/* Set handlers */
|
||
|
client->join_handler = guac_spice_user_join_handler;
|
||
|
client->leave_handler = guac_spice_user_leave_handler;
|
||
|
client->free_handler = guac_spice_client_free_handler;
|
||
|
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up mutex locks.");
|
||
|
|
||
|
/* Recursive attribute for locks */
|
||
|
pthread_mutexattr_init(&(spice_client->attributes));
|
||
|
pthread_mutexattr_settype(&(spice_client->attributes),
|
||
|
PTHREAD_MUTEX_RECURSIVE);
|
||
|
|
||
|
/* Init required locks */
|
||
|
pthread_rwlock_init(&(spice_client->lock), NULL);
|
||
|
pthread_mutex_init(&(spice_client->message_lock), &(spice_client->attributes));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int guac_spice_client_free_handler(guac_client* client) {
|
||
|
|
||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||
|
guac_spice_settings* settings = spice_client->settings;
|
||
|
|
||
|
/* Clean up SPICE client*/
|
||
|
SpiceSession* spice_session = spice_client->spice_session;
|
||
|
if (spice_session != NULL) {
|
||
|
|
||
|
/* Wait for client thread to finish */
|
||
|
pthread_join(spice_client->client_thread, NULL);
|
||
|
|
||
|
/* Disconnect the session, destroying data */
|
||
|
spice_session_disconnect(spice_session);
|
||
|
|
||
|
/* Free up the glib main loop. */
|
||
|
g_main_loop_unref(spice_client->spice_mainloop);
|
||
|
|
||
|
}
|
||
|
|
||
|
#ifdef ENABLE_COMMON_SSH
|
||
|
/* Free SFTP filesystem, if loaded */
|
||
|
if (spice_client->sftp_filesystem)
|
||
|
guac_common_ssh_destroy_sftp_filesystem(spice_client->sftp_filesystem);
|
||
|
|
||
|
/* Free SFTP session */
|
||
|
if (spice_client->sftp_session)
|
||
|
guac_common_ssh_destroy_session(spice_client->sftp_session);
|
||
|
|
||
|
/* Free SFTP user */
|
||
|
if (spice_client->sftp_user)
|
||
|
guac_common_ssh_destroy_user(spice_client->sftp_user);
|
||
|
|
||
|
guac_common_ssh_uninit();
|
||
|
#endif
|
||
|
|
||
|
/* Clean up recording, if in progress */
|
||
|
if (spice_client->recording != NULL)
|
||
|
guac_recording_free(spice_client->recording);
|
||
|
|
||
|
/* Free clipboard */
|
||
|
if (spice_client->clipboard != NULL)
|
||
|
guac_common_clipboard_free(spice_client->clipboard);
|
||
|
|
||
|
/* Free display */
|
||
|
if (spice_client->display != NULL)
|
||
|
guac_common_display_free(spice_client->display);
|
||
|
|
||
|
/* Free SPICE keyboard state */
|
||
|
guac_spice_keyboard_free(spice_client->keyboard);
|
||
|
spice_client->keyboard = NULL;
|
||
|
|
||
|
/* Free parsed settings */
|
||
|
if (settings != NULL)
|
||
|
guac_spice_settings_free(settings);
|
||
|
|
||
|
pthread_rwlock_destroy(&(spice_client->lock));
|
||
|
pthread_mutex_destroy(&(spice_client->message_lock));
|
||
|
|
||
|
/* Free generic data struct */
|
||
|
free(client->data);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void guac_spice_client_channel_handler(SpiceSession *spice_session,
|
||
|
SpiceChannel *channel, guac_client* client) {
|
||
|
|
||
|
guac_spice_client* spice_client = (guac_spice_client*) client->data;
|
||
|
guac_spice_settings* settings = spice_client->settings;
|
||
|
int id, type;
|
||
|
|
||
|
/* Get the channel ID and type. */
|
||
|
g_object_get(channel, SPICE_PROPERTY_CHANNEL_ID, &id, NULL);
|
||
|
g_object_get(channel, SPICE_PROPERTY_CHANNEL_TYPE, &type, NULL);
|
||
|
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "New channel created: %i", id);
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "New channel type: %i", type);
|
||
|
|
||
|
/* Check if this is the main channel and register handlers. */
|
||
|
if (SPICE_IS_MAIN_CHANNEL(channel)) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up main channel.");
|
||
|
spice_client->main_channel = SPICE_MAIN_CHANNEL(channel);
|
||
|
|
||
|
/* Register the main channel event handler and return. */
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_CHANNEL_EVENT,
|
||
|
G_CALLBACK(guac_spice_client_main_channel_handler), client);
|
||
|
|
||
|
/* Register clipboard handlers. */
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION,
|
||
|
G_CALLBACK(guac_spice_clipboard_selection_handler), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_GRAB,
|
||
|
G_CALLBACK(guac_spice_clipboard_selection_grab_handler), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_RELEASE,
|
||
|
G_CALLBACK(guac_spice_clipboard_selection_release_handler), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_REQUEST,
|
||
|
G_CALLBACK(guac_spice_clipboard_selection_request_handler), client);
|
||
|
|
||
|
/* Update the main display with our size. */
|
||
|
if (client->__owner != NULL)
|
||
|
spice_main_channel_update_display(spice_client->main_channel,
|
||
|
GUAC_SPICE_DEFAULT_DISPLAY_ID,
|
||
|
0,
|
||
|
0,
|
||
|
client->__owner->info.optimal_width,
|
||
|
client->__owner->info.optimal_height, TRUE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Check if this is the display channel and register display handlers. */
|
||
|
if (SPICE_IS_DISPLAY_CHANNEL(channel) && id == 0) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up display channel.");
|
||
|
int width, height;
|
||
|
SpiceDisplayPrimary primary;
|
||
|
g_object_get(channel, "width", &width, "height", &height, NULL);
|
||
|
spice_client->spice_display = SPICE_DISPLAY_CHANNEL(channel);
|
||
|
spice_client->display = guac_common_display_alloc(client, width, height);
|
||
|
|
||
|
/* Register callbacks fr the various display signals. */
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_INVALIDATE,
|
||
|
G_CALLBACK(guac_spice_client_display_update), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_MARK,
|
||
|
G_CALLBACK(guac_spice_client_display_mark), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_PRIMARY_CREATE,
|
||
|
G_CALLBACK(guac_spice_client_display_primary_create), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_PRIMARY_DESTROY,
|
||
|
G_CALLBACK(guac_spice_client_display_primary_destroy), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_GL_DRAW,
|
||
|
G_CALLBACK(guac_spice_client_display_gl_draw), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_STREAMING_MODE,
|
||
|
G_CALLBACK(guac_spice_client_streaming_handler), client);
|
||
|
|
||
|
/* Attempt to get the primary display, and set it up. */
|
||
|
if (spice_display_channel_get_primary(channel, 0, &primary)) {
|
||
|
guac_spice_client_display_primary_create(
|
||
|
spice_client->spice_display, primary.format,
|
||
|
primary.width, primary.height, primary.stride,
|
||
|
primary.shmid, primary.data, client);
|
||
|
guac_spice_client_display_mark(spice_client->spice_display,
|
||
|
primary.marked, client);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/* Check for audio playback channel and set up the channel. */
|
||
|
if (SPICE_IS_PLAYBACK_CHANNEL(channel) && settings->audio_enabled) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up audio playback channel.");
|
||
|
spice_client->playback_channel = SPICE_PLAYBACK_CHANNEL(channel);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_DATA,
|
||
|
G_CALLBACK(guac_spice_client_audio_playback_data_handler), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_GET_DELAY,
|
||
|
G_CALLBACK(guac_spice_client_audio_playback_delay_handler), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_START,
|
||
|
G_CALLBACK(guac_spice_client_audio_playback_start_handler), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_STOP,
|
||
|
G_CALLBACK(guac_spice_client_audio_playback_stop_handler), client);
|
||
|
}
|
||
|
|
||
|
/* Check for audio recording channel and set up the channel. */
|
||
|
if (SPICE_IS_RECORD_CHANNEL(channel) && settings->audio_input_enabled) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up audio record channel.");
|
||
|
spice_client->record_channel = SPICE_RECORD_CHANNEL(channel);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_RECORD_START,
|
||
|
G_CALLBACK(guac_spice_client_audio_record_start_handler), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_RECORD_STOP,
|
||
|
G_CALLBACK(guac_spice_client_audio_record_stop_handler), client);
|
||
|
}
|
||
|
|
||
|
/* Check for cursor channel and set it up. */
|
||
|
if (SPICE_IS_CURSOR_CHANNEL(channel)) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up cursor channel.");
|
||
|
spice_client->cursor_channel = SPICE_CURSOR_CHANNEL(channel);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_CURSOR_HIDE,
|
||
|
G_CALLBACK(guac_spice_cursor_hide), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_CURSOR_MOVE,
|
||
|
G_CALLBACK(guac_spice_cursor_move), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_CURSOR_RESET,
|
||
|
G_CALLBACK(guac_spice_cursor_reset), client);
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_CURSOR_SET,
|
||
|
G_CALLBACK(guac_spice_cursor_set), client);
|
||
|
}
|
||
|
|
||
|
/* Check if this is an inputs channel and set it up. */
|
||
|
if (SPICE_IS_INPUTS_CHANNEL(channel)) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up inputs channel.");
|
||
|
spice_client->inputs_channel = SPICE_INPUTS_CHANNEL(channel);
|
||
|
|
||
|
/* Register callback that sets keyboard modifiers */
|
||
|
g_signal_connect(channel, SPICE_SIGNAL_INPUTS_MODIFIERS,
|
||
|
G_CALLBACK(guac_spice_keyboard_set_indicators), client);
|
||
|
}
|
||
|
|
||
|
/* File transfer channel */
|
||
|
if (SPICE_IS_WEBDAV_CHANNEL(channel)) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Setting up webdav channel.");
|
||
|
if (settings->file_transfer
|
||
|
&& settings->file_directory != NULL
|
||
|
&& strcmp(settings->file_directory, "") != 0) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "USB redirection is not yet implemented.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
guac_client_log(client, GUAC_LOG_DEBUG, "Calling spice_channel_connect for channel %d.", id);
|
||
|
if (!spice_channel_connect(channel))
|
||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||
|
"Unable to connect the channel.");
|
||
|
|
||
|
}
|