From 2cff6c6b73906f403cb919ef664c91a134f3daec Mon Sep 17 00:00:00 2001 From: Virtually Nick Date: Sun, 3 Apr 2022 21:05:33 -0400 Subject: [PATCH] [WIP]: Add support for links channel. --- src/libguac/client.c | 59 +++++++++ src/libguac/guacamole/client.h | 18 +++ src/libguac/guacamole/protocol-constants.h | 2 +- src/libguac/guacamole/protocol-types.h | 8 +- src/libguac/guacamole/protocol.h | 15 +++ src/libguac/guacamole/user.h | 11 ++ src/libguac/protocol.c | 16 +++ src/libguac/user.c | 8 ++ src/protocols/rdp/Makefile.am | 2 + src/protocols/rdp/channels/rdpuri.c | 133 +++++++++++++++++++++ src/protocols/rdp/channels/rdpuri.h | 45 +++++++ src/protocols/rdp/rdp.c | 4 + 12 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/protocols/rdp/channels/rdpuri.c create mode 100644 src/protocols/rdp/channels/rdpuri.h diff --git a/src/libguac/client.c b/src/libguac/client.c index 9738c0c2..d2f194d6 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -717,3 +717,62 @@ int guac_client_supports_webp(guac_client* client) { } +/** + * A callback that it is invokved by the call to guac_client_owner_send_uri + * which sends the 'uri" instruction and parameter to the specified user, who + * is the owner of the connection. + * + * @param user + * The user to send the "uri" instruction and parameter to, who owns the + * connection. + * + * @param data + * The URI to send to the owner, cast as a void*. + * + * @return + * Zero if the operation succeeds or non-zero on failure. + */ +static void* guac_client_owner_send_uri_callback(guac_user* user, void* data) { + + const char* uri = (const char *) data; + + /* Send uri parameter to owner. */ + if (user != NULL) + return (void*) ((intptr_t) guac_protocol_send_uri(user->socket, uri)); + + return (void*) ((intptr_t) -1); +} + +int guac_client_owner_send_uri(guac_client* client, const char* uri) { + + /* Don't send the uri instruction if the client does not support it. */ + if (!guac_client_owner_supports_uri(client)) + return -1; + + return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_send_uri_callback, (void *) uri)); + +} + +/** + * Callback function that is invoked by guac_client_owner_supports_uri to + * determine if the owner of a connection supports the "uri" instruction, + * so that Guacamole can pass URIs to the client browser. + * + * @param user + * The user that is being checked for URI support, which should be the + * owner of the connection. + * + * @param data + * Additional data - this will always be null. + * + * @return + * A non-zero integer if the user supports the "uri" instruction, otherwise + * zero to indicate a lack of URI support. + */ +static void* guac_owner_supports_uri_callback(guac_user* user, void* data) { + return (void*) ((intptr_t) guac_user_supports_uri(user)); +} + +int guac_client_owner_supports_uri(guac_client* client) { + return (int) ((intptr_t) guac_client_for_owner(client, guac_owner_supports_uri_callback, NULL)); +} \ No newline at end of file diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index 4ffe5cf4..ea0a0b9d 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -565,6 +565,22 @@ int guac_client_get_processing_lag(guac_client* client); */ int guac_client_owner_send_required(guac_client* client, const char** required); +/** + * Sends the "uri" instruction to the given guac_client so that the remote + * client can process the URI, if the client supports it and has a handler + * configured for the URI. + * + * @param client + * The client to which to send the URI instruction. + * + * @param uri + * The URI to send to the remote client. + * + * @return + * Zero on success, non-zero on failure. + */ +int guac_client_owner_send_uri(guac_client* client, const char* uri); + /** * Streams the given connection parameter value over an argument value stream * ("argv" instruction), exposing the current value of the named connection @@ -736,6 +752,8 @@ int guac_client_owner_supports_required(guac_client* client); */ int guac_client_supports_webp(guac_client* client); +int guac_client_owner_supports_uri(guac_client* client); + /** * The default Guacamole client layer, layer 0. */ diff --git a/src/libguac/guacamole/protocol-constants.h b/src/libguac/guacamole/protocol-constants.h index 13d7f680..87e09ce6 100644 --- a/src/libguac/guacamole/protocol-constants.h +++ b/src/libguac/guacamole/protocol-constants.h @@ -38,7 +38,7 @@ * This version is passed by the __guac_protocol_send_args() function from the * server to the client during the client/server handshake. */ -#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_3_0" +#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_6_0" /** * The maximum number of bytes that should be sent in any one blob instruction diff --git a/src/libguac/guacamole/protocol-types.h b/src/libguac/guacamole/protocol-types.h index 51652d2b..83ae33d7 100644 --- a/src/libguac/guacamole/protocol-types.h +++ b/src/libguac/guacamole/protocol-types.h @@ -306,7 +306,13 @@ typedef enum guac_protocol_version { * allowing connections in guacd to request information from the client and * await a response. */ - GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300 + GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300, + + /** + * Protocol version 1.6.0, which supports the "uri" instruction, allowing + * remote systems to send + */ + GUAC_PROTOCOL_VERSION_1_6_0 = 0x010600 } guac_protocol_version; diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 362351c5..af7f6d0c 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -1037,6 +1037,21 @@ int guac_protocol_send_shade(guac_socket* socket, const guac_layer* layer, int guac_protocol_send_size(guac_socket* socket, const guac_layer* layer, int w, int h); +/** + * Send the uri instruction over the given guac_socket connection, + * providing a URI that the client should then process locally. + * + * @param socket + * The guac_socket connection to which to send the uri instruction. + * + * @param uri + * The URI that should be sent to the client. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_uri(guac_socket* socket, const char* uri); + /* TEXT INSTRUCTIONS */ /** diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index 963dbe68..3ba7c725 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -861,6 +861,17 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket, */ int guac_user_supports_required(guac_user* user); +/** + * Returns non-zero if the user supports the "uri" instruction. + * + * @param user + * The Guacamole user to check for support of the "uri" instruction. + * + * @return + * Non-zero if the user supports the "uri" instruction, otherwise zero. + */ +int guac_user_supports_uri(guac_user* user); + /** * Returns whether the given user supports WebP. If the user does not * support WebP, or the server cannot encode WebP images, zero is returned. diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 1c53c200..89e0d058 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -65,6 +65,7 @@ guac_protocol_version_mapping guac_protocol_version_table[] = { { GUAC_PROTOCOL_VERSION_1_0_0, "VERSION_1_0_0" }, { GUAC_PROTOCOL_VERSION_1_1_0, "VERSION_1_1_0" }, { GUAC_PROTOCOL_VERSION_1_3_0, "VERSION_1_3_0" }, + { GUAC_PROTOCOL_VERSION_1_6_0, "VERSION_1_6_0" }, { GUAC_PROTOCOL_VERSION_UNKNOWN, NULL } }; @@ -1274,6 +1275,21 @@ int guac_protocol_send_undefine(guac_socket* socket, } +int guac_protocol_send_uri(guac_socket* socket, const char* uri) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "3.uri,") + || __guac_socket_write_length_string(socket, uri) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} + int guac_protocol_send_video(guac_socket* socket, const guac_stream* stream, const guac_layer* layer, const char* mimetype) { diff --git a/src/libguac/user.c b/src/libguac/user.c index 4320ca77..d62ea1b5 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -325,6 +325,14 @@ int guac_user_supports_required(guac_user* user) { } +int guac_user_supports_uri(guac_user* user) { + + if (user == NULL) + return 0; + + return (user->info.protocol_version >= GUAC_PROTOCOL_VERSION_1_6_0); +} + int guac_user_supports_webp(guac_user* user) { #ifdef ENABLE_WEBP diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 839cc9af..17c12156 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -59,6 +59,7 @@ libguac_client_rdp_la_SOURCES = \ channels/rdpei.c \ channels/rdpsnd/rdpsnd-messages.c \ channels/rdpsnd/rdpsnd.c \ + channels/rdpuri.c \ client.c \ color.c \ decompose.c \ @@ -105,6 +106,7 @@ noinst_HEADERS = \ channels/rdpei.h \ channels/rdpsnd/rdpsnd-messages.h \ channels/rdpsnd/rdpsnd.h \ + channels/rdpuri.h \ client.h \ color.h \ decompose.h \ diff --git a/src/protocols/rdp/channels/rdpuri.c b/src/protocols/rdp/channels/rdpuri.c new file mode 100644 index 00000000..3d35c159 --- /dev/null +++ b/src/protocols/rdp/channels/rdpuri.c @@ -0,0 +1,133 @@ +/* + * 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 "channels/rdpuri.h" +#include "client.h" +#include "config.h" +#include "plugins/channels.h" +#include "rdp.h" +#include "unicode.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * Callback which associates handlers specific to Guacamole with the + * CliprdrClientContext instance allocated by FreeRDP to deal with received + * CLIPRDR (clipboard redirection) messages. + * + * This function is called whenever a channel connects via the PubSub event + * system within FreeRDP, but only has any effect if the connected channel is + * the CLIPRDR channel. This specific callback is registered with the PubSub + * system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is + * called. + * + * @param context + * The rdpContext associated with the active RDP session. + * + * @param e + * Event-specific arguments, mainly the name of the channel, and a + * reference to the associated plugin loaded for that channel by FreeRDP. + */ +static void guac_rdpuri_channel_connected(guac_rdp_common_svc* svc) { + + guac_client_log(svc->client, GUAC_LOG_DEBUG, "RDPURI (URI redirection) " + "channel connected."); + +} + +/** + * Callback which disassociates Guacamole from the CliprdrClientContext + * instance that was originally allocated by FreeRDP and is about to be + * deallocated. + * + * This function is called whenever a channel disconnects via the PubSub event + * system within FreeRDP, but only has any effect if the disconnected channel + * is the CLIPRDR channel. This specific callback is registered with the PubSub + * system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is + * called. + * + * @param context + * The rdpContext associated with the active RDP session. + * + * @param e + * Event-specific arguments, mainly the name of the channel, and a + * reference to the associated plugin loaded for that channel by FreeRDP. + */ +static void guac_rdpuri_channel_disconnected(guac_rdp_common_svc* svc) { + + guac_client_log(svc->client, GUAC_LOG_DEBUG, "RDPURI (URI redirection) " + "channel disconnected."); + +} + +static void guac_rdpuri_channel_receive(guac_rdp_common_svc* svc, wStream* input_stream) { + guac_client_log(svc->client, GUAC_LOG_DEBUG, "RDPURI (URI redirection) " + "channel received data."); + + // int len = Stream_Length(input_stream); + int len = Stream_Length(input_stream); + + guac_client_log(svc->client, GUAC_LOG_DEBUG, "Received URI %d in length.", len); + + if (len < 1) { + guac_client_log(svc->client, GUAC_LOG_WARNING, "Received URI data is too " + "small."); + return; + } + + /* Convert input stream to UTF8 and send it to the client. */ + char uri[len/2]; + guac_rdp_utf16_to_utf8(Stream_Pointer(input_stream), len/2 - 1, uri, sizeof(uri)); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "Received URI from server: %s", uri); + guac_client_owner_send_uri(svc->client, uri); + +} + +void guac_rdpuri_load_plugin(rdpContext* context) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + + /* Attempt to load FreeRDP support for the CLIPRDR channel */ + if (guac_rdp_common_svc_load_plugin(context, "rdpuri", 0, + guac_rdpuri_channel_connected, guac_rdpuri_channel_receive, + guac_rdpuri_channel_disconnected)) { + guac_client_log(client, GUAC_LOG_WARNING, + "Support for the RDPURI channel (URI redirection) " + "could not be loaded. This support normally takes the form of " + "a plugin which is built into FreeRDP. Lacking this support, " + "URI redirection will not work."); + return; + } + + guac_client_log(client, GUAC_LOG_DEBUG, "Support for RDPURI " + "(URI redirection) registered. Awaiting channel " + "connection."); + +} \ No newline at end of file diff --git a/src/protocols/rdp/channels/rdpuri.h b/src/protocols/rdp/channels/rdpuri.h new file mode 100644 index 00000000..9aae43a5 --- /dev/null +++ b/src/protocols/rdp/channels/rdpuri.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#ifndef GUAC_RDP_CHANNELS_RDPURI_H +#define GUAC_RDP_CHANNELS_RDPURI_H + +#include "channels/common-svc.h" + +#include +#include +#include +#include +#include + +/** + * Initializes clipboard support for RDP and handling of the RDPURI channel. + * If failures occur, messages noting the specifics of those failures will be + * logged, and the RDP side of URI redirection will not be functional. + * + * This MUST be called within the PreConnect callback of the freerdp instance + * for RDPURI support to be loaded. + * + * @param context + * The rdpContext associated with the FreeRDP side of the RDP connection. + */ +void guac_rdpuri_load_plugin(rdpContext* context); + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 5905f8e6..1ee13801 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -29,6 +29,7 @@ #include "channels/rdpdr/rdpdr.h" #include "channels/rdpei.h" #include "channels/rdpsnd/rdpsnd.h" +#include "channels/rdpuri.h" #include "client.h" #include "color.h" #include "common/cursor.h" @@ -123,6 +124,9 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { guac_rdpsnd_load_plugin(context); } + /* Load rdpuri service */ + guac_rdpuri_load_plugin(context); + /* Load RAIL plugin if RemoteApp in use */ if (settings->remote_app != NULL) guac_rdp_rail_load_plugin(context);